<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go 切片傳遞的隱藏危機

          共 4160字,需瀏覽 9分鐘

           ·

          2020-11-07 07:09

          提出疑問


          在Go的源碼庫或者其他開源項目中,會發(fā)現(xiàn)有些函數(shù)在需要用到切片入?yún)r,它采用是指向切片類型的指針,而非切片類型。這里未免會產(chǎn)生疑問:切片底層不就是指針指向底層數(shù)組數(shù)據(jù)嗎,為何不直接傳遞切片,兩者有什么區(qū)別?

          例如,在源碼log包中,Logger對象上綁定了formatHeader方法,它的入?yún)ο?/span>buf,其類型是*[]byte,而非[]byte。

          1func?(l?*Logger)?formatHeader(buf?*[]byte,?t?time.Time,?file?string,?line?int)?{}

          有以下例子

           1func?modifySlice(innerSlice?[]string)?{
          2????innerSlice[0]?=?"b"
          3????innerSlice[1]?=?"b"
          4????fmt.Println(innerSlice)
          5}
          6
          7func?main()?{
          8????outerSlice?:=?[]string{"a",?"a"}
          9????modifySlice(outerSlice)
          10????fmt.Print(outerSlice)
          11}
          12
          13//?輸出如下
          14[b?b]
          15[b?b]

          我們將modifySlice函數(shù)的入?yún)㈩愋透臑橹赶蚯衅闹羔?/span>

           1func?modifySlice(innerSlice?*[]string)?{
          2????(*innerSlice)[0]?=?"b"
          3????(*innerSlice)[1]?=?"b"
          4????fmt.Println(*innerSlice)
          5}
          6
          7func?main()?{
          8????outerSlice?:=?[]string{"a",?"a"}
          9????modifySlice(&outerSlice)
          10????fmt.Print(outerSlice)
          11}
          12
          13//?輸出如下
          14[b?b]
          15[b?b]

          在上面的例子中,兩種函數(shù)傳參類型得到的結(jié)果都一樣,似乎沒發(fā)現(xiàn)有什么區(qū)別。通過指針傳遞它看起來毫無用處,而且無論如何切片都是通過引用傳遞的,在兩種情況下切片內(nèi)容都得到了修改。

          這印證了我們一貫的認(rèn)知:函數(shù)內(nèi)對切片的修改,將會影響到函數(shù)外的切片。但,真的是如此嗎?

          考證與解釋


          在《你真的懂string與[]byte的轉(zhuǎn)換了嗎》一文中,我們講過切片的底層結(jié)構(gòu)如下所示。

          1type?slice?struct?{
          2????array?unsafe.Pointer
          3????len???int
          4????cap???int
          5}

          array是底層數(shù)組的指針,len表示長度,cap表示容量。

          我們對上文中的例子,做以下細(xì)微的改動。

           1func?modifySlice(innerSlice?[]string)?{
          2????innerSlice?=?append(innerSlice,?"a")
          3????innerSlice[0]?=?"b"
          4????innerSlice[1]?=?"b"
          5????fmt.Println(innerSlice)
          6}
          7
          8func?main()?{
          9????outerSlice?:=?[]string{"a",?"a"}
          10????modifySlice(outerSlice)
          11????fmt.Print(outerSlice)
          12}
          13
          14//?輸出如下
          15[b?b?a]
          16[a?a]

          神奇的事情發(fā)生了,函數(shù)內(nèi)對切片的修改竟然沒能對外部切片造成影響?

          為了清晰地明白發(fā)生了什么,將打印添加更多細(xì)節(jié)。

           1func?modifySlice(innerSlice?[]string)?{
          2????fmt.Printf("%p?%v???%p\n",?&innerSlice,?innerSlice,?&innerSlice[0])
          3????innerSlice?=?append(innerSlice,?"a")
          4????innerSlice[0]?=?"b"
          5????innerSlice[1]?=?"b"
          6????fmt.Printf("%p?%v?%p\n",?&innerSlice,?innerSlice,?&innerSlice[0])
          7}
          8
          9func?main()?{
          10????outerSlice?:=?[]string{"a",?"a"}
          11????fmt.Printf("%p?%v???%p\n",?&outerSlice,?outerSlice,?&outerSlice[0])
          12????modifySlice(outerSlice)
          13????fmt.Printf("%p?%v???%p\n",?&outerSlice,?outerSlice,?&outerSlice[0])
          14}
          15
          16//?輸出如下
          170xc00000c060?[a?a]???0xc00000c080
          180xc00000c0c0?[a?a]???0xc00000c080
          190xc00000c0c0?[b?b?a]?0xc000022080
          200xc00000c060?[a?a]???0xc00000c080

          在Go函數(shù)中,函數(shù)的參數(shù)傳遞均是值傳遞。那么,將切片通過參數(shù)傳遞給函數(shù),其實質(zhì)是復(fù)制了slice結(jié)構(gòu)體對象,兩個slice結(jié)構(gòu)體的字段值均相等。正常情況下,由于函數(shù)內(nèi)slice結(jié)構(gòu)體的array和函數(shù)外slice結(jié)構(gòu)體的array指向的是同一底層數(shù)組,所以當(dāng)對底層數(shù)組中的數(shù)據(jù)做修改時,兩者均會受到影響。

          但是存在這樣的問題:如果指向底層數(shù)組的指針被覆蓋或者修改(copy、重分配、append觸發(fā)擴容),此時函數(shù)內(nèi)部對數(shù)據(jù)的修改將不再影響到外部的切片,代表長度的len和容量cap也均不會被修改。

          為了讓讀者更清晰的認(rèn)識到這一點,將上述過程可視化如下。

          可以看到,當(dāng)切片的長度和容量相等時,發(fā)生append,就會觸發(fā)切片的擴容。擴容時,會新建一個底層數(shù)組,將原有數(shù)組中的數(shù)據(jù)拷貝至新數(shù)組,追加的數(shù)據(jù)也會被置于新數(shù)組中。切片的array指針指向新底層數(shù)組。所以,函數(shù)內(nèi)切片與函數(shù)外切片的關(guān)聯(lián)已經(jīng)徹底斬斷,它的改變對函數(shù)外切片已經(jīng)沒有任何影響了。

          注意,切片擴容并不總是等倍擴容。為了避免讀者產(chǎn)生誤解,這里對切片擴容原則簡單說明一下(源碼位于src/runtime/slice.go 中的 growslice 函數(shù)):

          切片擴容時,當(dāng)需要的容量超過原切片容量的兩倍時,會直接使用需要的容量作為新容量。否則,當(dāng)原切片長度小于1024時,新切片的容量會直接翻倍。而當(dāng)原切片的容量大于等于1024時,會反復(fù)地增加25%,直到新容量超過所需要的容量。

          到此,我們終于知道為什么有些函數(shù)在用到切片入?yún)r,它需要采用指向切片類型的指針,而非切片類型。

           1func?modifySlice(innerSlice?*[]string)?{
          2????*innerSlice?=?append(*innerSlice,?"a")
          3????(*innerSlice)[0]?=?"b"
          4????(*innerSlice)[1]?=?"b"
          5????fmt.Println(*innerSlice)
          6}
          7
          8func?main()?{
          9????outerSlice?:=?[]string{"a",?"a"}
          10????modifySlice(&outerSlice)
          11????fmt.Print(outerSlice)
          12}
          13
          14//?輸出如下
          15[b?b?a]
          16[b?b?a]

          請記住,如果你只想修改切片中元素的值,而不會更改切片的容量與指向,則可以按值傳遞切片,否則你應(yīng)該考慮按指針傳遞。


          例題鞏固


          為了判斷讀者是否已經(jīng)真正理解上述問題,我將上面的例子做了兩個變體,讀者朋友們可以自測。

          測試一

           1func?modifySlice(innerSlice?[]string)?{
          2????innerSlice[0]?=?"b"
          3??innerSlice?=?append(innerSlice,?"a")
          4????innerSlice[1]?=?"b"
          5????fmt.Println(innerSlice)
          6}
          7
          8func?main()?{
          9????outerSlice?:=?[]string{"a",?"a"}
          10????modifySlice(outerSlice)
          11????fmt.Println(outerSlice)
          12}

          測試二

           1func?modifySlice(innerSlice?[]string)?{
          2????innerSlice?=?append(innerSlice,?"a")
          3????innerSlice[0]?=?"b"
          4????innerSlice[1]?=?"b"
          5????fmt.Println(innerSlice)
          6}
          7
          8func?main()?{
          9????outerSlice:=?make([]string,?0,?3)
          10????outerSlice?=?append(outerSlice,?"a",?"a")
          11????modifySlice(outerSlice)
          12????fmt.Println(outerSlice)
          13}

          測試一答案

          1[b?b?a]
          2[b?a]

          測試二答案

          1[b?b?a]
          2[b?b]

          你做對了嗎?


          推薦閱讀


          福利

          我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號 「polarisxu」,回復(fù)?ebook?獲取;還可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。

          瀏覽 25
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  16—17女人毛片毛片国内 | 大陆美女操逼网站 | 国产高清无码在线观看 | 亚洲综合激情视频 | 亚洲免费永久精品国产无损音乐 |