Golang中函數傳參存在引用傳遞嗎?

上篇文章後,繼續來探討下面的幾個問題:c++

  1. 函數傳參中值傳遞、指針傳遞與引用傳遞到底有什麼不同?
  2. 爲何說 slicemapchannel 是引用類型?
  3. Go中 slice 在傳入函數時究竟是不是引用傳遞?若是不是,在函數內爲何能修改其值?
In a function call, the function value and arguments are evaluated in the usual order. After they are evaluated, the parameters of the call are passed by value to the function and the called function begins execution.
文檔地址: https://golang.org/ref/spec#C...

官方文檔已經明確說明:Go裏邊函數傳參只有值傳遞一種方式,爲了增強本身的理解,再來把每種傳參方式進行一次梳理。程序員

值傳遞

值傳遞是指在調用函數時將實際參數複製一份傳遞到函數中,這樣在函數中若是對參數進行修改,將不會影響到實際參數。

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

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

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

註釋內容是我機器的輸出,你若是運行會獲得不同的輸出c#

根據代碼來解釋下,所謂的值傳遞就是:實參 a 在傳遞給函數 vFoo 的形參 b 後,在 vFoo 的內部,b 會被看成局部變量在棧上分配空間,而且徹底拷貝 a 的值。segmentfault

代碼執行後,咱們看到的結果即是:a、b擁有徹底不一樣的內存地址, 說明他們雖然值相同(b拷貝的a,值確定同樣),可是分別在內存中不一樣的地方,也所以在函數 vFoo 內部若是改變 b 的值,a 是不會受到影響的。數組

funcCall

圖中左側是還未調用時,內存的分配,右側是調用函數後內存分別分配的變量。這裏須要注意,就算vFoo的參數名字是a,實參與形參也分別有本身的內存空間,由於參數的名字僅僅是給程序員看的,上篇文章已經說清楚了。ide

指針傳遞

形參爲指向實參地址的指針,當對形參的指向操做時,就至關於對實參自己進行的操做。

是否是雲裏霧裏的?仍是經過代碼結合來分析所謂的指針傳遞。函數

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 裏邊了。按照咱們定的結論,Go中只有值傳遞,那麼指針變量pa傳給函數的形參p後,形參將會是它在棧上的一份拷貝,他們自己將各自擁有不一樣的地址,可是兩者的值是同樣的(都是變量a的地址)。上面的註釋部分是我程序運行後的結果,pa 與 p 的地址各自互不相關,說明在參數傳遞中發生了值拷貝。lua

在函數 pFoo 中,形參 p 的地址與實參 pa 的地址並不同,可是他們在內存中的值都是變量 a 的地址,所以能夠經過指針相關的操做來改變a的值。
funcCallspa

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

引用傳遞

所謂引用傳遞是指在調用函數時將實際參數的地址傳遞到函數中,那麼在函數中對參數所進行的修改,將影響到實際參數。

因爲 Go 裏邊並不存在引用傳遞,咱們經常看到說 Go 中的引用傳遞也是針對:SliceMapChannel 這幾種類型(這是個錯誤觀點),所以爲了解釋清楚引用傳遞,先勞煩你們看一段 C++ 的代碼(固然很是簡單)。

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中定義一個引用,而後傳給函數 rFoo,那麼來看看正統的引用傳遞是什麼樣的?

這裏 b 是 a 的別名(引用,不清楚的能夠看我上篇文章),所以a、b一定具有相同的地址。那麼按照引用傳遞的定義,實參 b 傳給形參 ref 以後,ref 將是 b 的別名(也即a、b、ref都是同一個變量),他們將擁有相同地址。經過在 rFoo 函數中的打印信息,能夠看到三者具備徹底形同的地址,這是所謂的引用傳遞。

Go中沒有引用傳遞

Go中函數調用只有值傳遞,可是類型引用有引用類型,他們是:slicemapchannel。來看看官方的說法:

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++的引用是不一樣的,它是共享關聯數據的結構。關於這個問題的深刻討論我會放到 slice 相關文章中進行討論,如今回到今天討論的主題。

那麼Go的引用傳遞源起何處?我以爲讓你們誤解的是,map、slice、channel這類引用類型在傳遞到函數內部,能夠在函數內部對它的值進行修改而引發的誤會。

針對這種三種類型是 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
}

代碼中定義了一個數組 arr,而後用它生成了一個slice。若是go中存在引用傳遞,形參 s 的地址應該與實參 s1 同樣(上面c++的證實),經過實際的狀況咱們發現它們具有徹底不一樣的地址,也就是傳參依然發生了拷貝——值傳遞。

可是這裏有個奇怪的現象,你們看到了 arr 的地址與 s[0] 有相同的地址,這也就是爲何咱們在函數內部可以修改 slice 的緣由,由於當它做爲參數傳入函數時,雖然 slice 自己是值拷貝,可是它內部引用了對應數組的結構,所以 s[0] 就是 arr[0] 的引用,這也就是可以進行修改的緣由。

funcCall

小結

  • Go 中函數傳參僅有值傳遞一種方式;
  • slicemapchannel都是引用類型,可是跟c++的不一樣;
  • slice可以經過函數傳參後,修改對應的數組值,是由於 slice 內部保存了引用數組的指針,並非由於引用傳遞。

接下來的文章嘗試解析下:slice 爲何必定要用 make 進行初始話,它初始化作了哪些事情?它每次動態擴展容量的時候進行了什麼操做?

相關文章
相關標籤/搜索