您好,登錄后才能下訂單哦!
切片也是一種數(shù)據(jù)結(jié)構(gòu),它和數(shù)組非常相似,因為他是圍繞動態(tài)數(shù)組的概念設(shè)計的,可以按需自動改變大小,使用這種結(jié)構(gòu),可以更方便地管理和使用數(shù)據(jù)集合。
內(nèi)部實現(xiàn)
切片是基于數(shù)組實現(xiàn)的,它的底層是數(shù)組,它自己本身非常小,可以理解為對底層數(shù)組的抽象。因為機遇數(shù)組實現(xiàn),所以它的底層的內(nèi)存是連續(xù)非配的,效率非常高。它還有可以通過索引獲得數(shù)據(jù)、可以迭代以及垃圾回收優(yōu)化的好處。
切片對象非常小,是因為它是只有 3 個字段的數(shù)據(jù)結(jié)構(gòu):一個是指向底層數(shù)組的指針,一個是切片的長度,一個是切片的容量。這 3 個字段,就是Go語言操作底層數(shù)組的元數(shù)據(jù),有了它們,我們就可以任意地操作切片了。
聲明和初始化
切片創(chuàng)建的方式有好幾種,我們先看下最簡潔的make方式。
slice:=make([]int,5)
使用內(nèi)置的make
函數(shù)時,需要傳入一個參數(shù),指定切片的長度,例子中我們使用的時 5 ,這時候切片的容量也是 5 。當然我們也可以單獨指定切片的容量。
slice:=make([]int,5,10)
這時,我們創(chuàng)建的切片長度是 5 ,容量是 10 。需要注意的這個容量 10 其實對應的是切片底層數(shù)組的。
因為切片的底層是數(shù)組,所以創(chuàng)建切片時,如果不指定字面值的話,默認值就是數(shù)組的元素的零值。這里我們所以指定了容量是 10 ,但是我們只能訪問 5 個元素。因為切片的長度是 5 ,剩下的 5 個元素,需要切片擴充后才可以訪問。
容量必須>=長度,我們是不能創(chuàng)建長度大于容量的切片的。
還有一種創(chuàng)建切片的方式,是使用字面量,就是指定初始化的值。
slice:=[]int{1,2,3,4,5}
有沒有發(fā)現(xiàn),跟創(chuàng)建數(shù)組非常像,只不過不用制定[]
中的值。這時候切片的長度和容量是相等的,并且會根據(jù)我們指定的字面量推導出來。當然我們也可以像數(shù)組一樣,只初始化某個索引的值:
slice:=[]int{4:1}
這是指定了第 5 個元素為 1 ,其他元素都是默認值 0 。這時候切片的長度和容量也是一樣的。這里再次強調(diào)一下切片和數(shù)組的微小差別。
//數(shù)組 array:=[5]int{4:1} //切片 slice:=[]int{4:1}
切片還有nil切片和空切片,它們的長度和容量都是 0 。但是它們指向底層數(shù)組的指針不一樣,nil切片意味著指向底層數(shù)組的指針為nil,而空切片對應的指針是個地址。
//nil切片 var nilSlice []int //空切片 slice:=[]int{}
nil切片表示不存在的切片,而空切片表示一個空集合,它們各有用處。
切片另外一個用處比較多的創(chuàng)建是基于現(xiàn)有的數(shù)組或者切片創(chuàng)建。
slice := []int{1, 2, 3, 4, 5} slice1 := slice[:] slice2 := slice[0:] slice3 := slice[:5] fmt.Println(slice1) fmt.Println(slice2) fmt.Println(slice3)
基于現(xiàn)有的切片或者數(shù)組創(chuàng)建,使用[i:j]
這樣的操作符即可。它表示以i
索引開始,到j
索引結(jié)束,截取原數(shù)組或者切片,創(chuàng)建而成的新切片。新切片的值包含原切片的i
索引,但是不包含j
索引。對比Java的話,發(fā)現(xiàn)和String的subString
方法很像。
i
如果省略,默認是 0 ;j
如果省略,默認是原數(shù)組或者切片的長度。所以例子中的三個新切片的值是一樣的。這里注意的是i
和j
都不能超過原切片或者數(shù)組的索引。
slice := []int{1, 2, 3, 4, 5} newSlice := slice[1:3] newSlice[0] = 10 fmt.Println(slice) fmt.Println(newSlice)
這個例子證明了,新的切片和原切片共用的是一個底層數(shù)組。所以當修改的時候,底層數(shù)組的值就會被改變,所以原切片的值也改變了。當然對于基于數(shù)組的切片也一樣的。
我們基于原數(shù)組或者切片創(chuàng)建一個新的切片后,那么新的切片的大小和容量是多少呢?這里有個公式:
對于底層數(shù)組容量是k的切片slice[i:j]來說 長度:j-i 容量:k-i
比如我們上面的例子slice[1:3]
,長度就是3-1=2
,容量是5-1=4
。不過代碼中我們計算的時候不用這么麻煩,因為Go語言為我們提供了內(nèi)置的len
和cap
函數(shù)來計算切片的長度和容量。
slice := []int{1, 2, 3, 4, 5} newSlice := slice[1:3] fmt.Printf("newSlice長度:%d,容量:%d",len(newSlice),cap(newSlice))
以上是基于一個數(shù)組或者切片使用 2 個索引創(chuàng)建新切片的方法。此外還有一種 3 個索引的方法,第 3 個用來限定新切片的容量,其用法為slice[i:j:k]
。
slice := []int{1, 2, 3, 4, 5} newSlice := slice[1:2:3]
這樣我們就創(chuàng)建了一個長度為2-1=1
,容量為3-1=2
的新切片,不過第三個索引,不能超過原切片的最大索引值 5 。
使用切片
使用切片,和使用數(shù)組一樣,通過索引就可以獲取切片對應元素的值,同樣也可以修改對應元素的值。
slice := []int{1, 2, 3, 4, 5} fmt.Println(slice[2]) //獲取值 slice[2] = 10 //修改值 fmt.Println(slice[2]) //輸出10
切片只能訪問到其長度內(nèi)的元素,訪問超過長度外的元素,會導致運行時異常,與切片容量關(guān)聯(lián)的元素只能用于切片增長。
我們前面講了,切片算是一個動態(tài)數(shù)組,所以它可以按需增長,我們使用內(nèi)置append
函數(shù)即可。append
函數(shù)可以為一個切片追加一個元素,至于如何增加、返回的是原切片還是一個新切片、長度和容量如何改變這些細節(jié),append
函數(shù)都會幫我們自動處理。
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
函數(shù)為新創(chuàng)建的切片newSlice
,追加了一個元素 10 。我們發(fā)現(xiàn)打印的輸出,原切片slice
的第 4 個值也被改變了,變成了 10 。引起這種結(jié)果的原因是因為newSlice
有可用的容量,不會創(chuàng)建新的切片來滿足追加,所以直接在newSlice
后追加了一個元素 10 。因為newSlice
和slice
切片共用一個底層數(shù)組,所以切片slice
的對應的元素值也被改變了。
這里newSlice新追加的第 3 個元素,其實對應的是slice的第 4 個元素,所以這里的追加其實是把底層數(shù)組的第4個元素修改為 10 ,然后把newSlice長度調(diào)整為 3 。
如果切片的底層數(shù)組沒有足夠的容量時,就會新建一個底層數(shù)組,把原來數(shù)組的值復制到新底層數(shù)組里,再追加新值,這時候就不會影響原來的底層數(shù)組了。
所以一般我們在創(chuàng)建新切片的時候,最好要讓新切片的長度和容量一樣,這樣我們在追加操作的時候就會生成新的底層數(shù)組,和原有數(shù)組分離,就不會因為共用底層數(shù)組而引起奇怪問題,因為共用數(shù)組的時候修改內(nèi)容,會影響多個切片。
append
函數(shù)會智能地增長底層數(shù)組的容量,目前的算法是:容量小于 1000 個時,總是成倍的增長;一旦容量超過 1000 個,增長因子設(shè)為 1.25 ,也就是說每次會增加 25% 的容量。
內(nèi)置的append
也是一個可變參數(shù)的函數(shù),所以我們可以同時追加好幾個值。
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循環(huán)來迭代它,打印其中的每個元素以及對應的索引。
slice := []int{1, 2, 3, 4, 5} for i,v:=range slice{ fmt.Printf("索引:%d,值:%d\n",i,v) }
如果我們不想要索引,可以使用_
來忽略它。這是Go語言的用法,很多不需要的函數(shù)等返回值,都可以忽略。
slice := []int{1, 2, 3, 4, 5} for _,v:=range slice{ fmt.Printf("值:%d\n",v) }
這里需要說明的是range
返回的是切片元素的復制,而不是元素的引用。
除了for range循環(huán)外,我們也可以使用傳統(tǒng)的for循環(huán),配合內(nèi)置的len函數(shù)進行迭代。
slice := []int{1, 2, 3, 4, 5}
for i := 0; i < len(slice); i++ {
fmt.Printf("值:%d\n", slice[i])
}
在函數(shù)間傳遞切片
我們知道切片是 3 個字段構(gòu)成的結(jié)構(gòu)類型,所以在函數(shù)間以值的方式傳遞的時候,占用的內(nèi)存非常小,成本很低。在傳遞復制切片的時候,其底層數(shù)組不會被復制,也不會受影響,復制只是復制的切片本身,不涉及底層數(shù)組。
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]
仔細看,這兩個切片的地址不一樣,所以可以確認切片在函數(shù)間傳遞是復制的。而我們修改一個索引的值后,發(fā)現(xiàn)原切片的值也被修改了,說明它們共用一個底層數(shù)組。
在函數(shù)間傳遞切片非常高效,而且不需要傳遞指針和處理復雜的語法,只需要復制切片,然后根據(jù)自己的業(yè)務(wù)修改,最后傳遞回一個新的切片副本即可。這也是為什么函數(shù)間使用切片傳遞參數(shù),而不是數(shù)組的原因。
關(guān)于多維切片就不介紹了,還有多維數(shù)組,一來它和普通的切片數(shù)組一樣,只不過是多個一維組成的多維;二來我壓根不推薦用多維切片和數(shù)組,可讀性不好,結(jié)構(gòu)不夠清晰,容易出問題。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。