Go 語言中的slice類型可以理解為是數組array類型的描述符,包含了三個因素:
- 指向底層數組的指針
- slice目前使用到的底層數組的元素個數,即長度
- 底層數組的最大長度,即容量
因此當我們定義一個切片變量,s := make([]int, 5, 10),即為指向了一個最大長度為10的底層數組,目前切片s使用到的長度為5。
基於slice的定義,在使用slice時,有以下幾點注意事項:
1.對slice進行切分操作
對slice進行切分操作會生成一個新的slice變量,新slice和原來的slice指向同一個底層數組,只不過指向的起始位置可能不同,長度及容量可能也不相同。
- 當從左邊界有截斷時,會改變新切片容量大小
- 左邊界默認0,最小為0;右邊界默認slice的長度,最大為slice的容量
- 當然,因為指向同一個底層數組,對新slice的操作會影響到原來的slice
2.slice的賦值及函數間傳遞
a := []int{1, 2, 3, 4, 5} b := a
如上所示,則a, b指向同一個底層數組,且長度及容量因素相同,對b進行的操作會影響到a。
func main() { a := []int{1, 2, 3, 4, 5} modifySlice(a) //[10 2 3 4 5] fmt.Println(a) } func modifySlice(s []int) { s[0] = 10 }
如上所示,將slice作為參數在函數間傳遞的時候是值傳遞,產生了一個新的slice,只不過新的slice仍然指向原來的底層數組,所以通過新的slice也能改變原來的slice的值
func main() { a := []int{1, 2, 3, 4, 5} modifySlice(a) //[1 2 3 4 5] fmt.Println(a) } func modifySlice(s []int) { s = []int{10, 20, 30, 40, 50} }
示例代碼 https://play.golang.org/p/LbFovzP-Rj
但是,如上所示,在調用函數的內部,將s整體賦值一個新的slice,並不會改變a的值,因為modifySlice函數內對s重新的整體賦值,讓s指向了一個新的底層數組,而不是傳遞進來之前的a指向的那個數組,之后s的任何操作都不會影響到a了。
3.slice的append操作
append操作最容易踩坑,下面詳細說明一下。
append函數定義: func append(s []T, x ...T) []T
Append基本原則:對於一個slice變量,若slice容量足夠,append直接在原來的底層數組上追加元素到slice上;如果容量不足,append操作會生成一個新的更大容量的底層數組。
第一種情況:
func main() { a := make([]int, 2, 4) //通常append操作都是將返回值賦值給自己, //此處為了方便說明,沒有這樣做 b := append(a, 1) //改變b的前2個元素值 會影響到a b[0] = 99 //a: [99 0] &a[0]: 0x10410020 len: 2 cap: 4 fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a)) //b: [99 0 1] &b[0]: 0x10410020 len: 3 cap: 4 fmt.Println("b:", b, " &b[0]:", &b[0], " len:", len(b), " cap:", cap(b)) }
如上所示,對a進行append操作,若append后的新slice的實際元素個數沒有超出原來指向的底層數組的容量,所以仍然使用原來的底層數組:a, b的第一個值的地址一樣, 改變b的前2個元素也會影響到a。其實這時候a,b指向的同一個底層數組的第3位(索引2)已經變成了數值1,但是對slice而言,除了底層數組,還有長度,容量兩個因素,這時候a的長度仍然是2,所以輸出的a的值沒有變化。
第二種情況:
func main() { a := make([]int, 2, 4) //通常append操作都是將返回值賦值給自己, //此處為了方便說明,沒有這樣做 b := append(a, 1, 2, 3) //改變b的前2個元素值 不會影響到a b[0] = 99 //a: [0 0] &a[0]: 0x10410020 len: 2 cap: 4 fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a)) //b: [99 0 1 2 3] &b[0]: 0x10454000 len: 5 cap: 8 fmt.Println("b:", b, " &b[0]:", &b[0], " len:", len(b), " cap:", cap(b)) }
如上所示,若append后的新slice即b的實際元素個數已經超出了原來的a指向的底層數組的容量,那么就會分配給b一個新的底層數組,可以看到,a,b第一個元素的地址已經不同,改變b的前兩個元素值也不會影響到a,同時容量也發生了變化。
第三種情況:
func main() { a := make([]int, 2, 4) a[0] = 10 a[1] = 20 //a: [10 20] &a[0]: 0x10410020 len: 2 cap: 4 fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a)) //進行append操作 b := append(a[:1], 1) //a: [10 1] &a[0]: 0x10410020 len: 2 cap: 4 fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a)) //b: [10 1] &b[0]: 0x10410020 len: 2 cap: 4 fmt.Println("b:", b, " &b[0]:", &b[0], " len:", len(b), " cap:", cap(b)) }
如上所示,若append后的b的實際元素個數沒有超過原來的a指向的底層數組的容量,那么a,b指向同一個底層數組。
注意此時append的操作對象是:對a進行切分之后的切片,只取了a的第一個值,相當於一個新切片,長度為1,和a指向同一個底層數組,我們稱這個切分后的新切片為c吧,那么就相當於b其實是基於c切片進行append的,直接在長度1之后追加元素,所以append之后a的第二個元素變成了1。【所以切分操作和append操作放一起的時候,一定要小心】
第四種情況:
func main() { a := make([]int, 2, 4) a[0] = 10 a[1] = 20 //a: [10 20] &a[0]: 0x10410020 len: 2 cap: 4 fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a)) //進行append操作 //append是在第一個元素后開始追加,所以要超過容量,至少要追加4個,而不是之前例子的3個 b := append(a[:1], 1, 2, 3, 4) //a: [10 20] &a[0]: 0x10410020 len: 2 cap: 4 fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a)) //b: [10 1 2 3 4] &b[0]: 0x10454020 len: 5 cap: 8 fmt.Println("b:", b, " &b[0]:", &b[0], " len:", len(b), " cap:", cap(b)) }
如上所示,這種情況主要用來與第三種情況對比,如果append的元素數較多,超過了原來的容量,直接采用了新的底層數組,也就不會影響到a了。
上述的四種情況所用例子都比較簡單,所以比較容易看清。要小心如果在函數間傳遞slice,調用函數采用append進行操作,可能會改變原來的值的,如下所示:
func main() { a := make([]int, 2, 4) a[0] = 10 a[1] = 20 //a: [10 20] &a[0]: 0x10410020 len: 2 cap: 4 fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a)) testAppend(a[:1]) //a: [10 1] &a[0]: 0x10410020 len: 2 cap: 4 fmt.Println("a:", a, " &a[0]:", &a[0], " len:", len(a), " cap:", cap(a)) } func testAppend(s []int) { //進行append操作 s = append(s, 1) //s: [10 1] &s[0]: 0x10410020 len: 2 cap: 4 fmt.Println("s:", s, " &s[0]:", &s[0], " len:", len(s), " cap:", cap(s)) }
示例代碼 https://play.golang.org/p/mvUZkmBW8u