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。
数组是值类型¶
赋值和按值传参会复制整个数组,修改副本不会影响原数组:
这是数组与切片的第一个重要差异:复制切片只复制描述符,通常仍共享底层数组;复制数组则复制元素。
如果必须修改同一个数组,可传 *[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 若接收数组,会把长度写进类型契约;只有长度确实 不可变时才这样设计。不要为了“避免分配”盲目改用数组,逃逸分析、可读性和真实基准结果更重要。