關於 interface{} 會有啥注意事項?上

阿兵雲原生發表於2023-03-07

學習 golang ,對於 interface{} 介面型別,我們一定繞不過,我們們一起來看看 使用 interface{} 的時候,都有哪些注意事項吧

interface {} 可以用於模擬多型

xdm 我們們寫一個簡單的例子,就舉動物的例子

寫一個 Animal 的介面,類似於 java 裡面的抽象類 ,Animal 的介面 中有 2 個方案待實現

寫一個 Cat 來繼承 Animal實現 Eat 方法和 Drink 方法

  • 動物都有吃和喝的行為,小貓吃的行為是吃魚,小貓的喝的行為是喝可樂
  • 最後在主函式中,使用父類的指標,來指向子類的例項化的一個子類地址
type Animal interface {
    Eat(string) string
    Drink(string) string
}

type Cat struct{}

func (c *Cat) Eat(food string) string {
    if food != "fish" {
        return "i dislike"
    } else {
        return "i like"
    }

}
func (c *Cat) Drink(drink string) string {
    if drink == "coke" {
        return "i love"
    }else{
        return "abandon"
    }
}
func main(){
    var a Animal = &Cat{}
    fmt.Println(a.Eat("fish"))
    fmt.Println(a.Drink("water"))
}

看到上述程式碼,會不會有這樣的疑問,命名是 &Cat{} 是取地址的,為什麼 var a Animal 不寫成指標呢?

這裡需要注意,Animal 本身是 介面型別,自身就是一個指標

執行上述程式碼檢視效果

# go run main.go
i like
abandon

沒有毛病,小貓眯愛吃魚,不愛喝水

interface{} 需要注意空和非空的情況

什麼叫做空的 interface{} , 什麼又叫做非空的 interface{} 呢?

我們們還是用上面的例子, 新增一個 testInterface 函式,來實踐一下

func testInterface() Animal {
    var c *Cat
    return c
}

func main() {
    test := testInterface()
    if test == nil {
        fmt.Println("test is nil")
    } else {
        fmt.Println("test is not nil")
    }
}

可以猜猜看,上面這個小案例會輸出什麼結果

  • 理論上來看,testInterface 函式中我們只是建立了一個 Cat 指標,並沒有賦值,因此預設是一個零值,因此會是一個 nil,那麼 return 的時候,應該也是 return nil 才對吧,因此按照程式碼的邏輯來說應該是輸出 test is nil

執行上述程式碼後,檢視結果

# go run main.go
test is not nil

看到上面的結果,是不是覺得很奇怪,和自己的預期不一致

沒關係,之前的文章我們說到過,覺得一個技術點奇怪,不是我們所期望的效果,原因是我們對其原理不夠了解,不夠熟悉

現在先來回答一下上面的問題

空介面:意思是沒有方法的介面,interface{} 原始碼中表示為 eface 結構體

非空介面:表示有包含方法的介面 , interface{} 原始碼中表示為 iface 結構體

暫時先來直接介紹原始碼中的結構體

iface 結構體 , 非空

type iface struct {
    tab  *itab
    data unsafe.Pointer
}

type itab struct {
    inter  *interfacetype
    _type  *_type
    link   *itab
    hash   uint32 // copy of _type.hash. Used for type switches.
    bad    bool   // type does not implement interface
    inhash bool   // has this itab been added to hash?
    unused [2]byte
    fun    [1]uintptr // variable sized
}
  • tab

    指的是具體的型別資訊,是一個 itab 結構,結構中成員如上,這裡麵包含的都是藉口的關鍵資訊,例如 hash 值 ,函式指標,等等,後續詳細剖析 interface{} 原理的時候再統一說

  • data

具體的資料資訊

eface 結構體

type eface struct {
    _type *_type
    data  unsafe.Pointer
}
type _type struct {
    size       uintptr  // 表示的是 型別的大小
    ptrdata    uintptr  // 值的是字首指標的記憶體大小
    hash       uint32   // 計算資料的 hash 值
    tflag      tflag
    align      uint8    //  進行記憶體對齊的
    fieldalign uint8 
    kind       uint8 
    alg        *typeAlg 
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}
  • _type

型別資訊,和上面的 非空介面類似 , 這個_type 型別決定下面的 data 欄位如何去解釋資料

  • data

具體的資料資訊

看到這裡,細心的 xdm 是不是就可以看出來,我們上面寫的 Animal 介面,其實是一個非空介面,因為裡面有包含方法,所以他的底層是一個 iface 結構體 ,非空介面

那麼初始化的一個空指標 c ,實際上是 iface 結構體裡面的 data 欄位為空而已,資料為空而已,但是 iface 這個結構體自己不是空的,所以上述程式碼走的邏輯是 test is not nil

這裡順帶說一下,golang 中,還有哪些資料結構是和 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

原始碼中有說到,可以對 指標,通道,函式,介面,map,切片型別使用 nil

好了,本次就到這裡,知識點要用起來才有價值

歡迎點贊,關注,收藏

朋友們,你的支援和鼓勵,是我堅持分享,提高質量的動力

好了,本次就到這裡

技術是開放的,我們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。

我是阿兵雲原生,歡迎點贊關注收藏,下次見~

相關文章