這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
概念
Slice切片是對底層數組Array的封裝,在記憶體中的儲存本質就是數組,體現為連續的記憶體塊,Go語言中的數組定義之後,長度就已經固定了,在使用過程中並不能改變其長度,而Slice就可以看做一個長度可變的數組進行使用,最為關鍵的,是數組在使用的過程中都是值傳遞,將一個數組賦值給一個新變數或作為方法參數傳遞時,是將源數組在記憶體中完全複製了一份,而不是引用源數組在記憶體中的地址,為了滿足記憶體空間的複用和數組元素的值的一致性的應用需求,Slice出現了,每個Slice都是都源數組在記憶體中的地址的一個引用,源數組可以衍生出多個Slice,Slice也可以繼續衍生Slice,而記憶體中,始終只有源數組,當然,也有例外,後邊再說。
用法
1.Slice的定義
Slice可以通過兩種方式定義,一種是從源數組中衍生,一種是通過make函數定義,本質上來說都一樣,都是在記憶體中通過數組的初始化的方式開闢一塊記憶體,將其劃分為若干個小塊用來儲存數組元素,然後Slice就去引用整個或者局部數組元素。
從數組中切片構建Slice:
a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} s := a[2:8] fmt.Println(s)//輸出:[3 4 5 6 7 8]
定義一個數組a,截取下標為2到8之間部分(包括2不包括8),構建一個Slice。
通過make函數定義:
s := make([]int, 10, 20) fmt.Println(s) //輸出:[0 0 0 0 0 0 0 0 0 0]//make函數第一個參數表示構建的數組的類型,第二個參數為數組的長度,第三個參數可選,是slice的容量,預設為第二個參數值
2.Slice的長度和容量
Slice有兩個比較混淆的概念,就是長度和容量,何謂長度?這個長度跟數組的長度是一個概念,即在記憶體中進行了初始化實際存在的元素的個數。何謂容量?如果通過make函數建立Slice的時候指定了容量參數,那記憶體管理器會根據指定的容量的值先劃分一塊記憶體空間,然後才在其中存放有數組元素,多餘部分處於空閑狀態,在Slice上追加元素的時候,首先會放到這塊閒置記憶體中,如果添加的參數個數超過了容量值,記憶體管理器會重新劃分一塊容量值為原容量值*2大小的記憶體空間,依次類推。這個機制的好處在能夠提升運算效能,因為記憶體的重新劃分會降低效能。
a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} s := a[0:] s = append(s, 11, 22, 33) sa := a[2:7] sb := sa[3:5] fmt.Println(a, len(a), cap(a)) //輸出:[1 2 3 4 5 6 7 8 9 0] 10 10 fmt.Println(s, len(s), cap(s)) //輸出:[1 2 3 4 5 6 7 8 9 0 11 22 33] 13 20 fmt.Println(sa, len(sa), cap(sa)) //輸出:[3 4 5 6 7] 5 8 fmt.Println(sb, len(sb), cap(sb)) //輸出:[6 7] 2 5 //可以看出,數組的len和cap是永遠相等的,並且是在定義的時候就已經指定的,不能改變。切片s引用這個數組的全部元素,初始長度和容量都為10,繼續追加3個元素後,其長度變為13容量為20,。切片sa截取下標2到7的數組片段,長度為5,容量為8,這個容量的改變規則為原容量值減掉起始下標,此時若追加元素,會覆蓋掉原記憶體位址中存在的值。切片sb截取切片sa下標3到5的數組片段,注意,這裡的下標指的是sa的下標,不是源數組的下標,長度為2,容量為8-3=5。
3.Slice是參考型別
上邊已經提到過,Slice是對源數組的一個引用,改變Slice中的元素的值,實質上就是改變源數組的元素的值。
a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} sa := a[2:7] sa = append(sa, 100) sb := sa[3:8] sb[0] = 99 fmt.Println(a) //輸出:[1 2 3 4 5 99 7 100 9 0] fmt.Println(sa) //輸出:[3 4 5 99 7 100] fmt.Println(sb) //輸出:[99 7 100 9 0]//可以看到,不管是append操作,還是賦值操作,都影響了源數組或者其他引用同一數組的Slice的元素。Slice進行數組引用的時候,其實是將指標指向了記憶體中具體元素的地址,如數組的記憶體位址,事實上是數組中第一個元素的記憶體位址,Slice也是如此。a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} sa := a[2:7] sb := sa[3:8] fmt.Printf("%p\n", sa) //輸出:0xc084004290 fmt.Println(&a[2], &sa[0]) //輸出:0xc084004290 0xc084004290 fmt.Printf("%p\n", sb) //輸出:0xc0840042a8 fmt.Println(&a[5], &sb[0]) //輸出:0xc0840042a8 0xc0840042a8
4.Slice引用傳遞發生“意外”
上邊我們一直在說,Slice是參考型別,指向的都是記憶體中的同一塊記憶體,不過在實際應用中,有的時候卻會發生“意外”,這種情況只有在像切片append元素的時候出現,Slice的處理機制是這樣的,當Slice的容量還有閒置時候,append進來的元素會直接使用閒置容量空間,但是一旦append進來的元素個數超過了原來指定容量值的時候,記憶體管理器就是重新開闢一個更大的記憶體空間,用於儲存多出來的元素,並且會將原來的元素複製一份,放到這塊新開闢的記憶體空間。
a := []int{1, 2, 3, 4} sa := a[1:3] fmt.Printf("%p\n", sa) //輸出:0xc0840046e0 sa = append(sa, 11, 22, 33) fmt.Printf("%p\n", sa) //輸出:0xc084003200//可以看到執行了append操作後,記憶體位址發生了變化,說明已經不是引用傳遞。
總之,slice是封裝過的array,slice用起來真爽,不需要像c語言那樣 超過數組size,remalloc size後copy