06.Golang 接口(interface)源码分析
Golang 接口(interface)源码分析
注意当前go版本代码为1.23
前言
接口是高级语言中的一个规约,是一组方法签名的集合。在Golang中, 接口是非侵入式的,具体类型实现 interface 不需要在语法上显式的声明,只需要具体类型的方法集合是 interface 方法集合的超集,就表示该类实现了这一 interface。编译器在编译时会进行 interface 校验。interface 和具体类型不同,它不能实现具体逻辑,也不能定义字段。
底层数据结构
Golang中接口的底层结构体有两种,分别是iface
和 eface
,其中 runtime.iface
描述的接口包含方法,而 runtime.eface
则是不包含任何方法的空接口:interface{}
。
iface源码
1 |
|
iface
内部维护两个指针,tab 中存放的是类型、方法等信息。data
则指向接口具体的值,一般而言是一个指向堆内存的指针。
由于 Go 语言是强类型语言,编译时对每个变量的类型信息做强校验,所以每个类型的元信息要用一个结构体描述。再者 Go 的反射也是基于类型的元信息实现的。Type 就是所有类型最原始的元信息。
整体结构如下:
eface源码
源码路径: runtime.eface
1 |
|
eface作为空的 inferface{} 是没有方法集的接口。所以不需要 itab 数据结构。它只需要存类型和类型对应的值即可。对应的数据结构如下:
从这个数据结构可以看出,相比 iface
,eface
只需要维护 abi.Type
,表示空接口所承载的具体的实体类型和data
描述了具体的值。它们分别被称为动态类型
和动态值
。而接口值包括动态类型
和动态值
,只有当 2 个字段都为 nil,空接口才为 nil。空接口的主要目的有 2 个,一是实现“泛型”,二是使用反射。所以空接口并不一定等于nil,这是常见的犯错点。
关于type的结构体
1 |
|
Go 语言各种数据类型都是在 _type
字段的基础上,增加一些额外的字段来进行管理的,这些数据类型的结构体定义,也是反射实现的基础。
1 |
|
问题
1.空接口一定等于 nil
吗?
接口值的零值是指动态类型
和动态值
都为 nil
。当仅且当这两部分的值都为 nil
的情况下,这个接口值就才会被认为 接口值 == nil
。
1 |
|
2.【引申】 fmt.Println
函数的参数是 interface
。对于内置类型,函数内部会用穷举法,得出它的真实类型,然后转换为字符串打印。而对于自定义类型,首先确定该类型是否实现了 String()
方法,如果实现了,则直接打印输出 String()
方法的结果;否则,会通过反射来遍历对象的成员进行打印。
1 |
|
因为 Student
结构体没有实现 String()
方法,所以 fmt.Println
会利用反射挨个打印成员变量:{qcrao 18}
这是添加string()的实现:
1 |
|
打印结果:
1 |
|
但是如果是*Student为接受者类型呢?
1 |
|
打印结果:
1 |
|
原因:类型
T
只有接受者是T
的方法;而类型*T
拥有接受者是T
和*T
的方法。语法上T
能直接调*T
的方法仅仅是Go
的语法糖。
所以Student
结构体定义了接受者类型是值类型的 String()
方法时,通过**fmt.Println(s)或者fmt.Println(&s)**均可以按照自定义的格式来打印。
如果 Student
结构体定义了接受者类型是指针类型的 String()
方法时,只有通过**fmt.Println(&s)**才能按照自定义的格式打印。