本文 AI 產出,尚未審核

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)) 重新分配

最佳實踐

  1. 盡量使用切片:除非有明確需求(如固定長度、C 互操作),否則切片提供更彈性的操作與較低的拷貝成本。

  2. 使用 const 定義陣列長度:可避免硬編碼錯誤,且讓程式更具可讀性。

    const MaxUser = 100
    var users [MaxUser]string
    
  3. 利用 ... 省略長度:在字面值初始化時讓編譯器自行計算,減少維護成本。

  4. 傳遞指標或切片:對於大型陣列或需要在函式內部修改的情況,使用 *[N]T[]T

  5. 避免在迭代中修改切片底層陣列的長度:切片的 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 的旅程中玩得開心!