从多个问题出发,浅谈Go interface

·  阅读 1330

从多个问题出发,浅谈Go interface

〇、前言

这是《让我们一起Golang》专栏的第46篇文章,本文从多个问题浅谈Go的接口interface,不过由于笔者水平和工作经验限制,可能文章存在许多不足或错误,烦请指出斧正!

本专栏的其他文章:

一、Go语言“折中”的做法与传统静态语言、动态语言

静态语言是在编译时就会检查类型不匹配的错误,而动态语言需要程序运行到那一行才能知道类型的问题。而Go语言作为一门比较“年轻”的静态语言,既保留了静态语言的类型检查优点,有引入了动态语言的优点。它采用了一种“折中”的做法:实现接口时并不要求指定是哪个类型实现的,只要实现接口的某个方法编译器就可以检测到。

看下面这段代码:

type Human interface {
    eat()
    sleep()
}
​
type man struct {
    name string
    age  int
}
​
type woman struct {
    name string
    age  int
}
​
func (m *man) eat() {
    fmt.Println("a man is eating")
}
​
func (w *woman) eat() {
    fmt.Println("a woman is eating")
}
​
func (m *man) sleep() {
    fmt.Println("a man is sleeping")
}
​
func (w *woman) sleep() {
    fmt.Println("a woman is sleeping")
}
func main() {
    bob := man{
        name: "bob",
        age:  18,
    }
    lily := woman{
        name: "lily",
        age:  20,
    }
​
    bob.eat()
    lily.eat()
    bob.sleep()
    lily.sleep()
}
​
复制代码

是不是没有发现那些方法显式的实现了接口,但是Goland已经帮我们把它们的关系标注出来了,编译器能够检测到它们之间的关系。

image-20221127165153540

当编译器在调用eat()、sleep()时,会将man、woman对象转换成human类型,实际上这也是一种类型检查。

二、接口的底层实现iface和eface

iface和eface都为Go中接口的底层结构体,它们的区别是iface表示的接口类型变量包含方法,而eface表示的接口是不包含任何方法的空接口。

// $GOROOT/src/runtime/runtime2.go
type iface struct {
    tab  *itab
    data unsafe.Pointer
}
​
type eface struct {
    _type *_type
    data  unsafe.Pointer
}
复制代码
2.1 iface

可以看到iface包含两个指针tab以及data

tab用来存储接口本身的信息(如接口类型等),还存储赋给这个接口的具体类型。

data则指向当前被赋给接口类型变量的具体的值。

在来看一下tab字段的类型itab:

// $GOROOT/src/runtime/runtime2.go
type itab struct {
    inter *interfacetype
    _type *_type
    link  *itab
    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.
}
复制代码

itab的inter字段描述了接口本身的信息,type被用来描述实体的类型,hash是实体类型type的哈希,用于类型转换,fun用来存储实体类型实现接口方法的调用地址。

有人会疑惑了,为什么fun的大小为1,这样是不是代表只能接口只能定义一个方法。其实不是的,一个接口可以定义多个方法,此处存储的时第一个方法的函数指针,若有其他方法,会放在其后的内存空间之中,因为函数指针的大小是固定的,因此通过增加地址值即可找到接口的下一个函数指针位置。

另外看一看interfacetype结构体:

// $GOROOT/src/runtime/runtime2.go
type interfacetype struct {
    typ     _type
    pkgpath name
    mhdr    []imethod
}
复制代码

包含了接口类型typ, 接口的包路径名pkgpath和用来存储接口方法函数集的切片mhdr

2.2 eface

和iface相比,eface就比较简单了,其定义如下:

// $GOROOT/src/runtime/runtime2.go
type eface struct {
    _type *_type
    data  unsafe.Pointer
}
复制代码

和iface一样,_type表示具体类型的类型信息,data执行具体类型变量的值。

参考文献

理解Go interface的两种底层实现:iface和eface blog.frognew.com/2018/11/go-…

Interfaces in Go -Go 101 go101.org/article/int…

Go 语言与鸭子类型的关系 golang.design/go-question…

本文正在参加「金石计划 . 瓜分6万现金大奖」

收藏成功!
已添加到「」, 点击更改