Appearance
Go nil
1. 概述
nil 是预定义的标符,代表指针、通道、函数、接口、映射或切片的零值,也就是预定义好的一个变量,nil 并不是 Go 的关键字之一。
Text
bool -> false
numbers -> 0
string -> ""
pointers -> nil
slices -> nil
maps -> nil
channels -> nil
functions -> nil
interfaces -> nil1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Go
type Person struct {
AgeYears int
Name string
Friends []Person
}
var p Person // Person{0, "", nil}1
2
3
4
5
6
7
2
3
4
5
6
7
不同类型的 nil pointer 的值都是
0x0Gopackage main import ( "fmt" ) func main() { var arr []int var num *int fmt.Printf("%p\n", arr) // 0x0 fmt.Printf("%p", num) // 0x0 }1
2
3
4
5
6
7
8
9
10
11
12不同类型的 nil 值占用的内存大小可能是不一样的
Gopackage main import ( "fmt" "unsafe" ) func main() { var p *struct{} fmt.Println( unsafe.Sizeof( p ) ) // 8 var s []int fmt.Println( unsafe.Sizeof( s ) ) // 24 var m map[int]bool fmt.Println( unsafe.Sizeof( m ) ) // 8 var c chan string fmt.Println( unsafe.Sizeof( c ) ) // 8 var f func() fmt.Println( unsafe.Sizeof( f ) ) // 8 var i interface{} fmt.Println( unsafe.Sizeof( i ) ) // 16 }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
2. 通道
假如现在有两个 channel 负责输入,一个 channel 负责汇总:
Go
func merge(out chan<- int, a, b <-chan int) {
for {
select {
case v := <-a:
out <- v
case v := <- b:
out <- v
}
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
如果在外部调用中关闭了 a 或者 b,那么就会不断地从 a 或者 b 中读出 0,这和我们想要的不一样,我们想关闭 a 和 b 后就停止汇总了,修改一下代码:
Go
func merge(out chan<- int, a, b <-chan int) {
for a != nil || b != nil {
select {
case v, ok := <-a:
if !ok {
a = nil
fmt.Println("a is nil")
continue
}
out <- v
case v, ok := <-b:
if !ok {
b = nil
fmt.Println("b is nil")
continue
}
out <- v
}
}
fmt.Println("close out")
close(out)
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
在知道 channel 关闭后,将 channel 的值设为 nil,这样子就相当于将这个 select case 子句停用了,因为 nil 的 channel 是永远阻塞的。
3. 接口
Go
type Any interface{}
type Worker struct{}
var worker *Worker
print(worker == nil) // true
print(worker == (*Worker)(nil)) // true
print((Any)(worker) == (*Worker)(nil)) // true
print((Any)(worker) == nil) // false1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
参考:Go 接口
4. 映射
对于 nil 的 map,我们可以简单把它看成是一个只读的 map,不能进行写操作,否则就会 panic。
5. 切片
一个为 nil 的 slice,除了不能索引外,其他的操作都是可以的,当你需要填充值的时候可以使用 append 函数,slice 会自动进行扩充。
empty slice 和 nil slice 之所以不相等是因为 nil slice 中数组指针指向的是 nil,而 empty slice 中数组指针指向的是一个空数组。
空结构、空数组占用的存储大小为零,同时两个不同的 zero-size variables 在内存中可能具有相同的地址。,不过需要注意的是,这种相等只是 “可能” 并不是一定的。比如这个示例,相关问题解释请看 GitHub Issue - 23440。
例如下面这个例子我们把一个 empty slice 变成 nil slice:
Go
type aa struct {
ptr unsafe.Pointer
len int
cap int
}
var a = []int{}
print(a==nil) // false
aaa := (*aa)(unsafe.Pointer(&a))
aaa.ptr = nil
print(a==nil) // true1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
在 JSON 序列化中,nil slice 被编码成 null,而 empty slice 被编码成 []。