# 第 06 课:map 键值对 ## 学习目标 数组与切片通过数字索引访问数据,map 则通过“键”查找“值”。本课用姓名查成绩,学习增删改查、判断键是否存在、遍历及 nil map。 按 [main.go](./main.go) 中的 1~7 节逐段手敲。本课共享根目录的 `go.mod`,仅使用标准库,不需要新增依赖。 ## 1. 声明和创建 ```go scores := map[string]int{ "小朱": 90, "小李": 75, } ``` `map[string]int` 表示键是 `string`,值是 `int`。键唯一,给已有键赋值会覆盖原值,不会产生第二份同名记录。 多行复合字面量中,最后一项后面也需要逗号。 另一种方式是先创建空 map,再写入: ```go scores := make(map[string]int) scores["小朱"] = 90 ``` map 的键必须是可比较类型;本课先使用字符串。整数也可以做键,切片和 map 本身不能做键。 ## 2. 增删改查 | 操作 | 示例 | | --- | --- | | 新增或修改 | `scores["小李"] = 80` | | 读取 | `score := scores["小李"]` | | 判断是否存在 | `score, ok := scores["小李"]` | | 删除 | `delete(scores, "小李")` | | 元素数量 | `len(scores)` | 读取不存在的键会得到值类型的零值,**不会自动创建这个键**。因此值为 `0` 不代表键不存在,需要用 `ok` 判断。 删除不存在的键不会报错。map 没有切片那样的 `append`,新增直接通过键赋值,也不能用 `cap` 查询容量。 ## 3. if 初始化语句 ```go if score, ok := scores["小李"]; ok { fmt.Println(score) } else { fmt.Println("没有找到") } ``` 分号前是查询并声明变量,分号后才是布尔条件。这里的 `score`、`ok` 只在整个 if / else 语句内有效,不会泄漏到语句外。 ## 4. 遍历顺序不固定 ```go for name, score := range scores { fmt.Println(name, score) } ``` 不能依赖 map 的插入顺序,也不能假设连续两次遍历顺序相同。本例直接遍历时只做总分统计,不依赖顺序。 如果需要按指定顺序显示,可以准备一个姓名切片逐项查询;如果需要自动排序,可在后续学习标准库 `sort` 后先排序键。直接打印整个 map 的表现不能代替对 range 顺序的理解。 ## 5. nil map 与共享 ```go var scores map[string]int ``` 此时 `scores == nil`,长度为 `0`。读取得到零值,查询存在性得到 `false`,delete 也安全,但直接写入会触发运行时 panic。应先使用 `make` 或 map 字面量初始化。 ```go alias := scores ``` 对已初始化的 map 做这样的赋值不会独立复制元素,通过 alias 修改条目会影响 scores。需要独立副本时,应新建 map 并循环复制;未来涉及切片或指针值时还要区分浅拷贝和深拷贝。 ## 编译并运行 在**仓库根目录**执行: ```powershell New-Item -ItemType Directory -Force bin/06_maps | Out-Null go build -o ./bin/06_maps/maps.exe ./lessons/06_maps .\bin\06_maps\maps.exe ``` 修改源码后需要重新编译。快速编译并运行: ```powershell go run ./lessons/06_maps ``` 也可以从根目录进入本课: ```powershell Set-Location lessons/06_maps go run . ``` ## 预期输出 ```text === 创建与查询 === 学生数量:3 小朱的成绩:90 === 新增与修改 === 新增小陈:88,修改小李:80 学生数量:4 === 判断键是否存在 === 直接查询小赵:0 查询小王:成绩=0,存在=true 查询小赵:成绩=0,存在=false 找到小李,成绩:80 === 删除 === 删除后小王存在:false,学生数量:3 === 遍历与统计 === 总分:258 平均分:86.00 小朱:90 小李:80 小陈:88 === nil 与 make === 初始化前:nil=true,长度=0,读取结果=false 初始化后:nil=false,复习完成=true === 赋值与共享 === 通过 alias 修改后,小朱的原成绩也变为:95 ``` ## 动手练习 - [ ] 新增一个学生,检查 `len` 和总分如何变化。 - [ ] 查询成绩为 0 的学生和不存在的学生,比较 `ok`。 - [ ] 把 if 查询的姓名改成不存在的姓名,观察 else 分支。 - [ ] 自己写一次 `for name, score := range scores` 输出,理解顺序不保证固定。 - [ ] 删除一个键后,用 `_, ok := ...` 检查是否还存在。 - [ ] 创建空 map,循环复制 scores,修改副本后验证原 map 不受影响。 增加或删除学生后,固定姓名切片不会自动更新,需要同步调整。默认代码在修改 alias 前已经统计过总分,所以修改发生后,如需新总分,应重新计算。 若要另外手敲,放进本课 `practice/` 子目录,用 `go run ./lessons/06_maps/practice` 运行,不要在同一个包里重复声明 main。 下一课建议正式学习函数:参数、返回值与多返回值,把计算逻辑拆分成可复用的小块。 [上一课:数组与切片](../05_arrays_slices/README.md) · [学习总览](../../README.md)