map
切片按下标 0、1、2 找元素。很多时候想按名字找:苹果多少钱、小林考了几分。这就是 map:一个键对应一个值。
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 建:
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}
votes["红"]++ 第一次时,「红」不存在,读到零值 0,加 1 存回去。所以「红」是 2、「蓝」是 1。读 votes["绿"] 得到 0,但只是读,不会把「绿」加进 map,所以一共 2 项。
可是 0 也可能是真的存了 0。要分清「存的是 0」和「根本没有」,用两个变量接:
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, 键) 删掉一项。删一个不存在的键也不会出错:
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 故意打乱,免得有人依赖它)。这个网站为了方便单步,顺序是固定的,可别当真。
只要结果和顺序无关,直接遍历没问题,比如求总数。要按顺序显示,先把键放进切片排好序,再按这个顺序取值:
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:
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 当计数器,是最常见的用法之一:键是单词,值是出现了几次。
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 课会学怎么把一句话切成单词。
练一练
写好代码,点检查。提示随便看,实在想不出来再看答案。
查库存
✓写好 check:商品在 stock 里,返回 有货;不在,返回 没有这个商品。注意「尺子」存的是 0,它也算在 map 里,返回 有货(这一题只管在不在)。
有货 有货 没有这个商品
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}
统计投票
✓写好 tally:数一数每个名字得了几票,返回一个 map。main 数两组票,应该显示下面两行。
map[Ann:3 Bo:1 Cy:1] map[Bo:2]
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}
修好它:写进 nil map
✓addAll 把每样东西的数量记进 map,可是程序一运行就 panic。改好它,让它显示下面两行。
map[牛奶:2 面包:1] map[鸡蛋:1]
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}
这一课学会了
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]++数东西,不用先判断键在不在。