Go的反射原理(3)

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,调用方法和函数,但是这篇文章已经足够长了,我们将在以后的文章中讲解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值