持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第23天,点击查看活动详情
前言
总所周知,interface是Go中的一个关键字,是一组方法的集合,只定义了方法的形态,同时也是Go中多态的一种实现方式。
type interfaceName interface {
TODO()
// ...some method
}
关于接口的命名,我们通常以er作为后缀,表示某个行为。根据接口中是否有定义方法,运行时Go中分为了两个结构体来代表,分别是iface和eface。
在了解这两个结构体时,我们先来看看一个常见的interface面试题,下面这个例子输出什么?
type People interface {
Show()
}
type Student struct{}
func (stu *Student) Show() {
}
func live() People {
var stu *Student
return stu
}
func main() {
if live() == nil {
fmt.Println("AAAAAAA")
} else {
fmt.Println("BBBBBBB")
}
}
了解interface结构体就知道答案是输出:BBBBBBB。那么下面就来学习下Go对interface做了啥操作把,这两个结构体的源码在runtime/runtime2.go下就可以找到啦
iface
当接口里有定义方法时,就会使用iface这个结构体,它具有两个属性:
type iface struct {
tab *itab
data unsafe.Pointer
}
- tab用来存储接口本身的相关信息,例如:接口的类型信息、方法集信息、具体类型信息以及具体类型信息的方法集
- data则指向当前具体类型的值
type itab struct {
inter *interfacetype // 接口信息
_type *_type // 具体类型的信息
hash uint32 // 类型转换的hash值,用于接口转换为具体类型时
_ [4]byte // 对象对齐
fun [1]uintptr // 一组函数指针
}
type interfacetype struct {
typ _type // 接口类型
pkgpath name // 接口包路径
mhdr []imethod // 接口方法集
}
下面来看个例子
type Sayer interface {
Say(word string)
}
type People struct {
}
func (p *People) Say(word string) {
fmt.Println("People:", word)
}
func main() {
var people *People
var sayer Sayer
fmt.Println(people == nil) // true
fmt.Println(sayer == nil) // true
sayer = people
fmt.Println(sayer == nil) // false
}
- people是一个引用类型变量,因此它的零值是nil,所以
people==nil为true - sayer也同样是一个引用类型变量,它的两个属性
tab和data都为nil,因此sayer==nil也为true - 将people赋值给sayer,这时tab属性会存储相关的具体类型和接口类型,因此tab属性不为nil,data属性被赋值为具体属性people的值,同样是nil。这时在
sayer==nil就为false了。
因此只有一个
iface接口,tab属性和data属性都为nil时,该接口才为nil。
eface
因为eface只用来表示方法集为空的接口,因此eface就是iface的缩减版。
只使用_type属性指向具体类型信息,data同样指向具体类型变量的值。
type eface struct {
_type *_type // 具体类型信息
data unsafe.Pointer // 具体类型变量值
}
这时回头看那个面试题就知道为啥啦,live()方法返回stu变量,同时因为方法返回值为People类型,因此发生了一次隐形赋值,类似如下
var people People = stu
return people
这时iface结构体的tab属性就不为nil啦,表达式live()==nil为false。