切片也是一種數據結構,它和數組很是類似,由於他是圍繞動態數組的概念設計的,能夠按需自動改變大小,使用這種結構,能夠更方便地管理和使用數據集合。算法
內部實現數組
切片是基於數組實現的,它的底層是數組,它本身自己很是小,能夠理解爲對底層數組的抽象。由於機遇數組實現,因此它的底層的內存是連續非配的,效率很是高。它還有能夠經過索引得到數據、能夠迭代以及垃圾回收優化的好處。數據結構
切片對象很是小,是由於它是隻有 3 個字段的數據結構:一個是指向底層數組的指針,一個是切片的長度,一個是切片的容量。這 3 個字段,就是Go語言操做底層數組的元數據,有了它們,咱們就能夠任意地操做切片了。app
聲明和初始化ide
切片建立的方式有好幾種,咱們先看下最簡潔的make方式。函數
slice:=make([]int,5)
使用內置的make
函數時,須要傳入一個參數,指定切片的長度,例子中咱們使用的時 5 ,這時候切片的容量也是 5 。固然咱們也能夠單獨指定切片的容量。優化
slice:=make([]int,5,10)
這時,咱們建立的切片長度是 5 ,容量是 10 。須要注意的這個容量 10 其實對應的是切片底層數組的。spa
由於切片的底層是數組,因此建立切片時,若是不指定字面值的話,默認值就是數組的元素的零值。這裏咱們因此指定了容量是 10 ,可是咱們只能訪問 5 個元素。由於切片的長度是 5 ,剩下的 5 個元素,須要切片擴充後才能夠訪問。設計
容量必須>=長度,咱們是不能建立長度大於容量的切片的。指針
還有一種建立切片的方式,是使用字面量,就是指定初始化的值。
slice:=[]int{1,2,3,4,5}
有沒有發現,跟建立數組很是像,只不過不用制定[]
中的值。這時候切片的長度和容量是相等的,而且會根據咱們指定的字面量推導出來。固然咱們也能夠像數組同樣,只初始化某個索引的值:
slice:=[]int{4:1}
這是指定了第 5 個元素爲 1 ,其餘元素都是默認值 0 。這時候切片的長度和容量也是同樣的。這裏再次強調一下切片和數組的微小差異。
//數組 array:=[5]int{4:1} //切片 slice:=[]int{4:1}
切片還有nil切片和空切片,它們的長度和容量都是 0 。可是它們指向底層數組的指針不同,nil切片意味着指向底層數組的指針爲nil,而空切片對應的指針是個地址。
//nil切片 var nilSlice []int //空切片 slice:=[]int{}
nil切片表示不存在的切片,而空切片表示一個空集合,它們各有用處。
切片另一個用處比較多的建立是基於現有的數組或者切片建立。
slice := []int{1, 2, 3, 4, 5} slice1 := slice[:] slice2 := slice[0:] slice3 := slice[:5] fmt.Println(slice1) fmt.Println(slice2) fmt.Println(slice3)
基於現有的切片或者數組建立,使用[i:j]
這樣的操做符便可。它表示以i
索引開始,到j
索引結束,截取原數組或者切片,建立而成的新切片。新切片的值包含原切片的i
索引,可是不包含j
索引。對比Java的話,發現和String的subString
方法很像。
i
若是省略,默認是 0 ;j
若是省略,默認是原數組或者切片的長度。因此例子中的三個新切片的值是同樣的。這裏注意的是i
和j
都不能超過原切片或者數組的索引。
slice := []int{1, 2, 3, 4, 5} newSlice := slice[1:3] newSlice[0] = 10 fmt.Println(slice) fmt.Println(newSlice)
這個例子證實了,新的切片和原切片共用的是一個底層數組。因此當修改的時候,底層數組的值就會被改變,因此原切片的值也改變了。固然對於基於數組的切片也同樣的。
咱們基於原數組或者切片建立一個新的切片後,那麼新的切片的大小和容量是多少呢?這裏有個公式:
對於底層數組容量是k的切片slice[i:j]來講 長度:j-i 容量:k-i
好比咱們上面的例子slice[1:3]
,長度就是3-1=2
,容量是5-1=4
。不過代碼中咱們計算的時候不用這麼麻煩,由於Go語言爲咱們提供了內置的len
和cap
函數來計算切片的長度和容量。
slice := []int{1, 2, 3, 4, 5} newSlice := slice[1:3] fmt.Printf("newSlice長度:%d,容量:%d",len(newSlice),cap(newSlice))
以上是基於一個數組或者切片使用 2 個索引建立新切片的方法。此外還有一種 3 個索引的方法,第 3 個用來限定新切片的容量,其用法爲slice[i:j:k]
。
slice := []int{1, 2, 3, 4, 5} newSlice := slice[1:2:3]
這樣咱們就建立了一個長度爲2-1=1
,容量爲3-1=2
的新切片,不過第三個索引,不能超過原切片的最大索引值 5 。
使用切片
使用切片,和使用數組同樣,經過索引就能夠獲取切片對應元素的值,一樣也能夠修改對應元素的值。
slice := []int{1, 2, 3, 4, 5} fmt.Println(slice[2]) //獲取值 slice[2] = 10 //修改值 fmt.Println(slice[2]) //輸出10
切片只能訪問到其長度內的元素,訪問超過長度外的元素,會致使運行時異常,與切片容量關聯的元素只能用於切片增加。
咱們前面講了,切片算是一個動態數組,因此它能夠按需增加,咱們使用內置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]
例子中,經過append
函數爲新建立的切片newSlice
,追加了一個元素 10 。咱們發現打印的輸出,原切片slice
的第 4 個值也被改變了,變成了 10 。引發這種結果的緣由是由於newSlice
有可用的容量,不會建立新的切片來知足追加,因此直接在newSlice
後追加了一個元素 10 。由於newSlice
和slice
切片共用一個底層數組,因此切片slice
的對應的元素值也被改變了。
這裏newSlice新追加的第 3 個元素,其實對應的是slice的第 4 個元素,因此這裏的追加實際上是把底層數組的第4個元素修改成 10 ,而後把newSlice長度調整爲 3 。
若是切片的底層數組沒有足夠的容量時,就會新建一個底層數組,把原來數組的值複製到新底層數組裏,再追加新值,這時候就不會影響原來的底層數組了。
因此通常咱們在建立新切片的時候,最好要讓新切片的長度和容量同樣,這樣咱們在追加操做的時候就會生成新的底層數組,和原有數組分離,就不會由於共用底層數組而引發奇怪問題,由於共用數組的時候修改內容,會影響多個切片。
append
函數會智能地增加底層數組的容量,目前的算法是:容量小於 1000 個時,老是成倍的增加;一旦容量超過 1000 個,增加因子設爲 1.25 ,也就是說每次會增長 25% 的容量。
內置的append
也是一個可變參數的函數,因此咱們能夠同時追加好幾個值。
newSlice=append(newSlice,10,20,30)
此外,咱們還能夠經過...
操做符,把一個切片追加到另外一個切片裏。
slice := []int{1, 2, 3, 4, 5} newSlice := slice[1:2:3] newSlice=append(newSlice,slice...) fmt.Println(newSlice) fmt.Println(slice)
迭代切片
切片是一個集合,咱們可使用for range循環來迭代它,打印其中的每一個元素以及對應的索引。
slice := []int{1, 2, 3, 4, 5} for i,v:=range slice{ fmt.Printf("索引:%d,值:%d\n",i,v) }
若是咱們不想要索引,可使用_
來忽略它。這是Go語言的用法,不少不須要的函數等返回值,均可以忽略。
slice := []int{1, 2, 3, 4, 5} for _,v:=range slice{ fmt.Printf("值:%d\n",v) }
這裏須要說明的是range
返回的是切片元素的複製,而不是元素的引用。
除了for range循環外,咱們也可使用傳統的for循環,配合內置的len函數進行迭代。
slice := []int{1, 2, 3, 4, 5}
for i := 0; i < len(slice); i++ {
fmt.Printf("值:%d\n", slice[i])
}
在函數間傳遞切片
咱們知道切片是 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]
仔細看,這兩個切片的地址不同,因此能夠確認切片在函數間傳遞是複製的。而咱們修改一個索引的值後,發現原切片的值也被修改了,說明它們共用一個底層數組。
在函數間傳遞切片很是高效,並且不須要傳遞指針和處理複雜的語法,只須要複製切片,而後根據本身的業務修改,最後傳遞迴一個新的切片副本便可。這也是爲何函數間使用切片傳遞參數,而不是數組的緣由。
關於多維切片就不介紹了,還有多維數組,一來它和普通的切片數組同樣,只不過是多個一維組成的多維;二來我壓根不推薦用多維切片和數組,可讀性很差,結構不夠清晰,容易出問題。