Go语言之reflect包源码分析

442 阅读4分钟

「这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战

package reflect

建议对着源码看

包说明

文档中指出reflect包实现了运行时反射。 什么是反射,反射允许程序操纵任意类型的对象。 通常用法是:对于一个接口变量,使用TypeOf来提取动态类型,她会返回一个Type。

其他编程语言上的反射: 反射是一种能力,就是程序可以在运行时检查其右拥有的结构,特别是类型。 不同语言对反射机制的实现是不同的,Go使用的reflect包。 是元编程的一种形式。

Rob的反射三定律:

  • 接口对象可以转成反射对象
  • 反射对象可以转成接口对象,interface{}
  • 反射对象如果要修改,要符合传址。结构体中只有暴露字段才具有可修改性

包结构分析

除了测试文件,主要包含5个源文件: type.go value.go表示反射对象中的类型和值; swapper.go 提供了一个封装函数来交换切片中的两个元素; makefunc.go 封装了一些跟函数相关的东西,后面详细看; deepequal.go 底层比较两个变量是否一致,这个用的比较多。

总体说来,type.go是基础,value.go算是补全了reflect的绝大部分功能, 其他的是基于这两个文件来扩展的,所以先分析这两个。

Type

这里的Type是指Go的类型,type Type interface {...}, Type是一个接口类型,她里面包含了很多方法集,并不是所有方法都适用于Go的任意类型, 具体还是要看文档的说明,一般是先调用Kind方法,根据类型再调指定的方法, 好处是不会panic。

Type是支持==比较的,所以可以作为map的key。

下面列一下Type接口的方法集:

  • 适用于任意Go类型的方法集
    • 内存对齐是几字节
    • 结构体字段对齐是几字节
    • Method(int),获取第几个方法
      • 参数不能超出范围
      • 对于非接口类型,返回一个方法的Type和函数字段(接收者作为第一个参数)
      • 对于接口类型,返回方法的Type(就是方法的签名),函数字段是nil
      • 只能访问对外暴露的方法,按字典顺序排序
    • 按方法名获取指定方法
    • 计算暴露的方法个数
    • 获取包定义的类型名,未定义类型返回空字符串
    • 获取包路径,就是包的导入路径,如果是预定义类型或未定义类型,返回空字符串
    • 获取内存存储的大小,和unsafe.Sizeof得到的一致
    • 支持打印
    • Kind获取类型的种类,后面会提到具体哪些种类,底层类型,不区分别名系统
    • 是否实现了某个接口
    • 是否可赋值给某个类型的变量
    • 是否可转换成某个类型
    • 是否支持比较,这几个都是针对具体类型的,Type接口类型是支持比较的
  • 依赖具体类型,而适用的方法集
    • Bits 获取类型的位数(bit,不是字节数),适用于整形/浮点/复数
    • ChanDir 获取信道方向,只适用于信道
    • IsVariadic 是否支持可变参,只适用于函数
    • Elem 获取元素的具体类型,只适用于数组/信道/map/指针/切片
    • Field 获取结构体的第几个字段,只适用于结构体,范围不能越界
    • FieldByIndex 获取嵌套结构体的字段
    • FieldByName 获取结构体的指定名称的字段
    • FieldByNameFunc 广度优先寻找字段,只适用于结构体,特别适合有嵌入字段的
    • In 获取第几个入参的类型,只适用于函数,范围不能越界
    • Key 返回map的key类型
    • Len 返回数组的长度
    • NumField 返回结构体的字段个数
    • NumIn 返回函数入参个数
    • NumOut 返回函数返回值个数
    • Out 返回函数第几个返回值的类型

之后定义了Kind,就是一个枚举,列举了所有内置类型

还有一个rtype结构体,也做了很多事,可以在后面遇到了再分析。

Value

和Type类似,都有好多方法,不过Value是结构体, 其中Interface是将反射对象转成接口对象,Elem用于修改反射对象的值等

其他封装

DeepEqual比较两个数据的底层结构,都是比较常用的。