golang的nil在概念上和其它語言的null、None、nil、NULL一樣,都指代零值或空值。nil是預(yù)先說明的標識符,也即通常意義上的關(guān)鍵字。在golang中,nil只能賦值給指針、channel、func、interface、map或slice類型的變量。如果未遵循這個規(guī)則,則會引發(fā)panic。對此官方有明確的說明:http://pkg./pkg/builtin/#Type
golang中的interface類似于java的interface、PHP的interface或C++的純虛基類。接口就是一個協(xié)議,規(guī)定了一組成員。這個沒什么好說的,本文不打算對宏觀上的接口概念和基于接口的范式編程做剖析。golang語言的接口有其獨到之處:只要類型T的公開方法完全滿足接口I的要求,就可以把類型T的對象用在需要接口I的地方。這種做法的學(xué)名叫做Structural Typing,有人也把它看作是一種靜態(tài)的Duck Typing。所謂類型T的公開方法完全滿足接口I的要求,也即是類型T實現(xiàn)了接口I所規(guī)定的一組成員。
在底層,interface作為兩個成員來實現(xiàn),一個類型和一個值。對此官方也有文檔說明:http:///doc/go_faq.html#nil_error,如果您不習慣看英文,這里有一篇柴大的翻譯:Go中error類型的nil值和nil 。
接下來通過編寫測試代碼和gdb來看看interface倒底是什么。會用到反射,如果您不太了解golang的反射是什么,這里有刑星翻譯自官方博客的一篇文章:反射的規(guī)則,原文在:laws-of-reflection。
$GOPATH/src
----interface_test
--------main.go
main.go的代碼如下:
09 | var val interface{} = int64(58) |
10 | fmt.Println(reflect.TypeOf(val)) |
12 | fmt.Println(reflect.TypeOf(val)) |
我們已經(jīng)知道接口類型的變量底層是作為兩個成員來實現(xiàn),一個是type,一個是data。type用于存儲變量的動態(tài)類型,data用于存儲變量的具體數(shù)據(jù)。在上面的例子中,第一條打印語句輸出的是:int64。這是因為已經(jīng)顯示的將類型為int64的數(shù)據(jù)58賦值給了interface類型的變量val,所以val的底層結(jié)構(gòu)應(yīng)該是:(int64, 58)。我們暫且用這種二元組的方式來描述,二元組的第一個成員為type,第二個成員為data。第二條打印語句輸出的是:int。這是因為字面量的整數(shù)在golang中默認的類型是int,所以這個時候val的底層結(jié)構(gòu)就變成了:(int, 50)。借助于gdb很容易觀察到這點:
1 | $ cd $GOPATH/src/interface_test |
2 | $ go build -gcflags "-N -l" |
接下來說說interface類型的值和nil的比較問題。這是個比較經(jīng)典的問題,也算是golang的一個坑。
---來自柴大的翻譯
接著來看代碼:
08 | var val interface{} = nil |
10 | fmt.Println( "val is nil" ) |
12 | fmt.Println( "val is not nil" ) |
變量val是interface類型,它的底層結(jié)構(gòu)必然是(type, data)。由于nil是untyped(無類型),而又將nil賦值給了變量val,所以val實際上存儲的是(nil, nil)。因此很容易就知道val和nil的相等比較是為true的。
1 | $ cd $GOPATH/src/interface_test |
對于將任何其它有意義的值類型賦值給val,都導(dǎo)致val持有一個有效的類型和數(shù)據(jù)。也就是說變量val的底層結(jié)構(gòu)肯定不為(nil, nil),因此它和nil的相等比較總是為false。
上面的討論都是在圍繞值類型來進行的。在繼續(xù)討論之前,讓我們來看一種特例:(*interface{})(nil)。將nil轉(zhuǎn)成interface類型的指針,其實得到的結(jié)果僅僅是空接口類型指針并且它指向無效的地址。注意是空接口類型指針而不是空指針,這兩者的區(qū)別蠻大的,學(xué)過C的童鞋都知道空指針是什么概念。
關(guān)于(*interface{})(nil)還有一些要注意的地方。這里僅僅是拿(*interface{})(nil)來舉例,對于(*int)(nil)、(*byte)(nil)等等來說是一樣的。上面的代碼定義了接口指針類型變量val,它指向無效的地址(0x0),因此val持有無效的數(shù)據(jù)。但它是有類型的(*interface{})。所以val的底層結(jié)構(gòu)應(yīng)該是:(*interface{}, nil)。有時候您會看到(*interface{})(nil)的應(yīng)用,比如var ptrIface = (*interface{})(nil),如果您接下來將ptrIface指向其它類型的指針,將通不過編譯。或者您這樣賦值:*ptrIface = 123,那樣的話編譯是通過了,但在運行時還是會panic的,這是因為ptrIface指向的是無效的內(nèi)存地址。其實聲明類似ptrIface這樣的變量,是因為使用者只是關(guān)心指針的類型,而忽略它存儲的值是什么。還是以例子來說明:
08 | var val interface{} = (*interface{})(nil) |
11 | fmt.Println( "val is nil" ) |
13 | fmt.Println( "val is not nil" ) |
很顯然,無論該指針的值是什么:(*interface{}, nil),這樣的接口值總是非nil的,即使在該指針的內(nèi)部為nil。
1 | $ cd $GOPATH/src/interface_test |
interface類型的變量和nil的相等比較出現(xiàn)最多的地方應(yīng)該是error接口類型的值與nil的比較。有時候您想自定義一個返回錯誤的函數(shù)來做這個事,可能會寫出以下代碼:
09 | func ( this *data) Error() string { return "" } |
19 | fmt.Println( "e is nil" ) |
21 | fmt.Println( "e is not nil" ) |
但是很可惜,以上代碼是有問題的。
1 | $ cd $GOPATH/src/interface_test |
我們可以來分析一下。error是一個接口類型,test方法中返回的指針p雖然數(shù)據(jù)是nil,但是由于它被返回成包裝的error類型,也即它是有類型的。所以它的底層結(jié)構(gòu)應(yīng)該是(*data, nil),很明顯它是非nil的。
可以打印觀察下底層結(jié)構(gòu)數(shù)據(jù):
10 | func ( this *data) Error() string { return "" } |
23 | })(unsafe.Pointer(&e)) |
1 | $ cd $GOPATH/src/interface_test |
正確的做法應(yīng)該是:
09 | func ( this *data) Error() string { return "" } |
26 | fmt.Println( "e is nil" ) |
28 | fmt.Println( "e is not nil" ) |