NOTE

1.4 interface

1. 有哪些 1.1. 空接口interface{} 可以接收任意类型的数据 它只要记录这个数据在哪儿,是什么类型的就足够了 1.1.1. 举例 底层结构如下 - 赋值 - 1.2. 非空接口 非空接口就是有方法列表的接口类型 一个变量要想赋值给一个非空接口类型,其类型必须要实现该接口要求的所有方法

Go创建于 更新于 historical

这是历史学习笔记,可能存在过时或不完整的理解。

1. 有哪些

1.1. 空接口interface{}

可以接收任意类型的数据 它只要记录这个数据在哪儿,是什么类型的就足够了

1.1.1. 举例

var e interface{}

底层结构如下

type eface struct {
    _type *_type//指向接口的动态类型元数据
    data  unsafe.Pointer//指向接口的动态值
}
  • 赋值
f, _ := os.Open("eggo.txt")
e = f

1.2. 非空接口

非空接口就是有方法列表的接口类型 一个变量要想赋值给一个非空接口类型,其类型必须要实现该接口要求的所有方法才行

1.2.1. 举例

var rw io.ReadWriter

底层结构如下


type iface struct {
    tab   *itab//接口类型元数据+动态类型元数据
    data  unsafe.Pointer//接口的动态值
}


type itab struct {
    inter  *interfacetype//接口类型元数据
    _type  *_type//动态类型元数据
    hash   uint32
    _      [4]byte
    fun    [1]uintptr 
}


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

赋值

f, _ := os.Open("eggo.txt")
rw = f
  • 在声明一个变量并且赋值
var w io.Writer = f
  • 总结

    • 通过<接口类型, 动态类型>可以找到唯一的itab结构体
    • 因此golang把<接口类型, 动态类型>作为key,itab结构体作为value缓存在hash表中复用

2. interface是什么

一组方法的签名

3. 为什么需要interface

解耦上下游。上游只需要知道下游的接口即可,不需要知道下游具体的实现细节

4. 如何使用

4.1. 实现接口

go不需要显式实现接口,他用的是鸭子类型

4.1.1. 鸭子类型

如果某个东西长得像鸭子,像鸭子一样游泳,像鸭子一样嘎嘎叫,那它就可以被看成是一只鸭子。 换句话说只要实现了A的接口那么就可以认为是个A。 他更关注的是对象如何被使用而不是对象的类型本身

type People interface {
	Run()
}

type Student struct {
}

func (s *Student) Run() {
	fmt.Println("running...")
}

func TestInterface1(t *testing.T) {
	var p People = &Student{}
	p.Run()
}

//输出
running...

4.2. 多态

多态是一种运行期的行为,它可以使得一种类型具有多种类型的能力

  • 接口
//接口
type Phone interface {
	Call()
}

//多态的函数
func Call(phone Phone) {
	phone.Call()
}
  • 实现类
//实现类1
type Nokia struct {
	//Phone//写不写明实现的接口都行
}

func (n *Nokia) Call() {
	fmt.Println("I am Nokia.")
}

//实现类2
type Redmi struct {

}

func (r Redmi) Call() {
	fmt.Println("I am Redmi.")
}
  • 测试
func TestNokia_Call(t *testing.T) {
	Call(&Nokia{})
	Call(&Redmi{})
}

4.3. 继承

通过在结构体中嵌入匿名类型成员,可以继承匿名类型的属性和方法

4.3.1. 继承属性

type Point struct{ X, Y float64 }

type ColoredPoint struct {
	Point //继承匿名结构体
}

func TestInterface1(t *testing.T) {
	var cp ColoredPoint
	cp.X = 1
	cp.Point.Y = 2
	fmt.Println(cp.Point.X) // "1"
	fmt.Println(cp.Y)       // "2"
}
//输出
1
2

4.3.2. 继承方法

type Cache struct {
	m map[string]string
	sync.Mutex
}

func (c *Cache) Lookup(key string) string {
	c.Lock()         //编译器自动展开p.Mutex.Lock()
	defer c.Unlock() //编译器自动展开p.Mutex.Unlock()

	return c.m[key]
}

