interface

我理解的在计算机系统中的 interface 的意思是:某种中间层, 拥有默许的一套使用规则. 只要调用方遵循这个规则,就可以通过它实现一些操作. 就像我们常用的系统调用 open/read/write 等都是接口, 我们使用的时候只需要正常调用即可(接口名, 接口参数类型等配套)。

接口的好处:

上下游通过接口解耦, 接口隔离了底层的实现, 让我们将当前重点投入到自身逻辑中.

go interface

一个常规的go的interface是这样的:

type Ierror interface {
        Error() string
}

如果一个类型需要实现 error 接口, 那么它只需实现 Error() string 方法即可:

type MError struct {
        CodeNum int
        Message string
}

func (m *MError) Error() string { 
        return fmt.Sprintf("%s, codeNum:%d", m.Message, m.CodeNum)
}

func NewMError(code int, msg string) Ierror {
        return &MError{  //typecheck 3
                CodeNum: code,
                Message: msg,
        }
}

func getError(e Ierror) Ierror {
        return e
}

func main() {

        var mer Ierror = NewMError(404, "not found") // typecheck 1
        err := getError(mer)	// typecheck 2
        fmt.Println(err)
}

Go 语言在编译期间对代码进行类型检查,上述代码总共触发了三次类型检查:

  • 将 * NewMError 类型的变量赋值给 Ierror 类型的变量 mer;
  • 将 * NewMError 类型的变量 mer 传递给签名中参数类型为 Ierror 的 getError 函数;
  • 将 * NewMError 类型的变量从函数签名的返回值类型为 Ierror 的 NewMError 函数中返回; 从类型检查的过程来看,编译器仅在需要时才检查类型,类型实现接口时只需要实现接口中的全部方法

go中的 interface{} 类型不是任意类型(与C中的 void * 不同). 如果我们将类型转换成了 interface{}, 变量在运行期间的类型也会发生变化, 获取变量类型时会得到 interface{}.

func main() {
        type Test struct{}
        v := Test{}
        Print(v)
}

func Print(v interface{}) {
        println(v)
}

上述函数不接受任意类型的参数, 只接受 interface{} 类型的值, 在调用 Print 函数时会对参数 v 进行类型转换, 将原来的 Test 类型转换成 interface{} 类型, 本节会在后面介绍类型转换的实现原理.

go中的指针和接口

在 Go 语言中同时使用指针和接口时会发生一些让人困惑的问题, 接口在定义一组方法时没有对实现的接收者做限制, 所以我们会看到某个类型实现接口的两种方式:

type Duck interface{
        Walk()
        Quack()
}

type Cat struct{}

//1
func (c *Cat) Walk() {
        fmt.Println("cat walk")
}
func (c *Cat) Quack() {
        fmt.Println("miao")
}

//2 和 1 不能同时存在
func (c Cat) Walk() {  //c为非指针对象
        fmt.Println("cat walk")
}
func (c Cat) Quack() {
        fmt.Println("miao")
}

func main() {
        var c Duck = &Cat{}  //
        c.Quack() // 调用1

        c = Cat{}
        c.qUACK() //调用2
}

无论上述代码中初始化的变量 c 是 Cat{} 还是 &Cat{},使用 c.Quack() 调用方法时都会发生值拷贝:

  • 对于 &Cat{} 来说, 这意味着拷贝一个新的 &Cat{} 指针, 这个指针与原来的指针指向一个相同并且唯一的结构体, 所以编译器可以隐式的对变量解引用(dereference)获取指针指向的结构体;

  • 对于 Cat{} 来说, 这意味着 Quack 方法会接受一个全新的 Cat{}, 因为方法的参数是 * Cat, 编译器不会无中生有创建一个新的指针; 即使编译器可以创建新指针, 这个指针指向的也不是最初调用该方法的结构体;

nil or non-nil

type TestStruct struct{}

func NilOrNot(v interface{}) bool {
        return v == nil
}

func main() {
        var s *TestStruct
        fmt.Println(s == nil)      // #=> true
        fmt.Println(NilOrNot(s))   // #=> false
}

$ go run main.go
true
false

调用 NilOrNot 函数时发生了隐式的类型转换, 除了向方法传入参数之外, 变量的赋值也会触发隐式类型转换. 在类型转换时, * TestStruct 类型会转换成 interface{} 类型, 转换后的变量不仅包含转换前的变量, 还包含变量的类型信息 TestStruct, 所以转换后的变量与 nil 不相等.

注意: * TestStruct 类型会转换成 interface{} 类型, 转换后的变量不仅包含转换前的变量, 还包含变量的类型信息 TestStruct

数据结构

go 根据 接口类型是否包含一组方法将接口类型分为两类:

  • 使用 runtime.iface 结构体表示包含方法的接口类型
  • 使用 runtime.eface 结构体表示不包含任何方法的接口类型

runtime.eface:

type eface struct { //16字节
        _type   *_type  //该interface的真实类型
        data    unsafe.Pointer
}

由于 interface{} 类型不包含任何方法, 所以它的结构只包含指向底层数据和类型的两个指针. 从上述结构我们也能推断出Go 语言的任意类型都可以转换成 interface{}.

  • runtime._ type:
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
}
  • size: 类型占用的内存空间, 为内存空间的分配提供信息
  • hash: 类型的hash值, 可以快速帮我们在做类型比对时候确定类型是否相等
  • equal: 该字段用于判断该类型的多个对象是否相等

runtime.iface:

type iface struct{
        tab     *itab   //itab是interface的核心部分
        data    unsafe.Pointer
}

type itab struct { // 32 字节
        inter *interfacetype
        _type *_type
        hash  uint32
        _     [4]byte
        fun   [1]uintptr
}
  • inter和_type都是用来表示该interface{}的类型.
  • hash: 是对_type.hash的拷贝, 当我们需要比对类型时候, 用这个更快, 不需要通过_type.hash比对
  • fun: 是一个动态大小的数组, 它是一个用于动态派发的虚函数表, 存储了一组函数指针. 虽然该变量被声明成一个大小为1的固定数组, 但真正使用时候这个长度为1的数组里存放的是一个动态数组的地址.