一篇文章理解 golang 中切片與陣列的關係

Enda發表於2020-03-06

在 golang 文件中,對陣列與切片有一些詳細的講解,本文主要講解陣列與切片的關係

由於是個人理解,可能有些偏差,煩請指正

陣列

golang 的陣列比較簡單,我們理解幾個概念即可

  1. 陣列是固定長度與容量,並且具有相同型別的一組值
  2. 此定義的陣列長度為 5 ,那麼容量也會固定為 5
  3. 陣列的索引都是從 0 開始的

記住,我們在此定義了一個 int 型別的陣列,長度容量均為 5,在後面的切片講解中,我們將對此陣列進行切片

//  此定義的陣列長度為 5 ,那麼容量也會固定為 5
arr := [5]int{0, 1, 2, 3, 4}
//  陣列 p = 0xc00001c0f0,arr = [0 1 2 3 4],len = 5,cap = 5
fmt.Printf("陣列 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))
// 陣列的索引都是從 0  開始的
// 0 1
fmt.Println(arr[0], arr[1])

切片

切片的基礎概念

  1. 切片可以看做是一個可變長的陣列
  2. 切片可以看做是對陣列的一個片段的引用

建立切片

我們首先來建立一個對 arr 陣列的一個切片

** [1:3] 表示範圍從陣列的索引 1 至 陣列的索引 3,它是不包括 3 的,可以簡單理解為 index == 1 && index < 3 **
** 當切片生成後,索引預設也是從 0 開始, 切片索引是 [0,1] 對應著陣列的索引是 [1,2] **

arrSlice := arr[1:3]
// 切片 p = 0xc00000c060,arr = [1 2],len = 2,cap = 4
fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))

修改陣列,切片會發生什麼?

我們上面有提到過,切片是對陣列的一個引用,那麼我們修改陣列的值,切片會發生什麼呢?

// 把陣列 index = 1 的值修改為 10
arr[1] = 10

// 切片 p = 0xc00000c060,arr = [10 2],len = 2,cap = 4
fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice)

可以看到,我們切片對應的值也被修改為 10,切片的底層資料結構中存在的底層陣列是對我們上面陣列做的引用傳遞,關於引用傳遞和值傳遞大家應該還是分的清的

那麼對應的,我們修改切片,陣列也會發生相應的變化

