2 回答

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

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