|
|
@@ -0,0 +1,157 @@
|
|
|
+# 第 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)
|