跳转至

Go 语言数组

数组是长度固定、元素类型相同的值序列。数组长度属于类型的一部分,因此 [3]int 与 [4]int 是两个不同的类型,不能直接赋值或比较长度。

声明与初始化

package main

import "fmt"

func main() {
    var zero [3]int
    names := [3]string{"Go", "Rust", "Java"}
    ids := [...]int{10, 20, 30, 40}
    sparse := [5]int{1: 7, 4: 9}

    fmt.Println(zero, names, ids, sparse)
    fmt.Println(len(ids), ids[0], ids[len(ids)-1])
}

var zero [3]int 会把所有元素初始化为零值。[...]int 让编译器根据字面量推导长度, 但结果仍是数组,不是切片。索引从 0 开始,越界的常量索引会编译失败,运行时越界会 panic。

数组是值类型

赋值和按值传参会复制整个数组,修改副本不会影响原数组:

func changeCopy(a [3]int) {
    a[0] = 99
}

func changeOriginal(a *[3]int) {
    a[0] = 99
}

这是数组与切片的第一个重要差异:复制切片只复制描述符,通常仍共享底层数组;复制数组则复制元素。 如果必须修改同一个数组,可传 *[3]int,但多数业务 API 更适合接收 []int。

比较与遍历

当元素类型可比较时,同类型数组可使用 == 和 !=:

a := [3]int{1, 2, 3}
b := [3]int{1, 2, 3}
fmt.Println(a == b) // true

for i, value := range a {
    fmt.Println(i, value)
}

这是数组与切片的第二个差异:数组可能整体比较,而切片除与 nil 比较外不能使用 ==。 range 得到的 value 是元素值的副本;需要原地修改时应使用 a[i]。

适用边界

  • 数组适合长度是领域约束的值,例如 IPv4 的 4 字节、SHA-256 的 32 字节摘要。
  • 切片适合长度动态变化、需要追加或只处理某个区间的数据。
  • [0]T 是合法数组类型,但很少直接用于业务数据。
  • 多维数组写作 [2][3]int,每一维长度都属于类型。

常见错误

  • 误以为 [3]int 可以赋给 [4]int,长度不同即类型不同。
  • 把数组按值传给函数后,期待函数修改调用方数据。
  • 使用硬编码尾索引,数据长度调整后发生越界,应优先写 len(a)-1。
  • 为动态集合选择超大数组,造成无意义复制和栈空间压力。

性能与工程实践

小型定长数组通常布局紧凑,编译器还能进行边界检查消除。大型数组按值传递可能产生明显复制成本, 应通过基准测试决定使用数组指针还是切片。公开 API 若接收数组,会把长度写进类型契约;只有长度确实 不可变时才这样设计。不要为了“避免分配”盲目改用数组,逃逸分析、可读性和真实基准结果更重要。