Go语言基础 Map 函数值 闭包

一、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 闭包三大核心特征

  1. 本质是匿名函数(函数值)

  2. 能捕获、访问、修改外层函数的局部变量

  3. 每个闭包实例持有独立的变量副本,互不干扰

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

相关推荐
懂点Tech3 小时前
ctfshow MISC入门图片篇(信息附加):misc41解题思路
开发语言·python
catchadmin3 小时前
PHP 8.7 协程新提案 让跨协程代码共享请求身份
开发语言·php
念何架构之路4 小时前
go-grpc设计思想
开发语言·后端·golang
酷炫码神5 小时前
Day 10|Pinia 状态管理:把散落在各组件的数据,收进一个全局仓库
android·开发语言·javascript
microrain5 小时前
SagooIoT 的缓存换后端只改一行配置,文件后端连 Redis 都不需要
物联网·缓存·golang·sagooiot
Elastic 中国社区官方博客5 小时前
我们如何通过 70 多个拉取请求,将 OpenTelemetry Java agent 迁移到 invokedynamic
java·大数据·开发语言·elasticsearch·搜索引擎·全文检索·es
tyn1886 小时前
记录本地安装nacos服务的方法及异常
java·开发语言
步行cgn6 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
喵了几个咪7 小时前
GoWind ShortPlay 深度拆解:三服务 BFF + Core,一条请求的完整工程链路
golang·gowind
传奇开心果编程7 小时前
【Rust入门练中学】第5课:引用与借用
开发语言·rust·引用·编程语言理论·通用编程语言·借用