go在方法中修改结构体的值_Go语言通过反射修改变量的值

Go语言中类似x、x.f[1]和*p形式的表达式都可以表示变量,但是其它如x+1和f(2)则不是变量。一个变量就是一个可寻址的内存空间,里面存储了一个值,并且存储的值可以通过内存地址来更新。

对于reflect.Values也有类似的区别。有一些reflect.Values是可取地址的;其它一些则不可以。考虑以下的声明语句:x:=2//valuetypevariable?

a:=reflect.ValueOf(2)//2intno

b:=reflect.ValueOf(x)//2intno

c:=reflect.ValueOf(&x)//&x*intno

d:=c.Elem()//2intyes(x)

其中a对应的变量则不可取地址。因为a中的值仅仅是整数2的拷贝副本。b中的值也同样不可取地址。c中的值还是不可取地址,它只是一个指针&x的拷贝。实际上,所有通过reflect.ValueOf(x)返回的reflect.Value都是不可取地址的。但是对于d,它是c的解引用方式生成的,指向另一个变量,因此是可取地址的。我们可以通过调用reflect.ValueOf(&x).Elem(),来获取任意变量x对应的可取地址的Value。

我们可以通过调用reflect.Value的CanAddr方法来判断其是否可以被取地址:fmt.Println(a.CanAddr())//"false"

fmt.Println(b.CanAddr())//"false"

fmt.Println(c.CanAddr())//"false"

fmt.Println(d.CanAddr())//"true"

每当我们通过指针间接地获取的reflect.Value都是可取地址的,即使开始的是一个不可取地址的Value。在反射机制中,所有关于是否支持取地址的规则都是类似的。例如,slice的索引表达式e[i]将隐式地包含一个指针,它就是可取地址的,即使开始的e表达式不支持也没有关系。

以此类推,reflect.ValueOf(e).Index(i)对于的值也是可取地址的,即使原始的reflect.ValueOf(e)不支持也没有关系。

使用reflect.Value对包装的值进行修改时,需要遵循一些规则。如果没有按照规则进行代码设计和编写,轻则无法修改对象值,重则程序在运行时会发生宕机。

判定及获取元素的相关方法

使用reflect.Value取元素、取地址及修改值的属性方法请参考下表。

反射值对象的判定及获取元素的方法

方法名

备 注

Elem()Value

取值指向的元素值,类似于语言层*操作。当值类型不是指针或接口时发生宕机,空指针时返回nil的Value

Addr()Value

对可寻址的值返回其地址,类似于语言层&操作。当值不可寻址时发生宕机

CanAddr()bool

表示值是否可寻址

CanSet() bool

返回值能否被修改。要求值可寻址且是导出的字段

值修改相关方法

使用reflect.Value修改值的相关方法如下表所示。

反射值对象修改值的方法

Set(xValue)

将值设置为传入的反射值对象的值

Setlnt(xint64)

使用int64设置值。当值的类型不是int、int8、int16、 int32、int64时会发生宕机

SetUint(xuint64)

使用uint64设置值。当值的类型不是uint、uint8、uint16、uint32、uint64时会发生宕机

SetFloat(xfloat64)

使用float64设置值。当值的类型不是float32、float64时会发生宕机

SetBool(xbool)

使用bool设置值。当值的类型不是bod时会发生宕机

SetBytes(x[]byte)

设置字节数组[]bytes值。当值的类型不是[]byte时会发生宕机

SetString(xstring)

设置字符串值。当值的类型不是string时会发生宕机

以上方法,在reflect.Value的CanSet返回false仍然修改值时会发生宕机。

在已知值的类型时,应尽量使用值对应类型的反射设置值。

值可修改条件之一:可被寻址

通过反射修改变量值的前提条件之一:这个值必须可以被寻址。简单地说就是这个变量必须能被修改。示例代码如下:

