Go能实现接口的动态性和反射的基础是:编译期为运行时提供了类型信息。interface底层有两种结构,上一节讲了带方法的iface,这一节补充不带方法的eface结构。
interface之eface
Go中的任何对象都可以表示为interface{}。它扮演的角色与C中的void*差不多,区别在于interface{}中包含有类型信息,可以实现反射。
efece数据结构描述:
gcdata
域用于垃圾回收,size
描述类型的大小,hash
表示数据的hash值,align
是对齐,fieldalign
是这个数据嵌入结构体时的对齐,kind
是枚举值。alg
是函数指针的数组,存储了hash/equal/print/copy
四个函数操作。uncommentType
指向这个类型的方法集。
1 | type eface struct { |
运行时convT2E
T2E的转换 赋值给空interface{},运行时会调用conv类函数convT2E
。与convT2I
一样,向interface的类型转换var i interface{} = u
都存在内存拷贝。看iface.go源码时发现convI2I
等接口转换时没有分配内存和拷贝数据,原因可能是Go接口内部的data域,并不开放途径让外部修改,所以接口之间转换可以用同一块内存。
1 | func convT2E(t *_type, elem unsafe.Pointer, x unsafe.Pointer) (e interface{}) { |
下面的例子中修改u并不影响i interface的内存数据,因为i在赋值时通过convT2E对u进行了拷贝。这也是反射非指针变量时无法直接改变变量数据的原因,因为反射会先把变量转成interface类型,拿到的是变量的副本。
1 | u := User{1, "Tom"} |
nil的理解
未初始化的interface类型,指针,函数,slice,cannel和map都是nil的。对于interface比较特殊,只有eface的type和data都是nil,或者iface的type和data都是nil时,interface{}才是nil。
1 | type Duck interface{ |
type assertion
严格来说Go并不支持泛型编程,但通过interface可实现泛型编程,后面reflect浅析中有个通过reflect实现泛型的例子。interface像其它类型转换的时候一般需要断言。下面只给出了eface的例子,当然也可以通过断言来判断某个类型是否实现了某个接口。
1 | func do(v interface{}) { |
对应的go源码在iface.go当中。assertE2T
过程判断了eface的type字段是否和目标type相等,相等则还需要拷贝数据。assertI2T
也要拷贝数据,不过他比较的是iface.tab._type与目标type是否一致。
1 | func assertE2T(t *_type, e interface{}, r unsafe.Pointer) { |
reflect浅析
反射机制提供了检查存储在接口变量中的[类型 值]对的机制。根据Laws Of ReflectionGo的反射可以总结三点,即反射可以从interface中获取reflect对象;同时可以通过Interface()
方法恢复reflect对象为一个interface;如果要修改反射对象,该对象必须是settable
的。
TypeOf与ValueOf实现
获取反射对象的实现,是基于对interface底层数据的操作。首先对象经过了convT2E
,然后emptyInterface直接指向了eface的type字段。typeOf
返回的Type是接口,在类型_type上实现了很多操作。valueOf
返回的就是Value结构体,它包含了数据域和type域信息。
1 | func TypeOf(i interface{}) Type { |
1 | type Type interface{ |
通过Type获取Struct Field信息
可以通过reflect获取类型实例的结构体信息,比如每个field的名字类型或标签。
1 | // A StructField describes a single field in a struct. |
通过Value实现泛型
为了解决method接受不同类型的slice为入参,可以用反射来完成。对于可记长度和可随机访问的类型,可以通过v.Len()
和v.Index(i)
获取他们的第几个元素。
v.Index(i).Interface()将reflect.Value反射回了interface类型
1 | func method(in interface{}) (ok bool) { |
通过Elem修改reflect对象值
对LawsOfReflect第三点的理解 reflect.ValueOf
如果直接传入x,则v是x的一个副本的reflect对象。修改v的值并不会作用到x上。p是指向x的指针的reflect对象,修改p的值是在修改指针的指向,同样不会作用到x上,因此也是CanNotSet的。只有通过p.Elem()
相当于获取了*p的reflect对象,这时才能使用v.SetFloat(7.2)
来对原始的数据进行修改。
1 | var x float64 = 3.4 |