溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Golang中函數(shù)傳參是否存在引用傳遞

發(fā)布時間:2021-12-09 10:29:31 來源:億速云 閱讀:129 作者:柒染 欄目:大數(shù)據(jù)

今天就跟大家聊聊有關(guān)Golang中函數(shù)傳參是否存在引用傳遞,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

Go里邊函數(shù)傳參只有值傳遞一種方式。

值傳遞

值傳遞是指在調(diào)用函數(shù)時將實際參數(shù)復(fù)制一份傳遞到函數(shù)中,這樣在函數(shù)中如果對參數(shù)進行修改,將不會影響到實際參數(shù)。

概念總給人一種教科書的感覺,寫點代碼驗證下。

func main() {
    a := 10
    fmt.Printf("%#v\n", &a) // (*int)(0xc420018080)
    vFoo(a)
}

func vFoo(b int) {    fmt.Printf("%#v\n", &b) // (*int)(0xc420018090)
}

注釋內(nèi)容是我機器的輸出,你如果運行會得到不一樣的輸出

根據(jù)代碼來解釋下,所謂的值傳遞就是:實參 a 在傳遞給函數(shù) vFoo 的形參 b 后,在 vFoo 的內(nèi)部,b 會被當(dāng)作局部變量在棧上分配空間,并且完全拷貝 a 的值。

代碼執(zhí)行后,我們看到的結(jié)果便是:a、b擁有完全不同的內(nèi)存地址, 說明他們雖然值相同(b拷貝的a,值肯定一樣),但是分別在內(nèi)存中不同的地方,也因此在函數(shù) vFoo 內(nèi)部如果改變 b 的值,a 是不會受到影響的。

Golang中函數(shù)傳參是否存在引用傳遞

圖中左側(cè)是還未調(diào)用時,內(nèi)存的分配,右側(cè)是調(diào)用函數(shù)后內(nèi)存分別分配的變量。這里需要注意,就算vFoo的參數(shù)名字是a,實參與形參也分別有自己的內(nèi)存空間,因為參數(shù)的名字僅僅是給程序員看的,上篇文章已經(jīng)說清楚了。

指針傳遞

形參為指向?qū)崊⒌刂返闹羔?,?dāng)對形參的指向操作時,就相當(dāng)于對實參本身進行的操作。

是不是云里霧里的?還是通過代碼結(jié)合來分析所謂的指針傳遞。

func main() {
    a := 10
    pa := &a
    fmt.Printf("value: %#v\n", pa) // value: (*int)(0xc420080008)
    fmt.Printf("addr: %#v\n", &pa) // addr: (**int)(0xc420088018)
    pFoo(pa)
}

func pFoo(p * int) {    fmt.Printf("value: %#v\n", p) // value: (*int)(0xc420080008)    fmt.Printf("addr: %#v\n", &p) // addr: (**int)(0xc420088028)
}

定義了一個變量 a,并把地址保存在指針變量 pa 里邊了。按照我們定的結(jié)論,Go中只有值傳遞,那么指針變量pa傳給函數(shù)的形參p后,形參將會是它在棧上的一份拷貝,他們本身將各自擁有不同的地址,但是二者的值是一樣的(都是變量a的地址)。上面的注釋部分是我程序運行后的結(jié)果,pa 與 p 的地址各自互不相關(guān),說明在參數(shù)傳遞中發(fā)生了值拷貝。

在函數(shù) pFoo 中,形參 p 的地址與實參 pa 的地址并不一樣,但是他們在內(nèi)存中的值都是變量 a 的地址,因此可以通過指針相關(guān)的操作來改變a的值。

Golang中函數(shù)傳參是否存在引用傳遞

圖中 &a 表示a的地址,值為: 0xc420080008

引用傳遞

所謂引用傳遞是指在調(diào)用函數(shù)時將實際參數(shù)的地址傳遞到函數(shù)中,那么在函數(shù)中對參數(shù)所進行的修改,將影響到實際參數(shù)。

由于 Go 里邊并不存在引用傳遞,我們常??吹秸f Go 中的引用傳遞也是針對:Slice、Map、Channel 這幾種類型(這是個錯誤觀點),因此為了解釋清楚引用傳遞,先勞煩大家看一段 C++ 的代碼(當(dāng)然非常簡單)。

