Go 的陣列資料 - array 和 slice
array
Go 的 array 屬於長度固定的陣列形式,在建立一個陣列時需要指定長度和型別來創建一個新的陣列,也可以在後面帶上初始值,像是:
var numbers [5]int names := [3]string{"Jerry", "David", "Andrew"}
如果在給定初始值的情況下也可以不需要特別指定陣列的長度,而是使用 ... 使其自動計算長度。同時,給定的初始值也未必要和給定的陣列長度相等,例如:
names := [...]string{"Jerry", "David", "Andrew"} // 自動計算長度 fruits := [5]string{"apple", "banana"} // 初始值數量未必要等於陣列長度
不過在指定 array 長度時有一個限制,就是無法將變數值給賦予進去,由於陣列的長度需要在編譯時就被確定,因此無法以變數的形式來指定,像是以下建立陣列的方式便會出錯:
x := 5 var count [x]int
不過如果是以 const 的方式來指定常數的話,由於常數的值在編譯時便會被確定下來,所以這個常數就可以拿來指定給陣列的長度。
const N = 10 var nums [N]int
slice
slice 則相對於 array,是一種長度可變的陣列形式,建立 slice 的方式和 array 非常相似,差別在於不需要給定陣列的長度,也同樣可以帶入初始值進去:
var numbers []int names := []string{"Jerry", "David", "Andrew"}
make
make 是一個可以用來建立 slice 的方法,在官方文件中,它的用法會是像下面這樣:
func make([]T, len, cap) []T s = make([]byte, 5, 5) // 等同於 s = make([]byte, 5) // s == []byte{0, 0, 0, 0, 0}
這邊會和 slice 的底層機制有所關聯,事實上所有的資料還是透過 array 來儲存,而 slice 是由 ptr、len 和 cap 所組成,用來描述 array。ptr 是這個 slice 的 pointer,它會指向一個實際存在的 array,而 len 和 cap 則分別代表這個 slice 可存取的長度和底下 array 剩餘的容量。可以把整個儲存空間想像成一塊大畫布,而 array 佔據了畫布當中的一部分區塊,cap 就代表在這個 array 當中剩餘的容量,會隨著 ptr 的變動而有所變化,至於 len 像是再拿起一個框框放在這個 array 上,也就是最上層在撰寫 Go 的時候實際會看到的切片長度。


那如果透過上面提到的 var numbers []int 這樣的方式所建立的 slice,它的底層又會是什麼樣呢?事實上這樣所建立的 slice 會是以 ptr = nil、len = cap = 0 的方式,也就是一個不具任何長度和容量,且沒有指向任何實體 array 的空 slice。至於要對這個 slice 進行操作的話,可以透過後續介紹到的新增、移除等方法來處理,在底層會以動態分配的方法來為其分配 array,對於資源的運用來說是相對比較節省空間的方式。
移除元素、切片
想要對 slice 移除其中的元素或是進行切片的話,我們可以透過索引值來對陣列進行切割,來達到移除或是分割的效果,例如:
s = s[2:4] // 這邊的切割會包含起始的索引但不包含結尾的索引值
此時對於底層而言,實際上是透過移動 ptr 的方式來將起始的位置往前移動,並且調整 len 以符合切片的大小,作用的效果會像是下面呈現的樣子。值得注意的是這個時候 len 和 cap 的值便會產生分歧,後續可以透過再次切片的方式來拓展 len,但最大不能超過 cap 的值。

s = s[:cap(s)] // 將 len 拓展到和 cap 相同
從這邊也可以看出 slice 運作的一些特性,就是從始至終都是針對同一個 array 進行操作,因此即是使用不同變數對分片後的 slice 進行值的變動,原始的 slice 也會跟著變動。
nums := []int{1, 2, 3, 4, 5} subNums := nums[2:4] // []int{2, 3} subNums[1] = 10 // []int{2, 10} // 此時的 nums: []int{1, 2, 10, 4, 5}
增加元素
要對 slice 添加元素的話,可以使用 append 方法來達成,透過這個方法可以將原先的 cap 值拓展的更大 (但是如果觸發到需要擴展 cap 時,在底層會重新分配一個新的足夠大的陣列給這個 slice,和原本的陣列就不是同一個了),也可以一次性的添加多個元素,同時這個方法也可以用來合併兩個 slice,會使用 ... 當做展開來使用。
nums := []int{1, 2, 3, 4, 5} otherNums := []int{100, 101, 102} nums = append(nums, 10) // []int{1, 2, 3, 4, 5, 10} nums = append(nums, 6, 7, 8) // []int{1, 2, 3, 4, 5, 10, 6, 7, 8} nums = append(nums, otherNums...) // []int{1, 2, 3, 4, 5, 10, 6, 7, 8, 100, 101, 102}