今天本來想去外地玩耍,結果睡過頭錯過了動車,只好總結一下slice,希望能與slice之間做一個了斷。
文章由淺入深,遵從能用代碼說話就不bb的原則。
var stringSlice []string stringSlice := []string{"咖啡色的羊駝"} var intSlice []int64 intSlice := []int{18}
和數組的區別:就是**[]括號裏頭不加東西**。
初始化的的一些默認值:
func main() { var stringSlice []string var intSlice []int64 fmt.Printf("stringSlice ==> 長度:%v \t地址:%p \t零值是否nil:%v \n",len(stringSlice),stringSlice, stringSlice==nil) fmt.Printf("intSlice ==> 長度:%v \t地址:%p \t零值是否nil:%v",len(intSlice),intSlice, intSlice==nil) }
這裏需要注意的是:slice的key必須是數字 && 0開始逐漸增加
// 增 func add(slice []interface{}, value interface{}) []interface{} { return append(slice, value) } // 刪 func remove(slice []interface{}, i int) []interface{} { return append(slice[:i], slice[i+1:]...) } // 改 func update(slice []interface{}, index int, value interface{}) { slice[index] = value } // 查 func find(slice []interface{}, index int) interface{} { return slice[index] }
這裏需要注意的是:
1.slice的增加需要依賴於append,這裏會涉及到擴容機制(後文會說)
2.刪除的話,只能是通過切割的方式重拼了,由於slice是引用類型,存的是指針,性能上不會有太多影響
// 插入 func insert(slice *[]interface{}, index int, value interface{}) { rear := append([]interface{}{}, (*slice)[index:]...) *slice = append(append((*slice)[:index], value), rear...) } // 遍歷 func list(slice []interface{}) { for k, v := range slice { fmt.Printf("k:%d - v:%d", k,v) } } // 清空 func empty(slice *[]interface{}) { *slice = append([]interface{}{}) // *slice = nil }
// 複製 func main() { intSlice := []int{1,2,3,4,5,6} copySlice1 := make([]int,0,10) _ = copy(copySlice1,intSlice) fmt.Printf("長度爲0的時候:%v\n",copySlice1) copySlice2 := make([]int,6,10) _ = copy(copySlice2,intSlice) fmt.Printf("長度爲6的時候:%v",copySlice2) }
這裏需要注意的是:要保證目標切片有足夠的大小,注意是大小,而不是容量。
slice的基礎數據結構:
type slice struct { array unsafe.Pointer len int cap int }
字段 | 說明 |
---|---|
array | 指向指針,指向一個底層數組 |
len | slice中元素的個數 |
cap | slice的容量,允許slice中元素增長的最大個數 |
這裏的array需要單獨說下,這裏是指針類型,也說明了slice是引用類型。
在slice底層,指針指向的是另一個數組。
還是有必要看一下源碼中的實現:
// 創建一個slice func makeslice(et *_type, len, cap int) slice { // 檢查目標類型的最長長度,slice的len和cap都必須小於這個值 maxElements := maxSliceCap(et.size) if len < 0 || uintptr(len) > maxElements { panic(errorString("makeslice: len out of range")) } // !!! len必須<= cap !!! if cap < len || uintptr(cap) > maxElements { panic(errorString("makeslice: cap out of range")) } // 申請內存 p := mallocgc(et.size*uintptr(cap), et, true) // 返回一個slice return slice{p, len, cap} }
func main() { x:=[]int{1,2,3,4} y:=x[1:4] fmt.Println(y) } // 輸出:[2 3 4]
這裏需要注意的是:切割遵從左開右閉的原則,就是[1:4],取得是第二個元素到第四個以下的,不包括第四個
來一波圖解:
slice擴容機制還是比較有意思的,上源碼:
func growslice(et *_type, old slice, cap int) slice { ... newcap := old.cap doublecap := newcap + newcap if cap > doublecap { newcap = cap } else { if old.len < 1024 { newcap = doublecap } else { for newcap < cap { newcap += newcap / 4 } } } ... return slice{p, old.len, newcap} }
白話文講解:如果新cap的大小是當前2倍以上,則增長到新的cap大小;否則,如果當前cap大小<1024,則按照2倍增長,不然就每次就按照當前大小的1/4增長,直到大小>=新的cap大小。
前文很多操作都是基於append的,那麼slice在append的時候,如果發生了擴容,那麼底層的數組會重建,同時拷貝老的數據到新數組裏頭。
舉個例子:
func main() { initSlice := []int{1} // 進行擴容到2 initSlice = append(initSlice, 2) // 進行擴容到4 initSlice = append(initSlice, 3) x := append(initSlice, 4) y := append(initSlice, 5) fmt.Println(initSlice, x, y) }
會輸出:
[1 2 3] [1 2 3 5] [1 2 3 5]
圖解說明:
根據前文介紹的擴容機制,initSlice的擴容軌跡是1-2-4。而slice只是引用類型,所以x和y只是copy了initSlice的指針,他們三個都是指向同一個底層數組,所以最後第四個坑被y給覆蓋了。
再舉一個正好遇到擴容時候的例子:
我們知道擴容時候是會生成新的底層數組,然後拷貝老的數組值。
func main() { initSlice := []int{1} // 此時擴容1-2,並且全部裝滿 initSlice = append(initSlice, 2) // 以下任一append都會引發擴容 x := append(initSlice, 3) y := append(initSlice, 4) fmt.Println(initSlice, x, y) }
輸出:
[1 2] [1 2 3] [1 2 4]
圖解:
由於都遇到了擴容,所以x與y各自另立門戶,新建數組,slice指向的底層數組也不同了所以互不干擾了。
func main() { initSlice := []int{1,2,3} fmt.Printf("剛開始時候:%v\n",initSlice) doSomeThing(initSlice) fmt.Printf("一番操作後:%v\n",initSlice) } func doSomeThing(s []int) { s[0]=88 s = append(s, 10) fmt.Printf("函數返回前:%v\n",s) }
輸出:
剛開始時候:[1 2 3] 函數返回前:[88 2 3 10] 一番操作後:[88 2 3]
這裏面的變化情況抓住一個點:就是發送擴容時候底層數組是新建的!
然而我想說的是:函數傳slice,由於是引用類型,所以是會改變原值的,這時候需要考慮擴容新底層數組問題。