亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

為什么 Go 在將指針作為值傳遞給函數時不報告編譯錯誤?

為什么 Go 在將指針作為值傳遞給函數時不報告編譯錯誤?

Go
哈士奇WWW 2023-07-31 16:54:54
我想如果我嘗試將指針傳遞給函數,那么這個函數聲明也應該接收一個指針?不確定,我嘗試過這個:package mainimport (    "fmt")type I interface {    Get() int    Set(int)}type S struct {    Age int}func (s S) Get() int {    return s.Age}func (s *S) Set(age int) {    s.Age = age}func f(i I) {    i.Set(10)    fmt.Println(i.Get())}func main() {    s := S{}    f(&s) //4    fmt.Println(s.Get())}它打印1010我們看到 f 的函數是func f(i I)我不確定這是否是“按值傳遞”聲明,如果按值傳遞,則“i”不應該在函數“f”之外更改,它是“f”內部的副本。那么我到底錯在哪一點呢?
查看完整描述

2 回答

?
蕪湖不蕪

TA貢獻1796條經驗 獲得超7個贊

對于直接 C 代碼的相當不完美的類比,想象一下:

var?x?interface{?...?}?//?fill?in?the?`...`?part?with?functions

或者在本例中,聲明i Imakei具有您定義的接口類型,就像聲明具有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成為您指定類型的零值。


查看完整回答
反對 回復 2023-07-31
?
慕村9548890

TA貢獻1884條經驗 獲得超4個贊

f(&s)正在按值傳遞指針地址s- 就像任何其他 go 函數調用一樣。該函數采用接口參數這一事實并沒有改變這一事實。

現在關于接口如何工作:接口值包含 2 項:值和底層類型。本例中的值是指向該結構的指針。該類型驗證是否s滿足接口 - 因為它實現了 Get/Set 函數簽名。

由于方法的指針接收器可以更改接收器的數據字段 -&s可以由該方法更改Set。通過擴展,調用f(&s)(調用 Set)也會改變 structs的狀態。

PS 這種行為對于大多數 go 標準庫來說至關重要。例如,許多包都http依賴于io.Reader&io.Writer接口。接受實現這些接口的值的函數和方法依賴于改變狀態、讀取網絡端口、刷新緩存等的底層具體類型來工作——同時不會給調用者帶來這些內部副作用的負擔。


查看完整回答
反對 回復 2023-07-31
  • 2 回答
  • 0 關注
  • 152 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號