<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
Go陣列是值型別,陣列定義的時候就需要指定大小,不同大小的陣列是不同的型別,陣列大小固定之後不可改變。陣列的賦值和傳參都會複製一份。
func main() { arrayA := [2]int{100, 200} var arrayB [2]int arrayB = arrayA fmt.Printf("arrayA : %p , %vn", &arrayA, arrayA) fmt.Printf("arrayB : %p , %vn", &arrayB, arrayB) testArray(arrayA) } func testArray(x [2]int) { fmt.Printf("func Array : %p , %vn", &x, x) }
結果:
arrayA : 0xc4200bebf0 , [100 200]
arrayB : 0xc4200bec00 , [100 200]
func Array : 0xc4200bec30 , [100 200]
可以看到,三個記憶體地址都不同,這也就驗證了 Go 中陣列賦值和函數傳參都是值複製的。尤其是傳參的時候把陣列複製一遍,當陣列非常大的時候會非常消耗記憶體。可以考慮使用指標傳遞。
指標傳遞有個不好的地方,當函數內部改變了陣列的內容,則原陣列的內容也改變了。
因此一般引數傳遞的時候使用slice
切片本身並不是動態陣列或者陣列指標。它內部實現的資料結構通過指標參照底層陣列,設定相關屬性將資料讀寫操作限定在指定的區域內。切片本身是一個唯讀物件,其工作機制類似陣列指標的一種封裝。
切片(slice)是對陣列一個連續片段的參照,所以切片是一個參照型別。這個片段可以是整個陣列,或者是由起始和終止索引標識的一些項的子集。需要注意的是,終止索引標識的項不包括在切片內。切片提供了一個與指向陣列的動態視窗。
給定項的切片索引可能比相關陣列的相同元素的索引小。和陣列不同的是,切片的長度可以在執行時修改,最小為 0 最大為相關陣列的長度:切片是一個長度可變的陣列。
切片資料結構定義
type slice struct { array unsafe.Pointer len int cap int }
切片的結構體由3部分構成,Pointer 是指向一個陣列的指標,len 代表當前切片的長度,cap 是當前切片的容量。cap 總是大於等於 len 的。
如果想從 slice 中得到一塊記憶體地址,可以這樣做:
s := make([]byte, 200) ptr := unsafe.Pointer(&s[0])
使用make函數建立slice
// 建立一個初始大小是3,容量是10的切片 s1 := make([]int64,3,10)
底層方法實現:
func makeslice(et *_type, len, cap int) slice { // 根據切片的資料型別,獲取切片的最大容量 maxElements := maxSliceCap(et.size) // 比較切片的長度,長度值域應該在[0,maxElements]之間 if len < 0 || uintptr(len) > maxElements { panic(errorString("makeslice: len out of range")) } // 比較切片的容量,容量值域應該在[len,maxElements]之間 if cap < len || uintptr(cap) > maxElements { panic(errorString("makeslice: cap out of range")) } // 根據切片的容量申請記憶體 p := mallocgc(et.size*uintptr(cap), et, true) // 返回申請好記憶體的切片的首地址 return slice{p, len, cap} }
利用陣列建立切片
arr := [10]int64{1,2,3,4,5,6,7,8,9,10} s1 := arr[2:4:6] // 以arr[2:4]建立一個切片,且容量到達arr[6]的位置,即cap=6-2=4,如果不寫容量則預設為陣列最後一個元素
nil切片的指標指向的是nil
空切片指向的是一個空陣列
空切片和 nil 切片的區別在於,空切片指向的地址不是nil,指向的是一個記憶體地址,但是它沒有分配任何記憶體空間,即底層元素包含0個元素。
最後需要說明的一點是。不管是使用 nil 切片還是空切片,對其呼叫內建函數 append,len 和 cap 的效果都是一樣的
func main() { slice := []int{10, 20, 30, 40} newSlice := append(slice, 50) fmt.Printf("Before slice = %v, Pointer = %p, len = %d, cap = %dn", slice, &slice, len(slice), cap(slice)) fmt.Printf("Before newSlice = %v, Pointer = %p, len = %d, cap = %dn", newSlice, &newSlice, len(newSlice), cap(newSlice)) newSlice[1] += 10 fmt.Printf("After slice = %v, Pointer = %p, len = %d, cap = %dn", slice, &slice, len(slice), cap(slice)) fmt.Printf("After newSlice = %v, Pointer = %p, len = %d, cap = %dn", newSlice, &newSlice, len(newSlice), cap(newSlice)) }
輸出結果:
Before slice = [10 20 30 40], Pointer = 0xc4200b0140, len = 4, cap = 4
Before newSlice = [10 20 30 40 50], Pointer = 0xc4200b0180, len = 5, cap = 8
After slice = [10 20 30 40], Pointer = 0xc4200b0140, len = 4, cap = 4
After newSlice = [10 30 30 40 50], Pointer = 0xc4200b0180, len = 5, cap = 8
Go 中切片擴容的策略是這樣的:
如果切片的容量小於 1024 個元素,於是擴容的時候就翻倍增加容量。上面那個例子也驗證了這一情況,總容量從原來的4個翻倍到現在的8個。
一旦元素個數超過 1024 個元素,那麼增長因子就變成 1.25 ,即每次增加原來容量的四分之一。
不一定。當發生了擴容就肯定是新陣列,沒有發生擴容則是舊地址
不管切片是通過make建立還是字面量建立,底層都是一樣的,指向的是一個陣列。當使用字面量建立時,切片底層使用的陣列就是建立時候的陣列。修改切片中的元素或者往切片中新增元素,如果沒有擴容,則會影響原陣列的內容,切片底層和原陣列是同一個陣列;當切片擴容了之後,則修改切片的元素或者往切片中新增元素,不會修改陣列內容,因為切片擴容之後,底層陣列不再是原陣列,而是一個新陣列。
所以儘量避免切片底層陣列與原始陣列相同,儘量使用make建立切片
func main() { // slice := []int{10, 20, 30, 40} slice := [4]int{10, 20, 30, 40} for index, value := range slice { fmt.Printf("value = %d , value-addr = %x , slice-addr = %xn", value, &value, &slice[index]) } }
結果:
value = 10 , value-addr = c00000a0a8 , slice-addr = c000012360
value = 20 , value-addr = c00000a0a8 , slice-addr = c000012368
value = 30 , value-addr = c00000a0a8 , slice-addr = c000012370
value = 40 , value-addr = c00000a0a8 , slice-addr = c000012378
從上面結果我們可以看到,如果用 range 的方式去遍歷一個陣列或者切片,拿到的 Value 其實是切片裡面的值拷貝。所以每次列印 Value 的地址都不變。
由於 Value 是值拷貝的,並非參照傳遞,所以直接改 Value 是達不到更改原切片值的目的的,需要通過 &slice[index] 獲取真實的地址
尤其是在for迴圈中使用協程,一定不能直接把index,value傳入協程,而應該通過引數傳進去
錯誤範例:
func main() { s := []int{10,20,30} for index, value := range s { go func() { time.Sleep(time.Second) fmt.Println(fmt.Sprintf("index:%d,value:%d", index,value)) }() } time.Sleep(time.Second*2) }
結果:
index:2,value:30
index:2,value:30
index:2,value:30
正確範例:
func main() { s := []int{10,20,30} for index, value := range s { go func(i,v int) { time.Sleep(time.Second) fmt.Println(fmt.Sprintf("index:%d,value:%d", i,v)) }(index,value) } time.Sleep(time.Second*2) }
結果:
index:0,value:10
index:2,value:30
index:1,value:20
到此這篇關於深入瞭解Golang中的Slice底層實現的文章就介紹到這了,更多相關Golang Slice內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45