7.2 Go Map 进阶

7.2 Go Map 进阶

1. Map 的值为切片 --- map[K][]V

map 的值可以是切片,实现"一个键对应多个值"的数据结构:

go 复制代码
package main

import "fmt"

func main() {
    // map[string][]int --- 学生名 → 成绩列表
    studentScores := make(map[string][]int)

    // 直接赋值切片
    studentScores["Alice"] = []int{92, 88, 95}
    studentScores["Bob"] = []int{78, 82, 85}
    studentScores["Charlie"] = []int{90, 87, 93}

    // 也可以 append 添加(nil 切片 append 安全)
    // studentScores["David"] = append(studentScores["David"], 80)

    fmt.Println("Student scores:", studentScores)
}

执行结果:

复制代码
Student scores: map[Alice:[92 88 95] Bob:[78 82 85] Charlie:[90 87 93]]

要点

  • map[string][]int --- 值类型是 []int 切片,一个学生对应多个成绩
  • 赋值方式1:直接赋值切片 studentScores["Alice"] = []int{92, 88, 95}
  • 赋值方式2:逐个 append studentScores["Alice"] = append(studentScores["Alice"], 92)
  • nil 切片 append 安全:studentScores["David"] 为 nil,append 自动分配底层数组
  • 这是分组模式的基础:按键归类多个值

2. 嵌套 map --- 安全访问两层键

嵌套 map 访问时必须逐层检查键是否存在,否则可能 panic:

go 复制代码
package main

import "fmt"

func main() {
    // 嵌套 map:科目 → 学生 → 成绩
    gradebook := make(map[string]map[string]int)

    // 必须初始化内层 map!
    gradebook["Math"] = make(map[string]int)
    gradebook["Science"] = make(map[string]int)

    gradebook["Math"]["Alice"] = 92
    gradebook["Math"]["Bob"] = 85
    gradebook["Science"]["Alice"] = 89
    gradebook["Science"]["Bob"] = 88

    // 安全访问:两层 comma-ok 检查
    // 先检查 "Math" 是否存在,再检查 "Alice" 是否存在
    mathGrades, mathExists := gradebook["Math"]
    if mathExists {
        aliceMath, aliceExists := mathGrades["Alice"]
        if aliceExists {
            fmt.Printf("Alice's Math grade: %d\n", aliceMath)
        }
    }

    fmt.Println("Gradebook:", gradebook)
}

执行结果:

复制代码
Alice's Math grade: 92
Gradebook: map[Math:map[Alice:92 Bob:85] Science:map[Alice:89 Bob:88]]

要点

  • make(map[string]map[string]int) --- 外层科目,内层学生成绩
  • 必须初始化内层 mapgradebook["Math"] = make(map[string]int)
    • 不初始化:gradebook["Math"]["Alice"] = 92 会 panic(内层是 nil map)
  • 两层 comma-ok 安全访问:
    • 第1层:mathGrades, mathExists := gradebook["Math"]
    • 第2层:aliceMath, aliceExists := mathGrades["Alice"]
  • 不安全访问的后果:
    • gradebook["English"]["Alice"] --- "English" 不存在,返回 nil 内层 map,写入 panic
    • gradebook["Math"]["Unknown"] --- "Unknown" 不存在,返回零值 0(不 panic,但可能误判)

3. Map 的值为结构体 --- 修改需重新赋值

map 中结构体值是不可直接修改字段的,必须取出 → 修改 → 重新赋值:

go 复制代码
package main

import "fmt"

func main() {
    type Person struct {
        Age  int
        City string
    }

    people := make(map[string]Person)
    people["John"] = Person{Age: 30, City: "New York"}
    people["Jane"] = Person{Age: 25, City: "Los Angeles"}

    // 错误方式:直接修改字段(编译报错!)
    // people["John"].Age = 31  // compile error: cannot assign to struct field in map

    // 正确方式:取出 → 修改 → 重新赋值
    john := people["John"] // 取出结构体(值拷贝)
    john.Age = 31          // 修改拷贝
    people["John"] = john  // 重新赋值回 map

    fmt.Println("People:", people)
}

执行结果:

复制代码
People: map[Jane:{25 Los Angeles} John:{31 New York}]

