1. 程式人生 > >Go基礎系列:空介面

Go基礎系列:空介面

空介面

空介面是指沒有定義任何介面方法的介面。沒有定義任何介面方法,意味著Go中的任意物件都可以實現空介面(因為沒方法需要實現),任意物件都可以儲存到空介面例項變數中

空介面的定義方式:

type empty_int interface {
}

通常會簡寫為type empty_int interface{}

更常見的,會直接使用interface{}作為一種型別,表示空介面。例如:

// 宣告一個空介面例項
var i interface{}

再比如函式使用空介面型別引數:

func myfunc(i interface{})

在Go中很多地方都使用空介面型別的引數,用的最多的fmt

中的Print類方法:

$ go doc fmt Println
func Println(a ...interface{}) (n int, err error)

空介面資料結構

可以定義一個空介面型別的array、slice、map、strcut等,這樣它們就可以用來存放任意型別的物件,因為任意型別都實現了空介面。

例如,建立一個空介面的slice:

package main

import "fmt"

func main() {
    any := make([]interface{}, 5)
    any[0] = 11
    any[1] = "hello world"
    any[2] = []int{11, 22, 33, 44}
    for _, value := range any {
        fmt.Println(value)
    }
}

輸出結果:

11
hello world
[11 22 33 44]
<nil>
<nil>

顯然,通過空介面型別,Go也能像其它動態語言一樣,在資料結構中儲存任意型別的資料。

再比如,某個struct中,如果有一個欄位想儲存任意型別的資料,就可以將這個欄位的型別設定為空介面:

type my_struct struct {
    anything interface{}
    anythings []interface{}
}

拷貝資料結構到空介面資料結構

前面解釋了任意型別的物件都能賦值給空介面例項。

var any interface{}
any = "hello world"
any = 11

空介面是一種介面,它是一種指標型別的資料型別,雖然不嚴謹,但它確實儲存了兩個指標,一個是物件的型別(或iTable),一個是物件的值。所以上面的賦值過程是讓空介面any儲存各個資料物件的型別和物件的值。

換一種角度考慮,空介面有自己的記憶體佈局方式:兩個指標,佔用兩個機器字長。

Golang給的一個經典的示例:將某個slice中的資料拷貝到空介面slice中將報錯。

package main

import "fmt"

func main() {
    testSlice := []int{11,22,33,44}

    // 成功拷貝
    var newSlice []int
    newSlice = testSlice
    fmt.Println(newSlice)

    // 拷貝失敗
    var any []interface{}
    any = testSlice
    fmt.Println(any)
}

這是因為每個空介面的記憶體佈局都佔用兩個機器字長的內容。對於長度為N的空介面slice來說,它的每個元素都是以2機器字長為單元的連續空間,共佔用N*2個機器字長的空間。

而普通的slice,例如上面的testSlice,它的每個元素是int型別的,int型別的記憶體佈局和空介面不一樣。

這些物件的記憶體佈局在編譯期間就已經確定好了,所以沒法直接將不同記憶體佈局的資料結構進行拷貝。

要想完成期待的拷貝,可以使用for-range的方式,將testSlice中的每個元素賦值給空介面slice的空介面元素:也就是一個個的空介面例項。

var any []interface{}
for _,value := range testSlice{
    any = append(any,value)
}

這樣,空介面Slice中的每個空介面例項都指向更底層的各個資料物件。而不是像前面錯誤的拷貝方式:每個空介面元素想要當作這些資料物件。

不僅空介面的Slice如此,其它包含空介面的資料結構,也都類似。