1. 簡介
在go中,slice
是一種動態陣列型別,其底層實現中使用了陣列。slice
有以下特點:
*slice
本身並不是陣列,它只是一個引用型別,包含了一個指向底層陣列的指標,以及長度和容量。
*slice
的長度可以動態擴充套件或縮減,透過append
和copy
操作可以增加或刪除slice
中的元素。
*slice
的容量是指在底層陣列中slice
可以繼續擴充套件的長度,容量可以透過make
函式進行設定。
Slice 的底層實現是一個包含了三個欄位的結構體:
type`slice`struct {
ptr uintptr // 指向底層陣列的指標
len int // slice 的長度
cap int // slice 的容量
}
當一個新的slice
被建立時,Go會為其分配一個底層陣列,並且把指向該陣列的指標、長度和容量資訊儲存在slice
結構體中。底層陣列的長度一般會比slice
的容量要大,以便在append
操作時有足夠的空間儲存新元素。
當一個slice
作為引數傳遞給函式時,其實是傳遞了一個指向底層陣列的指標,這也就意味著在函式內部對slice
的修改也會反映到函式外部。
在進行切片操作時,slice 的指標和長度資訊不會發生變化,只有容量資訊會發生變化。如果切片操作的結果仍然是一個 slice,那麼它所引用的底層陣列仍然和原來的slice
是同一個陣列。
需要注意的是,當一個slice
被傳遞給一個新的變數或者作為引數傳遞給函式時,並不會複製底層陣列,而是會共享底層陣列。因此,如果對一個slice
的元素進行修改,可能會影響到共享底層陣列的其他slice
。如果需要複製一個slice
,可以使用copy
函式。
2. 使用
slice
的使用包括定義、初始化、新增、刪除、查詢等操作。
2.1 slice定義
slice
是一個引用型別,可以透過宣告變數並使用make()函式來建立一個slice
:
var sliceName []T
sliceName := make([]T, length, capacity)
其中,T代表該切片可以儲存的元素型別,length代表預留的元素數量,capacity代表預分配的儲存空間。
2.2 初始化
slice
有兩種初始化的方式:宣告時初始化和使用append()函式初始化:
// 宣告時初始化
sliceName := []T{value1, value2, ..., valueN}
// 使用append()函式進行初始化
sliceName := make([]T, 0, capacity)
sliceName = append(sliceName, value1, value2, ..., valueN)
2.3 獲取slice元素
slice
中的元素可以透過索引的方式來獲取,與c/c++類似,go的索引也是從0開始的:
sliceName[index]
2.4 新增元素到slice中
可以透過使用append()函式將元素新增到slice
中。如果slice
的容量不足,則會自動擴充套件。語法如下:
sliceName = append(sliceName, value1, value2, ..., valueN)
2.5 刪除slice中的元素
可以使用append()函式和切片操作來從slice
中刪除元素。使用append()函式時,需要將帶有要刪除元素的切片放在最後。語法如下:
// 透過切片操作刪除元素
sliceName = append(sliceName[:index], sliceName[index+1:]...)
// 透過append()函式刪除元素
sliceName = append(sliceName[:index], sliceName[index+1:]...)
如上所見,二者的表現形式是一樣的,但內部實現是不同的:
- 使用append()進行刪除的方式,實際上是將後面的元素向前移動一個位置,然後透過重新切片的方式來刪除最後一個元素。這種方式會建立一個新的底層陣列,並將原來的元素複製到新的陣列中,因此在刪除多個元素時可能會導致記憶體分配和複製開銷較大,影響效能
- 使用切片語法進行刪除,底層陣列中被刪除元素的位置仍然存在,但是這些位置不再包含有效的資料。這種方式的效能比使用append()進行刪除要好,尤其是在刪除多個元素時,因為它不需要建立新的底層陣列,也不需要複製元素。但是,這種方式可能會導致底層陣列中存在大量未使用的空間,浪費記憶體
需要注意的是,在切片中刪除元素時,會重新分配記憶體並複製元素,因此刪除元素的成本會相對較高。為了減少記憶體分配和複製元素的次數,可以使用copy
函式將後面的元素複製到前面,然後將切片的長度減少。具體實現方法可以參考下面的:
// 刪除切片中指定位置的元素
func removeElement(slice []int, index int) []int {
copy(slice[index:], slice[index+1:])
return slice[:len(slice)-1]
}
2.6 查詢slice中的元素
可以使用for和range遍歷slice
來實現元素查詢:
// 使用for迴圈和range關鍵字遍歷Slice
for index, value := range sliceName {
if value == targetValue {
// 找到了目標元素
break
}
}
2.7 切片操作
可以使用切片操作來獲取子切片,操作如下:
// 切片操作:獲取從第i個元素到第j個元素的子切片
sliceName[i:j]
// 切片操作:獲取從第i個元素到第j個元素,且容量為k的子切片
sliceName[i:j:k]
3. 關於slice擴容
在Go語言中,slice
會隨著元素的增加而動態擴容。當容量不足時,slice
會自動重新分配記憶體,將原有元素複製到新的底層陣列中,並在新陣列後面新增新的元素。
slice
的擴容機制可以描述為:當slice
的長度超過了底層陣列的容量時,Go語言會按照一定的策略重新分配一塊更大的記憶體,並將原來的元素複製到新的記憶體中,然後再新增新元素。具體的策略如下:
- 如果新長度(即len(s)+1)小於等於原長度(即cap(s)),則
slice
不需要擴容,直接新增元素即可。 - 如果新長度大於原長度且小於原長度的兩倍(即 cap(s)*2),則新
slice
的容量就是原來的兩倍,也就是說將底層陣列擴容為原來的兩倍,並將原來的元素複製到新的陣列中。 - 如果新長度大於原長度的兩倍,會嘗試使用新長度作為容量,如果仍然不夠,則按照擴容倍數(預設是 2)來擴容。
需要注意的是,slice
擴容是一個開銷比較大的操作,因為需要重新分配記憶體、複製資料等。所以在編寫程式碼時應該儘可能地減少slice
擴容的次數,以提高程式的效能。
宣告:本作品採用署名-非商業性使用-相同方式共享 4.0 國際 (CC BY-NC-SA 4.0)進行許可,使用時請註明出處。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 戀水無意