golang筆記——數組與切片


一、切片的定義

  我們可以從數組(go語言中很少直接使用數組)或者切片來初始化一個新的切片,也可以直接通過 make 來初始化一個所有元素為默認零值的切片。

//1、通過數組來初始化切片
    arr := [...]int{1, 2, 3, 4, 5} slice1 := arr[:]   // [1,2,3,4,5]
    slice2 := arr[2:4] // [3,4]
    slice3 := arr[:4]  // [1,2,3,4]
    slice4 := arr[2:]  // [3,4,5] //2、通過切片來初始化切片
    slice5 := slice1[3:] //[4,5] //3、直接初始化。可以通過 make 來初始化,格式為 make([]int, len, cap),所有的元素初始化為默認零值;也可以直接初始化。
    slice6 := make([]int, 3, 6) // [0,0,0]
    slice7 := []int{1, 2, 3, 4, 5} // [1,2,3,4,5]

  和其它大多數編程語言類似,Go語言里的這種索引形式也采用了左閉右開區間,包括m~n的第一個元素,但不包括最后那個元素(譯注:比如a = [1, 2, 3, 4, 5], a[0:3] = [1, 2, 3],不包含最后一個元素)。這樣可以簡化我們的處理邏輯。比如s[m:n]這個slice,0 ≤ m ≤ n ≤ len(s),包含n-m個元素。 

 

二、切片的特點

  slice 有點類似 C++ 中的 vector,都是可變長數組,有容量的概念(當切片容量不夠時,它會以2倍率來擴張),但是也有很多不同,如 slice 是基於數組的(該數組稱為底級數組),slice 的長度是從底級數組中取出來的元素個數,容量是底級數組的最大索引與取出元素的最小索引的差值。array 是值類型,但 slice 卻是引用類型。

func func1(s []int) { s[0] *= 10 //會修改原切片的值
} func func2(s []int) { for _, v := range s { //不會修改原切片的值,因 for ... range 是只讀的
        v *= 10 } } func func3(s []int) { for i := 0; i < len(s); i++ { //會修改原切片的值
        s[i] *= 10 } } func main() { s := []int{1, 2, 3, 4, 5} //func1(s) // 10,2,3,4,5, //func2(s) //1,2,3,4,5, //func3(s) //10,20,30,40,50,
    for _, v := range s { println(v, ",") } }

  如果修改某個數組的數據,則基於此數組的切片也會發生改變,反之亦然(基於同一數組的多個切片之間也會互相影響)。這與切片的存儲結構有關,切片的存儲結構是,依次是底層數組的指針、切片長度、切片容量、切片數據。我們知道,當切片的容量發生變量時,會重新分配地址,所以此時切片和原底級數組的關聯就會斷開,修改不再影響對方,這一點要注意。

    a := [...]int{1, 2, 3, 4, 5} s1 := a[:] s2 := a[:] s1[0] = 10 print_array(a[:]) // [10,2,3,4,5] 修改切片會影響到底級數組,反之亦然
    s2[0] = 20 print_array(s1) // [20,2,3,4,5] 修改切片會影響到基於同一底級數組的切片
 s1 = append(s1, []int{6, 7}...) //append 時容量不夠時會導致重新分配內存,與原底級數組關系斷開
    s1[0] = 100 print_array(s1) // [100,2,3,4,5,6,7]
    print_array(a[:]) //[20,2,3,4,5]
    print_array(s2)   //[20,2,3,4,5] //print_array 為自行封裝的格式化輸出切片的方法

  切片的刪除並沒有內置函數,如果需要刪除切片 list 的第 k 個元素,我們一般可以通過下面三種方式操作:

  1、通過 copy 方式:

func remove(slice []int, index int) []int { copy(slice[index:], slice[index+1:]) return slice[:len(slice)-1] }

   2、通過append方式

func remove(slice []int, index int) []int { return append(slice[0:k], slice[k+1:]...) }

  3、如果該slice不關心刪除后元素的排序順序,可以簡單將要刪除的元素值設置為最后一個元素值,然后取 [:len(slice)-1] 的子切片就好了

func remove(slice []int, i int) []int { slice[i] = slice[len(slice)-1] return slice[:len(slice)-1] }

 

三、切片的常用操作總結

  切片的操作比數組復雜一些,因為切片是可變長的,而且有容量的概念。當切片容量不夠時,它會以2倍率來擴張。

1、獲取切片長度:len(slice)

2、獲取切片容量:cap(slice)

3、為切片追加元素:slicename = append(slicename, element1, element2,...)  

  該函數是可變參函數,可變參部分也可傳入另一個切片,形式為 slicename = append(slicename, slicename2...) ,可以參考可變參函數的用法。其實之前我一直疑惑為什么要再賦值給原切片,其實是因為,但append函數調用時,如果切片容量不夠的話,會創建一個新的切片變量,這應該也就是為什么要賦值給原切片的原因吧。

4、切片沒有提供刪除元素的方法,但可以通過子切片的方式來達成此目的,但無法直接的從中間刪除元素;也不能從中間插入元素。

5、切片的復制: copy(target_slice, source_slice)

  復制切片要保證目標切片有足夠長度(注意與容量無關),如果目標切片長度不足,則只復制到目標切片長度的數據,而不會自行改變目標切片長度來填充其它數據。

 

 

四、特別注意

  把切片作為函數參數時,如果函數內部修改了此切片某些索引的值,會影響到原切片; 但如果在函數內部刪除或添加了切片長度,則不會影響到原切片。是不是很詭異?

  有人說是由擴容切片導致內存重新分配導致的,瞎扯淡,減小切片容量難道也會內存重新分配嗎?只要是把切片作為參數,函數內部就已經使用了該切片的一個副本,切片的每個元素對應的地址,也同時被復制了,所以當修改某個元素時,是在那個元素的地址上進行修改的,所以能影響到原切片,而增加刪除元素,是在切片副本上操作的,比如增加了一個元素,由於切片地址是連續的,經過測試發現原切片末尾地址后面的+n長度(由元素類型決定)的地址上也確實有值了,而且值就是函數內增加的那個元素值!說明函數內修改切片確實是修改了內存數據的!但是!原切片並不知道切片的長度發生了變化!!!函數內的修改只是讓切片副本知道了長度的變化!所以原切片認為它的長度仍然是之前的長度!  而通過引用方式將切片指針作為參數,就不存在這個問題了。

  如果要以引用方式傳切片,請務必將切片地址作為參數傳入。盡量避免將切片作為參數傳入,如果真的只需要傳值,可以在調用函數時,只傳入原切片的一個臨時副本變量(該方式不好,依賴於調用者),或者在函數開始處將對傳入切片做一個臨時副本,后面的處理只操作這個臨時副本。期待更好的方法,最好有一個什么特殊操作符,可以指定切片能以完全傳值方式工作就好了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM