2 回答

TA貢獻1796條經驗 獲得超7個贊
對于直接 C 代碼的相當不完美的類比,想象一下:
var?x?interface{?...?}?//?fill?in?the?`...`?part?with?functions
或者在本例中,聲明i I
makei
具有您定義的接口類型,就像聲明具有struct
兩個成員的 C,一個用于保存類型,另一個用于保存該類型的值:
struct I {
? ? struct type_info *type;
? ? union {
? ? ? ? void *p;
? ? ? ? int i;
? ? ? ? double d;
? ? ? ? // add more types if/as needed here
? ? } u;
};
struct I i;
i.type當您傳遞&sto時,編譯器會填充槽i,并填充i.u.p為指向對象s。1
當您調用 時i.Set(10),Go 編譯器會將其轉換為等價的:
(*__lookup_func(i, "Set"))(i.u.p)
where__lookup_func找到實際的func (s *S) Set(age int)并且過多的魔法發現它應該將指向s(from i.u.p) 的指針傳遞給該 setter 函數。2
事實上,某些接口類型的變量具有這兩個槽(“類型”部分和保存當前值的類似聯合的部分),這是這里真正的秘密武器。您可以使用類型斷言:
v, ok := i.(int)
或類型開關:
switch v := i.(type) {
case int: // code where `v` is `var v int`
case float64: // code where `v` is `var v float64` ...
// add more cases as desired
}
檢查類型槽,同時將值槽復制到新變量v。3
請注意,當且僅當兩個槽(和) 都為零時interface,變量才比較等于。總是讓人困惑的是,如果你從某種非接口類型初始化一個值,它的槽就不再是 nil,并且測試:nil i.typei.uinterfacetype
if i == nil { // handle the case ...
不起作用,即使值槽(i.u.p在我們的類比中)是 nil。
我將其顯示為多個 C 類型的聯合,但不包括struct
類型。事實上,interface
編譯器并沒有對值的第二個槽的大小做出任何承諾,盡管在當前的編譯器中,它與任何其他指針一樣只有 8 個字節。但是,如果您擁有的任何值類型對于實際的底層實現來說太大,編譯器會插入一個分配:該值進入一些額外的內存,并且聯合的指針字段被設置為指向該值。
編譯器在編譯時檢查您填充到某個接口中的實際值的類型是否適合該接口。接口類型具有它必須支持的函數列表。如果底層類型具有這些函數,則賦值就可以(并且編譯器知道構建腳注 2 中提到的適當的類似 vtable 的數據)。如果基礎類型缺少某些函數,您會收到編譯時錯誤。因此,您絕對可以保證以后對接口變量的函數查找總是會成功。
2這里的查找比隱含的字符串查找更快,因為Set
編譯器在編譯時為該特定接口類型分配了一個整數代碼值,并且內部struct type_info
有各種快速查找表(有點類似于 C++ vtable)來幫助它以及。
在大多數情況下,“過多的魔法”大大減少為“將正確的參數放入正確的參數寄存器或堆棧位置”:復制被調用者從未讀取的額外字節是無害的。不過,如果整數與浮點需要不同的參數寄存器,那就有點棘手了,而且我不確定當前的 Go 編譯器在這里實際上做了什么。
3在該v, ok := i.(int)
表單中,如果類型槽不包含int
,v
則設置為零,并且ok
設置為false
。無論實際類型如何,這都成立:所有類型都有默認零值,并v
成為您指定類型的零值。

TA貢獻1884條經驗 獲得超4個贊
f(&s)
正在按值傳遞指針地址s
- 就像任何其他 go 函數調用一樣。該函數采用接口參數這一事實并沒有改變這一事實。
現在關于接口如何工作:接口值包含 2 項:值和底層類型。本例中的值是指向該結構的指針。該類型驗證是否s
滿足接口 - 因為它實現了 Get/Set 函數簽名。
由于方法的指針接收器可以更改接收器的數據字段 -&s
可以由該方法更改Set
。通過擴展,調用f(&s)
(調用 Set)也會改變 structs
的狀態。
PS 這種行為對于大多數 go 標準庫來說至關重要。例如,許多包都http
依賴于io.Reader
&io.Writer
接口。接受實現這些接口的值的函數和方法依賴于改變狀態、讀取網絡端口、刷新緩存等的底層具體類型來工作——同時不會給調用者帶來這些內部副作用的負擔。
- 2 回答
- 0 關注
- 152 瀏覽
添加回答
舉報