這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
(PS:專欄所有的代碼都是基於go version go1.8 darwin/amd64)
流動的水沒有形狀,漂流的風找不到蹤跡,一切代碼都瞭然於心,我們在寫代碼的時候,總是有一種思維定式陪伴左右,在對事物做判斷的時候,往往這種思維定式會往正向或反向做推動作用,在開發的過程中如果不小心忽略,往往就是埋下了陷阱,以下代碼是大多數新手會遇到的坑,
package mainimport ("fmt")type People interface {Name() string}type Student struct{ name string }func (stu *Student) Name() string {return stu.name}func getPeople() People {var stu *Studentreturn stu}func main() {if getPeople() == nil {fmt.Println("AAAAA")} else {fmt.Println("BBBBB")}}
上面的代碼輸出什麼那?有些人會認為列印AAAAA,因為他們會認為getPeople方法裡面stu是nil 所以返回的就是nil,這樣想就大錯特錯,因為雖然返回的stu是nil 但是函數返回時People介面的結構的本身並不是nil,在我們不瞭解interface內部結構之前請往下看。
為什麼我會選擇去寫一個關於interface的文章那,我認為他在go語言裡面有這非常重要的地位,僅次於goroutine和channel的地位,我在未接觸go之前一直從事於c#的開發,介面對我來說就是不同組件之間的契約,對這個契約強制你必須去繼承介面,而go語言的設計就非常輕巧,只要實現了介面所要求的所有函數即可,go中的介面分為兩種一種是空的介面類似這樣:
var in interface{}
例外一種是非空的介面即在介面內部聲明了一些方法:
type People interface {Name() string}
接下來我就根據上面的例子來對比一下空介面和非空介面內部結構
type eface struct { //空介面_type *_type //類型資訊data unsafe.Pointer //指向資料的指標(go語言中特殊的指標類型unsafe.Pointer類似於c語言中的void*)}type iface struct { //帶有方法的介面tab *itab //儲存type資訊還有結構實現方法的集合data unsafe.Pointer //指向資料的指標(go語言中特殊的指標類型unsafe.Pointer類似於c語言中的void*)}
eface包含一個類型資訊,可以為reflect提供協助
type _type struct {size uintptr //類型大小ptrdata uintptr //首碼持有所有指標的記憶體大小hash uint32 //資料hash值tflag tflag align uint8 //對齊fieldalign uint8 //嵌入結構體時的對齊kind uint8 //kind 有些枚舉值kind等於0是無效的alg *typeAlg //函數指標數組,類型實現的所有方法gcdata *bytestr nameOffptrToThis typeOff}
iface比eface 中間多了一層itab結構
type itab struct {inter *interfacetype //介面類型_type *_type //結構類型link *itab bad int32inhash int32 fun [1]uintptr //可變大小 方法集合}
itab 儲存_type資訊和[]fun方法集,從上面的結構我們就可得出,因為data指向了nil 並不代表interface 是nil,所以傳回值並不為空白,這裡的fun(方法集)定義了介面的接收規則,在編譯的過程中需要驗證是否實現介面,介面的具體細節你可以閱讀Go Data Structures: Interfaces
接下來是第二個例子:
package mainimport ("fmt")type People interface {Speak(string) string}type Stduent struct{}func (stu *Stduent) Speak(think string) (talk string) {if think == "bitch" {talk = "You are a good boy"} else {talk = "hi"}return}func main() {var peo People = Stduent{}think := "bitch"fmt.Println(peo.Speak(think))}
上面的代碼是不能編譯過去的,會提示沒有實現該介面,只要我們把var peo People = Stduent{}修改為var peo People = &Stduent{}就可以了,為什麼會有這種限制,
這是因為介面定義不規定實現者是否應該使用指標接收還是值接收實現介面。當使用介面時,不能保證底層類型是值還是指標。我們上面的例子中,我們定義了指標接受方法,修改為值接受方法:
func (stu Stduent) Speak(think string) (talk string) {if think == "bitch" {talk = "You are a good boy"} else {talk = "hi"}return}
我們再次運行列印:
You are a good boy
通過上面測試我們得出一個結論使用值傳遞方法,介面賦值使用var peo People = Stduent{}或者var peo People = &Stduent{},如果使用指標作為參數傳遞,則只能使用var peo People = &Stduent{},正是由於interface的靈活性,可以使用golang實現多態的特性,所以我們更要對interface多做深入瞭解。(本文未來可能會做一些細微的調整)