接口是Go这门静态类型语言中唯一动静兼备的语言特性.
1.静态特性:
1).接口类型变量具有静态类型.比如var e error中变量e的静态类型为error.
2).支持在编译阶段的类型检查.当一个接口类型变量被赋值时.编译器会检查右值是否
实现了该接口集合中的所有方法.
2.动态特性:
1).接口类型变量兼具动态类型.在运行时存储在接口类型变量中的值的真实类型.比如
var i interface{}=13中接口变量i的类型为int.
2).接口类型变量在程序运行时可以被赋值为不同的动态类型变量.从而支持多态.
3.nill error !=nil?
示例:
```
type MyError struct {
error
}
var ErrBad = MyError{
errors.New("Bad"),
}
func Bad() bool {
return false
}
func ReturnError() error {
var p *MyError = nil
if Bad() {
p = &ErrBad
}
return p
}
```
main方法:
```
func main() {
err := Concurrent.ReturnError()
if err != nil {
fmt.Printf("error: %s\n", err)
return
}
fmt.Printf("ok")
}
```
执行结果:
从结果可以得知.并没有按照预想的打印ok.接下来就进入探索之路.
4.接口类型变量内部表示:
接口类型的动静兼备特性决定了变量内部表示不像静态类型那样.
源码位置:src/runtime/runtime2.go
```
type iface struct {
tab *itab
data unsafe.Pointer
}
type eface struct {
_type *_type
data unsafe.Pointer
}
```
可以看到在运行层面.接口类型变量有两种内部表示--eface和iface.
1).eface:用于表示没有方法的空接口(empty interface)类型变量.即interface{}
类型的变量.
2).iface:用于表示其余拥有方法的接口(interface)类型变量.
这两种结构的共同点是都有两个指针字段.并且第二个指针字段的功用相同.都指向当
前赋值给该接口类型变量的动态类型变量的值.
不通点在于eface所表示的空接口类型并无方法列表.因此第一个指针指向一个_type
类型结构.源码如下:
```
type Type struct {
Size_ uintptr
PtrBytes uintptr // number of (prefix) bytes in the type that can contain pointers
Hash uint32 // hash of type; avoids computation in hash tables
TFlag TFlag // extra type information flags
Align_ uint8 // alignment of variable with this type
FieldAlign_ uint8 // alignment of struct field with this type
Kind_ Kind // enumeration for C
// 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.
// Normally, GCData points to a bitmask that describes the
// ptr/nonptr fields of the type. The bitmask will have at
// least PtrBytes/ptrSize bits.
// If the TFlagGCMaskOnDemand bit is set, GCData is instead a
// **byte and the pointer to the bitmask is one dereference away.
// The runtime will build the bitmask if needed.
// (See runtime/type.go:getGCMask.)
// Note: multiple types may have the same value of GCData,
// including when TFlagGCMaskOnDemand is set. The types will, of course,
// have the same pointer layout (but not necessarily the same size).
GCData *byte
Str NameOff // string form
PtrToThis TypeOff // type for pointer to this type, may be zero
}
```
iface除了要存储动态类型信息外.还要存储接口本身的信息(接口的类型信息 方法 列
表信息)以及动态类型所实现的方法的信息.因此iface的第一个字段指向第一个itab
类型结构.源码如下:
```
type ITab struct {
Inter *InterfaceType
Type *Type
Hash uint32 // copy of Type.Hash. Used for type switches.
Fun [1]uintptr // variable sized. fun[0]==0 means Type does not implement Inter.
}
```
上面itab结构中的第一个字段inter指向的interfacetype结构存储这该接口类型自
身的信息.interfacetype源码如下:
```
type InterfaceType struct {
Type
PkgPath Name // import path
Methods []Imethod // sorted by hash
}
```
Type:结构类型信息.
pkgpath:包路径名.
mhdr:接口方法集合切片.
示例:
```
type T struct {
n int
s string
}
func main() {
var t = T{
n: 1,
s: "hello world",
}
var ei interface{} = t
println(ei)
}
```
```
type T struct {
n int
s string
}
func (T) M1() {
}
func (T) M2() {
}
type NonEmptyInterface interface {
M1()
M2()
}
func main() {
var t = T{
n: 1,
s: "hello world",
}
var ei NonEmptyInterface = t
println(ei)
}
```
从上面的图可以看出.每个接口类型变量在运行时的表示都是由两部分组成.这两种接
口类型分别简记为eface(Type,data)和iface(tab,data).虽然第一个字段有所差异.
tab和Type可以统一看作动态类型信息.Go语言中每种类型都有唯一的Type信息.无
论是内置原生类型还是自定义类型.Go运行时都会为程序内的全部类型建立只读的共
享Type信息表.因此拥有相同动态类型的同类接口类型变量的type和tab信息是相同
的.接口类型变量data部分则指向一个动态分配的内存空间.内存空间存储的是赋值给
接口类型变量的动态类型变量的值.未显示初始化的接口类型变量值为nil.即该变量的
type和data都为nil.所以判断两个接口类型变量是否相同.只需要判断type和data
是否相同.data判断的不是指针的值.是指针指向的数据的值.
eface和iface是runtime包中非导出结构定义.不能在包外运用.但是Go语言提供了
println预定义函数.
源码位置:src/runtime/print.go
```
func printeface(e eface) {
print("(", e._type, ",", e.data, ")")
}
func printiface(i iface) {
print("(", i.tab, ",", i.data, ")")
}
```
5.nil接口变量:
未赋初始值的接口类型变量的值为nil.这类变量即为nil接口变量.
示例如下:
```
// nil接口变量.
func PrintNilInterface() {
//空接口类型
var i interface{}
//非接口空类型
var err error
println(i)
println(err)
println("i==nil:", i == nil)
println("err==nil:", err == nil)
println("i==err:", i == err)
println(" ")
}
```
main方法:
```
func main() {
Concurrent.PrintNilInterface()
}
```
输出结果:
从结果可以看出.无论是空接口类型还是非空接口类型变量一旦变量值为nil.他们的内
部表示均为(0x0,0x0).类型信息和数据信息都为空.因此上面变量i和err等值判断为
true.
6.空接口类型变量:
示例如下:
```
// 非接口类型变量
func PrintNonEmptyInterface() {
//非接口类型
var err1 error
var err2 error
err1 = (*T)(nil)
println("err1", err1)
println("err1 == nil:", err1 == nil)
err1 = T{5}
err2 = T{6}
println("err1", err1)
println("err2", err2)
println("err1==err2", err1 == err2)
err2 = fmt.Errorf("%d\n", 5)
println("err1", err1)
println("err2", err2)
println("err1==err2", err1 == err2)
println(" ")
}
```
main方法:
func main() {
Concurrent.PrintNonEmptyInterface()
}
执行结果:
对于空接口类型变量.只有在type和data(不是数据指针一致)所指数据内容一致的情
况下.两个空接口类型变量之间才能画等号.
Go在创建eface时一般会为data重新分配内存空间.将动态类型变量的值复制到这块
内存空间.并将data指针指向这块内存空间.所以我们大多数情况下看到的data值是
不同的. 从结果可以看出上面问题的答案.虽然值为nil但是他们的类型不同.所以值比较是
false.
7.空接口类型变量与非空接口类型变量等值比较:
示例如下:
```
// 空接口类型变量与非空接口类型变量.
func PrintEmptyInterfaceAndNonEmptyInterface() {
var emptyInterface interface{} = T{5}
var err error = T{5}
println("emptyInterface", emptyInterface)
println("err", err)
println("err == emptyInterface:", err == emptyInterface)
err = T{6}
println("emptyInterface", emptyInterface)
println("err", err)
println("err == emptyInterface:", err == emptyInterface)
}
```
main方法:
func main() {
Concurrent.PrintEmptyInterfaceAndNonEmptyInterface()
}
执行结果:
空接口类型变量和非空接口类型变量内部表示的家结构有所不同.似乎一定不能相等.
但Go在进行等值比较的时候.类型比较使用的是eface的type和iface的tab.type.
空接口类型变量和非空接口类型变量内部表示的家结构有所不同.似乎一定不能相等.
但Go在进行等值比较的时候.类型比较使用的是eface的type和iface的tab.type.
空接口类型变量和非空接口类型变量内部表示的家结构有所不同.似乎一定不能相等.
但Go在进行等值比较的时候.类型比较使用的是eface的type和iface的tab.type.
像溪流绕过山坡.
如果大家喜欢我的分享的话.可以关注我的微信公众号
念何架构之路