Go | 陣列(Array)

Go 的陣列是連續空間內儲存了固定數目之相同型別物件的容器。當使用 var array [5]int 宣告時,陣列內儲存的型別或者長度都不能再改變,如果需要更多的空間來儲存新的物件,需要宣告一個新的陣列後把原先的值搬過去新的陣列。

在 Go 語言中宣告新的變數且無賦值時,這些變數所指向的物件都會以其零值(Zero Value)初始化。有關於 Go 的零值,請參考這邊。常用的零值:

類型 零值
數值 0
布林值 false
字串 ""

初始化陣列的同時賦值

// 宣告一個 array1 變數的 int 陣列,長度為 5 且值依序分別為 1,2,3,4,5
array1 := [5]int{1,2,3,4,5}

// 宣告一個 array2 變數的 int 陣列,長度為 5 且值依序分別為 0,1,2,0,0
array2 := [5]int{1: 1, 2: 2}

// 宣告一個 array3 變數的 int 陣列,長度為取決於賦值內容
// 如以下 array3 的長度為 3
array3 := [...]int{1,2,3}

陣列的操作

// 宣告一個 array 變數的 int 陣列,長度為 3 且值依序分別為 1,2,3
array := [3]int{1,2,3}

// 把 index 2 的值改為 5
array[2] = 5

使用指標為陣列的值

初始化 array := [3]*int{0: new(int), 1: new(int)} 時:

index 0 1 2
array[index] 0xc0000b6020 0xc0000b6028 nil
*array[index] 0 0 error: cannot use assignment (array[i]) = (nil) as value

試試看改變陣列指標的值 *array[0] = 100

index 0 1 2
array[index] 0xc0000b6020 0xc0000b6028 nil
*array[index] 0 100 error: cannot use assignment (array[i]) = (nil) as value

這邊可以注意到,指標的記憶體位址是一樣的,但值改變了。

假如想要替現在為 nil 的位置指定值,首先要在記憶體裡做出一個指標。

i := 200
array[2] = &i

透過 i := 200 的方式宣告了 i 以後,使用 &i 取得 i 的指標並賦值給 array[2] 後,會變成:

index 0 1 2
array[index] 0xc0000b6020 0xc0000b6028 0xc0000b6038
*array[index] 0 100 200

假如另外再賦值另一個變數 j 時,指標位址會變:

j := 200
array[2] = &j

這邊例外宣告了變數 j 給整數 200,其實是另外在記憶體裡找了一塊地方塞值 200 給指標 j,到此, ij 的位置不同,但值相同。

i := 200
j := 200
k := j

i == j // true
k == j // true

&i == &j // false
&k == &j // false

k = 300
k == j // false

突然跳來實驗一下 Go 的指標,可以發現每 := 了一個指標,都是在記憶體裡拿一塊新的區域來存值,這幾個指標都是不同位置,所以假若 *k 被改值時不會影響到 ij 這兩個指標所指向的值。

index 0 1 2
array[index] 0xc0000b6020 0xc0000b6028 0xc0000b6058
*array[index] 0 100 200

重新指定一個指標給 array[2] = new(int) 也是一樣的意思:

index 0 1 2
array[index] 0xc0000b6020 0xc0000b6028 0xc0000b6078
*array[index] 0 100 0

要注意的是,如果複製以指標為值的陣列時,指標的位置會一樣:

array1 := [2]*int{0: new(int), 1: new(int)}
array1[0] = 1
array1[1] = 2

array2 := array1

可以發現 array1array2 裡頭放的指標,地址都是一樣的。

index 0 1
array1[index] 0xc0000b6020 0xc0000b6028
array2[index] 0xc0000b6020 0xc0000b6028
*array1[index] 1 2
*array2[index] 1 2

當然,這就表示,如果改動指標指向的值,兩個陣列都會被影響,請注意!

傳送陣列給函數

由於 Golang 的函數是傳值(passed by value),所以把陣列直接傳進去函數裡面,對於程式效能會有負面影響。

可以試試看下面的實驗:

// 宣告一個 8MB 的陣列
var array [1e6]int

// 把陣列直接傳到 foo 函數裡
foo(array)

// foo 直接接收陣列
func foo(array [1e6]int) {
    ...
}

每一次 foo 這個函數被呼叫,就會有 8MB 的空間在 stack 被取用。然後傳入的陣列會被複製到這個新取用的空間裡。即使你的機器可以處理這樣的負載,但其實有更好的方式。

使用指標(Pointer)作為參數

var array [1e6]int

foo(&array)

func foo(array *[1e6]int) {
    ...
}

這麼做,呼叫 foo 的時候就不會浪費額外的記憶體空間去複製傳入的值,而是使用 array 這個已經建立好的陣列。

但這麼做要小心函數是否對於這個參數的值做改變,由於傳入的是指標,所以對於陣列的值做任何改變時,同一個記憶體中的數值會被更改。