如何做网站产品图片/媒体营销
在go的源码包及一些开源组件中,经常可以看到reflect反射包的使用,本文就与大家一起探讨go反射机制的原理、学习其实现源码
首先,了解一下反射的定义:
反射是指计算机程序能够在运行时,能够描述其自身状态或行为、调整或修改其状态或行为的能力。
具体到go的反射机制,对应为:
go提供了在运行时检查变量的值、更新变量的值和调用它们的方法的机制,而在编译时并不知道这些变量的具体类型
接口
反射与Interface息息相关,反射是通过Interface的类型信息实现的,为更方便学习go反射机制,先复习一下Interface相关知识
看一下空接口Interface的结构定义
//runtime/runtime2.go
type eface struct {_type *_typedata unsafe.Pointer
}
data表示指向数据的指针,_type是所有类型的公共描述信息
_type类型结构的具体定义为
//runtime/type.go
type _type struct {size uintptrptrdata uintptr // size of memory prefix holding all pointershash uint32 tflag tflagalign uint8 fieldalign uint8 kind uint8 alg *typeAlg // gcdata stores the GC type data for the garbage collector.// If the KindGCProg bit is set in kind, gcdata is a GC program.// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.gcdata *byte str nameOffptrToThis typeOff
}
其中size是类型的大小,hash是类型的哈希值;tflag是类型的tag信息,与反射相关,align和fieldalign与内存对齐相关,kind是类型编号,具体定义位于runtime/typekind.go中,gcdata是gc相关信息
通过空接口的定义可以看出,变量在转换为Interface时,接口值保存了变量的类型及指针两种信息,通过类型信息的对应实现接口与变量之间的互相转换。
go反射三定律
go反射机制在使用时可以总结为以下三定律
- 反射可以将“接口类型变量”转换为“反射类型对象”
- 反射可以将“反射类型对象”转换为“接口类型变量”
- 如果要修改“反射类型对象”,其值必须是“可写的”(settable)
接下来就结合这三个定律来学习reflect的源码
定律一
反射可以将“接口类型变量”转换为“反射类型对象”
反射类型对象有两种,分别是Type和Value,Type类型对应一个变量的类型信息,而Value类型则对应一个变量的值信息
Type
Type为一个接口类型,接口包含了所有可对类型信息执行的操作。其中可导出的方法偶尔会在一些源码包里看到,而未导出的方法只在内部使用,common方法返回类型基本信息,uncommon方法返回保留类型实现方法的uncommonType类型。
type Type interface {Align() intFieldAlign() intMethod(int) MethodMethodByName(string) (Method, bool)NumMethod() intName() stringString() stringField(i int) StructFieldFieldByName(name string) (StructField, bool)//....common() *rtypeuncommon() *uncommonType
}
反射包通过reflect.Typeof()方法返回Type类型,下面是方法的实现
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {eface := *(*emptyInterface)(unsafe.Pointer(&i))return toType(eface.typ)
}func toType(t *rtype) Type {if t == nil {return nil}return t
}
方法很简短,第一步将空接口类型强转为emptyInterface类型,第二步,toType取出其中typ字段。这个函数的关键在于第一步的类型转换,前面复习了空接口eface的定义,再来看一下emptyInterface的定义以及其中typ字段的定义
// emptyInterface is the header for an interface{} value.
type emptyInterface struct {typ *rtypeword unsafe.Pointer
}
type rtype struct {size uintptrptrdata uintptr // number of bytes in the type that can contain pointershash uint32 // hash of type; avoids computation in hash tablestflag tflag // extra type information flagsalign uint8 // alignment of variable with this typefieldAlign uint8 // alignment of struct field with this typekind uint8 // enumeration for Calg *typeAlg // algorithm tablegcdata *byte // garbage collection datastr nameOff // string formptrToThis typeOff // type for pointer to this type, may be zero
}
很清晰发现这两个结构完全相同,使用unsafe.Poniter做类型转换,也就取出了变量的类型信息及地址
reflect.Typeof方法最终返回了包含变量类型信息的rtype类型的指针,也就是表示,rtype实现了Type接口的所有方法,这些方法分为通用方法和类型特有方法两类
// 通用方法
func (t *rtype) String() string // 获取 t 类型的字符串描述,不要通过 String 来判断两种类型是否一致。func (t *rtype) Name() string // 获取 t 类型在其包中定义的名称,未命名类型则返回空字符串。func (t *rtype) PkgPath() string // 获取 t 类型所在包的名称,未命名类型则返回空字符串。func (t *rtype) Kind() reflect.Kind // 获取 t 类型的类别。func (t *rtype) Size() uintptr // 获取 t 类型的值在分配内存时的大小,功能和 unsafe.SizeOf 一样。func (t *rtype) Align() int // 获取 t 类型的值在分配内存时的字节对齐值。func (t *rtype) FieldAlign() int // 获取 t 类型的值作为结构体字段时的字节对齐值。func (t *rtype) NumMethod() int // 获取 t 类型的方法数量。func (t *rtype) Method() reflect.Method // 根据索引获取 t 类型的方法,如果方法不存在,则 panic。
// 如果 t 是一个实际的类型,则返回值的 Type 和 Func 字段会列出接收者。
// 如果 t 只是一个接口,则返回值的 Type 不列出接收者,Func 为空值。func (t *rtype) MethodByName(string) (reflect.Method, bool) // 根据名称获取 t 类型的方法。func (t *rtype) Implements(u reflect.Type) bool // 判断 t 类型是否实现了 u 接口。func (t *rtype) ConvertibleTo(u reflect.Type) bool // 判断 t 类型的值可否转换为 u 类型。func (t *rtype) AssignableTo(u reflect.Type) bool // 判断 t 类型的值可否赋值给 u 类型。func (t *rtype) Comparable() bool // 判断 t 类型的值可否进行比较操作
// 注意对于:数组、切片、映射、通道、指针、接口
func (t *rtype) Elem() reflect.Type // 获取元素类型、获取指针所指对象类型,获取接口的动态类型// 数组
func (t *rtype) Len() int // 获取数组的元素个数// Map
func (t *rtype) Key() reflect.Type // 获取映射的键类型// 通道
func (t *rtype) ChanDir() reflect.ChanDir // 获取通道的方向// 结构体
func (t *rtype) NumField() int // 获取字段数量func (t *rtype) Field(int) reflect.StructField // 根据索引获取字段func (t *rtype) FieldByName(string) (reflect.StructField, bool) // 根据名称获取字段func (t *rtype) FieldByNameFunc(match func(string) bool) (reflect.StructField, bool) // 根据指定的匹配函数 math 获取字段func (t *rtype) FieldByIndex(index []int) reflect.StructField // 根据索引链获取嵌套字段// 函数
func (t *rtype) NumIn() int // 获取函数的参数数量func (t *rtype) In(int) reflect.Type // 根据索引获取函数的参数信息func (t *rtype) NumOut() int // 获取函数的返回值数量func (t *rtype) Out(int) reflect.Type // 根据索引获取函数的返回值信息func (t *rtype) IsVariadic() bool // 判断函数是否具有可变参数。
// 如果有可变参数,则 t.In(t.NumIn()-1) 将返回一个切片。
在这些方法中,以String()、Field()、NumMethod()分别为例探究一下源码的实现
String()方法返回类型名称,当fmt输出时以%T格式输出变量类型名称,取的也是本方法的结果
rtype的str字段为类型名字偏移量,nameoff方法根据str偏移量转为name类型,最后调用name类型的name方法返回[]byte类型
func (t *rtype) String() string {s := t.nameOff(t.str).name()if t.tflag&tflagExtraStar != 0 {return s[1:]}return s
}
func (t *rtype) nameOff(off nameOff) name {return name{(*byte)(resolveNameOff(unsafe.Pointer(t), int32(off)))}
}
//resolveNameOff在/runtime/type.go中实现
func resolveNameOff(ptrInModule unsafe.Pointer, off nameOff) name {if off == 0 {return name{}}base := uintptr(ptrInModule)for md := &firstmoduledata; md != nil; md = md.next {if base >= md.types && base < md.etypes {res := md.types + uintptr(off)if res > md.etypes {println("runtime: nameOff", hex(off), "out of range", hex(md.types), "-", hex(md.etypes))throw("runtime: name offset out of range")}return name{(*byte)(unsafe.Pointer(res))}}}// No module found. see if it is a run time name.reflectOffsLock()res, found := reflectOffs.m[int32(off)]reflectOffsUnlock()if !found {println("runtime: nameOff", hex(off), "base", hex(base), "not in ranges:")for next := &firstmoduledata; next != nil; next = next.next {println("\ttypes", hex(next.types), "etypes", hex(next.etypes))}throw("runtime: name offset base pointer out of range")}return name{(*byte)(res)}
}
nameoff方法根据rtype类型的name偏移量str字段,得到名称*byte的全局地址,再根据name结构的方法解析格式,得出name、tag、pkgPath等信息。
// name is an encoded type name with optional extra data.
// The first byte is a bit field containing:
//
// 1<<0 the name is exported
// 1<<1 tag data follows the name
// 1<<2 pkgPath nameOff follows the name and tag
//
// The next two bytes are the data length:
//
// l := uint16(data[1])<<8 | uint16(data[2])
//
// Bytes [3:3+l] are the string data.
//
// If tag data follows then bytes 3+l and 3+l+1 are the tag length,
// with the data following.
type name struct {bytes *byte
}
//name结构方法,与runtime/type.go中一致
func (n name) nameLen() int
func (n name) tagLen() int
func (n name) name() (s string)
func (n name) tag() (s string)
func (n name) pkgPath() string
回过头来看runtime中resolveNameOff方法,firstmoduledata是一个全局的变量,保存了编译过程中所有类型的名称、类型等信息,与nameoff类型的str字段相同,rtype中typeOff类型的ptrToThis为类型信息偏移量,查找方法逻辑同resolveNameOff
下面通过一小段代码来验证类型和名称信息是全局唯一的,采用的方法同反射中以emptyInterface结构做类型转换一样
由于TypeOf返回为接口类型,不便于操作,我们以Value类型为例,Value类型包含一个rtype类型字段
type TestType struct {Name stringAge int
}
type value struct {typ *ltypeptr unsafe.Pointerflag uintptr
}
//ltype字段定义同rtype字段
type ltype struct {size uintptrptrdata uintptrhash uint32tflag uint8align uint8fieldAlign uint8kind uint8alg *intgcdata *bytestr int32ptrToThis int32
}
func main() {var a TestTypevar b TestTypevalueA := reflect.ValueOf(a)valueB := reflect.ValueOf(b)usA := (*value)(unsafe.Pointer(&valueA))usB := (*value)(unsafe.Pointer(&valueB))fmt.Printf("A.str:%X, B.str:%X\n", usA.typ.str, usB.typ.str)fmt.Printf("A.ptrToThis:%X, B.ptrToThis:%X\n", usA.typ.ptrToThis, usB.typ.ptrToThis)fmt.Printf("a.typ:%p, b.typ:%p\n", usA.typ, usB.typ)
}
通过value类型转换,分别取出a、b两个变量类型信息,再分别打印出a和b的name偏移量、类型偏移量,类型结构指向地址
来看一下结果,验证是否为全局信息
A.str:6206, B.str:6206
A.ptrToThis:B320, B.ptrToThis:B320
a.typ:0x4a5360, b.typ:0x4a5360
所以,编码过程中用到的所有变量,在编译构建过程中,已经将所有的类型信息保存在全局空间内,生成反射对象、接口类型转化等过程都是在取这一些类型信息
继续研究下一个Field方法
Field方法是struct类型的特有方法,方法首先就判断了类型,类型不匹配,直接panic
func (t *rtype) Field(i int) StructField {if t.Kind() != Struct {panic("reflect: Field of non-struct type")}tt := (*structType)(unsafe.Pointer(t))return tt.Field(i)
}
//与runtime/type.go中定义同步
type structType struct {rtypepkgPath namefields []structField // sorted by offset
}
// Field方法返回第i个Field
func (t *structType) Field(i int) (f StructField) {if i < 0 || i >= len(t.fields) {panic("reflect: Field index out of bounds")}p := &t.fields[i]f.Type = toType(p.typ)f.Name = p.name.name()f.Anonymous = p.embedded()if !p.name.isExported() {f.PkgPath = t.pkgPath.name()}if tag := p.name.tag(); tag != "" {f.Tag = StructTag(tag)}f.Offset = p.offset()f.Index = []int{i}return
}
方法关键的第一步,将rtype指针转为structType类型指针,structType类型第一个字段rtype为类型的基本信息,后面两个字段为类型特有信息,最终通过sturctType类型的Field方法返回StructField类型
再来看一下其他类型结构,其第一个字段均为rtype类型,且与runtime/type.go中定义同步
type sliceType struct {rtypeelem *rtype // slice element type
}
type ptrType struct { rtypeelem *rtype // pointer element (pointed at) type
}
type arrayType struct {rtypeelem *rtype // array element typeslice *rtype // slice typelen uintptr
}
type chanType struct {rtypeelem *rtype // channel element typedir uintptr // channel direction (ChanDir)
}
对于特殊类型,通用类型信息指针*rtype其实是执行其完整类型信息,只是再TypeOf时只取出了第一个字段
还是用一小段代码验证一下structType类型的转换
type TestType struct {Name stringAge int
}
type name struct {bytes *byte
}
type structType struct {ltypepkgPath namefields []structField
}
type structField struct {name nametyp *ltypeoffsetEmbed uintptr
}
func main() {var a TestTypevalueA := reflect.ValueOf(a)usA := (*value)(unsafe.Pointer(&valueA))stA := (*structType)(unsafe.Pointer(usA.typ))fmt.Printf("field length:%d\n", len(stA.fields))fmt.Printf("field 0 kind:%b\n", stA.fields[0].typ.kind)fmt.Printf("field 1 kind:%b\n", stA.fields[1].typ.kind)
}
再来验证一下结果,与TestType类型是否一致,字段数量2,第一个字段类型2值为24,类型为string,第二个字段类型值为2,类型为int,完全一致
field length:2
field 0 kind:11000
field 1 kind:10000010
最后是第三个方法NumMethod
Type接口的NumMethod方法返回方法数量,首先判断了是否是Interface类型,是调用Interface类型的NumMethod方法,与Field类似
func (t *rtype) NumMethod() int { if t.Kind() == Interface {tt := (*interfaceType)(unsafe.Pointer(t))return tt.NumMethod()}return len(t.exportedMethods())
}
func (t *rtype) exportedMethods() []method {ut := t.uncommon()if ut == nil {return nil } return ut.exportedMethods()
}
type uncommonType struct {pkgPath nameOff // import path; empty for built-in types like int, stringmcount uint16 // number of methodsxcount uint16 // number of exported methodsmoff uint32 // offset from this uncommontype to [mcount]method_ uint32 // unused
}
func (t *uncommonType) exportedMethods() []method {if t.xcount == 0 {return nil} return (*[1 << 16]method)(add(unsafe.Pointer(t), uintptr(t.moff), "t.xcount > 0"))[:t.xcount:t.xcount]
}
其他类型先根据uncommon方法得到uncommonType类型位置,调用exportedMethods方法取出method信息
这个方法的关键在于uncommon,看一下uncommon方法的源码
func (t *rtype) uncommon() *uncommonType {if t.tflag&tflagUncommon == 0 {return nil}switch t.Kind() {case Struct:return &(*structTypeUncommon)(unsafe.Pointer(t)).ucase Ptr:type u struct {ptrTypeu uncommonType}return &(*u)(unsafe.Pointer(t)).ucase Func:type u struct {funcTypeu uncommonType}return &(*u)(unsafe.Pointer(t)).ucase Slice:type u struct {sliceTypeu uncommonType}return &(*u)(unsafe.Pointer(t)).ucase Array:type u struct {arrayTypeu uncommonType}return &(*u)(unsafe.Pointer(t)).ucase Chan:type u struct {chanTypeu uncommonType}return &(*u)(unsafe.Pointer(t)).ucase Map:type u struct {mapTypeu uncommonType}return &(*u)(unsafe.Pointer(t)).ucase Interface:type u struct {interfaceTypeu uncommonType}return &(*u)(unsafe.Pointer(t)).udefault:type u struct {rtypeu uncommonType}return &(*u)(unsafe.Pointer(t)).u}
}
每种类型的处理方法都是一样的,定义一个临时类型,第一个字段为类型信息,第二个字段为uncommonType类型。说明所有实现了方法的类型,其类型信息的地址后都紧跟着一个uncommonType类型,这个类型里维护相关方法信息
同样的,一段代码验证一下
func (t TestType) Func1(in string) error {return nil
}
func (t TestType) Func2() {return
}
type u struct {structTypeu uncommonType
}
type uncommonType struct {pkgPath int32mcount uint16xcount uint16moff uint32_ uint32
}
func main() {var a TestTypevalueA := reflect.ValueOf(a)usA := (*value)(unsafe.Pointer(&valueA))ucA := (*u)(unsafe.Pointer(usA.typ))fmt.Printf("uncommonType mcount:%d\n", ucA.u.mcount)fmt.Printf("uncommonType moff:%d\n", ucA.u.moff)
}
对照一下验证结果
uncommonType mcount:2
uncommonType moff:64
mcount表示方法类型,moff方法信息偏移量,是64而不是16,所以可推论方法信息不是紧接着uncommonType排列的
简单来做第一个总结,所有类型的类型信息均在编译时确定,并保存在全局变量中,这些类型信息的排列如图:
可以通过通用类型指针,取到其类型特有信息,类型方法信息等
Value
Value是一个类型,通过reflect方法ValueOf方法返回,Value是存放类型值信息的结构,先来看一下定义
type Value struct {typ *rtype// Pointer-valued data or, if flagIndir is set, pointer to data.// Valid when either flagIndir is set or typ.pointers() is true.ptr unsafe.Pointer// flag holds metadata about the value.// The lowest bits are flag bits:// - flagStickyRO: obtained via unexported not embedded field, so read-only// - flagEmbedRO: obtained via unexported embedded field, so read-only// - flagIndir: val holds a pointer to the data// - flagAddr: v.CanAddr is true (implies flagIndir)// - flagMethod: v is a method value.// The next five bits give the Kind of the value.// This repeats typ.Kind() except for method values.// The remaining 23+ bits give a method number for method values.// If flag.kind() != Func, code can assume that flagMethod is unset.// If ifaceIndir(typ), code can assume that flagIndir is set.flag
相比Type接口的底层rtype不同,Value类型多了两个字段,分别是ptr指向数据的指针,flag标识位,指针指向变量数据。而相比空接口类型,多了这个flag字段,flag是Value内部方法中的一个比较关键的数据,用在很多判断条件中
func ValueOf(i interface{}) Value {if i == nil {return Value{}}escapes(i)return unpackEface(i)
}
func unpackEface(i interface{}) Value {e := (*emptyInterface)(unsafe.Pointer(&i))// NOTE: don't read e.word until we know whether it is really a pointer or not.t := e.typif t == nil {return Value{}}f := flag(t.Kind())if ifaceIndir(t) {f |= flagIndir}return Value{t, e.word, f}
}
//逃逸方法
func escapes(x interface{}) {if dummy.b {dummy.x = x}
}
var dummy struct {b boolx interface{}
}
ValueOf方法还是通过接口类型转为包内的emptyInterface类型,取出变量类型信息和变量地址
Value提供以下方法,可分为取值操作、设置值操作、类型特有方法三类
//用于获取值方法
func (v Value) Int() int64 // 获取int类型值,如果 v 值不是有符号整型,则 panic。func (v Value) Uint() uint64 // 获取unit类型的值,如果 v 值不是无符号整型(包括 uintptr),则 panic。func (v Value) Float() float64 // 获取float类型的值,如果 v 值不是浮点型,则 panic。func (v Value) Complex() complex128 // 获取复数类型的值,如果 v 值不是复数型,则 panic。func (v Value) Bool() bool // 获取布尔类型的值,如果 v 值不是布尔型,则 panic。func (v Value) Len() int // 获取 v 值的长度,v 值必须是字符串、数组、切片、映射、通道。func (v Value) Cap() int // 获取 v 值的容量,v 值必须是数值、切片、通道。func (v Value) Index(i int) reflect.Value // 获取 v 值的第 i 个元素,v 值必须是字符串、数组、切片,i 不能超出范围。func (v Value) Bytes() []byte // 获取字节类型的值,如果 v 值不是字节切片,则 panic。func (v Value) Slice(i, j int) reflect.Value // 获取 v 值的切片,切片长度 = j - i,切片容量 = v.Cap() - i。
// v 必须是字符串、数值、切片,如果是数组则必须可寻址。i 不能超出范围。func (v Value) Slice3(i, j, k int) reflect.Value // 获取 v 值的切片,切片长度 = j - i,切片容量 = k - i。
// i、j、k 不能超出 v 的容量。i <= j <= k。
// v 必须是字符串、数值、切片,如果是数组则必须可寻址。i 不能超出范围。func (v Value) MapIndex(key Value) reflect.Value // 根据 key 键获取 v 值的内容,v 值必须是映射。
// 如果指定的元素不存在,或 v 值是未初始化的映射,则返回零值(reflect.ValueOf(nil))func (v Value) MapKeys() []reflect.Value // 获取 v 值的所有键的无序列表,v 值必须是映射。
// 如果 v 值是未初始化的映射,则返回空列表。func (v Value) OverflowInt(x int64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是有符号整型。func (v Value) OverflowUint(x uint64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是无符号整型。func (v Value) OverflowFloat(x float64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是浮点型。func (v Value) OverflowComplex(x complex128) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是复数型。//设置值方法
func (v Value) SetInt(x int64) //设置int类型的值func (v Value) SetUint(x uint64) // 设置无符号整型的值func (v Value) SetFloat(x float64) // 设置浮点类型的值func (v Value) SetComplex(x complex128) //设置复数类型的值func (v Value) SetBool(x bool) //设置布尔类型的值func (v Value) SetString(x string) //设置字符串类型的值func (v Value) SetLen(n int) // 设置切片的长度,n 不能超出范围,不能为负数。func (v Value) SetCap(n int) //设置切片的容量func (v Value) SetBytes(x []byte) //设置字节类型的值func (v Value) SetMapIndex(key, val reflect.Value) //设置map的key和value,前提必须是初始化以后,存在覆盖、不存在添加//其他方法
//结构体相关:
func (v Value) NumField() int // 获取结构体字段(成员)数量func (v Value) Field(i int) reflect.Value //根据索引获取结构体字段func (v Value) FieldByIndex(index []int) reflect.Value // 根据索引链获取结构体嵌套字段func (v Value) FieldByName(string) reflect.Value // 根据名称获取结构体的字段,不存在返回reflect.ValueOf(nil)func (v Value) FieldByNameFunc(match func(string) bool) Value // 根据匹配函数 match 获取字段,如果没有匹配的字段,则返回零值(reflect.ValueOf(nil))//chan相关:
func (v Value) Send(x reflect.Value)// 发送数据(会阻塞),v 值必须是可写通道。func (v Value) Recv() (x reflect.Value, ok bool) // 接收数据(会阻塞),v 值必须是可读通道。func (v Value) TrySend(x reflect.Value) bool // 尝试发送数据(不会阻塞),v 值必须是可写通道。func (v Value) TryRecv() (x reflect.Value, ok bool) // 尝试接收数据(不会阻塞),v 值必须是可读通道。func (v Value) Close() // 关闭通道//函数相关
func (v Value) Call(in []Value) (r []Value) // 通过参数列表 in 调用 v 值所代表的函数(或方法)。函数的返回值存入 r 中返回。
// 要传入多少参数就在 in 中存入多少元素。
// Call 即可以调用定参函数(参数数量固定),也可以调用变参函数(参数数量可变)。func (v Value) CallSlice(in []Value) []Value // 调用变参函数
定律二
反射可以将“反射类型对象”转换为“接口类型变量”
Value类型可以转换为原空接口类型,方法如下:
func (v Value) Interface() (i interface{}) {return valueInterface(v, true)
}
func valueInterface(v Value, safe bool) interface{} {if v.flag == 0 {panic(&ValueError{"reflect.Value.Interface", 0})}if safe && v.flag&flagRO != 0 {panic("reflect.Value.Interface: cannot return value obtained from unexported field or method")}if v.flag&flagMethod != 0 {v = makeMethodValue("Interface", v)}if v.kind() == Interface {if v.NumMethod() == 0 {return *(*interface{})(v.ptr)}return *(*interface {M()})(v.ptr)}// TODO: pass safe to packEface so we don't need to copy if safe==true?return packEface(v)
}
func packEface(v Value) interface{} {t := v.typvar i interface{}e := (*emptyInterface)(unsafe.Pointer(&i))// First, fill in the data portion of the interface.switch {case ifaceIndir(t):if v.flag&flagIndir == 0 {panic("bad indir")}// Value is indirect, and so is the interface we're making.ptr := v.ptrif v.flag&flagAddr != 0 {// TODO: pass safe boolean from valueInterface so// we don't need to copy if safe==true?c := unsafe_New(t)typedmemmove(t, c, ptr)ptr = c}e.word = ptrcase v.flag&flagIndir != 0:// Value is indirect, but interface is direct. We need// to load the data at v.ptr into the interface data word.e.word = *(*unsafe.Pointer)(v.ptr)default:// Value is direct, and so is the interface.e.word = v.ptr}e.typ = treturn i
}
packEface对应之前的unpackEface,完成空接口类型与反射变量类型的转换
方法比较容易理解,其中有个flagIndir的判断需要再深入理解下,先详细看一下Value的flag字段含义
// - flagStickyRO: obtained via unexported not embedded field, so read-only// - flagEmbedRO: obtained via unexported embedded field, so read-only// - flagIndir: val holds a pointer to the data// - flagAddr: v.CanAddr is true (implies flagIndir)// - flagMethod: v is a method value.
flagIndir是标识当前指针即通过空接口传进来的指针是实际数据还是指向数据的指针,标识是在ValueOf中设置的,判断依据也是ifaceIndir这个方法
func ValueOf(i interface{}) Value {//...if ifaceIndir(t) {f |= flagIndir}//...
}
func ifaceIndir(t *rtype) bool {return t.kind&kindDirectIface == 0
}
//flagIndir
flagIndir flag = 1 << 7
rtype里kind字段为变量类型,变量类型小于1 << 5,
所以这个flagIndir是哪里定义?是什么逻辑
其定义位置如下
//compile/internal/gc/reflect.go:890
if isdirectiface(t) {i |= objabi.KindDirectIface
}func isdirectiface(t *types.Type) bool {if t.Broke() {return false}switch t.Etype {case TPTR,TCHAN,TMAP,TFUNC,TUNSAFEPTR:return truecase TARRAY:// Array of 1 direct iface type can be direct.return t.NumElem() == 1 && isdirectiface(t.Elem())case TSTRUCT:// Struct with 1 field of direct iface type can be direct.return t.NumFields() == 1 && isdirectiface(t.Field(0).Type)}return false
}
根据这个函数可以看出所有指针类型、只有一个元素且元素为指针类型的数组和struct均为direct类型,除此之外ValueOf的返回值Value中flag的flagIndir位均置为1
具体看一下packEface这个方法,按照三种case来分别举例说明
第一种Indirect类型,首先以int为例验证说明
//case ifaceIndir(t)
type value struct {typ *ltypeptr unsafe.Pointerflag uintptr
}
type eface struct {*ltypedata unsafe.Pointer
}
func main() {a := 100aValue := reflect.ValueOf(a)ua := (*value)(unsafe.Pointer(&aValue))fmt.Printf("flag:%b\n", ua.flag)fmt.Printf("kind:%b\n", ua.typ.kind)fmt.Printf("before ptr:%p\n", &a)fmt.Printf("value ptr:%p\n", ua.ptr)aInterface := aValue.Interface()ia := (*eface)(unsafe.Pointer(&aInterface))fmt.Printf("after ptr:%p\n", ia.data)
}
输出结果为:
flag:10000010
kind:10000010
before ptr:0xc000018060
before ptr:0xc000018068
after ptr:0xc000018068
由flag和kind判断其类型为int,flagIndir位为1,进入第一个case。原变量地址为0x8060,转为空接口时,进行了变量复制,到了Value类型内地址变为0xx8068,同时flagAddr为为0,返回interface内指针指向Value内复制变量地址
下面例子验证flagAddr为1
type Test struct {Name *stringAge int
}
func main() {a := "test"c := 100b := Test{&a, c}bValue := reflect.ValueOf(&b)nAge := bValue.Elem().Field(1)un := (*value)(unsafe.Pointer(&nAge))fmt.Printf("flag:%b\n", un.flag)fmt.Printf("kind:%b\n", un.typ.kind)fmt.Printf("before ptr age:%p\n", &b.Age)fmt.Printf("before ptr test:%p\n", &b)fmt.Printf("value ptr:%p\n", un.ptr)nInterface := nAge.Interface()in := (*eface)(unsafe.Pointer(&nInterface))fmt.Printf("after ptr:%p\n", in.data)
}
输出结果为:
flag:110000010
kind:10000010
before ptr age:0xc0000101f8
before ptr test:0xc0000101f0
value ptr:0xc0000101f8
after ptr:0xc000018080
由flag看出,其flagIndir与flagAddr均置为1,进入flagAddr条件判断内,返回interface底层数据指向新创建的类型地址,其内容一致。由于第一步传入为Test指针,故test地址为0x01f0,其Age字段为0x01f0,看到value内的地址为0x01f8,最终进行变量复制,返回复制新地址为0x8080
第二种Value为Indirect类型,返回interface为direct类型,以下例说明
func main() {a := "test"c := 100b := Test{&a, c}bValue := reflect.ValueOf(b)nName := bValue.Field(0)un := (*value)(unsafe.Pointer(&nName))fmt.Printf("flag:%b\n", un.flag)fmt.Printf("kind:%b\n", un.typ.kind)fmt.Printf("before ptr a:%p\n", &a)fmt.Printf("before ptr test:%p\n", &b)fmt.Printf("value ptr:%p\n", un.ptr)nInterface := nName.Interface()in := (*eface)(unsafe.Pointer(&nInterface))fmt.Printf("after ptr:%p\n", in.data)
}
输出结果为:
flag:10010110
kind:110110
before ptr a:0xc0000101e0
before ptr test:0xc0000101f0
value ptr:0xc000010200
after ptr:0xc0000101e0
由kind看出flagIndir为0,且flag中flagIndir为1,进入第二个case。原变量a指针地址为0x01e0,test结构地址为0x01f0,转为Value后,test结构地址变为0x0200,最终返回interface内指针指向地址,为0x0200地址内指针数据,指向原变量a地址
最终可以总结出Value类型内数据指针根据传入类型是否Indirect,可能是原数据,或者是原数据复制变量的指针,最终调用Interface方法,返回带数据的空接口类型时,其指针一定会转化为指向一个复制变量中,避免与通过反射更新原数据互相影响
接着做出第二个总结,关于reflect.Type、reflect.Value、interface{}三者的转化关系,如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EcCeApBu-1678360708936)(/Users/zsh/Desktop/111.png)]
定律三
如果要修改“反射类型对象”,其值必须是“可写的”(settable)
反射对象是否可写同样是由flag来决定的,其判断位有两个,flagAddr和flagRO
可以通过CanSet方法进行判断,其值是否是settable
func (v Value) CanSet() bool {return v.flag&(flagAddr|flagRO) == flagAddr
}
由代码可以看出,只有flagAddr设置,而flagRO未设置时,value可设置
接下来再来看一下这两个字段值以及如何设置
const (flagStickyRO flag = 1 << 5flagEmbedRO flag = 1 << 6flagAddr flag = 1 << 8flagRO flag = flagStickyRO | flagEmbedRO
)
flagRO包含两种只读,flagStickyRO和flagEmbedRO,均是未导出只读类型
先来看flagAddr的设置
func (v Value) Elem() Value {k := v.kind()switch k {case Interface:var eface interface{}if v.typ.NumMethod() == 0 {eface = *(*interface{})(v.ptr)} else {eface = (interface{})(*(*interface {M()})(v.ptr))}x := unpackEface(eface)if x.flag != 0 {}return xcase Ptr:ptr := v.ptrif v.flag&flagIndir != 0 {ptr = *(*unsafe.Pointer)(ptr)}if ptr == nil {return Value{}}tt := (*ptrType)(unsafe.Pointer(v.typ))typ := tt.elem//设置flagAddrfl := v.flag&flagRO | flagIndir | flagAddrfl |= flag(typ.Kind())return Value{typ, ptr, fl}}panic(&ValueError{"reflect.Value.Elem", v.kind()})
}
flagAddr在Elem()方法时set,即只有对指针类型调用Elem方法后才可以进行赋值操作
一段代码进行验证
func main() {i := 1v := reflect.ValueOf(i)vPtr := reflect.ValueOf(&i)elem := vPtr.Elem()fmt.Printf("value of i:%v\n", v.CanSet())fmt.Printf("value of &i:%v\n", vPtr.CanSet())fmt.Printf("value of &i elem:%v\n", elem.CanSet())elem.SetInt(10) fmt.Println(i)
}
输出结果为
value of i:false
value of &i:false
value of &i elem:true
10
Elem方法代表对指针变量进行解引用,即对应*arg这种操作,设置值操作可以理解为以下代码
func main() {i := 1v := &i*v = 10
}
flagRO针对结构体内field,其设置也是在Field方法内,参考代码
func (v Value) Field(i int) Value {if v.kind() != Struct {panic(&ValueError{"reflect.Value.Field", v.kind()})}tt := (*structType)(unsafe.Pointer(v.typ))if uint(i) >= uint(len(tt.fields)) {panic("reflect: Field index out of range")}field := &tt.fields[i]typ := field.typ// Inherit permission bits from v, but clear flagEmbedRO.fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind())// Using an unexported field forces flagRO.if !field.name.isExported() {if field.embedded() {fl |= flagEmbedRO} else {fl |= flagStickyRO}}ptr := add(v.ptr, field.offset(), "same as non-reflect &v.field")return Value{typ, ptr, fl}
}
通过代码来验证下flag设置
type TestStruct struct {stringName stringage intCity *stringprovince string
}
func main() {var test TestStructtest.age = 100test.Name = "test"temp := "guangzhou"test.City = &temptest.province = "guangdong"elem := reflect.ValueOf(&test).Elem()for i := 0; i < elem.NumField(); i++ {field := elem.Field(i)un := (*value)(unsafe.Pointer(&field))fmt.Printf("field index:%d, value:%v, flag:%b, canset:%v\n", i, field, un.flag, field.CanSet())}
}
输出结果为:
field index:0, value:, flag:111011000, canset:false
field index:1, value:test, flag:110011000, canset:true
field index:2, value:100, flag:110100010, canset:false
field index:3, value:0xc0000101e0, flag:110010110, canset:true
field index:4, value:guangdong, flag:110111000, canset:false
flagAddr为1<<8,flagRO为1<<5和1<<6,对照上面输出结果,CanSet返回结果与flag对应正确
调用Set、SetInt、SetFloat等方法进行值的更新,在set之前会进行settable的验证,如果不可set,会产生panic。所有一定在完全确认Value是settable之后,再调用方法
总结
- 反射对象类型有Type和Value两种,TypeOf和ValueOf方法入参均为interface{}类型,依赖空接口类型中类型信息及变量指针实现所有反射功能
- 类型信息维护在全局变量中,由编译时确定,类型信息包括基本类型信息、特定类型信息、类型方法信息三部分连续排列
- Type、Value和interface{}之间可以进行互相转换
- 利用反射类型进行更新操作,需先调用Elem()方法,并确保Value是settable
思考
反射功能强大,为什么不推荐使用反射?
- 性能:new对象多,且堆上分配。如Value类型Elem()、Field()方法返回新的Value类型,均会创建新的Value
- 可控性:一部分反射方法基于固定类型实现,传入类型不匹配,就会panic,对新手不友好
相关文章:

Golang反射源码分析
在go的源码包及一些开源组件中,经常可以看到reflect反射包的使用,本文就与大家一起探讨go反射机制的原理、学习其实现源码 首先,了解一下反射的定义: 反射是指计算机程序能够在运行时,能够描述其自身状态或行为、调整…...

Qt之悬浮球菜单
一、概述 最近想做一个炫酷的悬浮式菜单,考虑到菜单展开和美观,所以考虑学习下Qt的动画系统和状态机内容,打开QtCreator的示例教程浏览了下,大致发现教程中2D Painting程序和Animated Tiles程序有所帮助,如下图所示&a…...

易优cms attribute 栏目属性列表
attribute 栏目属性列表 attribute 栏目属性列表 [基础用法] 标签:attribute 描述:获取栏目的属性列表,或者单独获取某个属性值。 用法: {eyou:attribute typeauto} {$attr.name}:{$attr.value} {/eyou:attri…...

表格中的table-layout属性讲解
表格中的table-layout属性讲解 定义和用法 tableLayout 属性用来显示表格单元格、行、列的算法规则。 table-layout有三个属性值:auto、fixed、inherit。 fixed:固定表格布局 固定表格布局与自动表格布局相比,允许浏览器更快地对表格进行布…...

【MFA】windows环境下,使用Montreal-Forced-Aligner训练并对齐音频
文章目录一、安装MFA1.安装anaconda2.创建并进入虚拟环境3.安装pyTorch二、训练新的声学模型1.确保数据集的格式正确2.训练声音模型-导出模型和对齐文件3.报错处理1.遇到类似: Command ‘[‘createdb’,–host‘ ’, ‘Librispeech’]’ returned non-zero exit sta…...

C语言实验小项目实例源码大全订票信息管理系统贪吃蛇图书商品管理网络通信等
wx供重浩:创享日记 对话框发送:c项目 获取完整源码源文件视频讲解环境资源包文档说明等 包括火车订票系统、学生个人消费管理系统、超级万年历、学生信息管理系统、网络通信编程、商品管理系统、通讯录管理系统、企业员工管理系统、贪吃蛇游戏、图书管理…...

电脑图片损坏是怎么回事
电脑图片损坏是怎么回事?对于经常使用电脑的我们,总是会下载各种各样的图片,用于平时的使用中。但难免会遇到莫名其妙就损坏的图片文件,一旦发生这种情况,要如何才能修复损坏的图片呢?下面小编为大家带来常用的修复方…...

【论文研读】无人机飞行模拟仿真平台设计
无人机飞行模拟仿真平台设计 摘要: 为提高飞行控制算法的研发效率,降低研发成本,基于数字孪生技术设计一个无人机硬件在环飞行模拟仿真平台。从几何、物理和行为3个方面研究无人机数字模型构建方法,将物理实体以数字化方式呈现。设计一种多元融合场景建模法,依据属…...

【算法题】2379. 得到 K 个黑块的最少涂色次数
插: 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 坚持不懈,越努力越幸运,大家一起学习鸭~~~ 题目: 给你一个长度为 n 下标从 0 开始的…...

DJ1-3 计算机网络和因特网
目录 一、物理介质 1. 双绞线 2. 同轴电缆 3. 光纤线缆 4. 无线电磁波 二、端系统上的 Internet 服务 1. 面向连接的服务 TCP(Transmission Control Protocol) 2. 无连接的服务 UDP(User Datagram Protocol) TCP 和 UD…...

Git学习笔记(六)-标签管理
发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签…...

Semaphore 源码解读
一、Semaphore Semaphore 通过设置一个固定数值的信号量,并发时线程通过 acquire() 获取一个信号量,如果能成功获得则可以继续执行,否则将阻塞等待,当某个线程使用 release() 释放一个信号量时,被阻塞的线程则可以被唤…...

RZ/G2L工业核心板U盘读写速率测试
1. 测试对象HD-G2L-IOT基于HD-G2L-CORE工业级核心板设计,双路千兆网口、双路CAN-bus、2路RS-232、2路RS-485、DSI、LCD、4G/5G、WiFi、CSI摄像头接口等,接口丰富,适用于工业现场应用需求,亦方便用户评估核心板及CPU的性能。HD-G2L…...

《SQL与数据库基础》18. MySQL管理
SQL - MySQL管理MySQL管理系统数据库常用工具mysqlmysqladminmysqlbinlogmysqlshowmysqldumpmysqlimportsource本文以 MySQL 为例 MySQL管理 系统数据库 Mysql数据库安装完成后,自带了以下四个数据库,具体作用如下: 数据库含义mysql存储My…...

达梦关系型数据库
达梦关系型数据库一、DM8 安装1. 安装包下载2. Docker 安装3. Linux 安装4. Windows 安装二、DM 管理工具三、命令行交互工具 DIsql四、DM8 SQL使用1. 创建模式2. 创建表3. 修改表4. 读写数据5. 查看库下所有的表名6. 查看表字段信息GitHub: link. 欢迎star国产自主研发的大型…...

Postgresql | 执行计划
SQL优化主要从三个角度进行: (1)扫描方式; (2)连接方式; (3)连接顺序。 如果解决好这三方面的问题,那么这条SQL的执行效率就基本上是靠谱的。看懂SQL的执行计…...

Vue3之父子组件通过事件通信
前言 组件间传值的章节我们知道父组件给子组件传值的时候,使用v-bind的方式定义一个属性传值,子组件根据这个属性名去接收父组件的值,但是假如子组件想给父组件一些反馈呢?就不能使用这种方式来,而是使用事件的方式&a…...

在云服务器安装tomcat和mysql
将 linux 系统安装包解压到指定目录进入 bin 目录执行./startup.sh 命令启动服务器执行./shutdown.sh 关闭服务器在浏览器中访问虚拟机中的 tomcat ip端口具体操作入下解压tomcat压缩包解压,输入tom按table键自动补全tar -zxvf 启动tomcat进入bin目录在linux启动to…...

IO多路复用(select、poll、epoll网络编程)
目录一、高级IO相关1.1 同步通信和异步通信1.2 阻塞与非阻塞1.3 fcntl 函数二、五种IO模型2.1 阻塞式IO模型2.2 非阻塞式IO模型2.3 多路复用IO模型2.4 信号驱动式IO模型2.5 异步IO模型三、认识IO多路复用四、select4.1 认识select函数4.2 select函数原型4.3 select网络编程4.4 …...

Spark单机伪分布式环境搭建、完全分布式环境搭建、Spark-on-yarn模式搭建
搭建Spark需要先配置好scala环境。三种Spark环境搭建互不关联,都是从零开始搭建。如果将文章中的配置文件修改内容复制粘贴的话,所有配置文件添加的内容后面的注释记得删除,可能会报错。保险一点删除最好。Scala环境搭建上传安装包解压并重命…...

C++网络编程(一)本地socket通信
C网络编程(一) socket通信 前言 本次内容简单描述C网络通信中,采用socket连接客户端与服务器端的方法,以及过程中所涉及的函数概要与部分函数使用细节。记录本人C网络学习的过程。 网络通信的Socket socket,即“插座”,在网络中译作中文“套接字”,应…...

【Docker】Linux下Docker安装使用与Docker-compose的安装
【Docker】的安装与启动 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo sudo yum install docker-cesudo systemctl enable dockersudo systemct…...

构造函数与普通函数,显式原型与隐式原型,原型与原型链
原型与原型链1 学前先了解一些概念1.1 构造函数和普通函数的区别1.1.1 调用方式1.1.2 函数中this的指向不同1.1.3 写法不同1.2 问题明确2 原型与原型链2.1 原型2.2 显式原型与隐式原型2.3 原型链3 原型链环形结构1 学前先了解一些概念 1.1 构造函数和普通函数的区别 构造函数…...

跨过社科院与杜兰大学金融管理硕士项目入学门槛,在金融世界里追逐成为更好的自己
没有人不想自己变得更优秀,在职的我们也是一样。当我们摸爬滚打在职场闯出一条路时,庆幸的是我们没有沉浸在当下,而是继续攻读硕士学位,在社科院与杜兰大学金融管理硕士项目汲取能量,在金融世界里追逐成为更好的自己。…...

macOS 13.3 Beta 3 (22E5236f)With OpenCore 0.9.1开发版 and winPE双引导分区原版镜像
原文地址:http://www.imacosx.cn/112494.html(转载请注明出处)镜像特点完全由黑果魏叔官方制作,针对各种机型进行默认配置,让黑苹果安装不再困难。系统镜像设置为双引导分区,全面去除clover引导分区&#x…...

InceptionTime 复现
下载数据集: https://www.cs.ucr.edu/~eamonn/time_series_data/ 挂梯子,开全局模式即可 配置环境 虚拟环境基于python3.9, tensorflow下载:pip install tensorflow,不需要tensorflow-gpu(高版本python&…...

谷粒学院开发(二):教师管理模块
前后端分离开发 前端 html, css, js, jq 主要作用:数据显示 ajax后端 controller service mapper 主要作用:返回数据或操作数据 接口 讲师管理模块(后端) 准备工作 创建数据库,创建讲师数据库表 CREATE TABLE edu…...

2021牛客OI赛前集训营-提高组(第三场) T4扑克
2021牛客OI赛前集训营-提高组(第三场) 题目大意 小A和小B在玩扑克牌游戏,规则如下: 从一副52张牌(没有大小王)的扑克牌中随机发3张到每个玩家手上,每个玩家可以任意想象另外两张牌࿰…...

【OJ比赛日历】快周末了,不来一场比赛吗? #03.11-03.17 #12场
CompHub 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…)比赛。本账号同时会推送最新的比赛消息,欢迎关注!更多比赛信息见 CompHub主页 或 点击文末阅读原文以下信息仅供参考,以比赛官网为准目录2023-03-11&…...

C++-说一说异常机制
C异常机制是一种处理程序错误的高级方法。当程序出现错误时,可以通过抛出异常来通知调用者进行处理,或者在异常对象被捕获之后终止程序执行。 异常处理语法 在C中,可以使用 throw 抛出异常, try-catch 处理异常,try块中…...