Go Interface{}

Go Interface{}

与 C 语言中的void*不同的是 Go 中的interface{}是一个具体的类型,它有自己底层的数据结构表示。

1
2
3
4
5
6
7
8
9
10
11
12
type Dog struct {
name string
}

func main() {
var dog *Dog // 指针
var animal interface{} // interface{}
animal = dog

fmt.Println(dog == nil) // => true
fmt.Println(animal == nil) // => false animal保存了类型信息Dog,所以不为空
}

interface 底层表示

Go 语言根据接口类型是否包含一组方法,在底层将 interface{}用两种不同的数据结构表示:

graph LR
  A("interface{}/") -- 含有方法 --> B("iface")
  A -- 空接口 --> C("eface")

eface

eface 由于表示的是空接口不含方法列表所以它只包含底层数据底层数据类型两个指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// src/runtime/runtime2.go
type eface struct {
_type *_type // 指向底层数据类型信息
data unsafe.Pointer // 指向底层数据
}

// src/runtime/type.go
type _type struct {
size uintptr
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32 // interface{}类型断言时使用
tflag tflag
align uint8
fieldAlign uint8
kind uint8
// function for comparing objects of this type
// (ptr to object A, ptr to object B) -> ==?
equal func(unsafe.Pointer, unsafe.Pointer) bool
// gcdata stores the GC type data for the garbage collector.
// If the KindGCProg bit is set in kind, gcdata is a GC program.
// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
gcdata *byte
str nameOff
ptrToThis typeOff
}

iface

iface 表示接口包含方法列表所以它包含底层数据itab两个指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/runtime/runtime2.go
type iface struct {
tab *itab
data unsafe.Pointer
}

// src/runtime/runtime2.go
type itab struct {
inter *interfacetype // 接口自身类型信息
_type *_type // 底层数据类型信息
hash uint32 // copy of _type.hash. Used for type switches.
_ [4]byte
fun [1]uintptr // 动态派发虚函数表(下文介绍动态派发机制)
}

// src/runtime/type.go
type interfacetype struct {
typ _type
pkgpath name
mhdr []imethod
}

动态派发(dynamic dispatch)

在计算机科学中,动态派发 表示程序运行时选择哪一个多态操作的实现(函数或方法)的过程,动态调度通常被应用于面向对象编程(OOP)的语言和系统,并被认为是一个主要特点。 [1]

Go 在调用接口的方法如果在编译期无法确定,则在运行期决定该方法的具体实现。

1
2
3
4
5
func main() {
var d Animal = &Dog{Name: "petter"}
d.Bark() // 动态派发
d.(*Dog).Bark() // 编译期确定
}

上述动态派发的过程:

  1. Animal接口中获取保存Dog方法Bark()的指针iface(d).itab.fun[0]
  2. 接口变量在中的数据会被拷贝到栈顶
  3. 方法指针会被拷贝到寄存器中并通过汇编指令CALL触发

  1. Dynamic dispatch ↩︎

作者

m3m0ry

发布于

2020-04-11

更新于

2020-09-20

许可协议

评论