一、Map 基础
1.1 什么是 Map
Map(映射)用于将键(key) 映射到值(value),实现键值对存储。
1.2 nil Map 与 make 初始化
未初始化的 map 变量零值是 nil,此时内部没有任何键,不能直接执行赋值操作 ,否则会运行时报错 panic: assignment to entry in nil map。
必须使用内置函数 make(map[键类型]值类型) 初始化 map,分配内存后才能正常增删改查。
// 仅声明,m 是 nil map,不可写入
var m map[string]Vertex
// make 初始化,分配内存,可正常使用
m = make(map[string]Vertex)
// 赋值
m["Bell Labs"] = Vertex{40.68433, -74.39967}
1.3 结构体 Vertex
Vertex 是自定义结构体,用来打包一组关联数据。
type Vertex struct {
Lat, Long float64
}
-
Lat = Latitude(纬度),float64 高精度浮点数
-
Long = Longitude(经度),float64 高精度浮点数
简写写法等价于分开写两个字段,按顺序赋值。结合 map 使用时,实现「地名 → 精确地理坐标」的对应存储。
二、Map 字面量初始化
2.1 完整写法
声明 + 初始化 + 填充数据一步到位,无需先 make 再逐个赋值。
var m = map[string]Vertex{
"Bell Labs": Vertex{40.68433, -74.39967},
"Google": Vertex{37.42202, -122.08408},
}
2.2 简写写法(省略类型名)
如果顶层类型只是个类型名,可以从字面量元素中省略它。编译器自动识别 {} 对应 Vertex 结构体。
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
**注意:**省略时必须按结构体字段顺序传值,顺序不能颠倒。仅顶层类型为简单类型名时才能省略。
2.3 三种初始化方式对比
| 方式 | 适用场景 | 特点 |
|---|---|---|
| make 分步赋值 | 运行时动态新增键 | 代码较长,灵活 |
| 字面量完整写法 | 已知全部初始数据 | 清晰直观 |
| 字面量简写 | 工程常用 | 简洁,需按顺序 |
三、Map 增删改查四大操作
3.1 插入 / 更新元素
语法:m[key] = elem
-
key 不存在:新增键值对(插入)
-
key 已存在:覆盖原有值(更新)
3.2 检索元素(单返回值)
语法:elem = m[key]
-
key 存在:返回对应值
-
key 不存在:返回值类型零值(int 零值为 0)
3.3 删除元素
语法:delete(m, key)
内置函数 delete,键不存在也不会报错,无任何操作。
3.4 检测键是否存在(双返回值)
语法:elem, ok := m[key]
-
elem:键对应的值(不存在则为零值) -
ok:布尔值,true = 键存在,false = 键不存在
**易错点:**单取值无法区分「值为 0」和「键不存在」,必须用双返回值判断真实状态。
m := make(map[string]int)
// 插入
m["Answer"] = 42
// 更新
m["Answer"] = 48
// 删除
delete(m, "Answer")
// 双返回值判断
v, ok := m["Answer"] // v=0, ok=false
四、WordCount 单词计数练习
实现函数 WordCount(s string) map[string]int,统计字符串中每个单词出现的次数。
package main
import (
"golang.org/x/tour/wc"
"strings"
)
func WordCount(s string) map[string]int {
countMap := make(map[string]int)
words := strings.Fields(s)
for _, word := range words {
countMap[word]++
}
return countMap
}
func main() {
wc.Test(WordCount)
}
4.1 strings.Fields 函数
按任意空白字符分割字符串,返回单词切片 []string。自动忽略连续多个空白,不会产生空字符串。
区别于 strings.Split(s, " "):Split 按单个空格分割,会出现空元素;Fields 专门用来提取纯单词。
4.2 countMapword++ 原理
map 自增简写是语法糖,等价于完整展开版:
v, ok := countMap[word]
if ok {
countMap[word] = v + 1
} else {
countMap[word] = 1
}
五、函数值(Function Values)
5.1 核心概念
函数也是值(一等公民),可以赋值给变量、作为函数参数和返回值使用。
只要函数参数列表、返回值列表完全一致,就属于同一个函数类型,可以互相传递。
5.2 函数变量
匿名函数赋值给变量,之后可以像普通函数一样调用。
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12)) // 13
5.3 函数作为参数(高阶函数)
func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}
fmt.Println(compute(hypot)) // 5(勾股定理)
fmt.Println(compute(math.Pow)) // 81(3的4次方)
只要函数签名匹配(入参 2 个 float64,返回 1 个 float64),无论是自定义匿名函数还是标准库函数,都能作为参数传入。
六、闭包(Closures)
6.1 闭包定义
闭包是一种函数值,它能引用函数主体外部定义的变量,可以读取和修改这些外部变量,相当于函数和外部变量绑定在了一起。
6.2 闭包三大核心特征
-
本质是匿名函数(函数值)
-
能捕获、访问、修改外层函数的局部变量
-
每个闭包实例持有独立的变量副本,互不干扰
6.3 adder 闭包示例
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(pos(i), neg(-2*i))
}
}
6.4 关键理解
-
sum := 0只在调用adder()时执行一次,后续调用闭包不会重置 -
两次调用
adder()创建两个完全独立的 sum 变量,pos 和 neg 互不影响 -
变量生命周期延长:外层函数执行完毕,变量依然存在,供闭包持续使用
通俗理解: adder() 是计数器工厂,每次调用造一个全新计数器,自带独立账本 sum。每个计数器各自累加,互不干扰。
七、斐波那契闭包练习
实现 fibonacci() func() int,每次调用闭包依次输出斐波那契数列:0, 1, 1, 2, 3, 5, 8...
func fibonacci() func() int {
a, b := 0, 1
return func() int {
res := a
a, b = b, a+b
return res
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
执行流程
| 调用次数 | res(输出) | 更新后 a | 更新后 b |
|---|---|---|---|
| 第1次 | 0 | 1 | 1 |
| 第2次 | 1 | 1 | 2 |
| 第3次 | 1 | 2 | 3 |
| 第4次 | 2 | 3 | 5 |
| 第5次 | 3 | 5 | 8 |
输出结果:0, 1, 1, 2, 3, 5, 8, 13, 21, 34