打个广告:欢迎关注我的微信公众号,在这里您将获取更全面、更新颖的文章!
原文链接:深入 go interface 底层原理 欢迎点赞关注
什么是 interface
在 Go 语言中,interface(接口)是一种抽象的类型定义。它主要用于定义一组方法的签名,但不包含方法的实现细节。接口提供了一种规范,任何类型只要实现了接口中定义的所有方法,就被认为是实现了该接口。这使得不同的类型可以以一种统一的方式被处理,增强了代码的灵活性、可扩展性和可维护性。
例如,如果定义一个 Shape 接口,包含一个 Area 方法:
type Shape interface {
Area() float64
}
// 圆形结构体
type Circle struct {
Radius float64
}
// 圆形实现面积计算方法
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
// 矩形结构体
type Rectangle struct {
Length, Width float64
}
// 矩形实现面积计算方法
func (r Rectangle) Area() float64 {
return r.Length * r.Width
}
// 打印形状面积的函数
func printArea(s Shape) {
fmt.Printf("面积: %.2f\n", s.Area())
}
func main() {
circle := Circle{Radius: 5}
rectangle := Rectangle{Length: 4, Width: 6}
printArea(circle)
printArea(rectangle)
}
那么无论是 Circle 结构体还是 Rectangle 结构体,只要它们都实现了 Area 方法,就都可以被当作 Shape 类型来使用。
接口在 Go 语言中广泛应用于解耦代码、实现多态性、定义通用的行为规范等场景。它让代码更加模块化和易于管理,有助于提高代码的质量和可复用性。
底层数据结构
在 Go 语言中,有两种“interface”,一种是空接口(`interface{}`),它可以存储任意类型的值;另一种是非空接口,这种接口明确地定义了一组方法签名,只有实现了这些方法的类型才能被认为是实现了该非空接口。 下面讨论一下这两种接口的底层实现。
空接口与非空接口
在 Go 语言中,空接口的底层数据结构是 runtime.eface :
type eface struct {
_type *_type
data unsafe.Pointer
}
_type 字段指向一个 _type 结构体,该结构体包含了所存储值的详细类型信息,data 字段则是一个 unsafe.Pointer ,它直接指向实际存储的数据的内存地址。
非空接口的底层数据结构是 runtime.iface:
type iface struct {
tab *itab
data unsafe.Pointer
}
同样,data 字段也是一个指向实际数据的指针。然而,这里的重点是 tab 字段,它指向一个 itab 结构体。
itab 结构体
itab 结构体包含接口的类型信息和指向数据的类型信息:
type itab struct {
inter *interfacetype
_type *_type
hash uint32 // copy of _type.hash. Used for type switches.
_ [4]byte
fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}
type interfacetype struct {
typ _type // 接口的类型
pkgpath name // 接口所在路径
mhdr []imethod // 接口所定义的方法列表
}
-
inter 字段描述了接口自身的类型信息,包括接口所定义的方法等。
-
_type 字段存储了实际值的类型信息。
-
hash 字段是对 _type 结构体中哈希值的拷贝,它在进行类型比较和转换等操作时能够提供快速的判断依据。
-
fun 字段则是一个动态大小的函数指针数组,当fun[0]=0时,表示_type并没有实现该接口(这里指的是itab下的_type),当实现了接口时,fun存放了第一个接口方法的地址,其他方法依次往后存放。在这里fun存储的其实是接口方法对应的实际类型的方法,每次调用发方法时实行动态分派。
_type 结构体
_type是runtime对Go任意类