在学go语言前期,常常搞不清楚匿名函数和闭包的关系,便写个文档深入总结一下。
1 关系
匿名函数:是一种没有函数名的函数,可以直接在代码中定义和使用。它可以被赋值给变量,作为参数传递给其他函数,或者作为其他函数的返回值。
闭包:是指一个函数捕获并保存了其所在函数的外部变量的引用。换句话说,闭包是由函数及其相关引用环境组合而成的实体。闭包可以在函数内部访问其外部函数的变量,即使外部函数已经执行完毕,闭包仍然可以访问和操作其引用的外部变量。
关系:匿名函数可以形成闭包。当一个匿名函数引用了外部的变量时,Go语言会自动将这些变量包裹在闭包中,以便在匿名函数被调用时可以访问和修改这些变量。这种特性使得匿名函数可以在不同的上下文中使用,非常灵活。
package main
import "fmt"
func main() {
add := func(x, y int) int {
return x + y
}
fmt.Println(add(1, 2)) // 输出:3
x := 1
increment := func() int {
x++
return x
}
fmt.Println(increment()) // 输出:2
fmt.Println(increment()) // 输出:3
}
在上面的代码中,add和increment都是匿名函数。add是一个简单的函数,接受两个参数并返回它们的和。increment是一个闭包,它引用了外部的变量x,并在每次调用时将x的值加1。
2 匿名函数
我们再详细拆解一下匿名函数
在Go语言中,可以使用匿名函数创建一个没有名字的函数。匿名函数可以直接在代码中定义,并且可以被立即执行或者赋值给一个变量后再执行。
我们分别看看不同类型的匿名函数
package main
import "fmt"
func main() {
// 定义并执行一个匿名函数 (不带参数)
func() {
fmt.Println("Hello, World!")
}()
// 定义并执行一个带参数的匿名函数 (带参数)
func(name string) {
fmt.Println("Hello,", name)
}("Alice")
// 定义一个带参数的匿名函数并赋值给变量
greet := func(name string) {
fmt.Println("Hello,", name)
}
// 调用被赋值的匿名函数,并传递参数
greet("Bob")
}
在上面的例子中,我们首先定义了一个匿名函数,并在函数体后面使用()立即执行。这样,函数体中的代码会被立即执行,输出Hello, World!。
其次定义了一个带参数的匿名函数,参数列表是(name string),并在函数体中使用name打印出相应的问候语。然后,我们在定义匿名函数时直接传递了参数值"Alice",这样函数体中的代码会被立即执行,输出Hello, Alice。
最后我们将同样带参数的匿名函数赋值给了一个变量greet,然后通过调用greet("Bob")来执行该函数,并传递参数值"Bob"。
我们再看看执行结果
Hello, World!
Hello, Alice
Hello, Bob
匿名函数的执行顺序是在其定义之后立即执行,然后再继续执行函数体中的其他代码。 但是我们一般使用匿名函数不会这么用,否则失去了一大用处。接下来我们看看别的例子。
2.1 并发执行匿名函数
可以使用goroutine来创建并发执行的线程。当匿名函数与goroutine结合使用时,匿名函数会在一个新的goroutine中执行,与主goroutine并发运行。
下面是一个示例,展示了匿名函数与goroutine结合使用的情况:
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("Start")
// 创建一个新的goroutine,并在其中执行匿名函数
go func() {
fmt.Println("Inside anonymous function")
time.Sleep(1 * time.Second)
fmt.Println("End of anonymous function")
}()
fmt.Println("End")
time.Sleep(2 * time.Second)
}
在上面的例子中,我们首先输出Start,然后创建了一个新的goroutine,并在其中执行了一个匿名函数。匿名函数的代码会在新的goroutine中并发执行。
匿名函数中的代码输出Inside anonymous function,然后通过time.Sleep函数模拟了一定的耗时操作。最后,输出End of anonymous function。
在主goroutine中,我们输出了End,然后通过time.Sleep函数等待匿名函数执行的时间。这样可以确保匿名函数的代码有足够的时间执行完毕。
需要注意的是,匿名函数与goroutine结合使用时,它们的执行是并发的,具体的执行顺序是不确定的。因此,在输出中可能会看到不同goroutine的输出交错在一起。
通过使用goroutine,我们可以实现并发执行的匿名函数,从而充分利用多核处理器的性能。
Start
End
Inside anonymous function
End of anonymous function
3 闭包
在Go语言中,闭包是由函数和其相关的引用环境组合而成的。当一个函数引用了外部的变量时,该函数就被认为是一个闭包。
闭包的特点有以下几点:
- 闭包函数可以访问和修改外部函数中的局部变量。
- 闭包函数可以被赋值给其他变量,并在其他地方被调用。
- 闭包函数可以作为参数传递给其他函数。
- 闭包函数可以延长外部变量的生命周期,使其在函数执行完后仍然存在。
闭包在Go语言中的使用场景很广泛,常见的应用有:
- 实现函数工厂:通过闭包可以动态生成函数,每个函数都有自己独立的状态。
- 实现回调函数:将一个函数作为参数传递给另一个函数,并在需要的时候调用。
- 实现函数的柯里化:通过闭包可以将一个多参数的函数转化为一个单参数的函数。
- 实现单例模式:通过闭包可以保证一个对象只被创建一次。
直接上例子
package main
import "fmt"
func addGenerator() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
add := addGenerator()
fmt.Println(add(1)) // 输出:1
fmt.Println(add(2)) // 输出:3
fmt.Println(add(3)) // 输出:6
}
在上面的代码中,addGenerator 函数返回了一个闭包函数。闭包函数中引用了外部函数 addGenerator 中的局部变量 sum。每次调用闭包函数时,都会将传入的参数加到 sum 上,并返回累加后的结果。
在 main 函数中,我们通过调用 addGenerator 函数创建了一个闭包函数 add。我们可以看到,每次调用 add 函数时,都会保留之前的累加结果,并将新的参数加到累加结果上。这说明闭包函数中的 sum 变量的生命周期被延长了,它在函数执行完后仍然存在。