Go 闭包
最后修改时间 2024 年 4 月 11 日
在本文中,我们将展示如何在 Golang 中使用闭包。
在 Go 语言中,函数是一等公民,这意味着它们可以被赋值给变量、存储在集合中、动态创建或删除,以及作为参数传递给其他函数。这种灵活性使开发人员能够编写模块化且可重用的代码。
嵌套函数,也称为内部函数,是在另一个函数内部定义的函数。这些嵌套函数对于封装仅在围绕函数内相关的逻辑很有用。另一方面,匿名函数是没有绑定到标识符的函数。匿名函数通常用作高阶函数的参数,使其成为短暂操作或内联函数执行的理想选择。
Go 中的 闭包 是一个匿名嵌套函数,它保留对在其主体外部定义的变量的访问权限。这意味着即使在围绕函数返回后,闭包也可以继续引用和修改这些外部变量。
闭包维护自己的状态,允许多个实例保存独立的值。这使得闭包在实现函数工厂、维护计数器和处理跨不同调用的上下文数据方面特别有用。
package main
import "fmt"
func main() {
sum := func(a, b, c int) int {
return a + b + c
}(3, 5, 7)
fmt.Println("5+3+7 =", sum)
}
在此示例中,创建了一个匿名函数来对三个值求和。参数 `3`、`5` 和 `7` 在其定义之后立即传递给函数,演示了匿名函数如何被内联调用。
Go 闭包简单示例
在以下示例中,我们定义了一个简单的闭包。
package main
import "fmt"
func intSeq() func() int {
i := 0
return func() int {
i++
return i
}
}
func main() {
nextInt := intSeq()
fmt.Println(nextInt())
fmt.Println(nextInt())
fmt.Println(nextInt())
fmt.Println(nextInt())
nextInt2 := intSeq()
fmt.Println(nextInt2())
}
我们有 intSeq 函数,它生成一个整数序列。它返回一个递增 i 变量的闭包。
func intSeq() func() int {
intSeq 是一个返回一个返回整数的函数的函数。
func intSeq() func() int {
i := 0
return func() int {
i++
return i
}
}
在函数中定义的变量具有局部函数作用域。但是,在这种情况下,即使在 `intSeq` 函数返回后,闭包仍绑定到 `i` 变量。
nextInt := intSeq()
我们调用 `intSeq` 函数。它返回一个将递增计数器的函数。返回的函数关闭变量 `i` 以形成闭包。闭包绑定到 `nextInt` 名称。
fmt.Println(nextInt()) fmt.Println(nextInt()) fmt.Println(nextInt()) fmt.Println(nextInt())
我们多次调用闭包。
nextInt2 := intSeq() fmt.Println(nextInt2())
下一次调用 `intSeq` 函数会返回一个新的闭包。这个新的闭包有自己独立的状态。
$ go run closure.go 1 2 3 4 1
Go 闭包斐波那契示例
斐波那契数列是一个值序列,其中每个数字是前两个数字的总和,从 0 和 1 开始。序列的开头是:0、1、1、2、3、5、8、13、21、34、55、89、144……
package main
import "fmt"
func fibonacci() func() int {
a := 0
b := 1
return func() int {
a, b = b, a+b
return b-a
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
这是使用闭包实现的斐波那契数列。如果 `fibonacci` 函数没有保留 `a` 和 `b` 的值,计算将无法正常工作。
$ go run fibonacci.go 0 1 1 2 3 5 8 13 21 34
Go 闭包中间件
中间件是在服务器请求生命周期中执行的函数。中间件通常用于日志记录、错误处理或数据压缩。
在 Go 语言中,中间件通常借助闭包来创建。
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func main() {
http.HandleFunc("/now", logDuration(getTime))
fmt.Println("Server started at port 8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func logDuration(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
f(w, r)
end := time.Now()
fmt.Println("The request took", end.Sub(start))
}
}
func getTime(w http.ResponseWriter, r *http.Request) {
now := time.Now()
_, err := fmt.Fprintf(w, "%s", now)
if err != nil {
log.Fatal(err)
}
}
我们有一个简单的 HTTP 服务器,它响应 `/now` 并返回当前日期时间。
http.HandleFunc("/now", logDuration(getTime))
在 Go 语言中,函数可以作为参数传递给其他函数。我们将 `logDuration` 函数包装在 `getTime` 函数之上。
func logDuration(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
f(w, r)
end := time.Now()
fmt.Println("The request took", end.Sub(start))
}
}
logDuration 函数返回一个闭包,该闭包获取当前时间、调用原始函数、获取结束时间,并打印请求的持续时间。该闭包对于处理程序函数内部发生的具体操作是不可知的。
在本文中,我们已经学习了 Golang 中的闭包。
来源
The Go Programming Language Specification