从多个问题动身,浅谈Go interface

〇、前言

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

本专栏的其他文章:

  • 深化了解 Golang map 规划理念与完成原理 –

  • 云青青兮欲雨——Go的数组与切片傻傻分不清楚? –

  • 安得倚天抽宝剑——Go中new到底在堆仍是栈中分配 –

  • 从源码分析视点浅谈Golang Channel –

一、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已经帮咱们把它们的联系标示出来了,编译器能够检测到它们之间的联系。

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

当编译器在调用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万现金大奖」