【接口】interface的底层原理
Go 接口的底层实现原理
1、Go 接口的基本概念
接口定义了一组方法,但不包含具体的实现。任何类型只要实现了接口中定义的所有方法,就被认为实现了该接口,不用显式声明。
package main
import "fmt"
type Speaker interface {
Speak() string
}
type Person struct {
Name string
}
func (p Person) Speak() string {
return "Hello, " + p.Name
}
func main() {
var s Speaker
s = Person{"John"}
fmt.Println(s.Speak()) // 输出:Hello, John
}
Speaker 是一个接口,Person 是一个类型,并且 Person 类型实现了 Speak 方法。通过这种方式,Person 类型被认为实现了 Speaker 接口。
2、Go 接口的底层实现
Go 接口的底层实现基于一个叫做 接口值(interface value) 的结构。每个接口值由两部分组成:
类型信息(type):接口的具体类型,表示该接口值的动态类型是什么。
数据指针(data):实际数据的内存地址,它指向实现接口的类型的值。
type iface struct {
tab *itab//存储了接口的类型
data unsafe.Pointer //存储了接口中动态类型的数据指针
}
type itab struct {
inter *interfacetype //类型的静态类型信息,比如io.Reader
_type *_type //接口存储的动态类型
hash uint32 //接口动态类型的唯一标识,_type中hash的副本
_ [4]byte //用于内存对齐
fun [1]uintptr //动态类型的函数指针列表
hash是一个uint32类型的值,实际上可以看作是类型的校验码,当将接口转换成具体的类型的时候,会通过比较二者的hash值确定是否相等,只有hash相等 才能进行转换。
fun最终指向的是接口的方法集,即存储了接口所有方法的函数的指针。通过比较接口的方法集和类型的方法集,可以用来判断该类型是否实现了该接口。 把fun指向的方法集看作是一个虚函数表,也是很贴切的。
type _type struct {
size uintptr // 类型大小
ptrdata uintptr//偏移量
hash uint32 // 哈希
tflag tflag //标志
align uint8 // 对齐方式
fieldAlign uint8
kind uint8 // 类别
equal func(unsafe.Pointer, unsafe.Pointer) bool
gcdata *byte
str nameOff
ptrToThis typeOff
}
type interfacetype struct{
typ _type
pkgpath name //接口所在的包名
mhdr []imethod //接口中暴漏的方法在最终可执行文件中的名字和类型的偏移量
}
3、 接口的具体工作机制
类型断言 和 类型选择。
- 类型断言(Type Assertion)
用于从接口类型提取出底层类型。
//语法:
value, ok := x.(T)
其中,x 是一个接口类型,T 是你希望转换成的目标类型。如果 x 实际上持有的是 T 类型的值,那么断言成功,value 就是类型 T 的值,并且 ok 为 true。如果 x 并不持有 T 类型的值,则断言失败,ok 为 false。
例:
package main
import "fmt"
type Speaker interface {
Speak() string
}
type Person struct {
Name string
}
func (p Person) Speak() string {
return "Hello, " + p.Name
}
func main() {
var s Speaker = Person{"Alice"}
// 类型断言
p, ok := s.(Person)
if ok {
fmt.Println("类型断言成功,Person 的名字是:", p.Name)
} else {
fmt.Println("类型断言失败")
}
}
例子中,通过类型断言将 Speaker 接口转换为具体的 Person 类型。
- 空接口(interface{})
空接口是单独的唯一的一种接口类型,因此自然不需要itab中的接口类型字段了
空接口也没有任何的方法,因此自然也不存在itab中的方法集了
type eface struct{ // 两个指针,16byte
_type *_type // 指向一个内部表
data unsafe.Pointer // 指向所持有的数据
}
空接口是 Go 中非常特殊的接口类型,它没任何方法,因此任何类型都实现了空接口。空接口通常用于存储任何类型的数据。
var x interface{}
x = 42 // 存储 int
x = "hello" // 存储 string
x = []int{1, 2, 3} // 存储 slice
- 接口的零值
在 Go 中,接口的零值是 nil,就是说如果没给接口赋值,它的类型信息和数据指针都会是 nil。只有当接口类型的值有具体的类型和值时,接口才会存储这些信息。
4、接口与内存
由于 Go 接口值内部保存的是两个字段(类型和数据指针),这使得接口的操作相对轻量,只需要存一个指向实际数据的指针,而不用复制数据。
例子:查看接口内部存储:
package main
import "fmt"
type Speaker interface {
Speak() string
}
type Person struct {
Name string
}
func (p Person) Speak() string {
return "Hello, " + p.Name
}
func main() {
var s Speaker
p := Person{Name: "John"}
s = p
fmt.Println(s.Speak()) // "Hello, John"
// 检查接口内部的数据和类型
fmt.Printf("%#v\n", s) // 输出:main.Person{Name:"John"}
}
5、总结
接口值:Go 中的接口值由类型信息和数据指针组成,它使得 Go 支持动态类型绑定(多态)。
方法表(vtable):每个接口类型有一个方法表,记录了该类型实现的所有方法。当接口调用方法时,实际上是通过方法表来进行查找和调用的。
空接口:任何类型都实现了空接口 interface{},它可以用来存储任何类型的数据。
类型断言:可以通过类型断言来从接口类型中提取出具体的底层类型和数据。
原文地址:https://blog.csdn.net/qq_44794715/article/details/143819057
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!