要点

  • people["John"].Age = 31 --- 编译报错!Go 不允许直接修改 map 中结构体的字段

  • 原因:map 的值是不可寻址的(addressable)。people["John"] 返回的是值的拷贝,不是引用

  • 正确步骤:

    1. john := people["John"] --- 取出值拷贝
    2. john.Age = 31 --- 修改拷贝的字段
    3. people["John"] = john --- 把修改后的值重新赋值回 map
  • 这个限制只针对值类型 (struct、int 等),如果值是指针类型 则可以直接修改:

    go 复制代码
    people := make(map[string]*Person)
    people["John"] = &Person{Age: 30, City: "New York"}
    people["John"].Age = 31  // 可以!指针可寻址
  • 也可以用指针 map 避免反复取出赋值:map[string]*Person


4. Map 模拟集合(Set)--- 去重

map[key]bool 的键来模拟集合,实现去重:

go 复制代码
package main

import "fmt"

func main() {
    uniqueWords := make(map[string]bool)
    words := []string{"hello", "world", "hello", "go", "world"}

    // 添加到集合:键存在即表示该词出现过
    for _, word := range words {
        uniqueWords[word] = true
    }

    // 从集合获取所有元素
    var unique []string
    for word := range uniqueWords {
        unique = append(unique, word)
    }

    fmt.Println("Unique words:", unique)
}

执行结果:

复制代码
Unique words: [hello world go]

要点

  • uniqueWords[word] = true --- 用 bool 值标记,true 表示元素在集合中
  • "hello" 和 "world" 出现两次,但 map 键唯一,所以只保留一个
  • 从集合提取元素:遍历 map 的键,for word := range uniqueWords
  • 集合特性:自动去重、快速判断存在(uniqueWords[word] 返回 true/false)
  • 遍历顺序不确定:输出可能是 [hello world go][go hello world]

5. Map 合并 --- 后者覆盖前者

合并两个 map 时,重复键由后写入的值决定(覆盖):

go 复制代码
package main

import "fmt"

func main() {
    map1 := map[string]int{"a": 1, "b": 2}
    map2 := map[string]int{"b": 3, "c": 4} // "b" 在两个 map 中都存在

    merged := make(map[string]int)

    // 先复制 map1
    for k, v := range map1 {
        merged[k] = v // merged: {a:1, b:2}
    }

    // 再复制 map2("b" 的值被覆盖)
    for k, v := range map2 {
        merged[k] = v // merged: {a:1, b:3, c:4} --- b:2 被覆盖为 b:3
    }

    fmt.Println("Merged maps:", merged)
}

执行结果:

复制代码
Merged maps: map[a:1 b:3 c:4]

要点

  • 合并方法:遍历两个 map,依次写入新 map

  • map1b:2map2b:3,合并后 merged["b"] = 3(map2 的值覆盖了 map1 的)

  • 覆盖规则:后写入的值覆盖先写入的值

  • 如果想保留 map1 的值优先,先复制 map2 再复制 map1:

    go 复制代码
    for k, v := range map2 { merged[k] = v } // map2 先
    for k, v := range map1 { merged[k] = v } // map1 覆盖 → b:2
  • Go 没有内置的 map 合并函数,需要手动遍历合并


知识点总结

知识点 关键概念
map 值为切片 map[K][]V --- 一个键对应多个值,nil切片append安全
嵌套 map map[K1]map[K2]V --- 必须逐层初始化内层,两层 comma-ok 安全访问
map 值为结构体 不能直接修改字段,必须取出→修改→重新赋值;用指针可直接修改
map 模拟集合 map[K]bool 去重,键唯一自动去重
map 合并 依次遍历写入新 map,后者覆盖前者(重复键由最后写入的值决定)
相关推荐
西瓜很甜哟11 小时前
golang每日十题第2期
golang
程序员小八77715 小时前
Go 语言特性
开发语言·后端·golang
X1A0RAN19 小时前
pytest 动态报告:数据可视化篇 —— 用 Golang + Echo 打造自动化测试任务可视化平台
信息可视化·golang·pytest
圣殿骑士-Khtangc1 天前
Go-sync包并发原语详解从Mutex到Once的深度应用
golang
灯澜忆梦1 天前
【基于GO的Web开发15】gin路由和路由组
前端·后端·golang·gin
xcLeigh1 天前
Go入门:rune与byte的区别和使用场景
android·javascript·golang
深念Y1 天前
数据库层设计的取舍:ORM 便利性与手写 SQL 的安全性权衡
java·数据库·sql·golang·框架·语言·ome
深念Y1 天前
Makefile vs go run:Go 项目构建方式对比
java·开发语言·golang·k8s·编译·流水线·cicd
西瓜很甜哟1 天前
golang每日十题
golang