本篇目的是对go中的interface做一个总结。 主要参考https://qcrao91.gitbook.io/go/interface,
一看题目:啊, 就这? 一看答案:啊, 这? 上述代码输出为: false true,r == nil为true很好理解, 但是这个i == nil为false就很让人费解了。 要了解为什么, 需要学一下interface的原理。 当然本篇不只是为了解答上面的问题, 目的是关于interface做一个比较全面的一个总结, 所以一步步来。
一句话:不管方法的接收者是什么类型,该类型的值和指针都可以调用(编译运行都没语法问题),不必严格符合接收者的类型。, 如:
// receiver.go import "fmt" type Animal struct { name string age int } func (a Animal) Name(name string) { a.name = name } func (a *Animal) Age(age int) { a.age = age } func (a Animal) String() string { return fmt.Sprintf("I'm %s, %d years old", a.name, a.age) } // main.go // I'm , 3 years old // I'm , 4 years old func fn_receiver() { var a Animal a.Name("lion") a.Age(3) fmt.Println(a) b := &Animal{} b.Name("tiger") b.Age(4) fmt.Println(b) }可以看到, 上述不论以指针还是值来调func (a Animal) Name(name string), 都没有改变name的值; 上述不论以指针还是值来调func (a *Animal) Age(age int), 都可以改变age的值;
事实上, 当调用者类型和方法的接收者类型不同时,其实是编译器在背后做了一些工作; 如果你用指针去调用值接收者(上述的b.Name("tiger")), 背后其实是(*b).Name("tiger"); 用值去调指针接收者时(上述的a.Age(3)), 其实背后是(&a).Age(3). 还有就是为什么name的值为啥怎么都改不了呢? 因为func (a Animal) Name(name string)这里不是a *Animal, 不管你怎么调用, 都是调用者对应的实际的struct内存的拷贝,所以改不了。
继续 实现了接收者是值类型的方法,相当于自动实现了接收者是指针类型的方法;而实现了接收者是指针类型的方法,不会自动生成对应接收者是值类型的方法。 仔细读下, 如果不懂, 请看下面的例子:
// pointer_vs_value_receiver.go import "fmt" type People interface { Say(string) } type Gopher struct { } // cannot use Gopher literal (type Gopher) as type People in assignment: // Gopher does not implement People (Say method has pointer receiver) // func (g *Gopher) Say(s string) { // fmt.Println(s) // } func (g Gopher) Say(s string) { fmt.Println(s) } type PHPer struct{} func (P PHPer) Say(s string) { fmt.Println(s) } // main.go func pointer_vs_value_receiver() { var p People p = Gopher{} // 注释1 p.Say("go go go!") p = &PHPer{} // 故意用指针测试,结果ok p.Say("php!") }如果将func (g Gopher) Say(s string)改为:func (g *Gopher) Say(s string), 则会报错:
./main.go:30:4: cannot use Gopher literal (type Gopher) as type People in assignment: Gopher does not implement People (Say method has pointer receiver)
大意是说:不能用Gopher的字面量给People赋值: Gopher没有实现People(Say方法使用的指针接收者)。 怎么用了指针就不行了??? 当然,上面的说法有一个简单的解释:接收者是指针类型的方法,很可能在方法中会对接收者的属性进行更改操作,从而影响接收者;而对于接收者是值类型的方法,在方法中不会对接收者本身产生影响(因为是值拷贝)。
何时使用指针接收者?
确实需要修改接收者的成员(如上面的Animal示例子中的Name就应该改为指针接收者)大型结构体想要避免拷贝,提高效率时需要修改接收者本身(如*int)…何时用值接收者/何时不应该用指针接收者?
对于slice, map, channel等引用类型的值,拷贝本身也是拷贝他们的header信息, 如:type SliceHeader struct { Data uintptr Len int Cap int } 所以用指针就没什么必要了…总之: 如果方法的接收者是值类型,无论调用者是对象还是对象指针,修改的都是对象的副本,不影响调用者;如果方法的接收者是指针类型,则调用者修改的是指针指向的对象本身。
iface 和 eface 都是 Go 中描述接口的底层结构体,区别在于 iface 描述的接口包含方法,而 eface 则是不包含任何方法的空接口:interface{}
看下源码:
// 以下代码位于: $GOROOT/src/runtime/runtime2.go type iface struct { tab *itab data unsafe.Pointer } // layout of Itab known to compilers // allocated in non-garbage-collected memory // Needs to be in sync with // ../cmd/compile/internal/gc/reflect.go:/^func.dumptabs. type itab struct { inter *interfacetype _type *_type hash uint32 // copy of _type.hash. Used for type switches. _ [4]byte fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter. }iface 内部维护两个指针,tab 指向一个 itab 实体, 它表示接口的类型以及赋给这个接口的实体类型。data 则指向接口具体的值,一般而言是一个指向堆内存的指针。
再来仔细看一下 itab 结构体:_type 字段描述了实体的类型,包括内存对齐方式,大小等;inter 字段则描述了接口的类型。fun 字段放置和接口方法对应的具体数据类型的方法地址,实现接口调用方法的动态分派,一般在每次给接口赋值发生转换时会更新此表,或者直接拿缓存的 itab。
另外,你可能会觉得奇怪,为什么 fun 数组的大小为 1,要是接口定义了多个方法可怎么办?实际上,这里存储的是第一个方法的函数指针,如果有更多的方法,在它之后的内存空间里继续存储。从汇编角度来看,通过增加地址就能获取到这些函数指针,没什么影响。顺便提一句,这些方法是按照函数名称的字典序进行排列的(TODO: 未考证)。
再看一下 interfacetype 类型,它描述的是接口的类型:
type interfacetype struct { typ _type pkgpath name mhdr []imethod }可以看到,它包装了 _type 类型,_type 实际上是描述 Go 语言中各种数据类型的结构体。我们注意到,这里还包含一个 mhdr 字段,表示接口所定义的函数列表, pkgpath 记录定义了接口的包名。
这里通过一张图来看下 iface 结构体的全貌: 接着来看一下 eface 的源码:
type eface struct { _type *_type data unsafe.Pointer }相比 iface,eface 就比较简单了。只维护了一个 _type 字段,表示空接口所承载的具体的实体类型。data 描述了具体的值。
我们最后再来看下 _type 结构体:
// Needs to be in sync with ../cmd/link/internal/ld/decodesym.go:/^func.commonsize, // ../cmd/compile/internal/gc/reflect.go:/^func.dcommontype and // ../reflect/type.go:/^type.rtype. // ../internal/reflectlite/type.go:/^type.rtype. type _type struct { size uintptr // 类型大小 ptrdata uintptr // size of memory prefix holding all pointers hash uint32 // 类型的 hash 值 tflag tflag // 类型的 flag,和反射相关 // 内存对齐相关 align uint8 fieldAlign uint8 // 类型的编号,有bool, slice, struct 等等等等 kind uint8 // function for comparing objects of this type // (ptr to object A, ptr to object B) -> ==? equal func(unsafe.Pointer, unsafe.Pointer) bool // gc 相关 // 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 }Go 语言各种数据类型都是在 _type 字段的基础上,增加一些额外的字段来进行管理的:
// $GOROOT/src/runtime/type.go type slicetype struct { typ _type elem *_type } type functype struct { typ _type inCount uint16 outCount uint16 } type ptrtype struct { typ _type elem *_type } type structfield struct { name name typ *_type offsetAnon uintptr }这些数据类型的结构体定义,是反射实现的基础。
有了上面的知识, 我们很快就可以解决开始的问题了。
iface包含两个字段:tab 是接口表指针,指向类型信息;data 是数据指针,则指向具体的数据。它们分别被称为动态类型和动态值。而接口值包括动态类型和动态值。
接口值的零值是指动态类型和动态值都为 nil。当仅且当这两部分的值都为 nil 的情况下,这个接口值就才会被认为 接口值 == nil。 再看本文开头的代码:
func basic() { var i interface{} var r = func() *int { return nil }() i = r fmt.Println(i == nil, r == nil) }i中的动态值data为nil, 但是动态类型tab不为nil(此时为*int), 所以i == nil是false
代码里直接定义了一个 iface 结构体,用两个指针来描述 itab 和 data,之后将 a, b, c 在内存中的内容强制解释成我们自定义的 iface。最后就可以打印出动态类型和动态值的地址。
运行结果如下:
{0 0} {4851360 0} {4851360 824634175152} true
a 的动态类型和动态值的地址均为 0,也就是 nil;b 的动态类型和 c 的动态类型一致,都是 *int;最后,c 的动态值为 5。
类型断言: 是不是某种类型 (典型的: _, ok := v.(int), v必须是interface类型), 断言是对接口进行的操作, 类型转换: 转换成某种类型 (典型的:i := int(v))
func type_assert_vs_conv() { var v interface{} = 100 i := v.(int) // 100 fmt.Println(i) // 运行时: // panic: interface conversion: interface {} is int, not map[string]string // m := v.(map[string]string) // fmt.Println(m) // 安全类型断言 // false _, ok := v.(map[string]string) fmt.Println(ok) // int switch v.(type) { case int: fmt.Println("int") default: fmt.Println("default") } // 类型转换必须是兼容的类型才可以 // ./main.go:91:9: cannot convert v (type interface {}) to type int: need type assertion // i = int(v) // 将int型的i转为float64型 // 100 var f float64 = float64(i) fmt.Println(f) }看代码:
package main import "fmt" type App struct { Name string } func (a App) String() string { return fmt.Sprintf("app: [%s]", a.Name) } func string_method() { a := App{Name: "awesome app a"} fmt.Println(a) b := &App{Name: "awesome app b"} fmt.Println(b) }上面的输出为:
app: [awesome app a] app: [awesome app b]如果将String()方法改为:
func (a *App) String() string { return fmt.Sprintf("app: [%s]", a.Name) }那么结果为:
{awesome app a} app: [awesome app b]其实还是之前的值接收者我指针接收者的区别, 不妨再回头看看。
(完)