匿名函数&闭包(golang)
1.go匿名函数
1.1 基本概念
匿名函数是指没有函数名的函数定义,可以直接定义并使用。在Go中,函数是一等公民,可以像普通变量一样被赋值和传递。
1.2 基本语法
func(参数列表) 返回值类型 {
// 函数体
}
1.3 使用方式
1.3.1 直接调用
package main
import "fmt"
func main() {
result := func(a, b int) int {
return a + b
}(3, 6)
fmt.Println(result)
}
输出:
1.3.2 赋值给变量
package main
import "fmt"
func main() {
add := func(a, b int) int {
return a + b
}
fmt.Println(add(4, 9))
}
输出:
1.3.3 作为参数传递
package main
import "fmt"
func calculate(a, b int, op func(int, int) int) int {
return op(a, b)
}
func main() {
sum := calculate(3, 4, func(x, y int) int {
return x + y
})
fmt.Println(sum)
}
输出:
2.闭包
2.1 基本概念
闭包是匿名函数的一个特殊形式,它可以捕获并持有其外部作用域中的变量。即使外部函数已经执行完毕,闭包仍然可以访问这些变量。
2.2 闭包的特性
2.2.1 捕获外部变量(闭包可以访问定义它的函数中的变量)
package main
import "fmt"
func main() {
x := 10
closure := func() {
fmt.Println(x) // 可以访问外部变量x
x++ // 也可以修改它
}
closure() // 输出: 10
fmt.Println(x) // 输出: 11 (x被修改了)
}
2.2.2 保持状态(闭包会"记住"这些变量的值)
package main
import "fmt"
func main() {
nextInt := intSeq()
fmt.Println(nextInt()) // 1
fmt.Println(nextInt()) // 2
fmt.Println(nextInt()) // 3
}
func intSeq() func() int {
i := 0
return func() int {
i++
return i
}
}
2.2.3 独立状态(每次创建闭包都会创建新的状态)
package main
import "fmt"
func intSeq() func() int {
i := 0
return func() int {
i++
return i
}
}
func main() {
a := intSeq()
b := intSeq()
fmt.Println(a()) // 1
fmt.Println(a()) // 2
fmt.Println(b()) // 1 (新的闭包,新的状态)
fmt.Println(a()) // 3
}
2.3 基本示例
func makeCounter() func() int {
count := 0
return func() int {
count++
return count
}
}
func main() {
counter1 := makeCounter()
fmt.Println(counter1()) // 1
fmt.Println(counter1()) // 2
counter2 := makeCounter()
fmt.Println(counter2()) // 1 (新的闭包,新的状态)
fmt.Println(counter1()) // 3 (原来的闭包继续维持状态)
}
2.4 闭包的常见用途
2.4.1 实现生成器
package main
import "fmt"
func idGenerator() func() int {
id := 0
return func() int {
id++
return id
}
}
func main() {
gen := idGenerator()
fmt.Println(gen()) // 1
fmt.Println(gen()) // 2
}
2.4.2 函数工厂
package main
import "fmt"
func makeAdder(x int) func(int) int {
return func(y int) int {
return x + y
}
}
func main() {
add := makeAdder(5)
fmt.Println(add(3)) // 8
}
2.4.3 延迟执行
package main
import "fmt"
func main() {
x := 10
defer func() {
fmt.Println("延迟执行时x的值:", x)
}()
x = 20
}
输出:
原因分析:
1.defer 捕获的是变量,不是值
- 当使用 defer 调用一个匿名函数时,该函数会形成一个闭包,捕获的是变量 x 的引用,而不是 x 在 defer 语句执行时的值(即
10)。 - 因此,x 后续被修改为 20 时,defer 函数内部访问的是最终的 x 值。
2.执行顺序
- x := 10 → x 初始化为 10
- defer 注册匿名函数,但不立即执行(此时 x 是 10,但 defer 捕获的是 x 本身)
- x = 20 → x 被修改为 20
- main() 函数结束,defer 函数执行,此时读取 x 的值是 20
2.4.4 在并发编程中使用
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 5; i++ {
go func(n int) {
fmt.Println(n)
}(i) // 注意这里传递i作为参数
}
time.Sleep(time.Second)
}
输出:
3.匿名函数与闭包的区别
特性 | 匿名函数 | 闭包 |
---|---|---|
定义 | 没有函数名的函数 | 能捕获外部变量的匿名函数 |
变量访问 | 只能访问自己的局部变量 | 可以访问外部函数的变量 |
状态保持 | 无状态 | 可以保持状态 |
内存占用 | 较小 | 可能较大(因为要保存捕获的变量) |
典型用途 | 简单的一次性操作 | 需要保持状态的场景 |
4.注意事项
4.1 循环中的闭包陷阱
package main
import (
"fmt"
)
func main() {
var funcs []func()
for i := 0; i < 3; i++ {
funcs = append(funcs, func() {
fmt.Println(i) // 全部输出3
})
}
for _, f := range funcs {
f()
}
// 正确写法
var funcs2 []func()
for i := 0; i < 3; i++ {
j := i // 创建局部变量副本
funcs2 = append(funcs2, func() {
fmt.Println(j) // 输出0,1,2
})
}
for _, f := range funcs2 {
f()
}
}
4.2 内存泄露风险
package main
func main() {
bigData := make([]byte, 1<<20) // 1MB数据
// 即使bigData不再使用,闭包仍持有引用
closure := func() {
_ = bigData[0]
}
// closure仍然持有bigData的引用,无法被GC回收
_ = closure
}