ZetCode

Go 闭包

最后修改时间 2024 年 4 月 11 日

在本文中,我们将展示如何在 Golang 中使用闭包。

在 Go 语言中,函数是一等公民,这意味着它们可以被赋值给变量、存储在集合中、动态创建或删除,以及作为参数传递给其他函数。这种灵活性使开发人员能够编写模块化且可重用的代码。

嵌套函数,也称为内部函数,是在另一个函数内部定义的函数。这些嵌套函数对于封装仅在围绕函数内相关的逻辑很有用。另一方面,匿名函数是没有绑定到标识符的函数。匿名函数通常用作高阶函数的参数,使其成为短暂操作或内联函数执行的理想选择。

Go 中的 闭包 是一个匿名嵌套函数,它保留对在其主体外部定义的变量的访问权限。这意味着即使在围绕函数返回后,闭包也可以继续引用和修改这些外部变量。

闭包维护自己的状态,允许多个实例保存独立的值。这使得闭包在实现函数工厂、维护计数器和处理跨不同调用的上下文数据方面特别有用。

anonymous.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 闭包简单示例

在以下示例中,我们定义了一个简单的闭包。

simple_closure.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……

fibonacci.go
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 语言中,中间件通常借助闭包来创建。

middleware.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

作者

我的名字是 Jan Bodnar,我是一名充满热情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。至今,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有 Go 教程