084-反射(通过 reflect.Value 修改值)

前面我们学习了一些关于反射的用法,比如:

x := 2
// 拿到 x 的 Value 对象
a := reflect.ValueOf(x)

我们可以通过 a 来得知 x 的具体值是多少,那有没有办法通过 a 来修改 x 的值呢?在上面这个例子中,是不行的。

1. 可修改性

或者说叫可设置性。

Value 有一个方法,CanSet,它可以告知你是否可以设置值。还有一个方法就一并讲了,叫 CanAddr,表示能否取变量的地址。

比如:

x := 2
a := reflect.ValueOf(x)
fmt.Println(a.CanSet())  // Output: false
fmt.Println(a.CanAddr()) // Output: false

结果输出 false,这说明 a 不具有可设置性。原因在于 reflect.ValueOf 返回的变量 a 是通过 x 的副本生成的,因此,无论如何也你无法通过 a 来修改 x.

那换成 x 的地址怎么样?如下:

x := 2
a := reflect.ValueOf(&x)
fmt.Println(a.CanSet())  // Output: false
fmt.Println(a.CanAddr()) // Output: false

结果你发现,还是无法设置,也无法取地址。这是为什么呢?因为你拿到的 a 是变量 x 的地址的副本啊,这个副本同样无法设置,也无法取地址。

2. Elem 方法

这时候该轮到 Elem 上场了。其实在上一篇 json 序列化里你已经遇到过它了,它简单的可以理解成解引用。看下面的代码:

x := 2
a := reflect.ValueOf(&x)
// a 是指针的化身,通过 a 可以解出“真正的” x
b := a.Elem()
fmt.Println(b.CanSet())  // Output: true
fmt.Println(b.CanAddr()) // Output: true

这时候你会发现,b 已经可以设置,也可以提取地址了。

3. 设置值

3.1 通过指针直接设置

  • 方法一
x := 2
fmt.Printf("x = %d\n", x) // Output: x = 2
a := reflect.ValueOf(&x)
p := a.Interface().(*int)
*p = 10
fmt.Printf("x = %d\n", x) // Output: x = 10
  • 方法二
x := 2
fmt.Printf("x = %d\n", x) // Output: x = 2
a := reflect.ValueOf(&x)
b := a.Elem()
p := b.Addr().Interface().(*int)
*p = 10
fmt.Printf("x = %d\n", x) // Output: x = 10

上面两种方法其实差不多,只是第二种通过 a.Elem() 也可以拿到 x 的地址。

3.2 通过 Set 方法设置值

x := 2
fmt.Printf("x = %d\n", x) // Output: x = 2
a := reflect.ValueOf(&x)
b := a.Elem()
// 通过 SetInt 方法设置值
b.SetInt(10)
fmt.Printf("x = %d\n", x) // Output: x = 10

当然,如果变量的底层类型不是有符号类的整数,调用 SetInt 会引起 Panic.

除了 SetInt 方法,reflect.Value 还有 SetUint, SetFloat, SetBool, SetString 等类似的方法。

4. 总结

  • 掌握可取地址性
  • 掌握可设置性
  • 掌握 Elem 的用法

你可能感兴趣的:(Go,语言学习笔记(更新中...),Go,语言修炼指南)