go interface

Created

2024-10-28 13:22:59

Updated

2024-10-28 13:23:03

1 什么是接口

  1. 接口是一种协议, 一种规则, 规定了一些功能,具体怎么实现不关心
  2. 比如我们需要将数据进行存储读取,我们规定了读取保存功能,怎么实现不关心.
  3. 在现实中其实可以说我们很多时候经常说的都是”接口”
    1. 说打车, 车是一种实现了4个轮子能移动的接口, 不管是比亚迪还是宝马都行
    2. 说吃饭, 饭是一种实现了可以吃饱功能的接口,具体吃什么,还不清楚
  4. 所以实际上在开发中我们应当 面向接口开发(实际在设计阶段,沟通中一直说的都是”接口”), 接口是一种抽象, 不关心具体怎么实现,这样以后更换具体实现就会比较方便 ## 基本操作
Tip

当我们定义一个新的类型时, 只要保证其方法集合与某个接口相同即可隐式实现该接口

type Animal interface {
    Shout()
}

type Cat struct {
    Age  int
    Name string
}

// 隐式地实现了Animal 接口
// 结构体实现接口
func (c Cat) Shout() {
    fmt.Println("miao")
}
type Dog struct {
    Age  int
}
// 结构体指针实现接口
// 将结构体指针 当成一个整体, 这个东西实现了 方法Shout
func (d *Dog) Shout() {
    fmt.Println("wang")
}
func TestSlice(t *testing.T) {
    // 实际会做 类型是否实现接口 的检查
    // 注意这个实际上就是一种类型转换
    var a Animal = Cat{Age: 2, Name: "tom"}
    a.Shout()
    // 这样也是ok的,前面我们在结构体那里知道,编译器会自动给 Cat的同名方法生成一个*Cat的同名方法
    // 这个同名方式 的作用就在这里
    // 所以*Cat 实现了 Shout 方法,实现了 Animal接口
    var a2 Animal = &Cat{Age: 3, Name: "jjj"}
    a2.Shout()

    // 结构体指针 *Dog 实现了Shout方法, 所以可以
    var d Animal = &Dog{Age: 1}
    d.Shout()
    // 报错, 因为 Dog{Age: 1} 没有实现Shout() 方法
    // var d2 Animal = Dog{Age: 1}

    // 类型断言 方式一
    cat, ok := a.(Cat)
    if ok {
        fmt.Println(cat.Age)
    }
    // 类型断言 方式二
    switch a.(type) {
    case Cat:
        cat := a.(Cat)
        fmt.Println(cat.Name)
    }
}

2 数据结构

2.1 接口与元数据

关于接口的思考
  • var animal Animal = Cat{Age: 2, Name: "tom"}
    • animal是一个类型为Animal接口的变量
    • 这是一个类型转换的操作, 这句话看起来是个废话
  • Cat 能否转换为 接口Animal, 是如何判断的?
    • Cat 结构体是否实现了接口的方法
  • animal可以转换为Cat 对象, 也就是说 接口变量animal必定存储了Cat对象数据的信息,以及Cat的类型信息
  • 接口有方法, ==接口变量animal 必定存储了 接口定义的方法==
关于元数据的思考
  • 我们知道 当我们定义 一个 int64 类型的变量时, 编译时编译器最终会根据代码定义的 int64的 “类型信息” 来分配多少内存的
  • 这些 “类型信息” 我们能否得到呢?
    • 在关于接口的思考中,因为 接口变量和结构体可以互相转换,所以接口变量必定存储了 结构体实际数据以及类型的信息
    • 这个不就是我们想要的吗? 那么我们将变量转换成接口类型不就可以了吗!!! 这波推理可以!
  • 编译后的可执行文件里会有这些信息吗?
    • 没有, 通过二进制 的汇编指令 ,我们可以根据最终的一条条指令,得出指令里是没有所谓这些类型的概念

2.2 空接口 interface

Tip
  1. 前面我们说道只要实现了接口定义的方法, 就算实现了接口.
  2. 空接口 可以认为是没有方法的接口, 那么所有类型可以说都实现了该接口
  3. 那么所有的类型都可以转换为 空接口类型
空接口数据结构
type eface struct {
    _type *_type // 类型信息
    data  unsafe.Pointer // 数据信息
}

type _type struct {
    size       uintptr // 这个类型需要占用的内存大小
    ptrdata    uintptr // size of memory prefix holding all pointers
    hash       uint32  // 类型的hash值, 用来类型比较和查找
    tflag      tflag  // (1)
    align      uint8  // 类型的对齐边界
    fieldAlign uint8  // 类型字段的对齐边界
    kind       uint8  //类型的分类 (2)
    // 比较2个当前类型的变量是否相等
    equal func(unsafe.Pointer, unsafe.Pointer) bool
    gcdata    *byte   // gc 相关
    str       nameOff // 偏移, 可以通过这个得到 类型的名称等信息
    ptrToThis typeOff // 同上, 得到对应指针类型的信息
}
  1. tflag
