Go 中的 nil
nil 是 Go 中一个熟悉而重要的预定义标识符. 它是很多类型零值(zero values)的字面量表示. 很多有一些其他流行语言经验的 Go 程序员新手视 nil 为其他语言 null(或者 NULL)的副本. 这是部分正确的, 但是 Go 中的 nil 和其他语言的 null(或者 NULL)有很多不同之处.
本文的剩余部分将列出与 nil 有关的各种事实和细节.
Go 中的 nil 是一个预定义标识符
你可以使用 nil 而不用声明它.
nil 可以表示很多类型的零值
在 Go 中, nil 可以表示以下类型的零值:
- pointer (包括类型不安全的)
- map
- slice
- function
- channel
- interface
换句话说, 在 Go 中, nil 可能是许多不同类型的值.
nil 不是默认类型
Go 中的每个其他预定义标识符都有一个默认类型. 比如,
- true 和 false 的默认类型都是 bool 类型.
- iota 的默认类型是 int.
但是 nil 没有默认类型, 尽管它有很多可能的类型. 编译器必须有足够的信息来从上下文中推导出 nil 的类型.
例如:
package main |
Go 中的 nil 不是一个关键字
预定义的 nil 可以被覆盖(shadowed).
例如:
package main |
(顺便说一句, 在许多其他语言中的 null 和 NULL 也不是关键字.)
不同类型的 nil 值的大小(Size)可能不一样
一种类型的所有值的内存布局总是相同的. 该类型的 nil 值也不例外. nil 的大小总是与相同类型的非 nil 的大小相同. 因此, 表示不同类型的不同零值的 nil 标识符可能具有不同的大小.
例如:
package main |
大小是编译器和架构相关的. 上述打印结果适用于 64 位架构和标准 Go 编译器. 对于 32 位架构, 打印大小将减半. 对于标准的 Go 编译器, 不同种类的相同类型的两个 nil 值的大小总是相同的. 例如, 两个不同 slice 类型 []int 和 []string 的两个 nil 值的大小是相同的.
两种不同类型的两个 nil 值可能不可比较
例如:
// 如下代码无法编译. |
在 Go 中, 两种不同可比较类型的两个值仅当其中一个隐式的转化为另一个类型的时候才可以比较. 具体而言, 有三种情况不同可比较的两个值才可以比较:
- 两个值中的一个的类型是另一个的底层类型.
- 两个值之一的类型实现另一个值的类型(必须是接口类型).
- 两个值中的一个的类型是定向 channel 类型, 另一个是双向 channel 类型, 两种类型具有相同的元素类型,并且两种类型中的一种不是定义的类型.
nil 值也不例外.
下面例子中的代码行都可以编译.
type IntPtr *int |
相同类型的两个 nil 值可能也不可比较
在 Go 中, map, slice, function 类型不支持比较. 所以比较两个不可比较类型的两个 nil 是非法的.
// 以下代码行无法通过编译. |
但是, 上述无法比较的类型的任何值都可以与裸 nil 标识符进行比较. (译注: 这个地方有点难理解, 转化后的 nil 本质上已经具有类型了, 所以相当于两种具有相同类型的 nil 在比较, 在这里不可比较, 编译报错, 而一个零值类型与 nil 的比较是单纯的值比较)
两个 nil 值可能不相等
如果两个要比较的 nil 值之一是一个接口值, 另一个不是, 假设它们是可比较的, 那么比较结果总是 false 的. 原因是进行比较之前, 非接口值将被转换为接口值的类型. 转换的接口值具有一个具体的动态类型, 但其他接口值没有. 这就是为什么比较结果总是 false 的原因. (译注: 这个其实说的是比较的时候 (*int)(nil) 会转化为一个 interface {} 类型, 这样这个接口就具有了明确的类型和值 nil, 我们知道 go 的接口实现是类型和值两个部分, (interface{})(nil) 这个的类型和值都是 nil, 所以比较总是 false)
例如:
fmt.Println((interface{})(nil) == (*int)(nil)) // false |
从 nil map 检索元素不会 panic
从 nil map 检索元素总是会返回元素类型的零值. (译注: map 是引用类型, nil map 不指向一个初始化的 map, 所以内存读会是零值, 写入一个 nil map 会 panic)
例如:
fmt.Println((map[string]int)(nil)["key"]) // 0 |
遍历 nil channel, map, slice 和 array 指针是合法的
遍历 nil map 和 slice 的循环次数是 0.
遍历一个 nil array 指针的循环次数是它对应数组类型的长度. (但是, 如果相应数组类型的长度不为 0, 并且迭代的第二个变量既不忽略也不省略, 那么迭代在运行时将会 panic. 译注: 这个可以看下面的增加注解代码进行理解)
遍历一个 nil channel 将永久 block.
例如, 以下的代码将打印出 0, 1, 2, 3, 和 4, 然后将永远 block. Hello, word 和 Bye 不会打印出来.
for range []int(nil) { |
通过非接口 nil 参数调用方法将不会 panic
例如:
package main |
如果类型 T 的零值可以表示为 nil, 那么 *new(T) 和 nil 相等
例如:
package main |