Kodewick
开始学 →
第 8 课,共 24 课目录

map

第 8 课,共 24 课 · 第 2 部分 · 数据 · 约 15 分钟

切片按下标 0、1、2 找元素。很多时候想按名字找:苹果多少钱、小林考了几分。这就是 map:一个键对应一个值。

prog.go
1package main2 3import "fmt"4 5func main() {6	prices := map[string]int{7		"苹果": 5,8		"牛奶": 12,9		"面包": 8,10	}11	fmt.Println(prices["牛奶"])12	fmt.Println(prices)13}
输出
12
map[牛奶:12 苹果:5 面包:8]

map[string]int 是「键是 string、值是 int 的 map」。花括号里每一项写成 键: 值,每行末尾都要有逗号(最后一行也要)。prices["牛奶"] 按键取值。

fmt.Println 显示整个 map 时,写成 map[键:值 …],键排好了顺序。

注意: 汉字按编码排,不是按拼音或笔画。

存、改和 len

m[键] = 值 存一项:键还没有就加进去,已经有了就改掉原来的值。len(m) 是有几项。空 map 用 make 建:

prog.go
1package main2 3import "fmt"4 5func main() {6	stock := make(map[string]int)7	stock["铅笔"] = 408	stock["橡皮"] = 159	fmt.Println(stock, len(stock))10 11	stock["铅笔"] = 3512	stock["橡皮"] += 1013	fmt.Println(stock, len(stock))14}
输出
map[橡皮:15 铅笔:40] 2
map[橡皮:25 铅笔:35] 2

同一个键只有一项,所以改了「铅笔」以后,还是 2 项。

取一个不存在的键

取一个 map 里没有的键,不会出错,得到的是值类型的零值:int 是 0,string 是 ""。

先猜一猜

这段程序显示什么?

1package main2 3import "fmt"4 5func main() {6	votes := map[string]int{}7	votes["红"]++8	votes["蓝"]++9	votes["红"]++10	fmt.Println(votes["红"], votes["绿"], len(votes))11}

可是 0 也可能是真的存了 0。要分清「存的是 0」和「根本没有」,用两个变量接:

prog.go
1package main2 3import "fmt"4 5func main() {6	stock := map[string]int{"铅笔": 40, "尺子": 0}7 8	n, ok := stock["尺子"]9	fmt.Println(n, ok)10	n, ok = stock["胶水"]11	fmt.Println(n, ok)12 13	if n, ok := stock["铅笔"]; ok {14		fmt.Println("铅笔还有", n, "支")15	}16}
输出
0 true
0 false
铅笔还有 40 支

ok 是 true 说明这个键在 map 里。最后一种写法很常见:if 带初始化,在 if 里用 n。

delete:删掉一项

delete(m, 键) 删掉一项。删一个不存在的键也不会出错:

prog.go
1package main2 3import "fmt"4 5func main() {6	cart := map[string]int{"苹果": 3, "牛奶": 1, "面包": 2}7	delete(cart, "牛奶")8	delete(cart, "鸡蛋")9	fmt.Println(cart, len(cart))10}
输出
map[苹果:3 面包:2] 2

遍历的顺序不固定

for k, v := range m 能走过每一项。但是 map 不记顺序:真 Go 每次运行的顺序都可能不一样(Go 故意打乱,免得有人依赖它)。这个网站为了方便单步,顺序是固定的,可别当真。

只要结果和顺序无关,直接遍历没问题,比如求总数。要按顺序显示,先把键放进切片排好序,再按这个顺序取值:

prog.go
1package main2 3import (4	"fmt"5	"slices"6)7 8func main() {9	scores := map[string]int{"小林": 88, "阿明": 92, "大卫": 75}10 11	total := 012	for _, v := range scores {13		total += v14	}15	fmt.Println("总分", total)16 17	var names []string18	for name := range scores {19		names = append(names, name)20	}21	slices.Sort(names)22	for _, name := range names {23		fmt.Println(name, scores[name])24	}25}
输出
总分 255
大卫 75
小林 88
阿明 92

for name := range scores 只要键。slices.Sort 把切片从小到大排好,要 import "slices"。

写 nil map 会 panic

