学习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的一个用途示例:

  1. 将对象s的地址转换成Pointer类型,转换为uintptr类型

  2. 使用unsafe.Offsetof函数得到字段Address相对于对象头的偏移量,加到s的地址上,得到s.Address的地址

  3. 将s.Address转换成Pointer,再转换成*string类型

  4. 使用指针,修改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。

输出为:

可见有以下两点:

  1. 这是Student类型的方法,方法定义中没有参数,但实际上是把类型的对象,当做了首个参数

  2. *Student类型的方法,不是Student类型的方法

刚才获取的是Student类型的方法。如果在获取type的时候使用*Student呢?结果是不一样的:

  1. Student类型的方法也是*Student类型的方法,反之就不一样

  2. 即使是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