ter 和 typ 的方法都按方法名称进行了排序
// 并且方法名都是唯一的。所以循环的次数是固定的
// 只用循环 O(ni+nt),而非 O(ni*nt)
ni := len(inter.mhdr)
nt := int(x.mcount)
xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]
j := 0
for k := 0; k < ni; k++ {
i := &inter.mhdr[k]
itype := inter.typ.typeOff(i.ityp)
name := inter.typ.nameOff(i.name)
iname := name.name()
ipkg := name.pkgPath()
if ipkg == "" {
ipkg = inter.pkgpath.name()
}
for ; j < nt; j++ {
t := &xmhdr[j]
tname := typ.nameOff(t.name)
// 检查方法名字是否一致
if typ.typeOff(t.mtyp) == itype && tname.name() == iname {
pkgPath := tname.pkgPath()
if pkgPath == "" {
pkgPath = typ.nameOff(x.pkgpath).name()
}
if tname.isExported() || pkgPath == ipkg {
if m != nil {
// 获取函数地址,并加入到itab.fun数组中
ifn := typ.textOff(t.ifn)
*(*unsafe.Pointer)(add(unsafe.Pointer(&m.fun[0]), uintptr(k)*sys.PtrSize)) = ifn
}
goto nextimethod
}
}
}
// ……
m.bad = true
break
nextimethod:
}
if !locked {
throw("invalid itab locking")
}
// 计算 hash 值
h := itabhash(inter, typ)
// 加到Hash Slot链表中
m.link = hash[h]
m.inhash = true
atomicstorep(unsafe.Pointer(&hash[h]), unsafe.Pointer(m))
}
additab
会检查 itab
持有的 interfacetype
和 _type
是否符合,就是看 _type
是否完全实现了 interfacetype
的方法,也就是看两者的方法列表重叠的部分就是 interfacetype
所持有的方法列表。注意到其中有一个双层循环,乍一看,循环次数是 ni * nt
,但由于两者的函数列表都按照函数名称进行了排序,因此最终只执行了 ni + nt
次,代码里通过一个小技巧来实现:第二层循环并没有从 0 开始计数,而是从上一次遍历到的位置开始。
求 hash 值的函数比较简单:
func itabhash(inter *interfacetype, typ *_type) uint32 {
h := inter.typ.hash
h += 17 * typ.hash
return h % hashSize
}
hashSize
的值是 1009。
更一般的,当把实体类型赋值给接口的时候,会调用 conv
系列函数,例如空接口调用 convT2E
系列、非空接口调用 convT2I
系列。这些函数比较相似:
- 具体类型转空接口时,_type 字段直接复制源类型的 _type;调用 mallocgc 获得一块新内存,把值复制进去,data 再指向这块新内存。
- 具体类型转非空接口时,入参 tab 是编译器在编译阶段预先生成好的,新接口 tab 字段直接指向入参 tab 指向的 itab;调用 mallocgc 获得一块新内存,把值复制进去,data 再指向这块新内存。
- 而对于接口转接口,itab 调用 getitab 函数获取。只用生成一次,之后直接从 hash 表中获取。
9. 如何用 interface 实现多态
Go
语言并没有设计诸如虚函数、纯虚函数、继承、多重继承等概念,但它通过接口却非常优雅地支持了面向对象的特性。
多态是一种运行期的行为,它有以下几个特点:
- 一种类型具有多种类型的能力
- 允许不同的对象对同一消息做出灵活的反应
- 以一种通用的方式对待个使用的对象
- 非动态语言必须通过继承和接口的方式来实现
看一个实现了多态的代码例子:
package main
import "fmt"
func main() {
qcrao := Student{age: 18}
whatJob(&qcrao)
growUp(&qcrao)
fmt.Println(qcrao)
stefno := Programmer{age: 100}
whatJob(stefno)
growUp(stefno)
fmt.Println(stefno)
}
func whatJob(p Person) {
p.job()
}
func growUp(p Person) {
p.growUp()
}
type Person interface {
job()
growUp()
}
type Student struct {
age int
}
func (p Student) job() {
fmt.Println("I am a student.")
return
}
func (p *Student) growUp() {
p.age += 1
return
}
type Programmer struct {
age int
}
func (p Programmer) job() {
fmt.Println("I am a programmer.")
return
}
func (p Programmer) growUp() {
// 程序员老得太快 ^_^
p.age += 10
return
}
代码里先定义了 1 个 Person
接口,包含两个函数:
job()
growUp()
然后,又定义了 2 个结构体,Student
和 Programmer
,同时,类型 *Student
、Programmer
实现了 Person
接口定义的两个函数。注意,*Student
类型实现了接口, Student
类型却没有。
之后,我又定义了函数参数是 Person
接口的两个函数:
func whatJob(p Person)
func growUp(p Person)
main
函数里先生成 Student
和 Programmer
的对象,再将它们分别传入到函数 whatJob
和 growUp
。函数中,直接调用接口函数,实际执行的时候是看最终传入的实体类型是什么,调用的是实体类型实现的函数。于是,不同对象针对同一消息就有多种表现,多态
就实现了。
更深入一点来说的话,在函数 whatJob()
或者 growUp()
内部,接口 person
绑定了实体类型 *Student
或者 Programmer
。根据前面分析的 iface
源码,这里会直接调用 fun
里保存的函数,类似于: s.tab->fun[0]
,而因为 fun