# 第 05 课:数组与切片 ## 学习目标 学会保存一组同类型数据,并使用上一课的循环遍历它们。按 [main.go](./main.go) 中的 1~7 节逐段手敲,不急着学习复杂内存管理。 本课独立存放,共享根目录的 `go.mod`,仅使用标准库,无需安装第三方依赖。 ## 1. 数组:固定长度 ```go scores := [3]int{80, 90, 75} fmt.Println(scores[0]) // 第一个元素,值为 80。 scores[2] = 85 // 修改第三个元素。 ``` - `[3]int` 表示包含 3 个整数的数组,长度也是类型的一部分;`[3]int` 与 `[4]int` 是不同类型。 - 索引从 `0` 开始,最后一个元素的索引是 `len(scores)-1`。 - `var scores [3]int` 不给初始值时,三个元素都为 `0`。 - 数组长度不能在运行时改变,越界访问会导致编译错误或运行时 panic。 与 C 类似,可以通过索引访问数组;但 Go 会做边界检查,而且数组赋值会复制整个数组。 ## 2. 用 range 遍历 ```go for index, score := range scores { fmt.Println(index, score) } ``` `index` 是索引,`score` 是当前元素值的副本。只需要值时,写 `for _, score := range scores`。 修改 `score` 不会直接修改数组中的整数元素。要修改原元素,应通过索引,例如 `scores[index] = score + 1`。 ## 3. 切片:常用的序列类型 ```go numbers := []int{10, 20, 30} numbers = append(numbers, 40, 50) ``` | 对比 | 数组 | 切片 | | --- | --- | --- | | 类型示例 | `[3]int` | `[]int` | | 长度 | 固定,属于类型的一部分 | 运行时可变化,不属于类型的一部分 | | 追加 | 不能直接 append | 使用 append 并接收返回值 | | 赋值 | 复制整个数组 | 复制切片描述信息,可能共享底层数组 | `append` 返回更新后的切片值,要写回变量。底层空间不足时可能分配新的数组,不要假设追加后原来的共享关系始终不变,也不要依赖固定的容量增长倍数。 ## 4. 截取与共享 ```go middle := numbers[1:3] ``` 范围是**左闭右开**:包含索引 1、2,不包含 3;因此长度是 `3-1=2`。 常见写法还有 `numbers[:2]`(前两个元素)、`numbers[2:]`(从索引 2 到末尾)、`numbers[:]`(全部)。 截取得到的子切片不是独立副本,它与原切片共享底层数组。因此本课 `middle[0] = 200` 会修改 `numbers[1]`。 `alias := numbers` 也不是复制全部元素;修改 `alias` 中已有元素,可能影响 `numbers`。 ## 5. 独立复制 ```go backup := make([]int, len(numbers)) copied := copy(backup, numbers) ``` `make` 创建目标切片,`copy` 复制元素并返回实际复制数量,即两边长度的较小值。目标长度不够时,不会自动扩容。 本课元素是整数,所以这样能得到不共享元素存储的独立副本;将来元素包含切片或指针时,还需要理解浅拷贝。 ## 6. 零值、长度与容量 - `var tasks []string` 的零值为 `nil`,可以直接 append。 - `len` 是当前可访问的元素数量。 - `cap` 是从切片起点到底层数组末尾的容量,不是可直接使用索引访问的范围。 - 即使 `cap` 大于 `len`,也不能直接访问 `slice[len(slice)]`,应先 append 或合法地重新切片。 - 空的非 nil 切片也存在,例如 `[]int{}`;长度为 0 并不一定表示 nil。 ## 编译并运行 在**仓库根目录**执行: ```powershell New-Item -ItemType Directory -Force bin/05_arrays_slices | Out-Null go build -o ./bin/05_arrays_slices/arrays_slices.exe ./lessons/05_arrays_slices .\bin\05_arrays_slices\arrays_slices.exe ``` 修改源码后需要重新编译。快速编译并运行也可以用: ```powershell go run ./lessons/05_arrays_slices ``` 或者从根目录进入本课,再执行 `go run .`: ```powershell Set-Location lessons/05_arrays_slices go run . ``` ## 预期输出 ```text === 数组 === 成绩:[80 90 75],长度:3 第一个成绩:80 修改后的成绩:[80 90 85] === 遍历与求和 === 第 1 个成绩:80 第 2 个成绩:90 第 3 个成绩:85 总分:255,平均分:85.00 === 切片与追加 === 切片:[10 20 30 40 50],长度:5 === 截取与共享 === numbers[1:3]:[20 30] 修改子切片后,原切片:[10 200 30 40 50] === 复制切片 === 复制了 5 个元素,副本:[999 200 30 40 50] 原切片保持不变:[10 200 30 40 50] === 从空切片开始 === 追加前:nil=true,长度=0,容量=0 任务:复习循环 任务:学习切片 === 数组赋值是复制 === 数组副本:[100 90 85],原数组:[80 90 85] ``` ## 动手练习 - [ ] 修改三个成绩,重新核对总分和平均分。 - [ ] 给 `numbers` 再追加两个整数,观察长度变化。 - [ ] 改为截取 `numbers[:2]`,预测输出后再运行。 - [ ] 观察修改子切片与修改独立副本,对原切片的影响有何不同。 - [ ] 为 `tasks` 追加第三个任务。 - [ ] 在求和循环中结合 `if score >= 90`,统计优秀成绩数量。 特别注意:本例数组非空,可以计算平均值;未来处理空切片时,应先判断长度,避免除以零。 如果另外手敲一份,放进本课的 `practice/` 子目录,用 `go run ./lessons/05_arrays_slices/practice` 运行,不要在同一个包中声明两个 `main` 函数。 下一课建议学 `map`,认识按“键”查找数据的方式。 [上一课:for 循环](../04_loops/README.md) · [学习总览](../../README.md)