func TestInterface2(t *testing.T) {
	c := &Cache{}
	value := c.Lookup("test")
	fmt.Println(value)
}

4.4. 接口转换

4.4.1. 类型转换

<结果类型> := <目标类型> (<表达式>)

type MyInt int

func TestType1(t *testing.T) {
	var a MyInt = 1
	var b int = 1
	//fmt.Println(a == b)//类型不同,不同比较
	fmt.Println(a == MyInt(b))
}

//输出
true

4.4.2. 类型断言

A.(B) 空接口 非空接口 判断
具体类型 空接口.(具体类型) 非空接口.(具体类型) A的动态类型是否为B
非空接口 空接口.(非空接口) 非空接口.(非空接口) A的动态类型是否实现了B的动态类型的方法
4.4.2.1. 空接口.(具体类型)
  • 断言成功
var e interface{}
f,_ := os.Open("eggo.txt")
e = f
r,ok := e.(*os.File)
  • 断言失败
var e interface{}
f := "eggo"
e = f
r,ok := e.(*os.File)
4.4.2.2. 非空接口.(具体类型)

看iface.tab是否等于<接口类型, 动态类型>对应的itab

  • 断言成功
var rw io.ReadWriter
f,_ := os.Open("eggo.txt)
rw = f
r,ok := rw.(*os.File)
4.4.2.3. 空接口.(非空接口)
  • 断言成功
var e interface{}
f,_ := os.Open("eggo.txt")
e = f
rw,ok := e.(io.ReadWriter)
4.4.2.4. 非空接口.(非空接口)
  • 断言成功

var w io.Writer
f,_ := os.Open("eggo.txt")
w = f
rw,ok := w.(io.ReadWriter)

安全类型断言:<目标类型的值>,<布尔参数> := <表达式>.( 目标类型 )
非安全类型断言:<目标类型的值> := <表达式>.( 目标类型 )

func TestType2(t *testing.T) {
	var a interface{}
	var b int = 1

	a = b

	c := a.(int)//类型断言

	fmt.Println(c)

}
//输出
1
4.4.2.5. 静态类型、动态类型和动态值
func TestInterface3(t *testing.T) {
	var reader io.Reader //reader的静态类型为io.Reader,动态类型为nil,动态值为nil
	file, err := os.OpenFile("test", os.O_RDWR, 0)
	if err != nil {
		panic(err)
	}
	reader = file //reader的静态类型为io.Reader,动态类型为*os.File,动态值为file

	writer := reader.(io.Writer) //由于file实现了io.Writer接口,所以这里可以断言成Writer
	//writer静态类型为io.Writer,动态类型为*os.File,动态值为file

	var empty interface{}
	empty = writer //不需要断言,因为所有接口都实现了空接口
	fmt.Println(reader, file, writer, empty)
}

//输出
&{0xc000074a00} &{0xc000074a00} &{0xc000074a00} &{0xc000074a00}

4.4.3. 类型转换 vs 类型断言

类型转换 类型断言
相同点 把一个类型转换成另一个类型 把一个类型转换成另一个类型
不同点 操作普通变量 操作接口变量

5. 原理

5.1. iface和eface

iface eface
相同点 都用于描述接口 都用于描述接口
不同点 接口带方法 空接口interface{}

5.1.1. iface

type iface struct {
    tab  *itab//指向itab。主要有接口的类型和实体的类型
    data unsafe.Pointer//指向具体的值
}

type itab struct {
    inter  *interfacetype//描述了接口的类型
    _type  *_type//描述了实体的类型。包括内存对齐方式,大小等
    link   *itab
    hash   uint32 // copy of _type.hash. Used for type switches.
    bad    bool   // type does not implement interface
    inhash bool   // has this itab been added to hash?
    unused [2]byte
    fun    [1]uintptr // 和接口方法对应的具体数据类型的方法地址,实现接口调用方法的动态分派
}


type interfacetype struct {
    typ     _type//描述 Go 语言中各种数据类型的结构体
    pkgpath name//定义了接口的包名
    mhdr    []imethod//接口所定义的函数列表
}


type _type struct {
    // 类型大小
    size       uintptr
    ptrdata    uintptr
    // 类型的 hash 值
    hash       uint32
    // 类型的 flag,和反射相关
    tflag      tflag
    // 内存对齐相关
    align      uint8
    fieldalign uint8
    // 类型的编号,有bool, slice, struct 等等等等
    kind       uint8
    alg        *typeAlg
    // gc 相关
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}


//各种数据类型都是在 _type 字段的基础上,增加一些额外的字段来进行管理的

type arraytype struct {
    typ   _type
    elem  *_type
    slice *_type
    len   uintptr
}

type chantype struct {
    typ  _type
    elem *_type
    dir  uintptr
}

type slicetype struct {
    typ  _type
    elem *_type
}

type structtype struct {
    typ     _type
    pkgPath name
    fields  []structfield
}

5.1.2. eface

type eface struct {
    _type *_type//描述了实体的类型。包括内存对齐方式,大小等
    data  unsafe.Pointer//指向具体的值
}

5.2. interface和nil比较

首先interface的值为tab(指向类型信息)+data (指向具体的数据) 然后interface的零值则是tabdata都为nil 最后interface的零值和nil是相等的

type Coder interface {
	code()
}

type Gopher struct {
	name string
}

func (g Gopher) code() {
	fmt.Printf("%s is coding\n", g.name)
}


func getGopher() Coder {
	var g *Gopher
	fmt.Printf("g==nil: %v, Type: %T, Data: %v\n", g == nil, g, g)
	return g
}

func TestInterface1(t *testing.T) {
	var c Coder
	fmt.Printf("c==nil: %v, Type: %T, Data: %v\n", c == nil, c, c)

	c = getGopher()

	fmt.Printf("c==nil: %v, Type: %T, Data: %v\n", c == nil, c, c)

}


//输出
c==nil: true, Type: <nil>, Data: <nil>
g==nil: true, Type: *interface1.Gopher, Data: <nil>
c==nil: false, Type: *interface1.Gopher, Data: <nil>

5.3. 判断类型是否实现接口的原理

itab由接口类型和实体类型构成。

Go会使用类型的方法集接和口所需要的方法集进行匹配,如果类型的方法集完全包含接口的方法集,则可认为该类型实现了该接口。

例如某类型有 m 个方法,某接口有 n 个方法,则很容易知道这种判定的时间复杂度为 O(mn),Go 会对方法集的函数按照函数名的字典序进行排序,所以实际的时间复杂度为 O(m+n)。

5.4. 接口转换的原理

//inter 表示接口类型,i 表示绑定了实体类型的接口,r 则表示接口转换了之后的新的 iface
func convI2I(inter *interfacetype, i iface) (r iface) {
    tab := i.tab
    if tab == nil {
        return
    }
    if tab.inter == inter {
        r.tab = tab
        r.data = i.data
        return
    }
    r.tab = getitab(inter, tab._type, false)
    r.data = i.data
    return
}

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

    // 根据 inter, typ 计算出 hash 值
    h := itabhash(inter, typ)

    // look twice - once without lock, once with.
    // common case will be no lock contention.
    var m *itab
    var locked int
    for locked = 0; locked < 2; locked++ {
        if locked != 0 {
            lock(&ifaceLock)
        }

        // 遍历哈希表的一个 slot
        for m = (*itab)(atomic.Loadp(unsafe.Pointer(&hash[h]))); m != nil; m = m.link {

            // 如果在 hash 表中已经找到了 itab(inter 和 typ 指针都相同)
            if m.inter == inter && m._type == typ {
                // ……

                if locked != 0 {
                    unlock(&ifaceLock)
                }
                return m
            }
        }
    }

    // 在 hash 表中没有找到 itab,那么新生成一个 itab
    m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
    m.inter = inter
    m._type = typ

    // 添加到全局的 hash 表中
    additab(m, true, canfail)
    unlock(&ifaceLock)
    if m.bad {
        return nil
    }
    return m
}

6. 参考