README.md 4.8 KB

第 06 课:map 键值对

学习目标

数组与切片通过数字索引访问数据,map 则通过“键”查找“值”。本课用姓名查成绩,学习增删改查、判断键是否存在、遍历及 nil map。

按 main.go 中的 1~7 节逐段手敲。本课共享根目录的 go.mod,仅使用标准库,不需要新增依赖。

1. 声明和创建

scores := map[string]int{
    "小朱": 90,
    "小李": 75,
}

map[string]int 表示键是 string,值是 int。键唯一,给已有键赋值会覆盖原值,不会产生第二份同名记录。

多行复合字面量中,最后一项后面也需要逗号。

另一种方式是先创建空 map,再写入:

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 初始化语句

if score, ok := scores["小李"]; ok {
    fmt.Println(score)
} else {
    fmt.Println("没有找到")
}

分号前是查询并声明变量,分号后才是布尔条件。这里的 score、ok 只在整个 if / else 语句内有效,不会泄漏到语句外。

4. 遍历顺序不固定

for name, score := range scores {
    fmt.Println(name, score)
}

不能依赖 map 的插入顺序,也不能假设连续两次遍历顺序相同。本例直接遍历时只做总分统计,不依赖顺序。

如果需要按指定顺序显示,可以准备一个姓名切片逐项查询;如果需要自动排序,可在后续学习标准库 sort 后先排序键。直接打印整个 map 的表现不能代替对 range 顺序的理解。

5. nil map 与共享

var scores map[string]int

此时 scores == nil,长度为 0。读取得到零值,查询存在性得到 false,delete 也安全,但直接写入会触发运行时 panic。应先使用 make 或 map 字面量初始化。

alias := scores

对已初始化的 map 做这样的赋值不会独立复制元素,通过 alias 修改条目会影响 scores。需要独立副本时,应新建 map 并循环复制;未来涉及切片或指针值时还要区分浅拷贝和深拷贝。

编译并运行

在仓库根目录执行:

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

修改源码后需要重新编译。快速编译并运行:

go run ./lessons/06_maps

也可以从根目录进入本课:

Set-Location lessons/06_maps
go run .

预期输出

=== 创建与查询 ===
学生数量: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。

下一课建议正式学习函数:参数、返回值与多返回值,把计算逻辑拆分成可复用的小块。

上一课:数组与切片 · 学习总览