[TOC]
切片也是一種數據結構,它和數組非常相似,是圍繞 *動態數組* 的概念設計的,可以按需自動改變大小,使用這種結構,可以更方便的管理和使用數據集合。
> 本質是結構體
## 概念
* 其本身并不是數組,它指向底層數組
* 作為變長數組的替代方案,可以關聯底層數組的局部或者全部
* 為引用類型
* 可以直接創建或從底層數組生成
* 一般使用`make`創建
* 如果多個切片指向相同的底層數組,其中一個值改變會影響全部
* `meke([]T,len,cap)` cap省略時和len相同
* `len()`獲取元素個數,cap()獲取容量
## 內部實現
切片是基于數組實現的,它的底層是數組,它自己本身非常小,可以理解為對底層數組的抽象。因為基于數組實現,所以它的底層的內存是連續分配的,效率非常高,還可以通過索引獲得數據,可以迭代以及垃圾回收優化的好處。
切片對象非常小,是因為它是只有3個字段的數據結構:
1. 指向底層數組的指針
2. 切片的長度
3. 切片的容量
這3個字段,就是Go語言操作底層數組的元數據,有了它們,我們就可以任意的操作切片了。
## 切片內部結構,實質是結構體

## 切片共享存儲結構

## 聲明和初始化
### make方式
~~~
slice := make([]int,5)
slice := make([]int,5,10)
~~~
使用內置的make函數時,需要傳入一個參數,指定切片的`長度`,例子中我們使用的時5,這時候切片的容量也是5。可以第二個參數單獨指定切片的容量。這個容量10其實對應的是`切片底層數組`的。
因為切片的底層是數組,所以創建切片時,如果不指定字面值的話,默認值就是數組的元素的零值。這里我們所以指定了容量是10,但是我們職能訪問5個元素,因為切片的長度是5,剩下的5個元素,需要切片擴充后才可以訪問。
容量必須>=長度,我們是不能創建長度大于容量的切片的。
### 使用字面量
就是指定初始化的值。
~~~
slice := []int{1,2,3,4,5}
slice := []int{4:1}
~~~
此時切片的長度和容量是相等的,并且會根據我們指定的字面量推導出來。
### 基于現有的數組或者切片創建
~~~
slice := []int{1, 2, 3, 4, 5}
slice1 := slice[:]
slice2 := slice[0:]
slice3 := slice[:5]
fmt.Println(slice1)
fmt.Println(slice2)
fmt.Println(slice3)
~~~
### 共用底層數組
~~~
slice := []int{1, 2, 3, 4, 5}
newSlice := slice[1:3]
newSlice[0] = 10
fmt.Println(slice)
fmt.Println(newSlice)
~~~
這個例子證明了,新的切片和原切片共用的是一個底層數組,所以當修改的時候,底層數組的值就會被改變,所以原切片的值也改變了。當然對于基于數組的切片也一樣的。
## 使用切片
使用切片,和使用數組一樣,通過索引就可以獲取切片對應元素的值,同樣也可以修改對應元素的值。
~~~
slice := []int{1, 2, 3, 4, 5}
fmt.Println(slice[2]) //獲取值
slice[2] = 10 //修改值
fmt.Println(slice[2]) //輸出10
~~~
切片只能訪問到其長度內的元素,訪問超過長度外的元素,會導致運行時異常,與切片容量關聯的元素只能用于切片增長。
### append
切片算是一個動態數組,所以它可以按需增長,我們使用內置append函數即可。append函數可以為一個切片追加一個元素,至于如何增加、返回的是原切片還是一個新切片、長度和容量如何改變這些細節,append函數都會幫我們自動處理。
內置的append也是一個可變參數的函數,所以我們可以同時追加好幾個值。
~~~
slice := []int{1, 2, 3, 4, 5}
newSlice := slice[1:3]
newSlice=append(newSlice,10)
fmt.Println(newSlice)
fmt.Println(slice)
//Output
[2 3 10]
[1 2 3 10 5]
newSlice=append(newSlice, newSlice...)
~~~
例子中,通過append函數為新創建的切片newSlice,追加了一個元素10,我們發現打印的輸出,原切片slice的第4個值也被改變了,變成了10。引起這種結果的原因是因為newSlice有可用的容量,不會創建新的切片來滿足追加,所以直接在newSlice后追加了一個元素10,因為newSlice和slice切片共用一個底層數組,所以切片slice的對應的元素值也被改變了。
這里newSlice新追加的第3個元素,其實對應的是slice的第4個元素,所以這里的追加其實是把底層數組的第4個元素修改為10,然后把newSlice長度調整為3。
>[danger]如果切片的底層數組,沒有足夠的容量時,就會新建一個底層數組,把原來數組的值復制到新底層數組里,再追加新值,這時候就不會影響原來的底層數組了。
>[info]所以一般我們在創建新切片的時候,最好要讓新切片的長度和容量一樣,這樣我們在追加操作的時候就會生成新的底層數組,和原有數組分離,就不會因為共用底層數組而引起奇怪問題,因為共用數組的時候修改內容,會影響多個切片。
append函數會智能的增長底層數組的容量,目前的算法是:容量小于1000個時,總是成倍的增長,一旦容量超過1000個,增長因子設為1.25,也就是說每次會增加25%的容量。
## copy 拷貝切片
這里的 copy 不是引用傳遞,切片起始指針會改變
~~~
s1 := []int{1, 2,3, 4, 5}
s2 := make([]int, 5)
copy(s2, s1)
fmt.Printf("%p\n", s1)
fmt.Printf("%p\n", s2)
~~~
## 迭代切片
### for range循環
切片是一個集合,我們可以使用 for range 循環來迭代它,打印其中的每個元素以及對應的索引。
~~~
slice := []int{1, 2, 3, 4, 5}
for i,v:=range slice{
fmt.Printf("索引:%d,值:%d\n",i,v)
}
~~~
## 傳統的for循環
配合內置的len函數進行迭代。
slice := []int{1, 2, 3, 4, 5}
for i := 0; i < len(slice); i++ {
fmt.Printf("值:%d\n", slice[i])
}
## 在函數間傳遞切片
slice 是引用傳遞。
我們知道切片是3個字段構成的結構類型,所以在函數間以值的方式傳遞的時候,占用的內存非常小,成本很低。在傳遞復制切片的時候,其底層數組不會被復制,也不會受影響,復制只是復制的切片本身,不涉及底層數組。
~~~
func main() {
slice := []int{1, 2, 3, 4, 5}
fmt.Printf("%p\n", &slice)
modify(slice)
fmt.Println(slice)
}
func modify(slice []int) {
fmt.Printf("%p\n", &slice)
slice[1] = 10
}
~~~
打印的輸出如下:
~~~
0xc420082060
0xc420082080
[1 10 3 4 5]
~~~
仔細看,這兩個切片的地址不一樣,所以可以確認切片在函數間傳遞是復制的。而我們修改一個索引的值后,發現原切片的值也被修改了,說明它們共用一個底層數組。
在函數間傳遞切片非常高效,而且不需要傳遞指針和處理復雜的語法,只需要復制切片,然后根據自己的業務修改,最后傳遞回一個新的切片副本即可,這也是為什么函數間傳遞參數,使用切片,而不是數組的原因。
## string 與 slice
string 底層是一個 byte 的數組,有也可以進行切片操作。
```
str := "hello world"
s1 := str[0:5]
fmt.Println(s1)
```
## 利用切片對數組排序
```
package main
import (
"fmt"
"sort"
)
func testInts() {
a := [...]int{11,4,2,3,9,20}
sort.Ints(a[:])
fmt.Println(a)
}
func testStrings() {
s := [...]string{"ac", "a", "ab", "AC", "A", "AB"}
sort.Strings(s[:])
fmt.Println(s)
}
func testFload64s() {
f := [...]float64{3.1415, 2.713, 1.024, 1024}
sort.Float64s(f[:])
fmt.Println(f)
}
func main() {
testInts()
testStrings()
testFload64s()
}
```
### 查找索引
```
sort.SearchInts(a []int, b int) 從數組 a 中查找 b 的索引,前提是 a 已經排序
sort.SearchFloats(a []int, b int) 從數組 a 中查找 b 的索引,前提是 a 已經排序
sort.SearchStrings(a []int, b int) 從數組 a 中查找 b 的索引,前提是 a 已經排序
```
## nil切片和空切片
它們的長度和容量都是0,但是它們指向底層數組的指針不一樣。
nil切片意味著指向底層數組的指針為nil,表示不存在的切片;
空切片對應的指針是個地址,空切片表示一個空集合。
~~~
//nil切片
var nilSlice []int
//空切片
slice := []int{}
~~~