go进阶-接口


只要某个类型实现了一个接口定义的方法集,那就可以认为这个类型实现了接口,是接口类型。

定义

type Human interface {
    Say(s string) error
    // ...方法集
}

demo

type Animal interface {
    Say()
}

type Pig struct {
    name string
}

type Horse struct {
    name string
}

func (di *Pig) Say() {
    di.name = "yadi pig" // 指针能改变参数
    fmt.Println("Hi", di.name)
}

func (horse Horse) Say() {
    horse.name = "yadi horse" // 不能改变外部horse name
    fmt.Println("Hi", horse.name)
}

func SomeAnimalSay(animal Animal) {
    animal.Say()
}

func main() {
    p := Pig{"little pig"}
    h := Horse{"Big horse"}
    SomeAnimalSay(&p) // Hi yadi pig
    SomeAnimalSay(&h) // Hi yadi horse
    fmt.Printf("p.name: %v\n", p.name)
    fmt.Printf("h.name: %v\n", h.name)
    // p.name: yadi pig
    // h.name: Big horse
}

数据结构

Go 接口镀层在运行时分为两类:

  • runtime.iface
  • runtime.eface

eface 用于表示没有方法的空接口(empty interface)类型变量,也就是 interface{}类型的变量;

iface 用于表示其余拥有方法的接口 interface 类型变量。

eface

type eface struct {
    _type *_type // 底层的类型信息
    data  unsafe.Pointer // 指向的值信息指针
}

这是个空接口,不含任何方法。

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
}

alignfieldAlign的区别?

内存对齐

首先,对齐是用空间换时间,完成块读取、写入。如图:image-20220505214829151

预计是32字节。

type A struct {
    a bool
    b int32
    c int8
    d int64
    e byte
}

func main() {
    p1 := A{}
    fmt.Printf("unsafe.Sizeof(p1): %v\n", unsafe.Sizeof(p1)) 
    //unsafe.Sizeof(p1): 32
}

符合预料。

如果要节省内存,写法应为:

type A struct {
    a bool
    c int8
    e byte
    b int32
    d int64
}

func main() {
    p1 := A{}
    fmt.Printf("unsafe.Sizeof(p1): %v\n", unsafe.Sizeof(p1)) 
    //unsafe.Sizeof(p1): 16
}

align:一个变量在内存中对齐后所用的字节数。

FieldAlign:这种类型的变量如果是struct中的字段,对齐值。

type A struct {
    a bool
    b int32
    c int8
    d int64
    e byte
}

func main() {
    p1 := A{false, 6, 1, 12, 1}
    var bo bool
    var i int16
    fmt.Printf("unsafe.Alignof(bo): %v\n", unsafe.Alignof(bo))                           // 1
    fmt.Printf("unsafe.Alignof(i): %v\n", unsafe.Alignof(i))                             // 2
    fmt.Printf("reflect.TypeOf(p1).Align(): %v\n", reflect.TypeOf(p1).Align())           // 8
    fmt.Printf("reflect.TypeOf(p1).FieldAlign(): %v\n", reflect.TypeOf(p1).FieldAlign()) // 8
    fmt.Printf("reflect.TypeOf(p1).Size(): %v\n", reflect.TypeOf(p1).Size())             // 32
}

iface

type iface struct {
    tab  *itab
    data unsafe.Pointer
}

type itab struct {
    inter *interfacetype // 接口的类型信息
    _type *_type // 具体的类型信息
    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.
    // 存储接口方法集的具体实现,包含一组函数指针,实现了接口方法的动态分配,在每次接口发生变更时都会更新。
    // 后面详细说,留个标记================================
}

type interfacetype struct {
    typ     _type // 接口具体类型信息
    pkgpath name // 接口包信息
    mhdr    []imethod // 接口定义的函数列表
}

type imethod struct {
    name nameOff
    ityp typeOff
}

// name and offset
type nameOff struct {
    n   *ir.Name
    off int64
}

// Value
Name struct {
    Value string
    expr
}

type expr struct{ node }

type Pos struct {
    base      *PosBase
    line, col uint32
}

// A PosBase represents the base for relative position information:
// At position pos, the relative position is filename:line:col.
type PosBase struct {
    pos       Pos
    filename  string
    line, col uint32
    trimmed   bool // whether -trimpath has been applied
}

真尼玛老母猪dxz。

image-20220505222450040

类型断言

func main() {
    var i interface{} = "string"
    s := i.(string)
    fmt.Printf("1  s: %v\n", s)

    s, ok := i.(string)
    fmt.Printf("2  ok: %v\n", ok)

    switch i.(type) {
    case string:
        fmt.Println("string..")
    case int:
        fmt.Println("int")
    default:
        fmt.Println("else")
    }
}

// 1  s: string
// 2  ok: true
// string..

类型转换

image-20220505223343235

动态分派

之前在iface里的func提到了这个:

type iface struct {
    tab  *itab
    data unsafe.Pointer
}

type itab struct {
    inter *interfacetype // 接口的类型信息
    _type *_type // 具体的类型信息
    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.
    // 存储接口方法集的具体实现,包含一组函数指针,实现了接口方法的动态分配,在每次接口发生变更时都会更新。
}

func属性类型是[1]uintptr,只有一个元素,存放了接口方法集的首个方法的地址信息,接着顺序获取就好了。

itab 包含接口的静态类型信息、数据的动态类型信息、函数表。

func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
    // ......

    var m *itab

    // First, look in the existing table to see if we can find the itab we need.
    // This is by far the most common case, so do it without locks.
    // Use atomic to ensure we see any previous writes done by the thread
    // that updates the itabTable field (with atomic.Storep in itabAdd).
    t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable)))
    if m = t.find(inter, typ); m != nil {
        goto finish
    }

    // Not found.  Grab the lock and try again.
    lock(&itabLock)
    if m = itabTable.find(inter, typ); m != nil {
        unlock(&itabLock)
        goto finish
    }

    // Entry doesn't exist yet. Make a new entry & add it.
    m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*goarch.PtrSize, 0, &memstats.other_sys))
    m.inter = inter
    m._type = typ
    // The hash is used in type switches. However, compiler statically generates itab's
    // for all interface/type pairs used in switches (which are added to itabTable
    // in itabsinit). The dynamically-generated itab's never participate in type switches,
    // and thus the hash is irrelevant.
    // Note: m.hash is _not_ the hash used for the runtime itabTable hash table.
    m.hash = 0
    m.init()
    itabAdd(m)
    unlock(&itabLock)
finish:
    if m.fun[0] != 0 {
        return m
    }
    if canfail {
        return nil
    }
    // this can only happen if the conversion
    // was already done once using the , ok form
    // and we have a cached negative result.
    // The cached result doesn't record which
    // interface function was missing, so initialize
    // the itab again to get the missing function name.
    panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()})
}

第一个不加锁,查缓存,如果没找到,加锁查找,可能另一个协程并发写入,导致未找到,但是数据是存在的;如果还是找不到,插入。