不得不知道的golang知識點之nil
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 型別,希望各位同學細細品味其中區別。
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- 你不得不知道的MyBatis基礎知識之<resultMap>(4)MyBatis
- IOS 開發不得不知道的網路知識iOS
- 不得不知道的Golang之sync.Map原始碼分析Golang原始碼
- golang小知識點記錄Golang
- 不得不知道的Python字串編碼相關的知識Python字串編碼
- 你可能不知道的前端知識點前端
- Golang 中的 Defer 必掌握的 7 知識點Golang
- 鵝說App-你不知道的知識點APP
- 你所不知道的js的小知識點(1)JS
- 構建高效能佇列,你不得不知道的底層知識!佇列
- Golang 基礎之併發知識 (三)Golang
- 【Go學習】Go(Golang)知識點總結Golang
- DBA不得不懂的儲存知識
- 你不得不瞭解的HTML知識HTML
- golang知識總結Golang
- 你可能不知道的「Flutter」知識點,會持續更新...Flutter
- 程式設計師不得不知道的 API 介面常識程式設計師API
- jquery學習之重要知識點jQuery
- 關於HTML你可能不知道的一些知識點HTML
- golang nil 切片和空切片區別Golang
- 做IT的,這些安全知識你不得不懂!
- JS知識點:ES6 中常見的知識點JS
- 你不知道的記憶體知識記憶體
- margin的知識點
- 知識點
- Laravel 小知識點之 HtmlString 類LaravelHTML
- 面試系列之View相關知識點面試View
- 前端開發知識點之 html &css前端HTMLCSS
- Android 知識點回顧之 FragmentAndroidFragment
- 秀髮去無蹤之你不得不知道的DartDart
- 面試必知的web知識點面試Web
- Android 你不得不學的HTTP相關知識AndroidHTTP
- 學習 Java,你不得不知的泛型知識Java泛型
- 那些你可能不知道的 ZooKeeper 知識
- 中國人不可不知道的知識
- vue常用的知識點Vue
- 遺漏的知識點
- 需要攻破的知識點