void rFoo(int & ref) {
   printf("%p\n", &ref);// 0x7ffee5aef768
}
   
int main() {
   int a = 10;
   printf("%p\n", &a);// 0x7ffee7307768    int & b = a;
   printf("%p\n", &b);// 0x7ffee5aef768    rFoo(b);
   return 0; }

這里就是簡單的在main中定義一個引用,然后傳給函數(shù) rFoo,那么來看看正統(tǒng)的引用傳遞是什么樣的?

這里 b 是 a 的別名(引用,不清楚的可以看我上篇文章),因此a、b必定具備相同的地址。那么按照引用傳遞的定義,實參 b 傳給形參 ref 之后,ref 將是 b 的別名(也即a、b、ref都是同一個變量),他們將擁有相同地址。通過在 rFoo 函數(shù)中的打印信息,可以看到三者具有完全形同的地址,這是所謂的引用傳遞。

Go中沒有引用傳遞

Go中函數(shù)調(diào)用只有值傳遞,但是類型引用有引用類型,他們是:slice、map、channel。來看看官方的說法:

There’s a lot of history on that topic. Early on, maps and channels were syntactically pointers and it was impossible to declare or use a non-pointer instance. Also, we struggled with how arrays should work. Eventually we decided that the strict separation of pointers and values made the language harder to use.  Changing these types to act as references to the associated, shared data structures resolved these issues. This change added some regrettable complexity to the language but had a large effect on usability: Go became a more productive, comfortable language when it was introduced.

大概意思是說:最開始用的是指針語法,由于種種原因改成了引用,但是這個引用與C++的引用是不同的,它是共享關(guān)聯(lián)數(shù)據(jù)的結(jié)構(gòu)。關(guān)于這個問題的深入討論我會放到 slice 相關(guān)文章中進行討論,現(xiàn)在回到今天討論的主題。

那么Go的引用傳遞源起何處?我覺得讓大家誤解的是,map、slice、channel這類引用類型在傳遞到函數(shù)內(nèi)部,可以在函數(shù)內(nèi)部對它的值進行修改而引起的誤會。

針對這種三種類型是 by value 傳遞,我們用 slice 來進行驗證。

func main() {
    arr := [5]int{1, 3, 5, 6, 7}
    fmt.Printf("addr:%p\n", &arr)// addr:0xc42001a1e0
    s1 := arr[:]
    fmt.Printf("addr:%p\n", &s1)// addr:0xc42000a060

    changeSlice(s1)
}

func changeSlice(s []int) {    fmt.Printf("addr:%p\n", &s)// addr:0xc42000a080    fmt.Printf("addr:%p\n", &s[0])// addr:0xc42001a1e0
}

代碼中定義了一個數(shù)組 arr,然后用它生成了一個slice。如果go中存在引用傳遞,形參 s 的地址應(yīng)該與實參 s1 一樣(上面c++的證明),通過實際的情況我們發(fā)現(xiàn)它們具備完全不同的地址,也就是傳參依然發(fā)生了拷貝——值傳遞。

但是這里有個奇怪的現(xiàn)象,大家看到了 arr 的地址與 s[0] 有相同的地址,這也就是為什么我們在函數(shù)內(nèi)部能夠修改 slice 的原因,因為當(dāng)它作為參數(shù)傳入函數(shù)時,雖然 slice 本身是值拷貝,但是它內(nèi)部引用了對應(yīng)數(shù)組的結(jié)構(gòu),因此 s[0] 就是 arr[0] 的引用,這也就是能夠進行修改的原因。

  • Go 中函數(shù)傳參僅有值傳遞一種方式;

  • slice、map、channel都是引用類型,但是跟c++的不同;

  • slice能夠通過函數(shù)傳參后,修改對應(yīng)的數(shù)組值,是因為 slice 內(nèi)部保存了引用數(shù)組的指針,并不是因為引用傳遞。

看完上述內(nèi)容,你們對Golang中函數(shù)傳參是否存在引用傳遞有進一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI