1. 簡介 在go中,slice是一種動態數組類型,其底層實現中使用了數組。slice有以下特點: *slice本身並不是數組,它只是一個引用類型,包含了一個指向底層數組的指針,以及長度和容量。 *slice的長度可以動態擴展或縮減,通過append和copy操作可以增加或刪除slice中的元素。 ...
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: 戀水無意