go語言nil和interface詳解

作者 點擊上方👆👆



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


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

https://en.wikipedia.org/wiki/Structural_type_system

有人也把它看作是一種靜態的Duck Typing。所謂類型T的公開方法完全滿足接口I的要求,也即是類型T實現了接口I所規定的一組成員。

在底層,interface作爲兩個成員來實現,一個類型和一個值。對此官方也有文檔說明,這裏有一篇柴大的翻譯:Go中error類型的nil值和nil:

https://my.oschina.net/chai2010/blog/117923

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

https://mikespook.com/2011/09/%E5%8F%8D%E5%B0%84%E7%9A%84%E8%A7%84%E5%88%99/

原文在:laws-of-reflection:

http://blog.golang.org/laws-of-reflection


本文由“壹伴編輯器”提供技術支持


$GOPATH/src

        ----interface_test

                --------main.go

main.go的代碼如下:

package main

import (
 "fmt"
 "reflect"
)

func main() {
 var val interface{} = int64(58)
 fmt.Println(reflect.TypeOf(val))
 val = 50
 fmt.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 main

import "fmt"

func main() {

 var val interface{} = nil

 if 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_test
val is nil


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

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

關於(*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 main

import "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_test
val is not nil


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


package main

import "fmt"

type data struct{}

func (this *data) Error() string {
 return ""
}

func test() error {
 var p *data = nil
 return 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_test
e is not nil


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

可以打印觀察下底層結構數據:

package main

import (
 "fmt"
 "unsafe"
)

type data struct{}

func (this *data) Error() string {
 return ""
}

func test() error {
 var p *data = nil
 return p
}

func main() {
 var e error = test()
 d := (*struct {
   itab uintptr

   data uintptr
 })(unsafe.Pointer(&e))

 fmt.Println(d)
}


編譯運行:

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


正確的做法應該是:


package main

import "fmt"

type data struct{}

func (this *data) Error() string {
 return ""
}

func bad() bool {
 return true
}

func test() error {
 var p *data = nil
 if 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")
 }
}



END


  ◆    


原文鏈接:

https://blog.csdn.net/abv123456789/article/details/24174521


我是小碗湯,我們一起學習。



本文分享自微信公衆號 - 我的小碗湯(mysmallsoup)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章