【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.Value
的Interface()
方法可以以空接口的形式返回值,如果需要获取真实值则可以再次通过接口的断言语法来对接口进行转换- 也可以通过
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 |
|
- 想要修改结构体,在构造反射的时候就要传递结构体指针,然后再从结构体指针通过
Elem()
方法获取结构体值,然后再进行修改。因为reflect.ValueOf(s)
得到的是一个值的拷贝,不是原始变量本身,因此Field(0)
方法得到的是一个不可寻址的变量副本。而使用reflect.ValueOf(&s).Elem()
先是得到了内存地址的反射,然后得到了该内存地址下的数据反射本身,非副本,是可以寻址的,所以可以进行修改。
1 |
|
- 嵌套结构体也可以改,把
vb
改成结构体的反射就好了 - 获取结构体的方法可以使用
reflect.Type
的NumMethod()
和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 |
|
¶函数与其他类型相关
- 与结构体的方法调用差不多
- 如果需要传入指针参数,那么可以使用
reflect.New()
来生成指定类型的反射指针对象,是一个新对象的指针 - 可以使用
reflect.XXXOf
方法来构建指定reflect.Type
类型的反射 - 还可以使用
reflect.New()
根据反射类型分配相应大小的内存
底层原理
reflect.Type
实质上是空接口结构体中的typ
字段,是rtype
类型reflect.TypeOf
原理为将传递进来的接口变量转换为底层的实际空接口,并获取其类型值reflect.Value
包含了接口中存储的值和类型,还有特殊的flag标志。flag以位图(二进制)的形式存储了反射类型的元数据。其中低5位存储了类型的标志,6~10位代表字段特征,分别为以下-
- 是否为未导出的非嵌套字段(普通字段)
-
- 是否为未导出的嵌套字段(匿名字段)
-
- 是否为间接字段
-
- 是否可寻址
-
- 是否为方法
- 其他位存储了方法的index序号
reflect.ValueOf
原理就是将空接口解包然后返回ValueInterface
方法的本质就是将Value打包成接口,先创建一个空接口,然后将类型拷贝。对于空接口中的word字段,会直接将值新建一个副本,避免对原数据造成影响、- 动态调用的
Call
方法有以下几个步骤 -
- 获取函数的指针,会调用
methodReceiver
获取调用者的实际类型、函数类型以及函数指针的位置
- 获取函数的指针,会调用
-
- 有效性验证,例如函数的输入大小和个数是否匹配传参,传参是否能赋值给函数参数等
-
- 调用
funcLayout
,构建函数参数以及返回值的栈帧布局,会提供一个内存缓冲池,用于在没有返回值的场景中复用内存。完成调用后,若没有返回,则将args全部清空,放入缓冲池中;若有返回值,则清空args中输入参数部分,并将输出包装成切片返回。
- 调用
【Go笔记】反射
https://study.0x535a.cn/go-note/go-reflect/