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