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

來源:互聯網
上載者:User

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

  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裡邊函數傳參只有值傳遞一種方式,為了加強自己的理解,再來把每種傳參方式進行一次梳理。

值傳遞

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

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

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

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

根據代碼來解釋下,所謂的值傳遞就是:實參 a 在傳遞給函數 vFoo 的形參 b 後,在 vFoo 的內部,b 會被當作局部變數在棧上分配空間,並且完全拷貝 a 的值。

代碼執行後,我們看到的結果便是:a、b擁有完全不同的記憶體位址, 說明他們雖然值相同(b拷貝的a,值肯定一樣),但是分別在記憶體中不同的地方,也因此在函數 vFoo 內部如果改變 b 的值,a 是不會受到影響的。

圖中左側是還未調用時,記憶體的分配,右側是調用函數後記憶體分別分配的變數。這裡需要注意,就算vFoo的參數名字是a,實參與形參也分別有自己的記憶體空間,因為參數的名字僅僅是給程式員看的,上篇文章已經說清楚了。

指標傳遞

形參為指向實參地址的指標,當對形參的指向操作時,就相當於對實參本身進行的操作。

是不是雲裡霧裡的?還是通過代碼結合來分析所謂的指標傳遞。

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 的地址各自互不相關,說明在參數傳遞中發生了值拷貝。

在函數 pFoo 中,形參 p 的地址與實參 pa 的地址並不一樣,但是他們在記憶體中的值都是變數 a 的地址,因此可以通過指標相關的操作來改變a的值。

圖中 &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] 的引用,這也就是能夠進行修改的原因。

小結

  • Go 中函數傳參僅有值傳遞一種方式;
  • slicemapchannel都是參考型別,但是跟c++的不同;
  • slice能夠通過函數傳參後,修改對應的數組值,是因為 slice 內部儲存了引用數組的指標,並不是因為引用傳遞。

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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.