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

相关推荐
杜子不疼.1 小时前
【C++ 在线五子棋对战】- 会话管理模块实现
开发语言·c++
有点。1 小时前
C++深度优先搜索(DFS)的概念(一)
开发语言·c++·深度优先
时间的拾荒人2 小时前
C语言编译与链接:从源码到可执行程序的完整解析
c语言·开发语言
人道领域2 小时前
【0-1的agent进阶篇】Prompt 与上下文工程
java·开发语言·prompt·mcp
aaPIXa6222 小时前
C++大型项目模块化拆分实战记录
开发语言·c++
z落落2 小时前
C# WinForm 线程池与事件等待+进度条暂停恢复实战案例
开发语言·c#
石山代码2 小时前
C++23 新特性在 CLion 中的实战体验
开发语言·c++·c++23
浮江雾2 小时前
Flutter第四节------核心概念学习笔记:从基础语法到异步编程
linux·服务器·开发语言·笔记·flutter·入门·基础
北冥you鱼2 小时前
Go Modules 使用指南:从入门到精通
开发语言·后端·golang