type tflag uint8

const (
    tflagUncommon      tflag = 1 << 0
    tflagExtraStar     tflag = 1 << 1
    tflagNamed         tflag = 1 << 2
    tflagRegularMemory tflag = 1 << 3 
)
  1. kind
const (
    Invalid Kind = iota
    Bool
    Int
    Int8
    Int16
    Int32
    Int64
    Uint
    Uint8
    Uint16
    Uint32
    Uint64
    Uintptr
    Float32
    Float64
    Complex64
    Complex128
    Array
    Chan
    Func
    Interface
    Map
    Pointer
    Slice
    String
    Struct
    UnsafePointer
)
Caution

更多细节等,后续有时间再弄吧…

代码验证结构
package main

import (
    //  _ "test/pkg"
    "fmt"
    "unsafe"
)

type eface struct {
    _type *_type         // 类型信息
    data  unsafe.Pointer // 数据信息
}

type _type struct {
    size       uintptr // 这个类型需要占用的内存大小
    ptrdata    uintptr // size of memory prefix holding all pointers
    hash       uint32  // 类型的hash值, 用来类型比较和查找
    tflag      uint8   //
    align      uint8   // 类型的对齐边界
    fieldAlign uint8   // 类型字段的对齐边界
    kind       uint8   //类型的分类
    // 比较2个当前类型的变量是否相等
    equal     func(unsafe.Pointer, unsafe.Pointer) bool
    gcdata    *byte   // gc 相关
    str       nameOff // 偏移, 可以通过这个得到 类型的名称等信息
    ptrToThis int32   // 同上, 得到对应指针类型的信息
}

//go:linkname resolveNameOff runtime.resolveNameOff
func resolveNameOff(ptrInModule unsafe.Pointer, off nameOff) name

type Cat struct {
    Age  int
    Name string
}

func (c Cat) Show() {
    fmt.Println(11)
}
func (c Cat) pp() {
    fmt.Println(11)
}
func main() {
    var a int64 = 66
    var b interface{} = a
    c := *(*eface)(unsafe.Pointer(&b))
    fmt.Println(*(*int64)(c.data)) // 66
    fmt.Println(*c._type)
    d := resolveNameOff(unsafe.Pointer(c._type), c._type.str)
    fmt.Println(d.name())

    fmt.Println("--自定义类型 (结构体) 的情况 呢?----")
    cat := Cat{3, "tom"}
    var catAny interface{} = cat
    catEface := *(*eface)(unsafe.Pointer(&catAny))
    fmt.Println(*catEface._type)
    dd := resolveNameOff(unsafe.Pointer(catEface._type), catEface._type.str)
    fmt.Println(dd.name())

    // 结构体中字段的信息呢? 好像仅从eface 中没有.
    // 我们看源码中的 这个方法
    // func (t *_type) uncommon() *uncommontype
    // 从中知道 struct 类型 有额外的信息存储在 *_type 指针指向的内存
    catStructUncommon := *(*structUncommon)(unsafe.Pointer(catEface._type))
    // 可自行验证
    fmt.Println(catStructUncommon.fields[1].name.name())
    fmt.Println(catStructUncommon.u)
}

type structUncommon struct {
    structtype
    u uncommontype
}
type defaultUncommon struct {
    _type
    u uncommontype
}
type uncommontype struct {
    pkgpath nameOff
    mcount  uint16 // number of methods
    xcount  uint16 // number of exported methods
    moff    uint32 // offset from this uncommontype to [mcount]method
    _       uint32 // unused
}
type structtype struct {
    typ     _type
    pkgPath name
    fields  []structfield
}
type structfield struct {
    name   name
    typ    *_type
    offset uintptr
}
type name struct {
    bytes *byte
}
type nameOff int32

func (n name) name() string {
    if n.bytes == nil {
        return ""
    }
    i, l := n.readVarint(1)
    return unsafe.String(n.data(1+i, "non-empty string"), l)
}
func (n name) readVarint(off int) (int, int) {
    v := 0
    for i := 0; ; i++ {
        x := *n.data(off+i, "read varint")
        v += int(x&0x7f) << (7 * i)
        if x&0x80 == 0 {
            return i + 1, v
        }
    }
}
func (n name) data(off int, whySafe string) *byte {
    return (*byte)(add(unsafe.Pointer(n.bytes), uintptr(off), whySafe))
}
func add(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer {
    return unsafe.Pointer(uintptr(p) + x)
}

2.3 非空接口

type iface struct {
    tab  *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.
}


type interfacetype struct {
    typ     _type
    pkgpath name
    mhdr    []imethod  // 方法列表
}
type _type struct {
    size       uintptr
    ptrdata    uintptr // size of memory prefix holding all pointers
    hash       uint32
    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
}
Back to top