// 同樣的道理,修改切片的值也會影響到底層陣列
arrSlice[0] = 8
// 切片 p = 0xc00000c060,arr = [8 2],len = 2,cap = 4
fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
// 陣列 p = 0xc00001c0f0,arr = [0 8 2 3 4],len = 5,cap = 5
fmt.Printf("陣列 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

對切片進行追加操作,會發生什麼?

上面說到,我們修改切片或者是陣列,都會有對應的變化,那麼我們對切片進行追加呢?

// 對切片追加
arrSlice = append(arrSlice, 11)
// 切片 p = 0xc00008a020,arr = [8 2 11],len = 3,cap = 4
fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
// 陣列 p = 0xc000090060,arr = [0 8 2 11 4],len = 5,cap = 5
fmt.Printf("陣列 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

我們可以看到,陣列的 index = 3 的值被修改為 11,我是這麼理解的:

  1. 追加前的切片索引是 [0,1] 對應著陣列的 [1,2]
  2. 在這裡追加了一位,那麼切片的索引是 [0,1,2] 陣列對應的索引是 [1,2,3]
  3. 所以追加的值,也會修改陣列的值

切片的容量?

通過上面的程式碼,我們可以看到我們的 cap 一直都是 4,為什麼呢?這就要涉及到一個容量的計算公式了

  1. 在建立切片時,我們指定了對陣列的 [1,3] 進行切片,這裡切出來的長度為 2
  2. 切片在建立時,如果不指定容量,那麼容量會自動去計算,我們這裡沒有指定容量,那麼他會自動計算
  3. 建立時計算容量的公式為:建立時的長度 * 2,我們的長度是 2 ,計算出來的容量則為 4
  4. 在追加時,會先判斷容量夠不夠,如果容量足夠則容量不變;如果超出容量那麼判斷長度是否小於 1024 ,小於則容量 * 2,大於則容量 * 1.25

這就是為什麼我們的容量一直是 4 的原因

切片容量超過原陣列容量,會發生什麼?

我們一直在反覆提,切片是對陣列的引用,那麼當我切片已經超出陣列的範圍,會發生什麼呢?

我們寫程式碼操作一下

arrSlice = append(arrSlice, 12, 13, 14)
// 切片 p = 0xc00000c060,arr = [8 2 11 12 13 14],len = 6,cap = 8
fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
// 陣列 p = 0xc00001c0f0,arr = [0 8 2 11 4],len = 5,cap = 5
fmt.Printf("陣列 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

在上面程式碼中,我們追加了三個值 12,13,14

我們可以看到,陣列的值並沒有被修改,按照我們上一步講的來說,應該陣列的最後一位會變為 12,但是並沒有
這是一個很重要的概念,當我們的切片容量大於底層陣列容量時,會自動建立一個新的底層陣列,取消對原陣列的引用

當我們切片取消對原陣列引用時,我們再去修改切片,並不會影響到原陣列

arrSlice[0] = 18
// 切片 p = 0xc00008c020,arr = [18 2 11 12 13 14],len = 6,cap = 8
fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
// 陣列 p = 0xc000092060,arr = [0 8 2 11 4],len = 5,cap = 5
fmt.Printf("陣列 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

被取消引用的陣列去哪了?

根據查閱資料來看,大部分人都是說當被取消引用,並且也沒有別的地方去引用時,golang 的垃圾處理會自動回收;大家可以具體嘗試一下

完整程式碼

此文章完整程式碼如下

// Enda <endachao@gmail.com>
package main

import "fmt"

func main() {
    //陣列是固定長度與容量,並且具有相同型別的一組值
    //此定義的陣列長度為 5 ,那麼容量也會固定為 5
    arr := [5]int{0, 1, 2, 3, 4}
    // 陣列 p = 0xc00001c0f0,arr = [0 1 2 3 4],len = 5,cap = 5
    fmt.Printf("陣列 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))
    // 陣列的索引都是從 0  開始的
    // 0 1
    fmt.Println(arr[0], arr[1])

    // 切片可以看做是一個可變長的陣列
    // 切片可以看做是對陣列的一個片段的引用
    // [1:3] 表示範圍從陣列的索引 1 至 陣列的索引 3,它是不包括 3 的,可以簡單理解為  index == 1 && index < 3
    // 當切片生成後,索引預設也是從 0 開始, 切片索引是 [0,1] 對應著陣列的索引是 [1,2]
    arrSlice := arr[1:3]
    // 切片 p = 0xc00000c060,arr = [1 2],len = 2,cap = 4
    fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))

    // 由於切片是對底層資料的一個引用,所以修改底層陣列會更改切片的值
    arr[1] = 10
    // 切片 p = 0xc00000c060,arr = [10 2],len = 2,cap = 4
    fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))

    // 同樣的道理,修改切片的值也會影響到底層陣列
    arrSlice[0] = 8
    // 切片 p = 0xc00000c060,arr = [8 2],len = 2,cap = 4
    fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
    // 陣列 p = 0xc00001c0f0,arr = [0 8 2 3 4],len = 5,cap = 5
    fmt.Printf("陣列 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

    // 對切片追加
    // 追加前的切片索引是 [0,1] 對應著 陣列的 [1,2]
    // 在這裡追加了一位,那麼切片的索引是 [0,1,2] 陣列對應的索引是 [1,2,3]
    // 所以追加的值,也會修改陣列的值
    arrSlice = append(arrSlice, 11)
    // 切片 p = 0xc00008a020,arr = [8 2 11],len = 3,cap = 4
    fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
    // 陣列 p = 0xc000090060,arr = [0 8 2 11 4],len = 5,cap = 5
    fmt.Printf("陣列 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

    // 切片的容量為什麼是 4
    // 在建立切片時,我們指定了對陣列的 [1,3] 進行切片,這裡切出來的長度為 2
    // 切片在建立時,如果不指定容量,那麼容量會自動去計算
    // 建立時計算容量的公式為:建立時的長度 * 2
    // 追加時,會先判斷長度夠不夠,如果夠則容量不變,如果不夠那麼判斷長度是否小於 1024 ,小於則容量 * 2,大於則 容量 * 1.25

    // 當我們切片的長度超過了原陣列的長度
    // 我們可以看到,陣列的值並沒有被修改,按照我們上一步講的來說,應該陣列的最後一位會變為 12,但是並沒有
    // 這是一個很重要的概念,當我們的切片容量大於底層陣列容量時,會自動建立一個新的底層陣列
    arrSlice = append(arrSlice, 12, 13, 14)
    // 切片 p = 0xc00000c060,arr = [8 2 11 12 13 14],len = 6,cap = 8
    fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
    // 陣列 p = 0xc00001c0f0,arr = [0 8 2 11 4],len = 5,cap = 5
    fmt.Printf("陣列 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

    //當我再去修改切片的值時,並不會去操作我們上面定義的陣列了
    arrSlice[0] = 18
    // 切片 p = 0xc00008c020,arr = [18 2 11 12 13 14],len = 6,cap = 8
    fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
    // 陣列 p = 0xc000092060,arr = [0 8 2 11 4],len = 5,cap = 5
    fmt.Printf("陣列 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章