自学内容网 自学内容网

【接口】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、 接口的具体工作机制

类型断言 和 类型选择。

  1. 类型断言(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 类型。

  1. 空接口(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
  1. 接口的零值
    在 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)!