Go 数组

Go 中固定长度、值类型的序列容器,长度是类型的一部分,赋值和传参均发生拷贝。

#type / concept #status / growing #tech / dev #resource / go

[!info] related notes

Go 数组

一句话定义

数组是 Go 中长度固定、元素类型相同、以值语义存储的连续内存序列,其长度是类型的一部分。

核心机制 / 工作原理

Go 数组与 C 数组在内存布局上相似——一块连续的内存区域——但语义完全不同:

  1. 长度是类型的一部分[3]int[4]int 是两个完全不同的类型,不能互相赋值,也不能比较。这意味着无法编写一个函数同时处理不同长度的数组。

  2. 值语义:数组赋值、传参、返回时都会完整复制一份。修改副本不影响原始数组。

  3. 零值初始化:未显式初始化的数组,其每个元素都是对应类型的零值(数值为 0,字符串为 "",bool 为 false,指针/切片/map/channel 为 nil)。

  4. 比较运算:相同类型(长度相同 + 元素类型相同且可比较)的数组可以用 ==!= 比较,逐元素对比。

  5. 与 slice 的关系arr[:] 可以从数组创建一个引用原内存的切片。切片(slice)在实践中远比数组常用,数组常用于底层存储或特定的固定大小场景。

最小例子 / 最小场景

package main

import "fmt"

func main() {
	// 声明与初始化
	var a [5]int                          // 零值数组: [0 0 0 0 0]
	b := [3]string{"go", "is", "fun"}    // 字面量初始化
	c := [...]int{10, 20, 30}             // 编译器自动推断长度为 3

	// 值语义: 赋值产生副本
	original := [3]int{1, 2, 3}
	copy := original
	copy[0] = 999
	fmt.Println(original) // [1 2 3]  — 不受影响
	fmt.Println(copy)     // [999 2 3]

	// 长度是类型的一部分
	// var d [4]int = original  // 编译错误: cannot use original (type [3]int) as type [4]int

	// 比较
	fmt.Println([3]int{1, 2, 3} == [3]int{1, 2, 3}) // true
	fmt.Println([3]int{1, 2, 3} == [3]int{1, 2, 4}) // false

	// 数组转切片
	s := b[:]
	fmt.Println(s, len(s)) // [go is fun] 3
}

为什么重要

  • 性能可控:数组在栈上分配(小数组时),无需堆分配和 GC 压力,适合对性能极度敏感的热路径。
  • 固定大小协议:当数据大小在编译期已知且不可变时(如 IP 地址 [4]byte[16]byte UUID、哈希值 [32]byte),数组语义更精确。
  • 理解 slice 的基础:slice 的底层就是一个指向数组的指针 + 长度 + 容量,理解数组才能真正理解 slice。
  • 安全的值拷贝:在并发场景中,将小数组作为参数传入函数,天然得到一份不可变副本,无需额外加锁。

边界与易混淆点

  • 不能用变量指定数组长度n := 5; var a [n]int 在 Go 中是非法的(长度必须是编译期常量表达式),需要使用 slice。
  • 大数组值拷贝开销大[1000000]int 作为函数参数会复制 8MB 内存,实际开发中几乎总用 slice 或指针 *[N]T 来避免。
  • [...]T 不是动态长度[...]int{1,2,3} 只是让编译器在编译期数出元素个数,结果仍然是固定长度数组 [3]int
  • 数组是可比较的,slice 不是:这是两者的重要区别之一,[]int{1,2} == []int{1,2} 会编译报错。
  • range 遍历数组得到的是副本for i, v := range arrv 是元素的副本,修改 v 不影响原数组。
创建于 2026/6/25 更新于 2026/6/25