interface 在 golang 中是一个非常重要的特性。它相对于其它语言有很多优势:
duck typing, 使得我们无需显示的类型继承。duck typing?的动态语言那样,只能在运行时才能检查到类型的转换错误。而 golang 的 interface 特性可以让我们在编译时就能发现错误。本文将简单分析 interface 的实现原理。

eface?表示空的?interface{},它用两个机器字长表示,第一个字 _type 是指向实际类型描述的指针,第二个字 data 代表数据指针。iface?表示至少带有一个函数的 interface, 它也用两个机器字长表示,第一个字 tab 指向一个 itab 结构,第二个字 data 代表数据指针。data 用来保存实际变量的地址。
data 中的内容会根据实际情况变化,因为 golang 在函数传参和赋值时是?值传递?的,所以:
以 interface{} 的赋值为例:

上图中, i1 和 i2 是 interface,A 为要赋值给 interface 的对象。

itab 表示 interface 和 实际类型的转换信息。对于每个 interface 和实际类型,只要在代码中存在引用关系, go 就会在运行时为这一对具体的 <Interface, Type> 生成 itab 信息。

_type 表示类型信息。每个类型的 _type 信息由编译器在编译时生成。其中:

interfacetype 也并没有什么神奇的地方,只是 _type 为 interface 类型提供的另一种信息罢了。 它包括这个 interface 所申明的所有函数信息。
假设 interface 有 ni 个函数, struct 有 nt 个函数,那么 itab 中的函数表生成的时间复杂度为 O(ni*nt) (遍历 interface 的所有函数,对每次迭代都从 struct 中遍历找到匹配的函数)。 但实际上编译器对此做了优化,它将 interfacetype 中的函数列表和 uncommontype 中的函数列表都做了排序. 所以实现了 O(ni+nt) 时间复杂度的算法。
// 生成 itab 的 funcs 的算法
// 代码摘录自 $GOROOT/src/runtime/iface.go
// 经过了部分修改,只保留了最核心的逻辑
var j = 0
for k := 0; k < ni; k++ {
mi := inter.methods[k]
for ; j < nt; j++ {
mt := t.methods[j]
if isOk(mi, mt) {
itab.fun[k] = mt.f
}
}
}
type Binary uint64
func (i Binary) String() string {
return strconv.FormatUint(uint64(i), 10)
}
type Stringer interface {
String() string
}
func test(s Stringer) {
s.String()
}
func main() {
b := Binary(0x123)
test(b)
}
在上面的代码中,golang 的参数传递过程是:
当 test 函数执行 s.String 时,实际上就是在 s.tab 的 fun 中索引(索引由编译器在编译时生成)到 String 函数,并且调用它。
参考资料:
本文始发于https://zhuanlan.zhihu.com/p/60983066
原文:https://www.cnblogs.com/anjiawei/p/10664978.html