packagemainimport("reflect")funcmain(){//声明整型变量a并赋初值varaint=1024//获取变量a的反射值对象valueOfA:=reflect.ValueOf(a)//尝试将a修改为1(此处会发生崩溃)valueOfA.SetInt(1)}

程序运行崩溃,打印错误:panic:reflect:reflect.Value.SetIntusingunaddressablevalue

报错意思是:SetInt正在使用一个不能被寻址的值。从reflect.ValueOf传入的是a的值,而不是a的地址,这个reflect.Value当然是不能被寻址的。将代码修改一下,重新运行:

packagemainimport("fmt""reflect")funcmain(){//声明整型变量a并赋初值varaint=1024//获取变量a的反射值对象(a的地址)valueOfA:=reflect.ValueOf(&a)//取出a地址的元素(a的值)valueOfA=valueOfA.Elem()//修改a的值为1valueOfA.SetInt(1)//打印a的值fmt.Println(valueOfA.Int())}

代码输出如下:

1

下面是对代码的分析:

第14行中,将变量a取值后传给reflect.ValueOf()。此时reflect.ValueOf()返回的valueOfA持有变量a的地址。

第17行中,使用reflect.Value类型的Elem()方法获取a地址的元素,也就是a的值。reflect.Value的Elem()方法返回的值类型也是reflect.Value。

第20行,此时valueOfA表示的是a的值且可以寻址。使用SetInt()方法设置值时不再发生崩溃。

第23行,正确打印修改的值。

提示

当reflect.Value不可寻址时,使用Addr()方法也是无法取到值的地址的,同时会发生宕机。虽然说reflect.Value的Addr()方法类似于语言层的&操作;Elem()方法类似于语言层的*操作,但并不代表这些方法与语言层操作等效。

值可修改条件之一:被导出

结构体成员中,如果字段没有被导出,即便不使用反射也可以被访问,但不能通过反射修改,代码如下:

packagemainimport("reflect")funcmain(){typedogstruct{legCountint}//获取dog实例的反射值对象valueOfDog:=reflect.ValueOf(dog{})//获取legCount字段的值vLegCount:=valueOfDog.FieldByName("legCount")//尝试设置legCount的值(这里会发生崩溃)vLegCount.SetInt(4)}

程序发生崩溃,报错:panic:reflect:reflect.Value.SetIntusingvalueobtainedusingunexported field

报错的意思是:SetInt()使用的值来自于一个未导出的字段。

为了能修改这个值,需要将该字段导出。将dog中的legCount的成员首字母大写,导出LegCount让反射可以访问,修改后的代码如下:

typedogstruct{LegCountint}

然后根据字段名获取字段的值时,将字符串的字段首字母大写,修改后的代码如下:

vLegCount:=valueOfDog.FieldByName("LegCount")

再次运行程序,发现仍然报错:panic:reflect:reflect.Value.SetIntusingunaddressablevalue

这个错误表示第13行构造的valueOfDog这个结构体实例不能被寻址,因此其字段也不能被修改。修改代码,取结构体的指针,再通过reflect.Value的Elem()方法取到值的反射值对象。修改后的完整代码如下:

packagemainimport("reflect""fmt")funcmain(){typedogstruct{LegCountint}//获取dog实例地址的反射值对象valueOfDog:=reflect.ValueOf(&dog{})//取出dog实例地址的元素valueOfDog=valueOfDog.Elem()//获取legCount字段的值vLegCount:=valueOfDog.FieldByName("LegCount")//尝试设置legCount的值(这里会发生崩溃)vLegCount.SetInt(4)fmt.Println(vLegCount.Int())}

代码输出如下:

4

代码说明如下:

第11行,将LegCount首字母大写导出该字段。

第14行,获取dog实例指针的反射值对象。

第17行,取dog实例的指针元素,也就是dog的实例。

第20行,取dog结构体中LegCount字段的成员值。

第23行,修改该成员值。

第25行,打印该成员值。

值的修改从表面意义上叫可寻址,换一种说法就是值必须“可被设置”。那么,想修改变量值,一般的步骤是:

取这个变量的地址或者这个变量所在的结构体已经是指针类型。

使用reflect.ValueOf进行值包装。

通过Value.Elem()获得指针值指向的元素值对象(Value),因为值对象(Value)内部对象为指针时,使用set设置时会报出宕机错误。

使用Value.Set设置值。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值