之前的切片探索中,上篇通過一道算法題目,了解到切片的兩大特性:一是:切片是引用類型,指向底層數組,修改其底層數組的時候,會影響切片中的值。二是:向切片中添加元素的時候,切片可能會發生擴容,改變其底層指向的數組。在下篇中,我們談到了切片的底層實現原理以及擴容機制。在之后的學習中,切片的應用場景比較多,自己也有了一些新的發現,于是形成補充篇這個文章。接下來我會從切片的幾種創建方式說明切片和底層數組之間的關系、切片作為函數的參數時,實際上傳遞的是什么?
上下兩篇:
1.GO語言-切片底層探索(上)-CSDN博客
2.GO語言-切片底層探索(下)-CSDN博客
1. 查看切片底層依托的數組
func main() {//切片slice1基于array數組創建的array := [7]int{1, 2, 3, 4, 5, 0, 1}slice1 := array[3:5]//查看array數組的信息 [1 2 3 4 5 0 0] 7 7fmt.Println(array, len(array), cap(array))//查看slice1切片的信息 [4 5] 2 4fmt.Println(slice1[:], len(slice1[:]), cap(slice1[:]))//查看slice1切片依托底層數組的信息 [4 5 0 1] 4 4fmt.Println(slice1[:cap(slice1)], len(slice1[:cap(slice1)]), cap(slice1[:cap(slice1)]))
}
?在上面的代碼中,我們發現一個奇怪的現象:當我們使用切片表達式slice1[:]和slice1[:cap(slice1)]所取到的值是完全不同的。
我們常用到的切片表達式是slice[low:high]這種兩個參數的形式,我們稱其為簡單切片表達式。此外還有一種三個參數形式的切片表達式,我們稱其為擴展切片表達式,但是一般不經常使用,這里權當擴展一下。在簡單切片表達式中,切片的長度length = high-low,切片的容量cap = 底層數組的長度-low。這里的low和high都是可以省略的,如果省略low則默認為0,如果省略high則默認為切片的長度,而不是容量。
slice1[:]實際上取的是切片的范圍,也就是從切片的下標0到切片的長度-1。而slice1[:cap(slice1)]底層數組的范圍,從slice1所依托得底層數組從下標low(這里是3,因為我們的slice1= array[3:5]),到底層數組末尾。
雖然我們在項目中一般不使用slice[:cap(slice)]這種形式,但是我們知道如果通過獲取切片所依托的底層數組的方法,可以幫助我們更加清晰地理解切片和底層數組的關系,以及切片中的len和cap實際代表的是什么!
2.?切片和底層數組的關系
- 直接賦值方式?slice:= []int{1,2,3,4,5}
- make字面量方式 slice:= make([]int,0,10)
- 使用切片表達式根據切片或數組生成 slice:= array0[1:3]
通過之前的文章,我們知道,切片是依托于數組實現的,相比于數組而言,切片在容量不足的時候,會進行自動擴容,更具有靈活性。我們一般都是通過以上三種方式創建切片的,這三種不同的創建方式,將形成三種不同的(切片和其底層數組之間的)關系。
- 直接賦值創建方式,這種方式創建出來的切片長度等于容量,此時如果我們向切片中添加一個新的元素,就會觸發擴容機制,改變切片指向的底層數組。
- ?make字面量方式 slice:= make([]int,0,10),通過make創建切片,可以指定切片的長度和容量(底層數組的長度),后續向切片中添加元素的個數,如果沒有超過10就不會發生擴容。通過提前指定切片的容量,可以減少程序運行過程中,切片擴容帶來的資源消耗。
- 使用切片表達式根據切片或數組生成 slice:= array0[low:high]。使用切片表達式創建的切片,其長度為high-low,容量是底層數組的長度-low。
3. 切片作為函數的參數時,傳遞的是對底層數組的引用
func main() {baseArray := [5]int{1, 2, 3, 4, 5}//基于baseArray創建sliceslice := baseArray[:] modifySlice(slice, 0)fmt.Println(slice, len(slice), cap(slice)) // 輸出 [100 2 3 4 5]fmt.Println(baseArray, len(baseArray), cap(baseArray)) // 輸出 [100 2 3 4 5]
}func modifySlice(slice []int, index int) {slice[index] = 100
}
在代碼中,我們基于baseArray創建slice,我們發現當傳入的切片在函數中修改時,其依賴的底層數組也發生了修改。這說明,切片作為函數的參數時,實際上傳遞是對底層數組的引用。如果我們在函數的操作導致切片進行了擴容,那么我們的底層數組中的值將不會再發生變化了。
測試如下:
func main() {baseArray := [5]int{1, 2, 3, 4, 5}//基于baseArray創建sliceslice := baseArray[:]modifySlice(slice, 0)fmt.Println(slice, len(slice), cap(slice)) // 輸出 [100 2 3 4 5]fmt.Println(baseArray, len(baseArray), cap(baseArray)) // 輸出 [100 2 3 4 5]
}func modifySlice(slice []int, index int) {slice[index] = 100 //baseArray[index]被修改slice = append(slice, 1) //擴容,底層數組改變slice[index] = 1000 //baseArray[index]值不變fmt.Println(slice, len(slice), cap(slice)) //[1000 2 3 4 5 1] 6 10
}
4. 總結
在這篇博客中,我們主要講解切片和底層數組之間的關系,并且通過切片的三種創建方式來進行詳細的說明。我們在使用切片的時候,一定要注意切片擴容后,其底層指向的數組會發生變化,對切片的修改將不再作用與原來的底層數組。