<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>

          []*T *[]T *[]*T 傻傻分不清楚

          共 5302字,需瀏覽 11分鐘

           ·

          2021-07-29 09:54

          0bedf583371965ec81b8c43fcc8a4267.webp前言

          作為一個(gè) Go 語(yǔ)言新手,看到一切”詭異“的代碼都會(huì)感到好奇;比如我最近看到的幾個(gè)方法;偽代碼如下:

          func?FindA()?([]*T,error)?{
          }

          func?FindB()?([]T,error)?{
          }

          func?SaveA(data?*[]T)?error?{
          }

          func?SaveB(data?*[]*T)?error?{
          }

          相信大部分剛?cè)腴T(mén) Go 的新手看到這樣的代碼也是一臉懵逼,其中最讓人疑惑的就是:

          []*T
          *[]T
          *[]*T

          這樣對(duì)切片的聲明,先不看后面兩種寫(xiě)法;單獨(dú)看 []*T 還是很好理解的:該切片中存放的是所有 T 的內(nèi)存地址,會(huì)比存放 T 本身來(lái)說(shuō)要更省空間,同時(shí) []*T 在方法內(nèi)部是可以修改 T 的值,而[]T 是修改不了。

          func?TestSaveSlice(t?*testing.T)?{
          ?a?:=?[]T{{Name:?"1"},?{Name:?"2"}}
          ?for?_,?t2?:=?range?a?{
          ??fmt.Println(t2)
          ?}
          ?_?=?SaveB(a)
          ?for?_,?t2?:=?range?a?{
          ??fmt.Println(t2)
          ?}

          }
          func?SaveB(data?[]T)?error?{
          ?t?:=?data[0]
          ?t.Name?=?"1233"
          ?return?nil
          }

          type?T?struct?{
          ?Name?string
          }

          比如以上例子打印的是

          {1}
          {2}
          {1}
          {2}

          只有將方法修改為

          func?SaveB(data?[]*T)?error?{
          ?t?:=?data[0]
          ?t.Name?=?"1233"
          ?return?nil
          }

          才能修改 T 的值:

          &{1}
          &{2}
          &{1233}
          &{2}
          示例

          下面重點(diǎn)來(lái)看看 []*T*[]T 的區(qū)別,這里寫(xiě)了兩個(gè) append 函數(shù):

          func?TestAppendA(t?*testing.T)?{
          ?x:=[]int{1,2,3}
          ?appendA(x)
          ?fmt.Printf("main?%v\n",?x)
          }
          func?appendA(x?[]int)?{
          ?x[0]=?100
          ?fmt.Printf("appendA?%v\n",?x)
          }

          先看第一種,輸出是結(jié)果是:

          appendA?[1000?2?3]
          main?[1000?2?3]

          說(shuō)明在函數(shù)傳遞過(guò)程中,函數(shù)內(nèi)部的修改能夠影響到外部。


          下面我們?cè)倏匆粋€(gè)例子:

          func?appendB(x?[]int)?{
          ?x?=?append(x,?4)
          ?fmt.Printf("appendA?%v\n",?x)
          }

          最終結(jié)果卻是:

          appendA?[1?2?3?4]
          main?[1?2?3]

          沒(méi)有影響到外部。

          而當(dāng)我們?cè)僬{(diào)整一下會(huì)發(fā)現(xiàn)又有所不同:

          func?TestAppendC(t?*testing.T)?{
          ?x:=[]int{1,2,3}
          ?appendC(&x)
          ?fmt.Printf("main?%v\n",?x)
          }
          func?appendC(x?*[]int)?{
          ?*x?=?append(*x,?4)
          ?fmt.Printf("appendA?%v\n",?x)
          }

          最終的結(jié)果:

          appendA?&[1?2?3?4]
          main?[1?2?3?4]

          可以發(fā)現(xiàn)如果傳遞切片的指針時(shí),使用 append 函數(shù)追加數(shù)據(jù)時(shí)會(huì)影響到外部。

          slice 原理

          在分析上面三種情況之前,我們先來(lái)了解下 slice 的數(shù)據(jù)結(jié)構(gòu)。

          直接查看源碼會(huì)發(fā)現(xiàn) slice 其實(shí)就是一個(gè)結(jié)構(gòu)體,只是不能直接對(duì)外訪問(wèn)。

          3e065314ec1adcf1ff366850a81df4e0.webp

          源碼地址 runtime/slice.go

          其中有三個(gè)重要的屬性:

          屬性含義
          array底層存放數(shù)據(jù)的數(shù)組,是一個(gè)指針。
          len切片長(zhǎng)度
          cap切片容量 cap>=len

          提到切片就不得不想到數(shù)組,可以這么理解:

          切片是對(duì)數(shù)組的抽象,而數(shù)組則是切片的底層實(shí)現(xiàn)。

          其實(shí)通過(guò)切片這個(gè)名字也不難看出,它就是從數(shù)組中切了一部分;相對(duì)于數(shù)組的固定大小,切片可以根據(jù)實(shí)際使用情況進(jìn)行擴(kuò)容。

          所以切片也可以通過(guò)對(duì)數(shù)組"切一刀"獲得:

          x1:=[6]int{0,1,2,3,4,5}
          x2?:=?x[1:4]
          fmt.Println(len(x2),?cap(x2))
          acb0039a8787aad6917d94a0007a8628.webp

          其中 x1 的長(zhǎng)度與容量都是6。

          x2 的長(zhǎng)度與容量則為3和5。

          • x2 的長(zhǎng)度很容易理解。
          • 容量等于5可以理解為,當(dāng)前這個(gè)切片最多可以使用的長(zhǎng)度。

          因?yàn)榍衅?x2 是對(duì)數(shù)組 x1 的引用,所以底層數(shù)組排除掉左邊一個(gè)沒(méi)有被引用的位置則是該切片最大的容量,也就是5。

          同一個(gè)底層數(shù)組

          以剛才的代碼為例:

          func?TestAppendA(t?*testing.T)?{
          ?x:=[]int{1,2,3}
          ?appendA(x)
          ?fmt.Printf("main?%v\n",?x)
          }
          func?appendA(x?[]int)?{
          ?x[0]=?100
          ?fmt.Printf("appendA?%v\n",?x)
          }
          dd3abd21057e888537739fc8962bb3e1.webp

          在函數(shù)傳遞過(guò)程中,main 中的 x 與 appendA 函數(shù)中的 x 切片所引用的是同個(gè)數(shù)組。

          所以在函數(shù)中對(duì) x[0]=100main函數(shù)中也能獲取到。

          98d9c897d8fa8df28a4c47bb6d3caab1.webp

          本質(zhì)上修改的就是同一塊內(nèi)存數(shù)據(jù)。

          值傳遞帶來(lái)的誤會(huì)

          在上述例子中,在 appendB 中調(diào)用 append 函數(shù)追加數(shù)據(jù)后會(huì)發(fā)現(xiàn) main 函數(shù)中并沒(méi)有受到影響,這里我稍微調(diào)整了一下示例代碼:

          func?TestAppendB(t?*testing.T)?{
          ?//x:=[]int{1,2,3}
          ?x?:=?make([]int,?3,5)
          ?x[0]?=?1
          ?x[1]?=?2
          ?x[2]?=?3
          ?appendB(x)
          ?fmt.Printf("main?%v?len=%v,cap=%v\n",?x,len(x),cap(x))
          }
          func?appendB(x?[]int)?{
          ?x?=?append(x,?444)
          ?fmt.Printf("appendB?%v?len=%v,cap=%v\n",?x,len(x),cap(x))
          }

          主要是修改了切片初始化方式,使得容量大于了長(zhǎng)度,具體原因后續(xù)會(huì)說(shuō)明。

          輸出結(jié)果如下:

          appendB?[1?2?3?444]?len=4,cap=5
          main?[1?2?3]?len=3,cap=5

          main 函數(shù)中的數(shù)據(jù)看樣子確實(shí)沒(méi)有受到影響;但細(xì)心的朋友應(yīng)該會(huì)注意到 ?appendB 函數(shù)中的 x 在 append() 之后長(zhǎng)度 +1 變?yōu)榱?。

          而在 main 函數(shù)中長(zhǎng)度又變回了3.

          這個(gè)細(xì)節(jié)區(qū)別就是為什么 append() "看似" 沒(méi)有生效的原因;至于為什么要說(shuō)“看似”,再次調(diào)整了代碼:

          func?TestAppendB(t?*testing.T)?{
          ?//x:=[]int{1,2,3}
          ?x?:=?make([]int,?3,5)
          ?x[0]?=?1
          ?x[1]?=?2
          ?x[2]?=?3
          ?appendB(x)
          ?fmt.Printf("main?%v?len=%v,cap=%v\n",?x,len(x),cap(x))

          ?y:=x[0:cap(x)]
          ?fmt.Printf("y?%v?len=%v,cap=%v\n",?y,len(y),cap(y))
          }

          在剛才的基礎(chǔ)之上,以 append 之后的 x 為基礎(chǔ)再做了一個(gè)切片;該切片的范圍為 x 所引用數(shù)組的全部數(shù)據(jù)。

          再來(lái)看看執(zhí)行結(jié)果如何:

          appendB?[1?2?3?444]?len=4,cap=5
          main?[1?2?3]?len=3,cap=5
          y?[1?2?3?444?0]?len=5,cap=5

          會(huì)神奇的發(fā)現(xiàn) y 將所有數(shù)據(jù)都打印出來(lái),在 appendB 函數(shù)中追加的數(shù)據(jù)其實(shí)已經(jīng)寫(xiě)入了數(shù)組中,但為什么 x 本身沒(méi)有獲取到呢?

          d9ca0c2c0c9b5f621be53e5df254f256.webp

          看圖就很容易理解了:

          • appendB中確實(shí)是對(duì)原始數(shù)組追加了數(shù)據(jù),同時(shí)長(zhǎng)度也增加了。
          • 但由于是值傳遞,所以 slice 這個(gè)結(jié)構(gòu)體即便是修改了長(zhǎng)度為4,也只是對(duì)復(fù)制的那個(gè)對(duì)象修改了長(zhǎng)度,main 中的長(zhǎng)度依然為3.
          • 由于底層數(shù)組是同一個(gè),所以基于這個(gè)底層數(shù)組重新生成了一個(gè)完整長(zhǎng)度的切片便能看到追加的數(shù)據(jù)了。

          所以這里本質(zhì)的原因是因?yàn)?slice 是一個(gè)結(jié)構(gòu)體,傳遞的是值,不管方法里如何修改長(zhǎng)度也不會(huì)影響到原有的數(shù)據(jù)(這里指的是長(zhǎng)度和容量這兩個(gè)屬性)。

          切片擴(kuò)容

          還有一個(gè)需要注意:

          剛才特意提到這里的例子稍有改變,主要是將切片的容量設(shè)置超過(guò)了數(shù)組的長(zhǎng)度;

          如果不做這個(gè)特殊設(shè)置會(huì)怎么樣呢?

          func?TestAppendB(t?*testing.T)?{
          ?x:=[]int{1,2,3}
          ?//x?:=?make([]int,?3,5)
          ?x[0]?=?1
          ?x[1]?=?2
          ?x[2]?=?3
          ?appendB(x)
          ?fmt.Printf("main?%v?len=%v,cap=%v\n",?x,len(x),cap(x))

          ?y:=x[0:cap(x)]
          ?fmt.Printf("y?%v?len=%v,cap=%v\n",?y,len(y),cap(y))
          }
          func?appendB(x?[]int)?{
          ?x?=?append(x,?444)
          ?fmt.Printf("appendB?%v?len=%v,cap=%v\n",?x,len(x),cap(x))
          }

          輸出結(jié)果:

          appendB?[1?2?3?444]?len=4,cap=6
          main?[1?2?3]?len=3,cap=3
          y?[1?2?3]?len=3,cap=3

          這時(shí)會(huì)發(fā)現(xiàn) main 函數(shù)中的 y 切片數(shù)據(jù)也沒(méi)有發(fā)生變化,這是為什么呢?

          ee94250f9d486d3d490aa7cca391c7c8.webp

          這是因?yàn)槌跏蓟?x 切片時(shí)長(zhǎng)度和容量都為3,當(dāng)在 appendB 函數(shù)中追加數(shù)據(jù)時(shí),會(huì)發(fā)現(xiàn)沒(méi)有位置了。

          這時(shí)便會(huì)進(jìn)行擴(kuò)容:

          • 將老數(shù)據(jù)復(fù)制一份到新的數(shù)組中。
          • 追加數(shù)據(jù)。
          • 將新的數(shù)據(jù)內(nèi)存地址返回給 appendB 中的 x .

          同樣的由于是值傳遞,所以 appendB 中的切片換了底層數(shù)組對(duì) main 函數(shù)中的切片沒(méi)有任何影響,也就導(dǎo)致最終 main 函數(shù)的數(shù)據(jù)沒(méi)有任何變化了。

          傳遞切片指針

          有沒(méi)有什么辦法即便是在擴(kuò)容時(shí)也能對(duì)外部產(chǎn)生影響呢?

          func?TestAppendC(t?*testing.T)?{
          ?x:=[]int{1,2,3}
          ?appendC(&x)
          ?fmt.Printf("main?%v?len=%v,cap=%v\n",?x,len(x),cap(x))
          }
          func?appendC(x?*[]int)?{
          ?*x?=?append(*x,?4)
          ?fmt.Printf("appendC?%v\n",?x)
          }

          輸出結(jié)果為:

          appendC?&[1?2?3?4]
          main?[1?2?3?4]?len=4,cap=6

          這時(shí)外部的切片就能受到影響了,其實(shí)原因也很簡(jiǎn)單;

          剛才也說(shuō)了,因?yàn)?slice 本身是一個(gè)結(jié)構(gòu)體,所以當(dāng)我們傳遞指針時(shí),就和平時(shí)自定義的 struct 在函數(shù)內(nèi)部通過(guò)指針修改數(shù)據(jù)原理相同。

          最終在 appendC 中的 x 的指針指向了擴(kuò)容后的結(jié)構(gòu)體,因?yàn)閭鬟f的是 main 函數(shù)中 x 的指針,所以同樣的 main 函數(shù)中的 x 也指向了該結(jié)構(gòu)體。

          總結(jié)

          所以總結(jié)一下:

          • 切片是對(duì)數(shù)組的抽象,同時(shí)切片本身也是一個(gè)結(jié)構(gòu)體。
          • 參數(shù)傳遞時(shí)函數(shù)內(nèi)部與外部引用的是同一個(gè)數(shù)組,所以對(duì)切片的修改會(huì)影響到函數(shù)外部。
          • 如果發(fā)生擴(kuò)容,情況會(huì)發(fā)生變化,同時(shí)擴(kuò)容會(huì)導(dǎo)致數(shù)據(jù)拷貝;所以要盡量預(yù)估切片大小,避免數(shù)據(jù)拷貝。
          • 對(duì)切片或數(shù)組重新生成切片時(shí),由于共享的是同一個(gè)底層數(shù)組,所以數(shù)據(jù)會(huì)互相影響,這點(diǎn)需要注意。
          • 切片也可以傳遞指針,但場(chǎng)景很少,還會(huì)帶來(lái)不必要的誤解;建議值傳值就好,長(zhǎng)度和容量占用不了多少內(nèi)存。

          相信使用過(guò)切片會(huì)發(fā)現(xiàn)非常類(lèi)似于 ?Java ?中的 ArrayList,同樣是基于數(shù)組實(shí)現(xiàn),也會(huì)擴(kuò)容發(fā)生數(shù)據(jù)拷貝;這樣看來(lái)語(yǔ)言只是上層使用的選擇,一些通用的底層實(shí)現(xiàn)大家都差不多。

          這時(shí)我們?cè)倏礃?biāo)題中的 []*T *[]T *[]*T 就會(huì)發(fā)現(xiàn)這幾個(gè)并沒(méi)有什么聯(lián)系,只是看起來(lái)很像容易唬人。

          1d8e61cd9bd4b3fdf4d049a45963ea64.webp

          更多推薦內(nèi)容

          《Go 中的 channel 與 Java BlockingQueue 的本質(zhì)區(qū)別

          《寫(xiě)了一個(gè) gorm 樂(lè)觀鎖插件

          《一文搞懂參數(shù)傳遞原理

          《擼了一個(gè) Feign 增強(qiáng)包

          《手寫(xiě)一個(gè)詞法分析器

          《手把手實(shí)現(xiàn)延時(shí)消息

          如何參與一個(gè)頂級(jí)的開(kāi)源項(xiàng)目

          也許是東半球直接地氣的分庫(kù)分表實(shí)踐了

          《What?一個(gè) Dubbo 服務(wù)啟動(dòng)要兩個(gè)小時(shí)!

          《又一次生產(chǎn) CPU 高負(fù)載的排查實(shí)踐

          《一次分表踩坑的探討

          《一致性 Hash 算法的實(shí)際應(yīng)用

          《利用策略模式優(yōu)化過(guò)多 if else

          《長(zhǎng)連接的心跳及重連設(shè)計(jì)

          《為自己搭建一個(gè)分布式 IM(即時(shí)通訊) 系統(tǒng)

          《一次生產(chǎn) CPU 100% 排查優(yōu)化實(shí)踐

          《判斷一個(gè)元素在億級(jí)數(shù)據(jù)中是否不存在

          《設(shè)計(jì)一個(gè)可插拔的 IOC 容器

          《一次 HashSet 所引起的并發(fā)問(wèn)題

          《一次內(nèi)存溢出排查實(shí)踐》

          《如何優(yōu)雅的使用和理解線程池》


          瀏覽 46
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  黄色电影免费一级片 | 思思热精品 | 五十路视频在线 | 四虎成人网| 污污网站在线免费观看 |