首页 / Go 语言入门教程 / 数组

Go 语言入门教程

数组

本教程共 80 篇 · 第 27 篇 · 更新于 2026-07-27 · 约 7 分钟阅读

GoGo 入门教程数组array值类型Go 数组遍历

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)、指针、结构体、接口、泛型等进阶内容。