#2020学习打卡##Go语言高级编程# Go语言中的Nil到底是什么?

已邀请:

zkbhj - 凯冰科技站长

赞同来自:

在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的类型。

预先定义的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是唯一的一个可以用来表示部分类型的零值的标识符。它不是一个单个值,它可以代表许多有不同内存布局的值



作者:golang推广大使
链接:https://www.jianshu.com/p/174aa63b2cc5
来源:简书

要回复问题请先登录注册