咖啡伴侶

          呆在上海
          posts - 163, comments - 156, trackbacks - 0, articles - 2

          Go語(yǔ)言的傳參和傳引用

          Posted on 2013-09-16 09:23 oathleo 閱讀(4011) 評(píng)論(1)  編輯  收藏 所屬分類(lèi): Golang

          Go語(yǔ)言的傳參和傳引用[OSC源創(chuàng)會(huì)主題補(bǔ)充1]

          66人收藏此文章, 我要收藏發(fā)表于2天前(2013-09-14 22:10) , 已有1496次閱讀 ,共12個(gè)評(píng)論

          OSC源創(chuàng)會(huì)主題補(bǔ)充系列:

          1. Go語(yǔ)言的傳參和傳引用
          2. Go語(yǔ)言的類(lèi)型轉(zhuǎn)換和類(lèi)型斷言

          Go語(yǔ)言規(guī)范雖然很簡(jiǎn)單, 但是深入掌握Go語(yǔ)言卻需要很多底層知識(shí).

          本來(lái)第20期的武漢OSC源創(chuàng)會(huì)有Go語(yǔ)言的專(zhuān)題講座, 誰(shuí)知道說(shuō)取消就取消了.

          我最近也整理了一些Go語(yǔ)言資料, 有Go語(yǔ)言的歷史/現(xiàn)狀/未來(lái)發(fā)展的八卦和Go語(yǔ)言常見(jiàn)的問(wèn)題和陷阱兩個(gè)部分, 本來(lái)打算OSC源創(chuàng)會(huì)能和武漢的Gopher分享 下的, 誰(shuí)知道(由于不是贊助商也不是微軟的大牛)主辦方根本不給任何的機(jī)會(huì).

          100+人數(shù)的交流會(huì)基本都是扯淡, 還是小規(guī)模的討論沙龍比較靠譜, 以后再也不會(huì)去OSC源創(chuàng)會(huì)當(dāng)聽(tīng)眾了.

          現(xiàn)在計(jì)劃將各個(gè)小問(wèn)題暫時(shí)作為博客發(fā)表.

          傳參和傳引用的問(wèn)題

          很多非官方的文檔和教材(包括一些已經(jīng)出版的圖書(shū)), 對(duì)Go語(yǔ)言的傳參和引用的講解 都有很多問(wèn)題. 導(dǎo)致眾多Go語(yǔ)言新手對(duì)Go的函數(shù)參數(shù)傳參有很多誤解.

          而傳參和傳引用是編程語(yǔ)言的根本問(wèn)題, 如果這個(gè)問(wèn)題理解錯(cuò)誤可能會(huì)導(dǎo)致很多問(wèn)題.

          slice不是引用!

          首先, Go語(yǔ)言的函數(shù)調(diào)用參數(shù)全部是傳值的, 包括 slice/map/chan 在內(nèi)所有類(lèi)型, 沒(méi)有傳引用的說(shuō)法.

          具體請(qǐng)看Go語(yǔ)言的規(guī)范:

          After they are evaluated, the parameters of the call are passed by value to the function and the called function begins execution.

          from: http://golang.org/ref/spec#Calls

          什么叫引用?

          比如有以下代碼:

          var a Object doSomething(a) // 修改a的值 print(a) 

          如果函數(shù)doSomething修改a的值, 然后print打印出來(lái)的也是修改后的值, 那么就可以認(rèn)為doSomething是通過(guò)引用的方式使用了參數(shù)a.

          為什么slice不是引用?

          我們構(gòu)造以下的代碼:

          func main() {     a := []int{1,2,3}     fmt.Println(a)     modifySlice(a)     fmt.Println(a) }  func modifySlice(data []int) {     data = nil } 

          其中modifySlice修改了切片a, 輸出結(jié)果如下:

          [1 2 3] [1 2 3] 

          說(shuō)明a在調(diào)用modifySlice前后并沒(méi)有任何變化, 因此a必然是傳值的!

          為什么很多人誤以為slice是引用呢?

          可能是 因?yàn)楹芏嘈陆佑|Go語(yǔ)言的新手, 看到Go語(yǔ)言的文檔說(shuō)Go的切片和C語(yǔ)言的數(shù)組類(lèi)型, 而C語(yǔ)言的數(shù)組是傳地址的(注意: 不是傳引用!).

          下面這個(gè)代碼可能是錯(cuò)誤的根源:

          func main() {     a := []int{1,2,3}     fmt.Println(a)     modifySliceData(a)     fmt.Println(a) }  func modifySliceData(data []int) {     data[0] = 0 } 

          輸出為:

          [1 2 3] [0 2 3] 

          函數(shù)modifySliceData確實(shí)通過(guò)參數(shù)修改了切片的內(nèi)容.

          但是請(qǐng)注意: 修改通過(guò)函數(shù)修改參數(shù)內(nèi)容的機(jī)制有很多, 其中傳參數(shù)的地址就可以修改參數(shù)的值(其實(shí)是修改參數(shù)中指針指向的數(shù)據(jù)), 并不是只有引用一種方式!

          傳指針和傳引用是等價(jià)的嗎?

          比如有以下代碼:

          func main() {     a := new(int)     fmt.Println(a)     modify(a)     fmt.Println(a) }  func modify(a *int) {     a = nil } 

          輸出為:

          0xc010000000 0xc010000000 

          可以看出指針a本身并沒(méi)有變化. 傳指針或傳地址也只能修改指針指向的內(nèi)存的值, 并不能改變指針本身在值.

          因此, 函數(shù)參數(shù)傳傳指針也是傳值的, 并不是傳引用!

          所有類(lèi)型的函數(shù)參數(shù)都是傳值的!

          包括slice/map/chan等基礎(chǔ)類(lèi)型和自定義的類(lèi)型都是傳值的.

          但是因?yàn)?code style="padding: 2px 5px; margin: 0px 2px; border: 1px solid #dddddd; background-color: #f6f6f6; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; word-break: break-all;">slice和map/chan底層結(jié)構(gòu)的差異, 又導(dǎo)致了它們傳值的影響并不完全等同.

          重點(diǎn)歸納如下:

          • GoSpec: the parameters of the call are passed by value!
          • map/slice/chan 都是傳值, 不是傳引用
          • map/chan 對(duì)應(yīng)指針, 和引用類(lèi)似
          • slice 是結(jié)構(gòu)體和指針的混合體

          • slice 含 values/count/capacity 等信息, 是按值傳遞

          • slice 中的 values 是指針, 按值傳遞
          • 按值傳遞的 slice 只能修改values指向的數(shù)據(jù), 其他都不能修改

          • 以指針或結(jié)構(gòu)體的角度看, 都是值傳遞!

          那Go語(yǔ)言有傳引用的說(shuō)法嗎?

          Go語(yǔ)言其實(shí)也是有傳引用的地方的, 但是不是函數(shù)的參數(shù), 而是閉包對(duì)外部環(huán)境是通過(guò)引用訪問(wèn)的.

          查看以下的代碼:

          func main() {     a := new(int)     fmt.Println(a)     func() {         a = nil     }()     fmt.Println(a) } 

          輸出為:

          0xc010000000 <nil> 

          因?yàn)殚]包是通過(guò)引用的方式使用外部環(huán)境的a變量, 因此可以直接修改a的值.

          比如下面2段代碼的輸出是截然不同的, 原因就是第二個(gè)代碼是通過(guò)閉包引用的方式輸出i變量:

          for i := 0; i < 5; i++ {     defer fmt.Printf("%d ", i)     // Output: 4 3 2 1 0 }  fmt.Printf("\n")     for i := 0; i < 5; i++ {     defer func(){ fmt.Printf("%d ", i) } ()     // Output: 5 5 5 5 5 } 

          像第二個(gè)代碼就是于閉包引用導(dǎo)致的副作用, 回避這個(gè)副作用的辦法是通過(guò)參數(shù)傳值或每次閉包構(gòu)造不同的臨時(shí)變量:

          // 方法1: 每次循環(huán)構(gòu)造一個(gè)臨時(shí)變量 i for i := 0; i < 5; i++ {     i := i     defer func(){ fmt.Printf("%d ", i) } ()     // Output: 4 3 2 1 0 } // 方法2: 通過(guò)函數(shù)參數(shù)傳慘 for i := 0; i < 5; i++ {     defer func(i int){ fmt.Printf("%d ", i) } (i)     // Output: 4 3 2 1 0 } 

          總結(jié)

          • 函數(shù)參數(shù)傳值, 閉包傳引用!
          • slice 含 values/count/capacity 等信息, 是按值傳遞
          • 按值傳遞的 slice 只能修改values指向的數(shù)據(jù), 其他都不能修改
          • slice 是結(jié)構(gòu)體和指針的混合體

          Feedback

          # re: Go語(yǔ)言的傳參和傳引用  回復(fù)  更多評(píng)論   

          2014-09-19 00:34 by ranh
          結(jié)論正確,過(guò)程不對(duì),slice是因?yàn)榈讓訑?shù)組相同所有有傳引用的效果,你舉例的slice是array好吧
          主站蜘蛛池模板: 肃宁县| 永昌县| 秀山| 上林县| 慈溪市| 宁南县| 民勤县| 嘉荫县| 成安县| 安西县| 栾川县| 永兴县| 密山市| 石柱| 句容市| 宜兰县| 霍邱县| 西乌珠穆沁旗| 弥渡县| 苏尼特左旗| 石景山区| 轮台县| 九江市| 静海县| 阳东县| 清涧县| 泰安市| 双柏县| 乡宁县| 海口市| 楚雄市| 台北县| 新乐市| 肥西县| 济南市| 获嘉县| 青州市| 丹东市| 河间市| 双柏县| 汝南县|