我在用 golang 的 interface 时候,总共写了3篇文章,大家可以关联着看,希望可以解决大家开发中遇到的一些问题

1. 常见使用问题

1.1 var _ I = (*T)(nil) 是什么意思?

var _ Ivar variable type(* T)(nil)var variable *T nil
package main

import "fmt"

type I interface {
}

type I2 interface {
    say()
}

type TestStruct struct{}

func main() {
    var _ I = (*TestStruct)(nil)
    //var _ I2 = (*TestStruct)(nil) // 编译就报错了

    // 繁琐的写法
    var testStruct *TestStruct = nil
    var i I // 改成 var i I,则无法编译
    i  = testStruct // Verify that *T implements I.
    fmt.Println(i)
}

1.2 golang 结构体和指针实现接口

  • 当初始化为结构体指针的时候,不管实现方法的接受者是指针还是结构体都可以调用
package main

import "fmt"

type Duck interface {
    Quack()
}
type Cat struct{}

func (c Cat) Quack() {
    fmt.Println("meow")
}

//func (c *Cat) Quack() {
//  fmt.Println("meow")
//}

func main() {
    // 结构体
    var c Duck = &Cat{}
    c.Quack()

}
  • 当初始化为结构体的时候,实现方法的接受者为结构体的时候可以,实现方法的接受者为指针则不行
package main

import "fmt"

type Duck interface {
    Quack()
}
type Cat struct{}

// 这里可以
//func (c Cat) Quack() {
//  fmt.Println("meow")
//}

// 这里不行
func (c *Cat) Quack() {
    fmt.Println("meow")
}

func main() {
    // 结构体
    var c Duck = Cat{}
    c.Quack()

}
  • 总结如图


  • 原因分析
    • 首先我们先排除,结构体初始化变量,结构体实现接口 以及 结构体指针初始化变量,结构体指针初始化变量,因为这两个都一一对应,就很好理解
    • 其次我们分析,为什么结构体指针初始化变量,结构体实现接口可以进行调用?因为编译器可以通过指针获取到指向的结构体
    • 最后分析,为什么结构体初始化变量,指针实现接口不可以进行调用?因为反过来结构体并不能反向获取到那个实现了接口的指针
    • 简单总结就是(自己总结的):可以每一个指针对象都可以知道自己所属的类型。但是不是每一个类型,他都能知道是这个类型的所有指针。换到现实生活里面,一个班上的同学(指针)都知道自己是喜欢班花A或班草B(结构体),但是 班花A或班草B 并不知道具体有哪些同学喜欢自己.....
    • 官方一点就是:
      • 在golang中所有的参数传递都是值传递
      • 所以当接受者是*Cat的时候,调用者如果是结构体,我们是没办法从结构体推出来一个指针,编译器不会凭空创造一个指针,所以失败
      • 当接受者是Cat的时候,调用者如果是指针,那么我们是可以从指针反解出来类型,相当于就变成了调用者是结构体,接受者也是结构体

1.3 所有的 nil 都是相等的吗?

  • 结论是不会。我们从以下代码可以看出,t1和s1都是nil,但是他们两个不同类型 无法直接进行比较,也就是说nil本身是包含了类型信息了,否则两个nil相比较的时候不会提示类型不一致
package main

import "fmt"

type TestStruct struct{}

func main() {
    var t1 *TestStruct
    var s1 *string
    fmt.Println(t1) // nil
    fmt.Println(s1) // nil

    //fmt.Println(t1 == s1) // 编译报错
}
  • 经过搜索我们可以定位到builtin/builtin.go,证明nil他是一个变量,这个变量类型可以是pointer, channel, func, interface, map, or slice
// nil is a predeclared identifier representing the zero value for a
// pointer, channel, func, interface, map, or slice type.
var nil Type // Type must be a pointer, channel, func, interface, map, or slice type

1.4 空的interface一定是nil吗?

  • 以下代码输出true和false,第二个false证明了,哪怕打印出来时nil的interface也不一定就==nil
  • 要分析这个问题,首先我们要知道两点:
  • 第一点是nil底层是什么?参考 上面1.3
  • 第二点是interface底层是什么结构呢?
    • 首先要知道,一个interface{}类型的变量包含了两个指针,一个指向值的类型,另外一个指向实际的值
    • 其次,我们分析下面,我们从*TestStruct到interface{}其实是有发生类型转换的。
    • 然后,我们反着推,我们是可以从通过NilOrNot的传入参数,反射后得到传入参数的类型以及数据值的,也就是说经过类型转换后的值依旧保留着原有数据的类型以及值
    • 最后,就下面的例子,经过数据转换后,经过传入参数的值依旧为空,但是指向的类型并不是,所以的话就不等于nil了
package main

import "fmt"

type TestStruct struct{}

func NilOrNot(v interface{}) bool {
    fmt.Println(fmt.Sprintf("%v,%T",v,v)) // <nil>,*main.TestStruct
    return v == nil
}

func main() {
    var s *TestStruct
    fmt.Println(s == nil) // true
    fmt.Println(fmt.Sprintf("%v,%T",s,s))// <nil>,*main.TestStruct
    fmt.Println(NilOrNot(s)) // #=> false
}

2. interface 底层实现

2.1 interface 底层组成有哪几种?

ifaceeface

2.3 iface

  • 什么是iface? 含有方法的接口
type iface struct {
    tab  *itab // 指针类型,指向 itab 类型
    data unsafe.Pointer // 描述了具体的值
}

type itab struct {
    inter *interfacetype // 接口类型
    _type *_type // 通用的类型信息
    hash  uint32 // 是对于_type.hash的复制,便于快速判断目标类型
    _     [4]byte
    // 存储了接口方法对应的具体数据类型的方法地址,实现具体数据类型的
    fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}
  • runtime._type,代码如下
// _type 实际上是描述 Go 语言中各种数据类型的结构体
// 其余很多类型都是基于这个结构体进行管理
type _type struct {
    size       uintptr // 类型占用的内存空间大小
    ptrdata    uintptr // size of memory prefix holding all pointers
    hash       uint32 // 用于快速判断类型是否相等
    // 类型的flag,和反射相关
    tflag      tflag
    // 内存对齐相关
    align      uint8
    fieldAlign uint8
    // 类型的编号
    kind       uint8
    // function for comparing objects of this type
    // (ptr to object A, ptr to object B) -> ==?
    equal func(unsafe.Pointer, unsafe.Pointer) bool
    // gcdata stores the GC type data for the garbage collector.
    // If the KindGCProg bit is set in kind, gcdata is a GC program.
    // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}

// 举例基于_type的类型,数组 和 函数
type arraytype struct {
    typ   _type
    elem  *_type
    slice *_type
    len   uintptr
}
type functype struct {
    typ      _type
    inCount  uint16
    outCount uint16
}

2.2 eface

  • 什么是eface?没有含有方法的接口
  • 实现代码如下:
type eface struct {
    _type *_type // 同上
    data  unsafe.Pointer // 同上
}

3. 参考链接