你已经知道:
typeAnimalinterface{Speak()string}typeDogstruct{}func(d Dog)Speak()string{return"汪汪"}这里:
Animal = 接口 Dog = 结构体 Speak = 方法那么:
Go 到底是怎么知道:
vara Animal=Dog{}里面的 Dog 实现了 Animal?
又是怎么找到:
a.Speak()应该调用哪个函数的?
答案:
就是:
itab一、先看这段代码
typeAnimalinterface{Speak()string}typeDogstruct{}func(d Dog)Speak()string{return"汪汪"}funcmain(){vara Animal a=Dog{}fmt.Println(a.Speak())}输出:
汪汪二、问题来了
变量:
a类型是:
Animal但是:
真正装进去的是:
Dog{}那么:
执行:
a.Speak()的时候:
Go 怎么知道:
应该去调用:
Dog.Speak()而不是:
Cat.Speak()呢?
三、interface 底层结构
前面讲过:
interface 本质:
(Type, Value)例如:
vara Animal=Dog{}底层:
大概是:
T = Dog V = Dog{}四、但仅仅有类型还不够
因为:
Go 还需要知道:
这个类型实现了哪些接口方法例如:
Dog ↓ Speak() ↓ 对应哪个函数地址五、于是出现了 itab
itab:
全称:
interface table翻译:
接口方法表六、itab 是干什么的
一句话:
记录 某个具体类型 如何实现某个接口七、画图理解
例如:
vara Animal=Dog{}底层:
大概这样:
a │ ├── itab │ └── data八、data
data:
保存:
真正的数据也就是:
Dog{}九、itab
itab:
保存:
接口信息 + 具体类型信息 + 方法地址十、对应到你的代码
接口类型
typeAnimalinterface{Speak()string}对应:
Animal具体类型
typeDogstruct{}对应:
Dog方法实现
func(d Dog)Speak()string对应:
Dog.Speak()十一、itab 里面存什么
大概可以理解成:
itab ├── interface type ├── concrete type └── method table具体:
itab ├── Animal ├── Dog └── Dog.Speak()十二、画成表
| 项目 | 对应 |
|---|---|
| interface type | Animal |
| concrete type | Dog |
| method | Dog.Speak |
十三、执行 a.Speak() 时发生什么
代码:
a.Speak()Go:
先找到:
a.itab然后:
在里面找到:
Speak 对应的函数地址最终调用:
Dog.Speak()十四、整个过程
a.Speak() ↓ 找到 itab ↓ 找到 Speak 函数地址 ↓ 执行 Dog.Speak() ↓ 返回 汪汪十五、为什么接口调用比普通调用慢一点
普通调用:
dog.Speak()编译时:
就知道:
调用哪个函数接口调用:
a.Speak()需要:
先查 itab 再跳转函数所以:
会多一步。
十六、再举一个例子
typeCatstruct{}func(c Cat)Speak()string{return"喵喵"}此时:
vara Animal=Cat{}底层:
变成:
itab ├── Animal ├── Cat └── Cat.Speak()十七、为什么不用提前写 implements
Java:
classDogimplementsAnimalGo:
不用。
因为:
Go 编译器:
会自动检查:
Dog 有没有实现 Animal 的全部方法如果有:
自动生成:
Animal-Dog 的 itab十八、编译器什么时候创建 itab
当你写:
vara Animal=Dog{}时。
编译器发现:
Dog 实现了 Animal于是:
创建:
Animal ↔ Dog对应的 itab。
十九、为什么接口断言能成功
例如:
vara Animal=Dog{}执行:
dog:=a.(Dog)Go:
会看:
itab 里的 concrete type发现:
Dog于是:
断言成功。
二十、结合 interface = (Type, Value) 再理解
很多教程说:
interface = (Type, Value)其实:
更准确:
应该理解成:
interface ├── itab └── data其中:
data
保存:
真正的数据例如:
Dog{}itab
保存:
Dog 的类型 Animal 的信息 Dog.Speak 的地址二十一、最终完整图(重点)
var a Animal = Dog{}底层:
a │ ├── itab │ ├── interface type : Animal │ ├── concrete type : Dog │ └── method table │ └── Speak → Dog.Speak │ └── data └── Dog{}二十二、最后一句总结(必须记住)
itab 本质:
接口方法映射表作用:
记录 某个具体类型 如何实现某个接口在你的例子里:
Animal ← 接口类型 Dog ← 具体类型 Dog.Speak ← 方法实现Go 会生成:
itab ├── Animal ├── Dog └── Speak → Dog.Speak当执行:
a.Speak()时:
实际上:
就是:
通过 itab 找到 Dog.Speak() 再调用所以:
interface 能装不同类型 却还能正确调用对应方法核心秘密:
就是:
itab + data