随笔 - 21  文章 - 0  评论 - 0  阅读 - 6157

go 接口

接口的值

接口的值简单来说,是由两部分组成的,就是类型和数据。

那么判断两个接口是相等,就是看他们的这两部分是否相等;另外类型和数据都为nil才代表接口是nil,这里就解释了上面的问题。由于golang的err实现是一个接口,所以很容易在err的处理过程中写错。

接口的数据结构

go的接口有两种结构,一种是有方法定义的接口,一种是空接口,分别对应两种实现。

空接口的实现

空接口通过eface结构定义实现,位于/src/runtime/runtime2.go

type eface struct {
    _type *_type
    data unsafe.Pointer
}

eface包含了2个元素,一个是_type,指向对象的类型元数据,一个 data,数据指针。

 

 

复制代码
type _type struct {
    size       uintptr // 类型占用内存大小
    ptrdata    uintptr // 包含所有指针的内存前缀大小
    hash       uint32  // 类型 hash
    tflag      tflag   // 标记位,主要用于反射
    align      uint8   // 对齐字节信息
    fieldAlign uint8   // 当前结构字段的对齐字节数
    kind       uint8   // 基础类型枚举值
    equal func(unsafe.Pointer, unsafe.Pointer) bool // 比较两个形参对应对象的类型是否相等
    gcdata    *byte    // GC 类型的数据
    str       nameOff  // 类型名称字符串在二进制文件段中的偏移量
    ptrToThis typeOff  // 类型元信息指针在二进制文件段中的偏移量
}
复制代码

1,kind,这个字段描述的是如何解析基础类型。在 Go 语言中,基础类型是一个枚举常量,有 26 个基础类型,如下。枚举值通过 kindMask 取出特殊标记位。

 

复制代码
const (
    kindBool = 1 + iota
    kindInt
    kindInt8
    kindInt16
    kindInt32
    kindInt64
    kindUint
    kindUint8
    kindUint16
    kindUint32
    kindUint64
    kindUintptr
    kindFloat32
    kindFloat64
    kindComplex64
    kindComplex128
    kindArray
    kindChan
    kindFunc
    kindInterface
    kindMap
    kindPtr
    kindSlice
    kindString
    kindStruct
    kindUnsafePointer

    kindDirectIface = 1 << 5
    kindGCProg      = 1 << 6
    kindMask        = (1 << 5) - 1
复制代码

以下面两个例子为例:

指针的类型元数据在_type结构体后面,记录着一个*_type,指向其存储元素的类型元数据。
如一个空接口被赋值了指向int的一个指针,那么ptrtype中的elem就指向int的元数据结构体。
type ptrtype struct { type __type elem *_type }

 

 非空接口

空接口由于不包含方法,所以结构简单。非空接口的底层实现:

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

tab中存放的是类型,方法等信息,data指针指向的是iface绑定对象的原始数据。

复制代码
type itab struct {
        //inter 和 _type 确定唯一的 _type类型
    inter *interfacetype //接口自身定义的类型信息,用于定位到具体interface类型
    _type *_type  // 接口实际指向值的类型信息-实际对象类型,用于定义具体interface类型
    hash  uint32 //_type.hash的拷贝,用于快速查询和判断目标类型和接口中类型是一致
    _     [4]byte
    fun   [1]uintptr //动态数组,接口方法实现列表(方法集),即函数地址列表,按字典序排序
                         //如果数组中的内容为空表示 _type 没有实现 inter 接口
}
复制代码

 

itab.inter是interface的类型元数据,它记录了这个接口类型的描述信息,接口要求的方法列表就记录在interfacetype.mhdr这里。

type interfacetype struct {
    typ _type
    pkgpath name
    mhdr []imethod
}

tab._type就是接口的动态类型,也就是被赋给接口类型的那个变量的类型元数据。itab中的_type和iface中的data能简要描述一个变量。_type是这个变量对应的类型,data是这个变量的值。

 

 itab.hash是从itab.type中拷贝来的,是类型的哈希值,用于快速判断类型是否相等时使用。

itab.fun记录的是动态类型实现的那些接口要求的方法的地址,是从方法元数据中拷贝来的,为的是快速定位到方法。

如果itab._type对应的类型没有实现这个接口,则itab.fun[0]=0,这在类型断言时会用到。当fun[0]为0时,说明_type并没有实现该接口,当实现接口时,fun存放了第一个接口方法的地址。

 

 

 

itab缓存

对于itab来说,既然一个非空接口类型和一个动态类型就可以确定一个itab的内容,那么这个itab结构体自然是可以被接口类型和动态类型均相同的接口变量复用的。这就是itabTable.

const itabInitSize = 512

type itabTableType struct {
    size uintptr
    count uintptr
    entries [itabInitSize]*itab
}
    

可以看出这个全局的itabTable是以数组的形式存储的,size记录数组的大小,总是2的次幂

count记录数组中已使用了多少。

entries 是一个 *itab数组,初始大小为512.

需要一个itab时,会首先去itabTable里查找,计算哈希值时会用到接口类型(itab.inter)和动态类型(itab._type)的类型哈希值。

func itabHashFunc(inter *interfacetype, typ *_type) uintptr {
    return uintptr(inter.typ.hash ^ typ.hash)
}

如果能查询到对应的itab指针,就直接拿来使用。若没有就要再创建,然后添加到itabTable中。

 

 

 

 类型断言

一,空接口.(具体类型)

 

 每个具体类型的元数据都是全局唯一的。

 二,非空接口.(具体类型)

 

 三,空接口.(非空接口)

 

 

 

 先去itab缓存中查找,查到后检查是否itab.fun[0] == 0。若缓存中不存在,就去检查_type对应的类型元数据的方法是否在非空接口中都有对应。

四,非空接口.(非空接口)

 

 

 

 

posted on   博览天下with天涯海角  阅读(89)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示