Go中,數(shù)組的長度是不可變的,但是Go提供了一種靈活的內(nèi)置類型:切片(也稱為動態(tài)數(shù)組)。
切片的長度不固定,可以追加元素,也可以擴容。
slice 的本質(zhì)
slice是引用類型,是一個指向數(shù)組的指針。
在內(nèi)存中,一個slice是一個結(jié)構(gòu)體:
struct Slice
{
byte* array; // actual data
uintgo len; // number of elements
uintgo cap; /// allocated number of elements
}
所以,一個slice是一個包含三個域的結(jié)構(gòu)體:指向slice中第一個元素的指針,slice的長度,slice的容量。
slice的長度是slice中元素的個數(shù),slice的容量是slice中最多能有的元素的個數(shù)。
因此長度是下標(biāo)操作的上界,如x[i]中 i 必須小于長度。容量是分割操作的上界,如x[i:j]中 j 不能大于容量。
如下圖:
定義切片
var s1 []int // 聲明一個未指定大小的數(shù)組來定義切片
var s2 []int = make([]int, len) // 使用make() 來創(chuàng)建切片
s3 := make([]int, len) // 同上
s4 := make([]int, len, cap) // 也可以指定容量。
切片未初始化之前默認(rèn)為 nil,長度為 0 。
內(nèi)置函數(shù) len() 和 cap() 提過了獲取切片長度和容量的方法。
切片初始化和截取
s1 := []int{1, 2, 3} // 直接初始化,其 cap = len = 3
arr := [...]int{1, 3, 5, 7, 9, 11} // arr 是一個數(shù)組
s2 := arr[:] // 切片s2 是數(shù)組arr的引用
s3 := arr[2:5] // s3 = [5, 7, 9]
s3 := arr[2:] // s3 = [5 7 9 11] 缺省endIndex時,就直到arr的最后一個元素
s3 := arr[:3] // s2 = [1, 2, 5] 缺省startIndex時,就從arr的第一個元素開始
s4 := s3[startIndex:endIndex] // 用切片s3 初始化 s4,但它們只是不同的結(jié)構(gòu)體,指向了同一片內(nèi)存區(qū),共享底層數(shù)據(jù)
切片追加元素
內(nèi)置函數(shù) append() 可以向切片追加新元素。
s1 = append(s1, 4) // s1 = [1 2 3 4]
s1 = append(s1, 5, 6) // s1 = [1 2 3 4 5 6]
對 slice 進(jìn)行 append 操作時,可能會造成 slice 自動擴容。其擴容時的規(guī)則是:
- 如果新的大小是當(dāng)前大小的2倍以上,則大小增長為新大小
- 否則循環(huán)一下操作:如果當(dāng)前大小小于1024,按每次2倍增長,否則每次按當(dāng)前大小的 1/4 增長。直達(dá)增長的大小超過或等于新大小。
我們通常不知道 append 調(diào)用是否導(dǎo)致了內(nèi)存的重新分配,因此我們不能確認(rèn)新的slice和原始的slice是否引用的是相同的底層數(shù)組空間。同樣,我們不能確認(rèn)在原先的slice上的操作會不會影響到新的 slice,因此,通常是將 append 返回的結(jié)果直接復(fù)制給輸入的 slice 變量, 即 s = append(s, r) 。
手動擴容
s5 := make([]int, len(s3), (cap(s3))*2) // 創(chuàng)建切片s5 是 s3 的兩倍容量
copy(s5, s3) // 拷貝 s3 的內(nèi)容到 s5
slice作為函數(shù)參數(shù)
向函數(shù)傳遞 slice 將允許在函數(shù)內(nèi)部修改底層數(shù)組的元素。
其他
- 不能使用 == 來判斷兩個slice 是給含有全部相同元素
- slice 唯一合法的比較是 和 nil 比較。
- 判斷 slice 是否為空,使用
len(s) == 0, 而不是s == nil。