Golang 陣列(arrays)的宣告與操作
簡介
在 Go 語言中,**陣列(array)**是最基礎的集合型別,它提供了固定長度、連續記憶體布局的資料儲存方式。雖然在日常開發中我們常使用切片(slice)取代陣列,但了解陣列的行為對於掌握 Go 的記憶體模型、效能調校以及與 C/C++ 互操作都相當重要。
本篇文章將從 陣列的宣告、初始化、存取與傳遞 等核心概念出發,結合實用範例說明其運作方式,並討論常見的陷阱與最佳實踐,最後提供幾個真實的應用情境,幫助讀者在程式設計中正確且有效地使用陣列。
核心概念
1. 陣列的基本型別與語法
在 Go 中,陣列的型別寫法為 [*元素型別*][*長度*]*元素型別*,例如 var a [5]int 表示「長度為 5、每個元素皆為 int」的陣列。陣列的長度是型別的一部份,兩個長度不同的陣列即使元素型別相同,也屬於不同型別,無法直接相互賦值。
var a [3]int // [0,0,0],預設值為零值
var b = [3]int{1,2,3} // 直接使用字面值初始化
2. 宣告與初始化
Go 提供了多種方式來建立陣列:
| 方法 | 範例 | 說明 |
|---|---|---|
| 零值宣告 | var a [4]float64 |
所有元素皆為 0.0 |
| 字面值初始化 | b := [3]string{"go","lang","!"} |
直接在宣告時給定值 |
| 省略長度 | c := [...]int{10,20,30} |
由編譯器自動推算長度 |
| 指定索引 | d := [5]int{2: 100, 4: 200} |
只設定特定位置,其餘為零值 |
使用 new |
e := new([2]bool) |
回傳指向陣列的指標 *[2]bool |
// 省略長度的寫法,編譯器會根據元素個數決定長度
arr := [...]int{5, 10, 15, 20}
fmt.Println(len(arr)) // 4
3. 取得長度與容量
陣列只有 長度(length),沒有容量(capacity)的概念;len(arr) 會回傳固定的長度,而 cap(arr) 也會回傳相同值。
var a [10]byte
fmt.Println(len(a), cap(a)) // 10 10
4. 存取與修改元素
使用索引(從 0 開始)即可存取或修改元素。若索引超出範圍,程式在執行時會觸發 runtime panic,因此必須自行確保索引合法。
scores := [3]int{80, 90, 85}
scores[1] = 95 // 修改第二筆成績
fmt.Println(scores[1]) // 95
5. 陣列的遍歷
| 方法 | 範例 | 說明 |
|---|---|---|
| 傳統 for | for i:=0; i<len(a); i++ {} |
可同時取得索引與值 |
| range | for i, v := range a {} |
迭代時會自動產生索引與拷貝值 |
// 使用 range 迭代陣列
nums := [4]int{1, 2, 3, 4}
for i, v := range nums {
fmt.Printf("index=%d, value=%d\n", i, v)
}
注意:
range迭代陣列時會產生 值的拷貝,若要在迭代中直接修改原始陣列,必須使用索引nums[i] = newValue。
6. 陣列的值傳遞(Copy Semantics)
在 Go 中,陣列是 值類型。將陣列賦值給另一個變數或作為函式參數傳遞時,會完整複製整個陣列的內容,而非僅傳遞指標。這意味著:
func modify(a [3]int) {
a[0] = 999 // 只改變本地副本
}
orig := [3]int{1, 2, 3}
modify(orig)
fmt.Println(orig) // 輸出 [1 2 3],未被改變
如果希望在函式內部修改原始陣列,必須傳遞 指標:
func modifyPtr(p *[3]int) {
(*p)[0] = 999
}
modifyPtr(&orig)
fmt.Println(orig) // 輸出 [999 2 3]
7. 陣列與切片的關係
切片是基於陣列的抽象層,切片本身不儲存資料,而是持有指向底層陣列的指標、長度與容量。從陣列取得切片的語法如下:
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // 包含索引 1、2、3 的子集合
fmt.Println(slice) // [2 3 4]
切片的 動態伸縮 能解決陣列長度固定的限制,但在需要保證資料佈局固定、或與 C 語言互動時,陣列仍是首選。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 建議的做法 |
|---|---|---|
| 長度不匹配的賦值 | a := [3]int{1,2,3,4} 會編譯錯誤 |
使用 [...]int{...} 讓編譯器自行推算長度 |
| 越界存取 | arr[5] 會在執行時 panic |
在迴圈或索引計算前先檢查 i < len(arr) |
| 不必要的陣列拷貝 | 把大陣列傳給函式會產生完整拷貝,影響效能 | 若資料量大,改用 *[N]T 指標或切片 |
| range 產生值拷貝 | for _, v := range arr { v = 0 } 不會改變原陣列 |
使用索引 arr[i] = 0 或傳遞指標 |
| 陣列與切片混用時的容量誤解 | s := arr[:]; cap(s) != len(arr) 可能造成誤判 |
了解 cap(s) = len(arr) - startIndex,必要時使用 make([]T, len(arr)) 重新分配 |
最佳實踐
盡量使用切片:除非有明確需求(如固定長度、C 互操作),否則切片提供更彈性的操作與較低的拷貝成本。
使用
const定義陣列長度:可避免硬編碼錯誤,且讓程式更具可讀性。const MaxUser = 100 var users [MaxUser]string利用
...省略長度:在字面值初始化時讓編譯器自行計算,減少維護成本。傳遞指標或切片:對於大型陣列或需要在函式內部修改的情況,使用
*[N]T或[]T。避免在迭代中修改切片底層陣列的長度:切片的
append可能會重新配置底層陣列,導致原始陣列不再同步。若必須保留原始陣列,請在迭代前先複製。
實際應用場景
| 場景 | 為何選擇陣列 | 範例說明 |
|---|---|---|
| 固定大小的緩衝區 | 需要確保記憶體佈局不變、避免 GC 重新配置 | 網路封包的接收緩衝區 var buf [1500]byte |
| 多維矩陣運算 | 多維陣列在編譯期就確定尺寸,存取效率高 | type Matrix3x3 [3][3]float64 用於圖形變換 |
| 查表(lookup table) | 常數長度、快速索引 | var crc8Table = [256]byte{...} |
| 與 C 程式庫互動 | C 函式通常接受指向固定長度陣列的指標 | C.some_func((*C.int)(unsafe.Pointer(&arr[0]))) |
| 編譯期常量集合 | 利用陣列的長度作為類型參數(Go 1.18+ 泛型) | type FixedSlice[T any, N int] struct { data [N]T } |
總結
陣列是 Go 語言中最基礎且 具備固定長度、值語意 的集合型別。了解它的宣告方式、初始化技巧、遍歷與傳遞機制,能讓開發者在需要 確保記憶體布局、提升效能或與底層 C 程式庫互動 時,作出正確的設計選擇。
- 陣列是值類型,賦值與傳參會完整拷貝;若要避免拷貝,請使用指標或切片。
- 長度是型別的一部份,不同長度的陣列不可直接相互賦值。
- 遍歷時要注意
range產生的是值的拷貝,若要修改原始資料請使用索引。 - 常見陷阱 包括越界存取、無意的拷貝與
range的副本行為,透過前述的最佳實踐可以有效避免。
掌握了陣列的核心概念後,配合切片的彈性使用,開發者將能在 Go 專案中更靈活地處理資料集合,寫出 高效、可讀且安全 的程式碼。祝你在 Golang 的旅程中玩得開心!