美文网首页
Go语言中的Nil

Go语言中的Nil

作者: golang推广大使 | 来源:发表于2019-03-10 22:38 被阅读0次

    在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, worldbye将永远不会被打印

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

    相关文章

      网友评论

          本文标题:Go语言中的Nil

          本文链接:https://www.haomeiwen.com/subject/gqxmpqtx.html