我在用 golang 的 interface 时候,总共写了3篇文章,大家可以关联着看,希望可以解决大家开发中遇到的一些问题
1. 常见使用问题
1.1 var _ I = (*T)(nil) 是什么意思?
var _ Ivar 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()
}
- 总结如图:
- 原因分析:
- 首先我们先排除,结构体初始化变量,结构体实现接口 以及 结构体指针初始化变量,结构体指针初始化变量,因为这两个都一一对应,就很好理解
- 其次我们分析,为什么结构体指针初始化变量,结构体实现接口可以进行调用?因为编译器可以通过指针获取到指向的结构体
- 最后分析,为什么结构体初始化变量,指针实现接口不可以进行调用?因为反过来结构体并不能反向获取到那个实现了接口的指针
- 简单总结就是(自己总结的):可以每一个指针对象都可以知道自己所属的类型。但是不是每一个类型,他都能知道是这个类型的所有指针。换到现实生活里面,一个班上的同学(指针)都知道自己是喜欢班花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 底层组成有哪几种?
ifaceeface
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 // 同上
}