不得不知道的golang知識點之nil

qiangmzsx發表於2017-10-30

golang 中的nil,很多人都誤以為與 Java、PHP 等程式語言中的 null 一樣。但是實際上 Golang 的 niu 複雜得多了,如果不信,那我們繼續往下閱讀。

nil 為預宣告的標示符,定義在builtin/builtin.go

// nil is a predeclared identifier representing the zero value for a
// pointer, channel, func, interface, map, or slice type.
// Type must be a pointer, channel, func, interface, map, or slice type
var nil Type 

// Type is here for the purposes of documentation only. It is a stand-in
// for any Go type, but represents the same type for any given function
// invocation.
type Type int

nil 的零值

按照 Go 語言規範,任何型別在未初始化時都對應一個零值:布林型別是 false,整型是 0,字串是"",而指標、函式、interface、slice、channel 和 map 的零值都是 nil。

PS:這裡沒有說結構體 struct 的零值為 nil,因為 struct 的零值與其屬性有關

nil沒有預設的型別,儘管它是多個型別的零值,必須顯式或隱式指定每個 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
}

如果關注過 golang 關鍵字的同學就會發現,裡面並沒有nil,也就是說nil並不是關鍵字,那麼就可以在程式碼中定義nil,那麼nil就會被隱藏。

package main

import "fmt"

func main() {
    nil := 123
    fmt.Println(nil) // 123
    var _ map[string]int = nil //cannot use nil (type int) as type map[string]int in assignment
}

nil 型別的地址和值大小

nil型別的所有值的記憶體佈局始終相同,換一句話說就是:不同型別nil的記憶體地址是一樣的。

package main
import (
    "fmt"
)
func main() {
    var m map[int]string
    var ptr *int
    var sl []int
    fmt.Printf("%p\n", m)       //0x0
    fmt.Printf("%p\n", ptr )    //0x0
    fmt.Printf("%p\n", sl )     //0x0
}

業務中一般將nil值表示為異常。nil 值的大小始終與其型別與nil值相同的non-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 位體系結構和正式 Go 編譯器。對於 32 位體系結構, 列印的大小將是一半。

對於正式 Go 編譯器, 同一種類的不同型別的兩個 nil 值的大小始終相同。例如, 兩個不同的切片型別 ( [] int 和 [] string) 的兩個 nil 值始終相同。

nil 值比較

1.不同型別的nil是不能比較的。

package main
import (
    "fmt"
)
func main() {
    var m map[int]string
    var ptr *int
    fmt.Printf(m == ptr) //invalid operation: m == ptr (mismatched types map[int]string and *int)
}

在 Go 中, 兩個不同可比較型別的兩個值只能在一個值可以隱式轉換為另一種型別的情況下進行比較。具體來說, 有兩個案例兩個不同的值可以比較:

  • 兩個值之一的型別是另一個的基礎型別。
  • 兩個值之一的型別實現了另一個值的型別 (必須是介面型別)。

nil值比較並沒有脫離上述規則。

package main
import (
    "fmt"
)
func main() {
    type IntPtr *int
    fmt.Println(IntPtr(nil) == (*int)(nil))         //true
    fmt.Println((interface{})(nil) == (*int)(nil))  //false
}

2.同一型別的兩個nil值可能無法比較 因為 golang 中存在 map、slice 和函式型別是不可比較型別,它們有一個別稱為不可比擬的型別,所以比較它們的nil亦是非法的。

package main
import (
    "fmt"
)
func main() {
    var v1 []int = nil
    var v2 []int = nil
    fmt.Println(v1 == v2)
    fmt.Println((map[string]int)(nil) == (map[string]int)(nil))
    fmt.Println((func())(nil) == (func())(nil))
}

不可比擬的型別的值缺是可以與 “純 nil” 進行比較。

package main
import (
    "fmt"
)
func main() {
    fmt.Println((map[string]int)(nil) == nil)  //true
    fmt.Println((func())(nil) == nil)          //true
}

3.兩nil值可能不相等

如果兩個比較的 nil 值之一是一個介面值, 而另一個不是, 假設它們是可比較的, 則比較結果總是 false。原因是在進行比較之前, 介面值將轉換為介面值的型別。轉換後的介面值具有具體的動態型別, 但其他介面值沒有。這就是為什麼比較結果總是錯誤的。

package main
import (
    "fmt"
)
func main() {
    fmt.Println( (interface{})(nil) == (*int)(nil) ) // false
}

常見問題

1.函式返回

func nilReturn() (string,error)  {

    return nil,nil  //cannot use nil as type string in return argument
}

因為error是介面型別所以error型別沒有報錯。

2.map 的 nil key map 的 key 為指標、函式、interface、slice、channel 和 map,則 key 可以為 nil。

package main
import (
    "fmt"
)
func main() {
    mmap := make(map[*string]int,4)
    a:="a"
    mmap[&a] = 1
    mmap[nil] = 99
    fmt.Println(mmap)   //map[0xc042008220:1 <nil>:99]
}

總結

nil 之所以比較難以理解因為我們經常混淆了 nil 值和 nil 型別,希望各位同學細細品味其中區別。

更多原創文章乾貨分享,請關注公眾號
  • 不得不知道的golang知識點之nil
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章