学习golang有一段时间了,但对框架中的反射代码仍然不是很清楚,同Java相比,golang中多出了“指针”这一概念,在学习的时候需要稍加注意。
golang中有“指针”的概念,但是和C/C++比起来,又有少许的不同,比如有:
C语言中的指针可以做+/-操作,典型的就是指向数组的指针。golang中不允许这种操作。
golang不允许指针类型的直接转换。
此时就需要用到unsafe.Pointer和uintptr两个类。
unsafe.Pointer
Pointer定义在unsafe包下unsafe.go文件中,其定义很简单:
ArbitraryType类型是一个占位符一般的存在,没有任何意义,如同他的字面意思一样,可以代替任何类型。这样,可以把Pointer理解成为C语言中的(void *)类型。
golang中的指针转换都要依赖于Pointer类,如下:
打印出来的结果是:
也就是拿到了a的地址,把a使用的内存使用float32 1.024重写,再当做int32打印出来,得到的结果。这段代码如果没有第二行,直接把int32转换成float32,编译将报错。
Pointer类的注释中描述了类的使用方法:
任意的指针类型可以转换成Pointer
Pointer可以转换成任意的指针类型
uintptr可以转换成Pointer
Pointer可以转换成uintptr
更详细的使用方法,可以参考Pointer类的注释。
uintptr
uintptr定义在builtin包的builtin.go文件中。从注释看,uintptr就是一个整型值,但是足够大,可以保存任意指针的地址。
若一个对象仅能通过一个uintptr类型的指针访问到,却不再有其他引用时,这个对象会被回收。换句话说,uintptr又不像是一个指针,它不算在对象的引用计数中,也不能阻止对象被回收。
uintptr的一个用途示例:
将对象s的地址转换成Pointer类型,转换为uintptr类型
使用unsafe.Offsetof函数得到字段Address相对于对象头的偏移量,加到s的地址上,得到s.Address的地址
将s.Address转换成Pointer,再转换成*string类型
使用指针,修改s.Address的值
从输出可见,对象的Address字段的值被成功修改。
预定义一个Student类:
使用reflect.TypeOf方法,即可以获得对象的type信息。
Kind
type上的Kind方法,可以知道该type是哪一“种类”的对象。Kind只是一个枚举值,但是type的很多方法都要先判断Kind。比如,Elem()方法,获得指针、切片、数、通道等的“元素”的type,如果不是这几种type,调用Elem()方法时会抛出panic。
Kind枚举的定义如下,大体分为:所有自带的基础类型(int16,int32……)、指针、集合类型(数组、切片、映射)、通道、函数、结构……
使用的示例如下:
结果就是如期打印出了字符串。
Field
使用NumField方法得到字段的数量,再通过Field方法得到第X个字段。当然,也有FieldByName方法,根据字段的名称得到字段。
Student的最后两个字段分别是[]string,*string类型,因此可以使用Elem方法,获得其“元素”的类型。
json序列化时在字段后面反引号(``)中的内容称作Tag,可以使用反射解析出来(相当于Java中字段的注解)。代码片段的最后两行演示了如何获取其中内容。
输出:
Method
Method和Field的获取方法非常类似,通过NumMethod方法获得数量,再通过Method方法或者MethodByName方法,获得方法的引用。
Method相比Field,没有Tag,但是有参数、返回值的概念,通过NumIn、NumOut方法获得其数量,通过In、Out方法获取第X个参数/返回值的Type。
输出为:
可见有以下两点:
这是Student类型的方法,方法定义中没有参数,但实际上是把类型的对象,当做了首个参数
*Student类型的方法,不是Student类型的方法
刚才获取的是Student类型的方法。如果在获取type的时候使用*Student呢?结果是不一样的:
Student类型的方法也是*Student类型的方法,反之就不一样
即使是Student类型的方法,此时首个参数也是*Student
Type中存储的是“类型”相关的信息,例如类型、字段、方法的属性等,要通过反射操作对象的值,需要使用value。
这是一个使用的示例。先用ValueOf得到反射的Value,拿到字段,获取这个字段的值,并用SetXXX方法修改该字段的值。
输出结果:
注意v的初始化方式:先获取其指针的value,再通过Elem得到对象本身的value。如果像被注释的一行一样,直接获取s的value,则其字段不能够被赋值。
调用方法的示例:先用MethodByName得到方法的value,再通过Call执行这个方法。参数、返回值都是以[]reflect.Value的形式传进去的。
转自:https://blog.csdn.net/muddledape/article/details/102147365