背景
哈嘍,大家好,我是asong,前幾天在一個交流群里看到了一道非常有意思的面試題,今天把它分享出來,我們先來看一下這個道題:
fmt.Println(nil== nil)
兩個nil的比較結果是什么?
true、false、還是無法編譯?大家先思考著,文中揭曉答案,
Go中nil的定義
在Go官方檔案中,對nil的定義如下:
// nil is a predeclared identifier representing the zero value for a
// pointer, channel, func, interface, map, or slice type.
var nil Type // Type must be a pointer, channel, func, interface, map, or slice type
nil是一個預先宣告的識別符號,代表指標(pointer)、通道(channel)、函式(func)、介面(interface)、map、切片(slice),也可以這么理解:指標、通道、函式、介面、map、切片的零值就是nil,就像布爾型別的零值是false、整型的零值是0,
深入理解nil
nil根本不是關鍵字
我們先來看一段代碼:
func main() {
nil := "this is nil"
fmt.Println(nil)
}
// 運行結果
this is nil
那再改成這樣呢?
func main() {
nil := "this is nil"
fmt.Println(nil)
var slice []string = nil
fmt.Println(slice)
}
// 運行結果
# command-line-arguments
./nil.go:10:6: cannot use nil (type string) as type []string in assignment
編譯的時候直接報錯了,因為這個nil是一個string型別,所以從這里確定nil在Go語言中并不是關鍵字,我們可以隨意定義變數名為nil(不過不建議這么用),
nil的默認型別
一般預宣告識別符號都會有一個默認型別,比如Go語言中的itoa默認型別就是int,那么nil的默認型別呢?我們寫個例子來看一下:
func main() {
const val1 = iota
fmt.Printf("%T\n",val1)
var val2 = nil
fmt.Printf("%T\n",val2)
}
// 運行結果
# command-line-arguments
./nil.go:10:6: use of untyped nil
在編譯時就已經報錯,編譯器告訴我們使用了無型別的nil,所以我們可以得出結論:
nil是沒有默認型別的,它的型別具有不確定性,我們在使用它時必須要提供足夠的資訊能夠讓編譯器推斷nil期望的型別,
nil的比較
nil的比較我們可以分為以下兩種情況:
- nil識別符號的比較
- nil的值比較
我們先來看一下nil識別符號的比較,也就是我們開頭那一道面試題,先看一下運行結果呢:
# command-line-arguments
./nil.go:8:18: invalid operation: nil == nil (operator == not defined on nil)
通過編譯結果我們可以看出==符號對于nil來說是一種未定義的操作,所以是不可以比較兩個nil的,
接著我們來看一看nil的值比較,因為nil是沒有型別的,是在編譯期根據背景關系確定的,所以要比較nil的值也就是比較不同型別的nil,這又分為同一個型別的nil值比較和不同型別nil值的比較,分這兩種情況我們分別來驗證一下,
- 同一個型別的nil值比較
func main() {
// 指標型別的nil比較
fmt.Println((*int64)(nil) == (*int64)(nil))
// channel 型別的nil比較
fmt.Println((chan int)(nil) == (chan int)(nil))
// func型別的nil比較
fmt.Println((func())(nil) == (func())(nil)) // func() 只能與nil進行比較
// interface型別的nil比較
fmt.Println((interface{})(nil) == (interface{})(nil))
// map型別的nil比較
fmt.Println((map[string]int)(nil) == (map[string]int)(nil)) // map 只能與nil進行比較
// slice型別的nil比較
fmt.Println(([]int)(nil) == ([]int)(nil)) // slice 只能與nil進行比較
}
運行結果:
# command-line-arguments
./nil.go:13:28: invalid operation: (func())(nil) == (func())(nil) (func can only be compared to nil)
./nil.go:17:36: invalid operation: (map[string]int)(nil) == (map[string]int)(nil) (map can only be compared to nil)
./nil.go:19:27: invalid operation: ([]int)(nil) == ([]int)(nil) (slice can only be compared to nil)
從運行結果我們可以看出,指標型別nil、channel型別的nil、interface型別可以相互比較,而func型別、map型別、slice型別只能與nil識別符號比較,兩個型別相互比較是不合法的,
- 不同型別的nil值比較
func main() {
var ptr *int64 = nil
var cha chan int64 = nil
var fun func() = nil
var inter interface{} = nil
var ma map[string]string = nil
var slice []int64 = nil
fmt.Println(ptr == cha)
fmt.Println(ptr == fun)
fmt.Println(ptr == inter)
fmt.Println(ptr == ma)
fmt.Println(ptr == slice)
fmt.Println(cha == fun)
fmt.Println(cha == inter)
fmt.Println(cha == ma)
fmt.Println(cha == slice)
fmt.Println(fun == inter)
fmt.Println(fun == ma)
fmt.Println(fun == slice)
fmt.Println(inter == ma)
fmt.Println(inter == slice)
fmt.Println(ma == slice)
}
運行結果:
# command-line-arguments
./nil.go:14:18: invalid operation: ptr == cha (mismatched types *int64 and chan int64)
./nil.go:15:18: invalid operation: ptr == fun (mismatched types *int64 and func())
./nil.go:17:18: invalid operation: ptr == ma (mismatched types *int64 and map[string]string)
./nil.go:18:18: invalid operation: ptr == slice (mismatched types *int64 and []int64)
./nil.go:20:18: invalid operation: cha == fun (mismatched types chan int64 and func())
./nil.go:22:18: invalid operation: cha == ma (mismatched types chan int64 and map[string]string)
./nil.go:23:18: invalid operation: cha == slice (mismatched types chan int64 and []int64)
./nil.go:25:18: invalid operation: fun == inter (operator == not defined on func)
./nil.go:26:18: invalid operation: fun == ma (mismatched types func() and map[string]string)
./nil.go:27:18: invalid operation: fun == slice (mismatched types func() and []int64)
./nil.go:27:18: too many errors
從運行結果我們可以得出,只有指標型別和channel型別與介面型別可以比較,其他型別的之間是不可以相互比較的,為什么指標型別、channel型別可以和介面型別進行比較呢?
這個答案,先空著,因為我也沒有想明白,不是說/任何型別都實作了interface{}型別嗎?這里沒想明白,期待你們的解答,
nil在不同型別中使用需要注意的問題
interface與nil比較要注意的一個點
我們先來看一個例子:
func main() {
err := Todo()
fmt.Println(err == nil)
}
type Err interface {
}
type err struct {
Code int64
Msg string
}
func Todo() Err {
var res *err
return res
}
// 運行結果
false
輸出結果是false,在Todo方法內我們宣告了一個變數res,這個變數是一個指標型別,零值是nil,回傳的是介面型別,按理說回傳值介面型別也應是nil才對,但是結果卻不是這樣,這是因為我們忽略了介面型別的一個概念,interface 不是單純的值,而是分為型別和值,所以必須要型別和值同時都為 nil 的情況下,interface 的 nil 判斷才會為 true,
這是一個新手很容易出現的問題,大家一定要注意這個問題,
一個nil的map讀寫資料是否會發生panic
對于這種問題,我們直接寫個例子測驗一下就好:
func main() {
var m map[string]string
fmt.Println(m["asoong"])
m["asong"] = "Golang夢工廠"
}
// 運行結果
panic: assignment to entry in nil map
goroutine 1 [running]:
main.main()
go/src/asong.cloud/Golang_Dream/code_demo/slice_demo/nil.go:10 +0xed
根據運行結果我們可以看出,一個nil的map可以讀資料,但是不可以寫入資料,否則會發生panic,所以要使用map一定要使用make進行初始化,
關閉nil的channel會引發panic
func main() {
var cha chan int
close(cha)
}
運行結果:
panic: close of nil channel
goroutine 1 [running]:
main.main()
/go/src/asong.cloud/Golang_Dream/code_demo/slice_demo/nil.go:5 +0x2a
根據運行結果我們可以得出關閉一個nil的channel會導致程式panic,在使用上我們要注意這個問題,還有有一個需要注意的問題:一個nil的channel讀寫資料都會造成永遠阻塞,
一個為nil的slice使用注意事項
func main() {
var slice []int64 = nil
fmt.Println(len(slice))
fmt.Println(cap(slice))
for range slice{
}
fmt.Println(slice[0])
}
// 運行結果
0
0
panic: runtime error: index out of range [0] with length 0
goroutine 1 [running]:
main.main()
/go/src/asong.cloud/Golang_Dream/code_demo/slice_demo/nil.go:14 +0xf2
根據這個例子,我們可以得出如下結論:
一個為nil的索引,不可以進行索引,否則會引發panic,其他操作是可以,
方法接收者為nil時是否會引發panic
func main() {
var m *man
fmt.Println(m.GetName())
}
type man struct {
}
func (m *man)GetName() string {
return "asong"
}
// 運行結果
asong
根據運行結果我們可以看出,方法接收者為nil時,我們仍然可以訪問對應的方法,但是要注意方法內的寫法,否則也會引發panic,上面的代碼改成這樣:
func main() {
var m *man
fmt.Println(m.GetName())
}
type man struct {
Name string
}
func (m *man)GetName() string {
return m.Name
}
// 運行結果
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x10a6ec3]
goroutine 1 [running]:
main.(*man).GetName(...)
go/src/asong.cloud/Golang_Dream/code_demo/slice_demo/nil.go:18
main.main()
go/src/asong.cloud/Golang_Dream/code_demo/slice_demo/nil.go:9 +0x23
這樣就是直接引發panic,所以為了程式健壯性我們要做一次指標判空處理,
空指標是一個沒有任何值的指標
func main() {
var a = (*int64)(unsafe.Pointer(uintptr(0x0)))
fmt.Println(a == nil) //true
}
// 運行結果
true
這里我們用了0x0做了一個小實驗,正好證明了空指標就是一個沒有指向任何值的指標,
總結
文章接近尾聲啦,我們來揭曉一下文章開始的答案,用文中nil比較的知識點正好可以解答這個問題,nil識別符號是沒有型別的,所以==對于nil來說是一種未定義的操作,不可以進行比較,而這個在python中是可以比較的,在 python 中,兩個None 值永遠相等,不要弄混了朋友們,
最后建議你們看一下這個視頻:https://www.youtube.com/watch?v=ynoY2xz-F8s 需要翻墻哈,看完這個你對nil會有一個新的理解,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/286449.html
標籤:區塊鏈
上一篇:Linux及shell編程
下一篇:爪哇教育位元組跳動第一章
