golang 闭包的使用
·
一:什么是闭包?先理解概念
闭包 = 函数 + 它“记住”的外部变量
打个比方:
-
你有一个函数(内部函数)
-
这个函数用到了外面函数里的变量
-
当外面函数返回这个内部函数时,这个内部函数一直记得那些变量
二:Go 中的闭包实现
Go 通过匿名函数 + 外部变量引用实现闭包。
基础语法
func 外部函数() func() 返回类型 {
变量 := 初始值
return func() 返回类型 {
// 使用外部变量
变量...
return 变量
}
}
三:第一个例子:计数器
package main
import "fmt"
// 创建一个计数器函数
func createCounter() func() int {
count := 0 // 这个变量会被闭包记住
// 返回匿名函数(闭包)
return func() int {
count++ // 修改外部变量
return count
}
}
func main() {
// 创建两个独立的计数器
counter1 := createCounter()
counter2 := createCounter()
fmt.Println(counter1()) // 1
fmt.Println(counter1()) // 2
fmt.Println(counter1()) // 3
fmt.Println(counter2()) // 1(独立的,重新开始)
fmt.Println(counter2()) // 2
}
解释:
-
count是createCounter的局部变量 -
返回的函数“记住”了
count -
每次调用返回的函数,
count都会保留上次的值 -
不同的调用创建不同的“记忆”
四:第二个例子:累加器(带初始值)
package main
import "fmt"
// 创建累加器,可以设置初始值
func makeAccumulator(initial int) func(int) int {
sum := initial
return func(add int) int {
sum += add
return sum
}
}
func main() {
// 从 10 开始累加
acc := makeAccumulator(10)
fmt.Println(acc(5)) // 15 (10+5)
fmt.Println(acc(3)) // 18 (15+3)
fmt.Println(acc(2)) // 20 (18+2)
}
五:实际应用场景
场景1:中间件(权限验证)
package main
import "fmt"
// 创建权限验证中间件
func authMiddleware(role string) func(string) bool {
// 闭包记住需要的角色
return func(userRole string) bool {
return userRole == role
}
}
func main() {
// 创建不同的权限验证器
adminAuth := authMiddleware("admin")
userAuth := authMiddleware("user")
fmt.Println(adminAuth("admin")) // true(管理员有权限)
fmt.Println(adminAuth("guest")) // false(游客没权限)
fmt.Println(userAuth("user")) // true
fmt.Println(userAuth("admin")) // false
}
场景2:数据缓存/记忆函数
package main
import "fmt"
// 创建一个带缓存的斐波那契计算器
func makeFibonacci() func(int) int {
cache := make(map[int]int) // 闭包记住这个缓存
var fib func(int) int
fib = func(n int) int {
// 先从缓存查找
if val, exists := cache[n]; exists {
return val
}
// 计算并存入缓存
if n <= 1 {
cache[n] = n
} else {
cache[n] = fib(n-1) + fib(n-2)
}
return cache[n]
}
return fib
}
func main() {
fib := makeFibonacci()
fmt.Println(fib(10)) // 55(计算并缓存)
fmt.Println(fib(10)) // 55(直接从缓存取,很快)
fmt.Println(fib(5)) // 5(已经从缓存得到)
}
场景3:迭代器生成器
package main
import "fmt"
// 生成偶数的迭代器
func evenNumberGenerator() func() int {
current := 0
return func() int {
current += 2
return current
}
}
// 生成指定范围内的数字
func rangeGenerator(start, end int) func() (int, bool) {
current := start
return func() (int, bool) {
if current > end {
return 0, false
}
value := current
current++
return value, true
}
}
func main() {
// 偶数生成器
nextEven := evenNumberGenerator()
fmt.Println(nextEven()) // 2
fmt.Println(nextEven()) // 4
fmt.Println(nextEven()) // 6
// 范围迭代器
nextNum, hasNext := rangeGenerator(1, 3)
for {
if num, ok := nextNum(); ok {
fmt.Println(num) // 1, 2, 3
} else {
break
}
}
}
场景4:延迟执行(装饰器模式)
package main
import (
"fmt"
"time"
)
// 计时装饰器
func timingDecorator(f func()) func() {
return func() {
start := time.Now()
f() // 执行原函数
elapsed := time.Since(start)
fmt.Printf("执行耗时:%v\n", elapsed)
}
}
// 日志装饰器
func logDecorator(f func(string), prefix string) func(string) {
return func(msg string) {
fmt.Printf("[%s] %s: %s\n", time.Now().Format("15:04:05"), prefix, msg)
f(msg)
}
}
func main() {
// 原始函数
work := func() {
time.Sleep(1 * time.Second)
fmt.Println("执行任务...")
}
// 添加计时功能(闭包记住了work函数)
timedWork := timingDecorator(work)
timedWork()
// 添加日志功能
printMsg := func(msg string) {
fmt.Println("输出:", msg)
}
loggedPrint := logDecorator(printMsg, "INFO")
loggedPrint("Hello World")
}
六:闭包的内存原理(通俗版)
func outer() func() {
x := 10
return func() {
fmt.Println(x)
}
}
发生了什么:
-
创建
outer函数时,x分配在堆上(不是栈) -
返回的匿名函数持有
x的引用 -
即使
outer执行完毕,x不会被销毁 -
只要返回的函数还存在,
x就继续存活
七:常见陷阱和注意事项
陷阱1:循环变量问题
package main
import "fmt"
func main() {
// ❌ 错误示例
funcs := []func(){}
for i := 0; i < 3; i++ {
funcs = append(funcs, func() {
fmt.Println(i) // 所有闭包都引用同一个i
})
}
for _, f := range funcs {
f() // 输出: 3 3 3(不是 0 1 2)
}
// ✅ 正确示例:创建副本
funcs2 := []func(){}
for i := 0; i < 3; i++ {
i2 := i // 创建新变量
funcs2 = append(funcs2, func() {
fmt.Println(i2)
})
}
for _, f := range funcs2 {
f() // 输出: 0 1 2
}
}
陷阱2:意外共享变量
package main
import "fmt"
func main() {
// 错误:共享同一个变量
var funcs []func()
shared := 0
for i := 0; i < 3; i++ {
funcs = append(funcs, func() {
shared += i
fmt.Println(shared)
})
}
// 正确:每次创建独立的闭包
makeFunc := func(base int) func() {
return func() {
fmt.Println(base)
}
}
}
八:闭包 vs 普通函数对比
| 特性 | 普通函数 | 闭包 |
|---|---|---|
| 状态保持 | 需要全局变量或参数传递 | 自动记住外部变量 |
| 独立性 | 所有调用共享全局状态 | 每次创建独立状态 |
| 代码复用 | 通过参数配置 | 通过创建时配置 |
| 内存 | 函数执行完释放 | 闭包存在就保留状态 |
九:什么时候用闭包?
✅ 适合用的场景:
-
需要保持状态的小型功能(计数器、累加器)
-
函数工厂(根据参数生成不同行为的函数)
-
延迟执行或装饰器模式
-
迭代器/生成器
-
回调函数需要访问上下文
❌ 不适合的场景:
-
逻辑复杂、代码量大的功能(用结构体+方法更好)
-
需要大量共享状态(用结构体更清晰)
-
性能要求极高(闭包有额外内存开销)
十:总结
闭包的本质:函数 + 它所引用的外部变量的环境
Go 实现闭包的关键:
-
匿名函数(
func() { ... }) -
引用外部函数的变量
-
返回匿名函数
记忆口诀:
-
函数套函数,里用外变量
-
返回里函数,记住外变量
-
每次调用,独立状态
更多推荐


所有评论(0)