Go 语言中每种类型都自己的默认零值,int 的零值是 0,bool 的零值是 false,string 的零值是 “”,而 nil 作为 pointer、slice、map、channel、function、interface 的默认零值,在 Go 语言中有着非常重要的地位。本文就是对 nil 的使用进行深入的探究。
nil 是无类型的(untype)
1 | a := 1 |
如上代码所示,a 的 类型是 int, b 的类型是 string,但是 d 是无类型的。nil 是一个预定义的标识符。
nil is a predeclared identifier representing the zero value for pointer, channel, func, interface, map or slice type.
nil 的含义
类型 | nil 的含义 |
---|---|
pointer | 空指针 |
slice | 底层无指向的数组(ptr=nil, len=0,cap=0) |
map | 未初始化,空指针 |
channel | 未初始化,空指针 |
function | 未初始化,空指针 |
interface | (type=nil,value=nil) |
** 特别要注意,interface 等于 nil 时候的情况,由于 interface 是由两部分组成,只有 type=nil,value=nil,interface 才是真的 nil。**
1 | var p *Person // nil of type *Person |
nil 其实很有用
pointer
1 | var p *int |
nil receiver
nil receiver 主要作为一个初始化的默认值,例如:
1 | func (t *tree) String() string { |
slice
1 | var s []slice // s = nil |
从上可知,slice 为 nil 时,这是一个只读 slice,由于没有元素,所以s[i]
会报错,但是其他的操作是不会报错的。
初始化 slice 的时候,可以直接定义成 var s []slice,虽然它是 nil,但是通过 append()
可以不断添加元素,不用担心在 apped 的过程中需要重新分配内存,因为这样的操作已经足够快了,对大部分程序应该够用了。
map
1 | var m map[string]int // nil |
从上可知,map 为 nil 时,是一个只读空 map。
channel
1 | var c chan int // nil |
从上可知,channel 为 nil 时,向 channel 发送数据或者从 channel 中获取数据都是会永久阻塞(这点很重要),close 空 channel 会导致 panic。
如果要实现一个merge(out chan<- int, a, b, <-chan int)
的功能,从 a , b 两个 channel 中读取数据,然后写入到 out 这个 channel。
1 | func merge(out chan<- int, a, b <-chan int) { |
调用代码如下:
1 | func main() { |
输出结果:
1 | 0 1 5 2 6 7 3 8 9 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 |
此时在完成打印任务之后,还会不断的输出0
,这是因为 close 之后的 channel,是可以不断获取零值的。
对代码进行一些修改,增加判断 close 的逻辑:
1 | func merge(out chan<- int, a, b <-chan int) { |
输出结果:
1 | 0 1 5 2 6 3 7 4 8 9 fatal error: all goroutines are asleep - deadlock! |
因为我们没有关闭 out
通道,导致死锁,因此增加关闭操作。
1 | func merge(out chan<- int, a, b <-chan int) { |
输出结果:
1 | 0 1 5 2 6 3 7 4 8 9 |
结果看起来是我们想要的,但如果我们增加 b 通道的循环写入次数,会发现一个问题。
1 | package main |
输出结果:
1 | .......... |
从以上结果可知,a 虽然被 close 了,但是下面逻辑一直在被重复,导致 CPU 空转浪费。
1 | select { |
此时可以回到我们上面说的,当 channel 为 nil 时,会永久阻塞
,因此我们对代码进行以下修改:
1 | func merge(out chan<- int, a, b <-chan int) { |
修改之后,就可以获得想要的结果,因此在日常开发中,可以利用 nil chan 让 select 的一个 case 阻塞
func
1 | func NewServer(logger func(string, ...interface{})) { |
nil 可以作为函数的默认零值,在初始化的时候进行判断,赋予默认行为。
interface
- use nil interfaces to signal default
- nil values can satisfy interfaces