只声明不建的 map 是 nil。读它没问题(得到零值),往里写就会 panic:

prog.go
1package main2 3import "fmt"4 5func main() {6	var counts map[string]int7	fmt.Println(counts["a"], len(counts))8	counts["a"] = 19	fmt.Println(counts)10}
输出
0 0
panic: assignment to entry in nil map

goroutine 1 [running]:
main.main(...)
	/tmp/sandbox/prog.go:8 +0x1d
exit status 2

发生了什么: map 还没有 make 就往里写。先 m := make(map[…]…) 或写成 map[…]…{}。

assignment to entry in nil map:往一个 nil map 里存东西。var counts map[string]int 只有一个空名字,背后还没有 map。用 make(map[string]int) 或者 map[string]int{} 建好再存。

数单词

把 map 当计数器,是最常见的用法之一:键是单词,值是出现了几次。

prog.go
1package main2 3import "fmt"4 5func main() {6	words := []string{"苹果", "香蕉", "苹果", "橙子", "苹果", "香蕉"}7	counts := make(map[string]int)8	for _, w := range words {9		counts[w]++10	}11	fmt.Println(counts)12	fmt.Println("苹果出现了", counts["苹果"], "次")13}
输出
map[橙子:1 苹果:3 香蕉:2]
苹果出现了 3 次

counts[w]++ 不用先判断键在不在:不在的时候读到 0,加 1 正好。点单步,看 map 里的项一个个多起来、数一个个变大。第 9 课会学怎么把一句话切成单词。

练一练

写好代码,点检查。提示随便看,实在想不出来再看答案。

练习 1

查库存

写好 check:商品在 stock 里,返回 有货;不在,返回 没有这个商品。注意「尺子」存的是 0,它也算在 map 里,返回 有货(这一题只管在不在)。

要显示
有货
有货
没有这个商品
exercise1.go
1package main2 3import "fmt"4 5func check(stock map[string]int, item string) string {6	return ""7}8 9func main() {10	stock := map[string]int{"铅笔": 40, "尺子": 0}11	fmt.Println(check(stock, "铅笔"))12	fmt.Println(check(stock, "尺子"))13	fmt.Println(check(stock, "胶水"))14}
输出
点「运行」或按 Ctrl+Enter
练习 2

统计投票

写好 tally:数一数每个名字得了几票,返回一个 map。main 数两组票,应该显示下面两行。

要显示
map[Ann:3 Bo:1 Cy:1]
map[Bo:2]
exercise2.go
1package main2 3import "fmt"4 5func tally(votes []string) map[string]int {6	return nil7}8 9func main() {10	fmt.Println(tally([]string{"Ann", "Bo", "Ann", "Cy", "Ann"}))11	fmt.Println(tally([]string{"Bo", "Bo"}))12}
输出
点「运行」或按 Ctrl+Enter
练习 3

修好它:写进 nil map

addAll 把每样东西的数量记进 map,可是程序一运行就 panic。改好它,让它显示下面两行。

要显示
map[牛奶:2 面包:1]
map[鸡蛋:1]
exercise3.go
1package main2 3import "fmt"4 5func addAll(items []string) map[string]int {6	var cart map[string]int7	for _, it := range items {8		cart[it]++9	}10	return cart11}12 13func main() {14	fmt.Println(addAll([]string{"牛奶", "面包", "牛奶"}))15	fmt.Println(addAll([]string{"鸡蛋"}))16}
输出
点「运行」或按 Ctrl+Enter

这一课学会了

  • map[string]int{…} 或 make(map[string]int) 建 map;m[k] = v 存或改,m[k] 取,len(m) 是项数。
  • 取不存在的键得到零值;v, ok := m[k] 的 ok 说明键在不在。
  • delete(m, k) 删掉一项。
  • 遍历 map 的顺序真 Go 每次不一样:要按顺序显示,先把键放进切片 slices.Sort 排好。fmt.Println(m) 会把键排好序。
  • 只声明的 map 是 nil,读可以,写会 panic:assignment to entry in nil map。
  • counts[w]++ 数东西,不用先判断键在不在。
这一课学完了吗?点一下标记。进度存在这个浏览器里,随时可以撤销。