数组
本教程共 80 篇 · 第 27 篇 · 更新于 2026-07-27 · 约 7 分钟阅读
27. 数组
本节目标:掌握 Go 数组的声明和使用,理解「长度是类型的一部分」和「值语义」这两个关键特性。
数组是什么
数组是固定长度的、同一类型的元素序列。长度在声明时确定,之后不能改变。
var nums [5]int // 长度为5的int数组
这和切片不同。切片长度可变,数组长度固定。实际开发中切片用得更多,但数组是切片的基础,必须先理解。
声明数组
基本声明
var arr [5]int // 5个int,零值都是0
var names [3]string // 3个string,零值都是""
var flags [4]bool // 4个bool,零值都是false
数组声明时,所有元素自动初始化为零值。
声明并初始化
arr := [5]int{1, 2, 3, 4, 5}
部分初始化
arr := [5]int{1, 2} // [1, 2, 0, 0, 0],未指定的为零值
自动推断长度
用 ... 让编译器根据元素数量推断长度:
arr := [...]int{1, 2, 3, 4, 5} // 长度自动推断为5
fmt.Println(len(arr)) // 5
Warning
...不能省略。[]int{1, 2, 3}是切片不是数组。[...]int{1, 2, 3}才是数组。
指定索引初始化
arr := [5]int{1: 10, 3: 30}
// [0, 10, 0, 30, 0],索引1的值为10,索引3的值为30
用 new 创建
arr := new([5]int) // 返回 *[5]int 指针
fmt.Println(arr) // &[0 0 0 0 0]
new([5]int) 返回的是指针 *[5]int,不是数组值。
长度是类型的一部分
这是 Go 数组最重要的特性:长度不同的数组是不同的类型。
var a [5]int
var b [3]int
var c [5]int
// a = b // 报错:cannot use b as [5]int
a = c // 正确,类型相同
[5]int 和 [3]int 是不同的类型,不能互相赋值。即使底层都是 int,长度不同就是不同类型。
这也意味着,数组长度在编译期就必须确定:
n := 5
var arr [n]int // 报错:non-constant array length n
长度必须是常量或常量表达式:
const size = 5
var arr [size]int // 正确
var arr2 [size + 1]int // 正确,常量表达式
var arr3 [5]int // 正确
Note如果需要运行时确定长度的序列,用切片
[]int,不用数组。
数组是值类型
这是 Go 数组和 C/Java 数组的根本区别:Go 数组是值类型,不是引用。
赋值即拷贝
a := [3]int{1, 2, 3}
b := a // 完整拷贝
b[0] = 100
fmt.Println(a) // [1 2 3],a 不受影响
fmt.Println(b) // [100 2 3]
赋值 b = a 复制了整个数组。修改 b 不影响 a。
函数参数也是拷贝
func modify(arr [3]int) {
arr[0] = 100 // 修改的是副本
}
func main() {
a := [3]int{1, 2, 3}
modify(a)
fmt.Println(a) // [1 2 3],不受影响
}
数组传给函数时,整个数组被复制。函数内的修改不影响原数组。
Warning如果数组很大(比如
[1000000]int),值拷贝会消耗大量内存和时间。这种情况下应该传指针或用切片。
传指针避免拷贝
func modify(arr *[3]int) {
arr[0] = 100 // 通过指针修改原数组
}
func main() {
a := [3]int{1, 2, 3}
modify(&a)
fmt.Println(a) // [100 2 3]
}
传指针 *[3]int 就能修改原数组,且不复制数据。
访问和修改
索引访问
arr := [5]int{10, 20, 30, 40, 50}
fmt.Println(arr[0]) // 10
fmt.Println(arr[2]) // 30
修改元素
arr := [5]int{10, 20, 30, 40, 50}
arr[1] = 200
fmt.Println(arr) // [10 200 30 40 50]
数组长度
arr := [5]int{1, 2, 3, 4, 5}
fmt.Println(len(arr)) // 5
越界访问会 panic
arr := [3]int{1, 2, 3}
fmt.Println(arr[5]) // panic: index out of range
遍历数组
用 for 索引遍历
arr := [5]int{10, 20, 30, 40, 50}
for i := 0; i < len(arr); i++ {
fmt.Printf("arr[%d] = %d\n", i, arr[i])
}
用 for-range 遍历
arr := [5]int{10, 20, 30, 40, 50}
for index, value := range arr {
fmt.Printf("索引:%d,值:%d\n", index, value)
}
只需要值
for _, value := range arr {
fmt.Println(value)
}
只需要索引
for i := range arr {
fmt.Println(i)
}
Go 1.22 range over int
for i := range len(arr) {
fmt.Println(arr[i])
}
range len(arr) 等价于 range 5,从 0 到 4。
多维数组
Go 支持多维数组:
var grid [3][4]int // 3行4列的二维数组
初始化
grid := [3][4]int{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
}
访问
fmt.Println(grid[1][2]) // 7(第2行第3列)
遍历
for i := 0; i < len(grid); i++ {
for j := 0; j < len(grid[i]); j++ {
fmt.Printf("%3d ", grid[i][j])
}
fmt.Println()
}
用 for-range 遍历
for i, row := range grid {
for j, val := range row {
fmt.Printf("grid[%d][%d] = %d\n", i, j, val)
}
}
数组比较
长度相同的数组可以用 == 比较:
a := [3]int{1, 2, 3}
b := [3]int{1, 2, 3}
c := [3]int{1, 2, 4}
fmt.Println(a == b) // true
fmt.Println(a == c) // false
长度不同的数组不能比较(类型不同):
a := [3]int{1, 2, 3}
b := [4]int{1, 2, 3, 4}
// fmt.Println(a == b) // 报错:mismatched types
数组 vs 切片
| 特性 | 数组 | 切片 |
|---|---|---|
| 长度 | 固定 | 可变 |
| 类型 | [N]T,长度是类型一部分 | []T,长度不是类型的一部分 |
| 传参 | 值拷贝 | 传递切片头(轻量) |
| 零值 | 各元素为零值 | nil |
| 比较 | 可以用 == | 不能用 ==(只能用 reflect.DeepEqual) |
什么时候用数组
- 长度固定且已知:比如
[3]float64表示 RGB 颜色 - 需要值语义:每次赋值都拷贝一份
- 小数组:拷贝开销可以忽略
什么时候用切片
- 长度不确定或需要变化
- 传参给函数(避免大数组拷贝)
- 需要动态增删元素
Tip实际开发中 99% 用切片。数组主要用于:固定大小的配置、矩阵运算、哈希算法等场景。
实际示例
RGB 颜色
type Color [3]uint8 // [R, G, B]
func main() {
red := Color{255, 0, 0}
green := Color{0, 255, 0}
blue := Color{0, 0, 255}
fmt.Printf("红色:R=%d G=%d B=%d\n", red[0], red[1], red[2])
}
矩阵
type Matrix [3][3]float64
func (m Matrix) Determinant() float64 {
return m[0][0]*(m[1][1]*m[2][2]-m[1][2]*m[2][1]) -
m[0][1]*(m[1][0]*m[2][2]-m[1][2]*m[2][0]) +
m[0][2]*(m[1][0]*m[2][1]-m[1][1]*m[2][0])
}
func main() {
m := Matrix{
{1, 2, 3},
{4, 5, 6},
{7, 8, 10},
}
fmt.Println("行列式:", m.Determinant())
}
固定大小缓冲区
func main() {
var buf [1024]byte
n, _ := os.Stdin.Read(buf[:]) // buf[:] 转成切片
fmt.Printf("读取了 %d 字节\n", n)
fmt.Printf("内容:%s\n", buf[:n])
}
Note
buf[:]把数组转成切片,这是数组和切片之间的桥梁。后面切片章节会详细讲。
常见问题
用变量做数组长度
n := 10
var arr [n]int // 报错:non-constant array length
数组长度必须是编译期常量。需要运行时长度用切片:
n := 10
s := make([]int, n) // 正确
数组传参后修改无效
func modify(arr [5]int) {
arr[0] = 100
}
a := [5]int{1, 2, 3, 4, 5}
modify(a)
fmt.Println(a[0]) // 1,没有改变
数组传参是值拷贝。要修改原数组,传指针 *[5]int 或传切片 []int。
混淆数组和切片
arr := [3]int{1, 2, 3} // 数组
s := []int{1, 2, 3} // 切片
fmt.Printf("%T\n", arr) // [3]int
fmt.Printf("%T\n", s) // []int
有没有长度数字是数组还是切片的关键区别。
学完这节你能做什么
- 声明、初始化、访问数组
- 理解长度是类型的一部分
- 知道数组是值类型,赋值和传参会拷贝
- 遍历数组(for、for-range、range over int)
- 判断什么时候用数组,什么时候用切片
到这里,第 01-27 章全部完成。接下来的章节会进入复合类型(切片、map)、指针、结构体、接口、泛型等进阶内容。