3、反射的第三条规则:被修改的反射对象,其值必须可写
第三条规则是最不易理解,具有迷惑性的,但是我们从第一条规则开始分析就慢慢就懂了。
下面是代码不能正常工作,但是值得分析。
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.2) // Error: 将抛出Panic
如果你运行这段代码,panic错误信息如下
panic: reflect.Value.SetFloat using unaddressable value
出现报错不是7.1不是不可寻址,而是v;是否可写是反射变量Value的一种特征,并不是所有的反射Value都是可写的。
CanSet函数标识了反射Value的可写性;举例来说,
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println(“settability of v:”, v.CanSet())
如果不可写变量调用Set方法将报错。但是什么是可写性呢?
可写有点像可寻址,但是更加严格。反射变量可以修改原变量的值是一种特征。可写性是由反射变量所承载的原值所决定的。当我们说
var x float = 3.4
v := reflect.ValueOf(x)
我们传递是的x的复本给reflect.ValueOf,并不是x本身。因此,如果
v.SetFloat(7.1)
可以执行成功,也不会更新x,即使v看起来是由x创建的。这样的效果是无效的,并且让人困惑,所以是非法的。
这看起来非常怪异,实则不然,这其实是一种非常常见的特征。想象一下,如果我们传递x给一个函数:
f(x)
我们并不希望f可以改变原值x,因为我们传的是x的副本,并不x自己。如果我们希望通过修改f来直接修改x,我们就必须传递的是x的地址(也就是x的指针):
f(&x)
反射也是同样的。如果要修改通过反射修改x的值,就必须给传递变量的指针。
举个例子。首先像之前一样初始化x,创建一个变量x的指针的反射
Value p.
var x float64 = 3.4
p := reflect.Value(&x) // 注意:取x的指针
fmt.Println(“type of p:”, p.Type())
fmt.Println(“settability of p:”, p.CanSet())
输出:
Typeof p: *float64
Settability of p: false
反射对象p是不可写的,但是我们并不是想要写p,而是写*p。为了获取p指向的值,我们调用Elem方法,并将结果保存在v中:
v := p.Elem()
fmt.Println(“settability of v:”, v.CanSet())
现在v是一个可以写的对象了,结果输出
Settability of v: true
因为他代表了x,我们就可以用v.SetFloat去修改x的值:
v.SetFloat(7.1)
fmt.Println(v.Interface)
fmt.Println(x)
输出结果和预想的一样
7.1
7.1
Structs
在前面的例子中v并不是指针本身,,它只是从指针类型派生出的一种类型。一种更通用的情况是通过反射来修改struct结构。只要我们得到结构体的指针,我们就可以修改结构体的各个域。
下面是一个简单的分析结结构体struct的值t的例。我们使用结构体的地址创建一个反射对象,因为我们希望之后能够修改它。然后设置typeOfT为它的类型,遍历它的各个数据域。需要注意的是,我们提取了各个域的类型,但是各个域本身就是reflect.Value类型。
type T struct {
A int
B string
}
t := T{23, “skidoo”}
S := reflect.ValueOf(&t).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field()
fmt.Printf(“%d: %s = %v\n”, i, typeOfT.Field(i).Name, f.Type(), f.Interface())
}
程序的输出是
0: A int - 23
1: B string = skidoo
关于可写性这里还有一点需要介绍:结构体T的各个域名字都是大写的,因为只有外部域才是可以写的。
因为s包含一系列可写对象,我们才可以修改结构体的各个域。
s.Field(0).SetInt(77)
s.Field(1).SetString(“Sunset Strip”)
fmt.Println(“t is now”, t)
结果如下
t is now {77 Sunset strip}
如果我们不是通过&t,而是t来创建s,那么调用SetInt和SetString将会失败,因为t是不可写的。
总结
这里总结反射的规律
(1)反射由interface值转成reflection对象
(2)反射由reflection对象转为interface值
(3)修改reflection对象的值必须是可写的
一旦你理解了这些反射的规则,Go将变得更易用。反射是一个强大的工具,应该谨慎使用,除非有必要一般应减少使用。
还有很多反射的内容,我们没有涉及,比如channel中的发送和接收,分配内存、使用slices和maps,调用方法和函数,但是这篇文章已经足够长了,我们将在以后的文章中讲解。