golang: 詳解interface和nil

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

golang的nil在概念上和其它語言的null、None、nil、NULL一樣,都指代零值或空值。nil是預先說明的標識符,也即通常意義上的關鍵字。在golang中,nil只能賦值給指標、channel、func、interface、map或slice類型的變數。如果未遵循這個規則,則會引發panic。對此官方有明確的說明:http://pkg.golang.org/pkg/builtin/#Type

golang中的interface類似於java的interface、PHP的interface或C++的純虛基類。介面就是一個協議,規定了一群組成員。這個沒什麼好說的,本文不打算對宏觀上的介面概念和基於介面的範式編程做剖析。golang語言的介面有其獨到之處:只要類型T的公開方法完全滿足介面I的要求,就可以把類型T的對象用在需要介面I的地方。這種做法的學名叫做Structural Typing,有人也把它看作是一種靜態Duck Typing。所謂類型T的公開方法完全滿足介面I的要求,也即是類型T實現了介面I所規定的一群組成員。

在底層,interface作為兩個成員來實現,一個類型和一個值。對此官方也有文檔說明:http://golang.org/doc/go_faq.html#nil_error,如果您不習慣看英文,這裡有一篇柴大的翻譯:Go中error類型的nil值和nil 。

接下來通過編寫測試代碼和gdb來看看interface倒底是什麼。會用到反射,如果您不太瞭解golang的反射是什麼,這裡有刑星翻譯自官方部落格的一篇文章:反射的規則,原文在:laws-of-reflection。

$GOPATH/src

----interface_test

--------main.go

main.go的代碼如下:

package mainimport ("fmt""reflect")func main() {var val interface{} = int64(58)fmt.Println(reflect.TypeOf(val))val = 50fmt.Println(reflect.TypeOf(val))}

 

我們已經知道介面類型的變數底層是作為兩個成員來實現,一個是type,一個是data。type用於儲存變數的動態類型,data用於儲存變數的具體資料。在上面的例子中,第一條列印語句輸出的是:int64。這是因為已經顯示的將類型為int64的資料58賦值給了interface類型的變數val,所以val的底層結構應該是:(int64, 58)。我們暫且用這種二元組的方式來描述,二元組的第一個成員為type,第二個成員為data。第二條列印語句輸出的是:int。這是因為字面量的整數在golang中預設的類型是int,所以這個時候val的底層結構就變成了:(int, 50)。藉助於gdb很容易觀察到這點:

$ cd $GOPATH/src/interface_test$ go build -gcflags "-N -l"$ gdb interface_test

 

接下來說說interface類型的值和nil的比較問題。這是個比較經典的問題,也算是golang的一個坑。

                                                                                                                ---來自柴大的翻譯

接著來看代碼:

package mainimport ("fmt")func main() {var val interface{} = nilif val == nil {fmt.Println("val is nil")} else {fmt.Println("val is not nil")}}

 

變數val是interface類型,它的底層結構必然是(type, data)。由於nil是untyped(無類型),而又將nil賦值給了變數val,所以val實際上儲存的是(nil, nil)。因此很容易就知道val和nil的相等比較是為true的。

$ cd $GOPATH/src/interface_test$ go build$ ./interface_testval is nil

 

對於將任何其它有意義的實值型別賦值給val,都導致val持有一個有效類型和資料。也就是說變數val的底層結構肯定不為(nil, nil),因此它和nil的相等比較總是為false。

上面的討論都是在圍繞實值型別來進行的。在繼續討論之前,讓我們來看一種特例:(*interface{})(nil)。將nil轉成interface類型的指標,其實得到的結果僅僅是空介面類型指標並且它指向無效的地址。注意是空介面類型指標而不是null 指標,這兩者的區別蠻大的,學過C的童鞋都知道null 指標是什麼概念。

關於(*interface{})(nil)還有一些要注意的地方。這裡僅僅是拿(*interface{})(nil)來舉例,對於(*int)(nil)、(*byte)(nil)等等來說是一樣的。上面的代碼定義了介面指標類型變數val,它指向無效的地址(0x0),因此val持有無效的資料。但它是有類型的(*interface{})。所以val的底層結構應該是:(*interface{}, nil)。有時候您會看到(*interface{})(nil)的應用,比如var ptrIface = (*interface{})(nil),如果您接下來將ptrIface指向其它類型的指標,將通不過編譯。或者您這樣賦值:*ptrIface = 123,那樣的話編譯是通過了,但在運行時還是會panic的,這是因為ptrIface指向的是無效的記憶體位址。其實聲明類似ptrIface這樣的變數,是因為使用者只是關心指標的類型,而忽略它儲存的值是什麼。還是以例子來說明:

package mainimport ("fmt")func main() {var val interface{} = (*interface{})(nil)// val = (*int)(nil)if val == nil {fmt.Println("val is nil")} else {fmt.Println("val is not nil")}}

 

很顯然,無論該指標的值是什麼:(*interface{}, nil),這樣的介面值總是非nil的,即使在該指標的內部為nil。

$ cd $GOPATH/src/interface_test$ go build$ ./interface_testval is not nil

 

 interface類型的變數和nil的相等比較出現最多的地方應該是error介面類型的值與nil的比較。有時候您想自訂一個返回錯誤的函數來做這個事,可能會寫出以下代碼:

package mainimport ("fmt")type data struct{}func (this *data) Error() string { return "" }func test() error {var p *data = nilreturn p}func main() {var e error = test()if e == nil {fmt.Println("e is nil")} else {fmt.Println("e is not nil")}}

 

但是很可惜,以上代碼是有問題的。

$ cd $GOPATH/src/interface_test$ go build$ ./interface_teste is not nil

 

我們可以來分析一下。error是一個介面類型,test方法中返回的指標p雖然資料是nil,但是由於它被返回成封裝的error類型,也即它是有類型的。所以它的底層結構應該是(*data, nil),很明顯它是非nil的。

可以列印觀察下底層結構資料:

package mainimport ("fmt""unsafe")type data struct{}func (this *data) Error() string { return "" }func test() error {var p *data = nilreturn p}func main() {var e error = test()d := (*struct {itab uintptrdata uintptr})(unsafe.Pointer(&e))fmt.Println(d)}

 

$ cd $GOPATH/src/interface_test$ go build$ ./interface_test&{3078907912 0}

 

正確的做法應該是:

package mainimport ("fmt")type data struct{}func (this *data) Error() string { return "" }func bad() bool {return true}func test() error {var p *data = nilif bad() {return p}return nil}func main() {var e error = test()if e == nil {fmt.Println("e is nil")} else {fmt.Println("e is not nil")}}
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.