Go语言反射reflect深入理解(6)

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

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

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

package main import "reflect" func main() { type dog struct { legCount int } //获取dog实例的反射值对象 valueOfDog := reflect.ValueOf(&dog{}) valueOfDog = valueOfDog.Elem() //获取legCount字段的值 vLegCount := valueOfDog.FieldByName("legCount") //尝试设置legCount的值(这里会发生崩溃) vLegCount.SetInt(4) }

程序发生崩溃,报错:

panic: reflect: reflect.Value.SetInt using value obtained using unexported field

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

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

package main import ( "fmt" "reflect" ) func main() { type dog struct { LegCount int } //获取dog实例的反射值对象 valueOfDog := reflect.ValueOf(&dog{}) //// 取出dog实例地址的元素 valueOfDog = valueOfDog.Elem() //获取legCount字段的值 vLegCount := valueOfDog.FieldByName("LegCount") //尝试设置legCount的值 vLegCount.SetInt(4) fmt.Println(vLegCount.Int()) }

代码输出如下:

4

代码说明如下:

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

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

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

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

第 24 行,修改该成员值。

第 26 行,打印该成员值。

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

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

使用 reflect.ValueOf 进行值包装。

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

使用 Value.SetXXX 设置值。

通过类型信息创建实例

当已知 reflect.Type 时,可以动态地创建这个类型的实例,实例的类型为指针。例如 reflect.Type 的类型为 int 时,创建 int 的指针,即*int,代码如下:

package main import ( "fmt" "reflect" ) func main() { var a int //取变量a的反射类型对象 typeOfA := reflect.TypeOf(a) //根据反射类型对象创建类型实例 aIns := reflect.New(typeOfA) //输出Value的类型和种类 fmt.Println(aIns.Type(), aIns.Kind()) }

代码输出结果如下

*int ptr

代码说明如下:

第 13 行,获取变量 a 的反射类型对象。

第 16 行,使用 reflect.New() 函数传入变量 a 的反射类型对象,创建这个类型的实例值,值以 reflect.Value 类型返回。这步操作等效于:new(int),因此返回的是 *int 类型的实例。

第 19 行,打印 aIns 的类型为 *int,种类为指针。

通过反射调用函数

如果反射值对象(reflect.Value)中值的类型为函数时,可以通过 reflect.Value 调用该函数。使用反射调用函数时,需要将参数使用反射值对象的切片 []reflect.Value 构造后传入 Call() 方法中,调用完成时,函数的返回值通过 []reflect.Value 返回。

下面的代码声明一个加法函数,传入两个整型值,返回两个整型值的和。将函数保存到反射值对象(reflect.Value)中,然后将两个整型值构造为反射值对象的切片([]reflect.Value),使用 Call() 方法进行调用。

反射调用函数:

package main import ( "fmt" "reflect" ) //普通函数 func add(a, b int) int { return a + b } func main() { //将函数包装为反射值对象 funcValue := reflect.ValueOf(add) //构造函数参数,传入两个整形值 paramList := []reflect.Value{reflect.ValueOf(2), reflect.ValueOf(3)} //反射调用函数 retList := funcValue.Call(paramList) fmt.Println(retList[0].Int()) }

代码说明如下:

第 9~12 行,定义一个普通的加法函数。

第 17 行,将 add 函数包装为反射值对象。

第 20 行,将 10 和 20 两个整型值使用 reflect.ValueOf 包装为 reflect.Value,再将反射值对象的切片 []reflect.Value 作为函数的参数。

第 23 行,使用 funcValue 函数值对象的 Call() 方法,传入参数列表 paramList 调用 add() 函数。

第 26 行,调用成功后,通过 retList[0] 取返回值的第一个参数,使用 Int 取返回值的整数值。

提示

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/98792baee2e8e1e9e62ecdf5d74bb646.html