Go 从 0 到精通 · 第 08 课:数组
Go 从 0 到精通 · 第 08 课:数组
学习定位:这是整套 Go 教程的第 8 课。
前置要求:已经完成第 7 课,掌握了指针的基本概念(&、*、nil、指针参数),理解了值传递与指针传递的区别。
本课目标:理解数组的定义、初始化、遍历方式,掌握数组是值类型的特性,为下一课学习切片建立对比基础。
1. 本课你要解决的核心问题
前面你用的变量一次只能存一个值。如果你想存一组数据——比如 5 个学生的成绩、7 天的气温——怎么办?
数组就是用来存固定数量的同类型元素的数据结构。
你需要搞明白以下问题:
- 数组怎么声明、怎么初始化
- 数组的长度是类型的一部分意味着什么
- 怎么访问和修改数组元素
- 怎么遍历数组
- 数组作为参数传递时会发生什么
- 为什么 Go 中数组用得比切片少
学完这一课,你就能用数组存储一组数据了,同时也会理解为什么下一课的切片才是真正的主角。
2. 数组的基本概念
2.1 什么是数组
数组是一块连续的内存空间,存放固定数量的同类型元素。
你可以把数组想象成一排编号从 0 开始的储物柜:
1 | 索引: [0] [1] [2] [3] [4] |
- 每个格子叫一个元素
- 每个元素有一个索引(从 0 开始)
- 所有元素的类型必须相同
- 数组的长度在创建时就确定了,不能再改
2.2 数组和之前学的变量的区别
| 普通变量 | 数组 |
|---|---|
| 存一个值 | 存多个值 |
x := 42 |
arr := [5]int{1,2,3,4,5} |
| 用变量名访问 | 用下标访问 arr[0] |
3. 数组的声明与初始化
3.1 声明并指定长度
1 | package main |
[5]int 表示"长度为 5 的 int 数组"。
没有显式赋值时,每个元素是类型的零值(int 的零值是 0)。
3.2 声明时初始化
1 | package main |
花括号里的值按顺序赋给每个元素。
3.3 用 ... 让编译器推断长度
1 | package main |
[...]int 表示"你帮我数有多少个元素"。编译器会根据初始化值的数量确定长度。
3.4 按索引初始化
1 | package main |
0: 10 表示"下标 0 的元素是 10"。没有指定的元素保持零值。
4. 访问和修改数组元素
4.1 通过下标访问
1 | package main |
arr[0] 表示"取下标为 0 的元素"。
4.2 通过下标修改
1 | package main |
4.3 下标越界
1 | arr := [5]int{1, 2, 3, 4, 5} |
数组下标从 0 开始,长度为 5 的数组合法下标是 0 到 4。访问 arr[5] 会越界。
Go 在编译时如果能确定下标越界,会直接报编译错误。如果下标是变量,运行时会 panic。
5. 数组的遍历
5.1 用 for 经典循环遍历
1 | package main |
输出:
1 | arr[0] = 10 |
len(arr) 返回数组的长度。
5.2 用 for range 遍历(推荐)
1 | package main |
输出同上。
for range 每次迭代返回两个值:
i:当前元素的索引v:当前元素的值(是拷贝)
5.3 只需要索引或只需要值
1 | // 只需要索引 |
6. 数组是值类型:这是重点
6.1 赋值会复制整个数组
1 | package main |
b = a 把 a 的所有元素复制了一份给 b。修改 b 不影响 a。
这和切片、map 不同——后面学到时你会看到,切片和 map 赋值后修改会影响原数据。
6.2 数组作为函数参数:也是值传递
1 | package main |
输出:
1 | 函数内部: [999 2 3 4 5] |
数组传给函数时,整个数组被复制。函数内部修改的是副本。
6.3 用指针传递数组避免复制
如果不想复制整个数组,可以传指针:
1 | package main |
传 &a 就能把数组的地址传过去,函数内部直接操作原数组。
但请注意:[5]int 和 [3]int 是不同的类型,*[5]int 不能传给期望 *[3]int 的函数。
7. 数组的长度是类型的一部分
这是 Go 数组最重要的特性之一:
1 | var a [3]int |
[3]int 和 [5]int 是完全不同的类型。
这意味着:
- 不能把
[3]int赋给[5]int - 不能把
[3]int传给期望[5]int的函数 - 数组长度必须在编译时确定
1 | // 编译错误:类型不匹配 |
这种设计的好处是:编译器能在编译期发现长度不匹配的错误。但副作用是:数组不够灵活——你不能根据运行时的输入决定数组大小。这就是为什么切片(下一课)更常用。
8. 多维数组
8.1 二维数组
1 | package main |
[3][4]int 表示"3 个 [4]int",即 3 行每行 4 个元素。
8.2 初始化二维数组
1 | matrix := [2][3]int{ |
9. 一段综合示例
1 | package main |
输出:
1 | 成绩: [85 92 78 95 88] |
10. 常见坑总结
10.1 以为数组赋值是引用
1 | a := [3]int{1, 2, 3} |
数组赋值是完整复制,不是共享引用。如果你想要"共享数据、修改互相影响"的行为,应该用切片。
10.2 忘记数组长度是类型的一部分
1 | func process(arr [5]int) {} |
这导致数组作为函数参数非常不方便——每次改长度都要改函数签名。切片就没有这个问题。
10.3 数组长度必须是编译时常量
1 | n := 5 |
Go 的数组长度必须是编译时能确定的常量。如果长度在运行时才知道,应该用切片。
10.4 for range 中的值是拷贝
1 | arr := [3]int{1, 2, 3} |
for range 返回的 v 是元素的副本,修改 v 不影响数组。要修改数组,用下标 arr[i]。
11. 本课练习
练习 1:声明和初始化
要求:
- 声明一个长度为 6 的
int数组 - 用字面量初始化为
{1, 2, 3, 4, 5, 6} - 打印数组和它的长度
练习 2:遍历求和
要求:
- 定义一个包含若干个
float64的数组 - 用
for range遍历求和 - 打印总和与平均值
练习 3:数组翻转
要求:
- 定义一个
int数组 - 不使用额外数组,原地翻转(提示:头尾交换)
- 打印翻转前后的数组
练习 4:验证值复制
要求:
- 定义一个数组
a - 将
a赋给b,修改b的某个元素 - 打印
a和b,验证a没有被影响
练习 5:用指针传递数组
要求:
- 定义函数
zeroAll(arr *[5]int),将数组所有元素设为 0 - 在
main中调用,验证原数组被修改
练习 6:二维数组
要求:
- 定义一个 3x3 的二维数组
- 初始化为单位矩阵(对角线为 1,其余为 0)
- 打印这个矩阵
12. 自测题
12.1 概念题
- Go 数组的声明语法是什么?
[3]int和[5]int是同一个类型吗?- 数组赋值
b = a是复制还是引用? - 数组传给函数时是值传递还是引用传递?
- 怎么在函数里修改传入的数组?
for range遍历数组时,返回的值是拷贝还是引用?- 数组长度能用变量指定吗?比如
var arr [n]int(n是变量)? - 怎么用
...让编译器自动推断数组长度?
如果你能流畅回答这些问题,说明数组的基本概念你已经掌握了。
13. 本课总结
这一课你学到了 Go 中最基础的集合类型——数组。
你现在应该已经理解:
- 数组用
[N]type声明,长度固定,不可变 - 通过下标
arr[i]访问和修改元素,下标从 0 开始 - 数组是值类型:赋值和传参都会复制整个数组
- 要修改原数组,需要传指针
*[N]int - 数组长度是类型的一部分:
[3]int和[5]int是不同类型 - 用
for range遍历数组最方便 - 数组在实际开发中用得不多,因为长度不可变且是类型的一部分,切片更灵活
14. 下一课预告
下一课我们学习 Go 最重要的数据结构之一:切片 slice。
会重点讲:
- 切片是什么,和数组有什么区别
- 切片的创建方式
- 长度
len和容量cap的区别 append怎么用,切片什么时候会扩容- 切片截取(slicing)和底层共享问题
- 为什么切片才是 Go 日常开发中的主角
学完下一课,你就掌握了 Go 中处理"一组数据"的主力工具。






