【Go笔记】反射

简介

  • 反射是程序在运行时检查、修改自身结构和行为的能力,有点相当于自定义runtime
  • 不会使用特别多,一般会作为框架或基础服务的一部分打包使用

使用方法

基本

  • 两个基本方法 - func ValueOf(i interface{}) Value, func TypeOf(i interface{}) Type
  • 可以将reflect.Value看做反射的值,将reflect.Type看做反射的实际类型。后者是一个接口,包含和类型有关的许多方法签名,比如Align, String方法等。reflect.Value是一个结构体,里面有很多方法。二者可以直接用fmt.Println打印出来

通用

  • 还可以使用reflect.Type.Kind()来验证反射的类型是否相同
  • reflect.ValueInterface()方法可以以空接口的形式返回值,如果需要获取真实值则可以再次通过接口的断言语法来对接口进行转换
  • 也可以通过reflect.Value中的String(), Int(), Float()方法快速将类似物转换成标准的string, int64和float64
  • func (v Value) Elem() Value - 使用reflect.ValueOf(&a).Elem()来返回指针或接口内数据的值。注意,如果括号内不是指针或接口,就会报错无法运行
  • func (v Value) Set (x Value) - 使用这个来修改反射中存储的值,要求反射中类类型必须为指针。value提供了CanSet()方法来判断是否可以给该反射赋值

结构体相关

  • 遍历结构体
1
2
3
4
5
6
7
getValue := reflect.ValueOf(s)
getType := reflect.TypeOf(s)
for i := 0; i < getType.NumField(); i++ {
field := getType.Field(i) // 字段名称,使用Name方法获取字段名
value := getValue.Field(i).Interface() // 字段数据,空接口
...
}
  • 想要修改结构体,在构造反射的时候就要传递结构体指针,然后再从结构体指针通过Elem()方法获取结构体值,然后再进行修改。因为reflect.ValueOf(s)得到的是一个值的拷贝,不是原始变量本身,因此Field(0)方法得到的是一个不可寻址的变量副本。而使用reflect.ValueOf(&s).Elem()先是得到了内存地址的反射,然后得到了该内存地址下的数据反射本身,非副本,是可以寻址的,所以可以进行修改。
1
2
3
4
vs := reflect.ValueOf(&s).Elem()
vs := vs.Field(0)
vb := reflect.ValueOf(123)
vx.Set(vb)
  • 嵌套结构体也可以改,把vb改成结构体的反射就好了
  • 获取结构体的方法可以使用reflect.TypeNumMethod()Method(0)等方法,也可以使用MethodByName("string")方法获取value,然后通过.Type()转换为reflect.Type()类型
  • 获取该结构体方法的reflect.Value()后,可以使用Call([]reflect.Value{reflect.ValueOf(1), reflect.ValueOf("string")})来调用
  • 如果方法是指针接收者,那么反射动态调用者的类型也必须是指针
  • 可以使用reflect.StructOf()创建编译时不存在的结构体,其参数是[]reflect.StructField,一个structField里面有Name和Type两个类型
1
2
3
4
5
6
7
8
9
10
11
12
13
func MakeStruct(vals ...interface{}) reflect.Value {
var sfs []reflect.StructField
for k, v := range vals {
t := reflect.TypeOf(v)
sf := reflect.StructField{
Name: fmt.Sprintf("F%d", (k+1)),
Type: t,
}
sfs = append(sfs, sf)
}
st := reflect.StructOf(sfs)
return reflect.New(st)
}

函数与其他类型相关

  • 与结构体的方法调用差不多
  • 如果需要传入指针参数,那么可以使用reflect.New()来生成指定类型的反射指针对象,是一个新对象的指针
  • 可以使用reflect.XXXOf方法来构建指定reflect.Type类型的反射
  • 还可以使用reflect.New()根据反射类型分配相应大小的内存

底层原理

  • reflect.Type实质上是空接口结构体中的typ字段,是rtype类型
  • reflect.TypeOf原理为将传递进来的接口变量转换为底层的实际空接口,并获取其类型值
  • reflect.Value包含了接口中存储的值和类型,还有特殊的flag标志。flag以位图(二进制)的形式存储了反射类型的元数据。其中低5位存储了类型的标志,6~10位代表字段特征,分别为以下
    • 是否为未导出的非嵌套字段(普通字段)
    • 是否为未导出的嵌套字段(匿名字段)
    • 是否为间接字段
    • 是否可寻址
    • 是否为方法
  • 其他位存储了方法的index序号
  • reflect.ValueOf原理就是将空接口解包然后返回Value
  • Interface方法的本质就是将Value打包成接口,先创建一个空接口,然后将类型拷贝。对于空接口中的word字段,会直接将值新建一个副本,避免对原数据造成影响、
  • 动态调用的Call方法有以下几个步骤
    • 获取函数的指针,会调用methodReceiver获取调用者的实际类型、函数类型以及函数指针的位置
    • 有效性验证,例如函数的输入大小和个数是否匹配传参,传参是否能赋值给函数参数等
    • 调用funcLayout,构建函数参数以及返回值的栈帧布局,会提供一个内存缓冲池,用于在没有返回值的场景中复用内存。完成调用后,若没有返回,则将args全部清空,放入缓冲池中;若有返回值,则清空args中输入参数部分,并将输出包装成切片返回。

【Go笔记】反射
https://study.0x535a.cn/go-note/go-reflect/
Author
Stephen Zeng
Posted on
August 26, 2025
Licensed under