golang-interface(二) 常见问题/底层实现

662 阅读6分钟

1. 常见使用问题

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

  • 作用:用简单的语法,检查T这个struct是否实现了I这个接口
  • 细化理解:可以把=左右两边分开来看
    • 左边:var _ I等价于我们平时用的 var 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()

}
  • 总结如图
    • image.png
  • 原因分析
    • 首先我们先排除,结构体初始化变量,结构体实现接口 以及 结构体指针初始化变量,结构体指针初始化变量,因为这两个都一一对应,就很好理解
    • 其次我们分析,为什么结构体指针初始化变量,结构体实现接口可以进行调用?
      • 因为编译器可以通过指针获取到指向的结构体
    • 最后分析,为什么结构体初始化变量,指针实现接口不可以进行调用?
      • 因为反过来结构体并不能反向获取到那个实现了接口的指针
    • 简单总结就是(自己总结的):可以每一个指针对象都可以知道自己所属的类型。但是不是每一个类型,他都能知道是这个类型的所有指针。换到现实生活里面,一个班上的同学(指针)都知道自己是喜欢班花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 底层组成有哪几种?

  • interface分类:分为两种ifaceeface
  • 源码地址:runtime/runtime2.go
  • 备注以下分析我就直接在代码里面中文注解各个字段大概意思,目前也没有实战接触到这块(可能后面细化深入写反射的时候会用到?),所以就不细讲了,想深入了解的可以网上搜一下其他的

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. 参考链接