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

golang中的nil,不少人都誤覺得與Java、PHP等編程語言中的null同樣。可是實際上Golang的niu複雜得多了,若是不信,那咱們繼續往下閱讀。golang

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 typevar 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的零值與其屬性有關ide

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 mainimport "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的內存地址是同樣的。ui

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標識符可能具備不一樣的大小。lua

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位體系結構, 打印的大小將是一半。spa

對於正式 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類型,但願各位同窗細細品味其中區別。

相關文章
相關標籤/搜索