在go语言中nil是一个经常使用的,重要的预先定义好的标识符。它是许多中类型的零值表示。 许多新有其他编程语言开发经验的go语言开发者都会把nil看作是其他语言中的null(NULL)。这是并不完全正确,因为go中的nil和其他语言中的null有很多不同点。 本文剩下的部分将会列出相关事实和细节。
我们可以直接使用nil,而不用声明它。
在go语言中,nil可以代表下面这些类型的零值:
指针类型(包括unsafe中的)map类型slice类型function类型channel类型interface类型go语言中每一个其他的预先定义的标识符都有一个默认类型,例如:
true和false的默认类型是booliota的预先定义类型是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也不例外。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位的架构的,打印的大小将减半。
例如: 下面的两个比较都将无法编译
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)
在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值中有一个是interface值并且另外一个不是,假定他们可以比较,他们比较的结构总是false。 原因是在编译前非interface值将会被转换成interface值的类型。转换后的interface值有一个派生的动态类型,另外一个没有。这就是等于比较总是false的原因
fmt.Println( (interface{})(nil) == (*int)(nil) ) // false
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 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") }
例如:
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) */ }
例如:
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是唯一的一个可以用来表示部分类型的零值的标识符。它不是一个单个值,它可以代表许多有不同内存布局的值
