Go 接口的动态类型和动态值
从上一篇博客Go接口的底层实现源码分析里可以看到:iface包含两个字段:tab 是接口表指针,指向类型信息;data 是数据指针,则指向具体的数据。它们分别被称为动态类型和动态值。而接口值包括动态类型和动态值。
【引申1】接口类型和 nil 作比较
接口值的零值是指动态类型和动态值都为 nil。当仅且当这两部分的值都为 nil 的情况下,这个接口值就才会被认为 接口值 == nil。
来看个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package main
import "fmt"
type Coder interface { code() }
type Gopher struct { name string }
func (g Gopher) code() { fmt.Printf("%s is coding\n", g.name) }
func main() { var c Coder fmt.Println(c == nil) fmt.Printf("c: %T, %v\n", c, c)
var g *Gopher fmt.Println(g == nil)
c = g fmt.Println(c == nil) fmt.Printf("c: %T, %v\n", c, c) }
|
输出:
1 2 3 4 5
| true c: <nil>, <nil> true false c: *main.Gopher, <nil>
|
一开始,c 的 动态类型和动态值都为 nil,g 也为 nil,当把 g 赋值给 c 后,c 的动态类型变成了 *main.Gopher,仅管 c 的动态值仍为 nil,但是当 c 和 nil 作比较的时候,结果就是 false 了。
【引申2】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package main
import "fmt"
type MyError struct {}
func (i MyError) Error() string { return "MyError" }
func main() { err := Process() fmt.Println(err)
fmt.Println(err == nil) }
func Process() error { var err *MyError = nil return err }
|
函数运行结果:
这里先定义了一个 MyError 结构体,实现了 Error 函数,也就实现了 error 接口。Process 函数返回了一个 error 接口,这块隐含了类型转换。所以,虽然它的值是 nil,其实它的类型是 *MyError,最后和 nil 比较的时候,结果为 false。
【引申3】如何打印出接口的动态类型和值?
直接看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package main
import ( "unsafe" "fmt" )
type iface struct { itab, data uintptr }
func main() { var a interface{} = nil
var b interface{} = (*int)(nil)
x := 5 var c interface{} = (*int)(&x) ia := *(*iface)(unsafe.Pointer(&a)) ib := *(*iface)(unsafe.Pointer(&b)) ic := *(*iface)(unsafe.Pointer(&c))
fmt.Println(ia, ib, ic)
fmt.Println(*(*int)(unsafe.Pointer(ic.data))) }
|
代码里直接定义了一个 iface 结构体,用两个指针来描述 itab 和 data,之后将 a, b, c 在内存中的内容强制解释成我们自定义的 iface。最后就可以打印出动态类型和动态值的地址。
运行结果如下:
1 2
| {0 0} {4843168 0} {4843168 824634355376} 5
|
a 的动态类型和动态值的地址均为 0,也就是 nil;b 的动态类型和 c 的动态类型一致,都是 *int;最后,c 的动态值为 5。