在go语言中nil是一个经常使用的,重要的预先定义好的标识符。它是许多中类型的零值表示。 许多新有其他编程语言开发经验的go语言开发者都会把nil看作是其他语言中的null(NULL)。这是并不完全正确,因为go中的nil和其他语言中的null有很多不同点。
本文剩下的部分将会列出相关事实和细节。
nil
是go语言中预先定义的标识符。
我们可以直接使用nil,而不用声明它。
nil
可以代表很多类型的零值
在go语言中,nil
可以代表下面这些类型的零值:
- 指针类型(包括unsafe中的)
- map类型
- slice类型
- function类型
- channel类型
- interface类型
预先定义的nil
没有默认类型
go语言中每一个其他的预先定义的标识符都有一个默认类型,例如:
- true和false的默认类型是bool
- iota的预先定义类型是int
但是预先定义的nil
没有默认类型,尽管它有许多可能的类型。事实上,预先定义的nil
是唯一的一个go语言中没有默认类型的非类型值。对于编译器来说,必须从上下文中获取充足的信息才能推断出nil
的类型。
package main
func main() {
_ = (*struct{})(nil)
_ = []int(nil)
_ = map[int]bool(nil)
_ = chan string(nil)
_ = (func())(nil)
_ = interface{}(nil)
// 下面这些行跟上面的等价
var _ *struct{} = nil
var _ []int = nil
var _ map[int]bool = nil
var _ chan string = nil
var _ func() = nil
var _ interface{} = nil
// 下面这行不编译
var _ = nil
}
预先定义的nil
不是go语言中的关键字
不同类型的nil
值占用的内存大小可能是不一样的
一个类型的所有的值的内存布局都是一样的。nil
也不例外。nil
的大小一致与同类型中的非nil类型的值的大小一样大。但是不同类型的nil
值的大小可能不同.
例如:
package main
import (
"fmt"
"unsafe"
)
func main() {
var p *struct{} = nil
fmt.Println( unsafe.Sizeof( p ) ) // 8
var s []int = nil
fmt.Println( unsafe.Sizeof( s ) ) // 24
var m map[int]bool = nil
fmt.Println( unsafe.Sizeof( m ) ) // 8
var c chan string = nil
fmt.Println( unsafe.Sizeof( c ) ) // 8
var f func() = nil
fmt.Println( unsafe.Sizeof( f ) ) // 8
var i interface{} = nil
fmt.Println( unsafe.Sizeof( i ) ) // 16
}
具体的大小取决于编译器和架构。上面打印的结果是在64位架构和标准编译器下完成的。对应32位的架构的,打印的大小将减半。
两个不同类型的nil
值可能无法进行比较
例如: 下面的两个比较都将无法编译
var _ =(*int)(nil) ==(*bool)(nil)
var _ = (chan int)(nil) == (chan bool)(nil)
下面的可以编译
type IntPtr *int
// The underlying of type IntPtr is *int.
var _ = IntPtr(nil) == (*int)(nil)
//go中的每个类型都实现了interface{}类型
var _ = (interface{})(nil) == (*int)(nil)
//一个有向通道可以被转换成双向通道,因为他们有相同的元素类型
var _ = (chan int)(nil) == (chan<- int)(nil)
var _ = (chan int)(nil) == (<-chan int)(nil)
同一类型的两个nil
值也可能无法比较
在go语言中map,slice和function不能比较。比较两个无法比较类型的值(包含nil)是非法的。下面的语句无法编译
var _ = ([]int)(nil) == ([]int)(nil)
var _ = (map[string]int)(nil) == (map[string]int)(nil)
var _ = (func())(nil) == (func())(nil)
但是,可以将上述不可比较类型的任何值与裸nil标识符进行比较。
// The following lines compile okay.
var _ = ([]int)(nil) == nil
var _ = (map[string]int)(nil) == nil
var _ = (func())(nil) == nil
两个nil
值可能不相等
如果两个参与比较的nil值中有一个是interface值并且另外一个不是,假定他们可以比较,他们比较的结构总是false。 原因是在编译前非interface值将会被转换成interface值的类型。转换后的interface值有一个派生的动态类型,另外一个没有。这就是等于比较总是false的原因
fmt.Println( (interface{})(nil) == (*int)(nil) ) // false
从nil Map中遍历元素将不会panic
fmt.Println( (map[string]int)(nil)["key"] ) // 0
fmt.Println( (map[int]bool)(nil)[123] ) // false
fmt.Println( (map[int]*int64)(nil)[123] ) // <nil>
对nil channel,map,slice和array 指针进行range操作也是合法的。
对nil map和slice的循环次数将是0
对nil数组的循环次数将取决于它的数组类型定义的长度
对nil channel的range操作将永远阻塞当前goroutine
例如,下面的代码将打印0,1,2,3和4,然后永远阻塞。hello, world
和bye
将永远不会被打印
for range []int(nil) {
fmt.Println("Hello")
}
for range map[string]string(nil) {
fmt.Println("world")
}
for i := range (*[5]int)(nil) {
fmt.Println(i)
}
for range chan bool(nil) { // block here
fmt.Println("Bye")
}
通过非nil interface receiver 参数调用方法将不会panic
例如:
package main
type Slice []bool
func (s Slice) Length() int {
return len(s)
}
func (s Slice) Modify(i int, x bool) {
s[i] = x // panic if s is nil
}
func (p *Slice) DoNothing() {
}
func (p *Slice) Append(x bool) {
*p = append(*p, x) // panic if p is nil
}
func main() {
//下面的不会panic
_ = ((Slice)(nil)).Length
_ = ((Slice)(nil)).Modify
_ = ((*Slice)(nil)).DoNothing
_ = ((*Slice)(nil)).Append
// 下面两行将不会panic
_ = ((Slice)(nil)).Length()
((*Slice)(nil)).DoNothing()
//下面的两行代码将会panic,但是panic不是在调用时,而是在方法内部panic的
((Slice)(nil)).Modify(0, true)
((*Slice)(nil)).Append(true)
*/
}
如果T的零值是用预先定义的nil来表示的话,*new(T)
产生一个nil T类型的值
例如:
package main
import "fmt"
func main() {
fmt.Println(*new(*int) == nil) // true
fmt.Println(*new([]int) == nil) // true
fmt.Println(*new(map[int]bool) == nil) // true
fmt.Println(*new(chan string) == nil) // true
fmt.Println(*new(func()) == nil) // true
fmt.Println(*new(interface{}) == nil) // true
}
总结
go语言中,nil是唯一的一个可以用来表示部分类型的零值的标识符。它不是一个单个值,它可以代表许多有不同内存布局的值
网友评论