首页 > 编程笔记 > Go语言笔记 阅读:5

Go语言闭包的用法(附带实例)

闭包是函数和与其相关的引用环境组合而成的一个整体。

在 Go语言中,支持通过闭包来使用匿名函数。在定义一个不需要命名的内联函数时,匿名函数是很好用的。闭包可在其他函数中声明,形成嵌套,内层的变量可以遮盖同名的外层变量,外层变量可以直接在内层中使用。只要闭包还在使用这些变量或常量,它们就会一直存在,不会被当作垃圾回收。

示例代码如下:
// intSeq函数会返回一个在它函数体内定义的匿名函数
// 这个返回的函数使用闭包的方式隐藏变量i
func intSeq() func() int {
   i := 0
   return func() int {
      i += 1
      return i
  }
}
注意,这里的变量 i 称为外层变量,func 称为内层函数,内层函数引用了外层变量。

下面继续来看示例代码:
func main() {
    // 调用intSeq函数,将返回值(也是一个函数)赋给函数变量nextInt
    //这个函数的值包含了自己的变量i,每次调用函数变量nextInt时都会更新i的值
    nextInt := intSeq()

    // 通过多次调用函数nextInt来测试闭包的效果
    fmt.Println(nextInt())
    fmt.Println(nextInt())
    fmt.Println(nextInt())

    // 为了确认这个状态对于这个特定的函数来说是唯一的,重新创建闭包的环境并测试
    newInts := intSeq()
    fmt.Println(newInts())
}
上面的代码展示了闭包的调用过程。闭包的调用过程如下:
1) 函数 intSeq 在编译阶段分配内存给函数自身、变量 i 和匿名函数 func。为了后面描述方便,我们假设内存地址分别为 0x11111、0x22222、0x33333。intSeq 函数声明了变量 i 并为它赋值,最后的结果是返回一个匿名函数 func,而这个匿名函数 func 的值是针对外层的变量 i 执行加 1 操作后返回的值。

2) nextInt := intSeq() 语句会按照 intSeq 函数执行的顺序执行,最后返回的结果相当于 nextInt=func。也就是说,变量 nextInt 指向了 func 的地址 0x11111,并且还包括一个闭包环境中的变量 i。

3) 第 1 次执行 fmt.Println(nextInt()) 时,其实就是在执行匿名函数 func,根据匿名函数 func 中的逻辑,此时的变量 i=0,故返回的结果是 1(i=0;i+=1)。

4) 第 2 次执行 fmt.Println(nextInt()) 时,仍然是执行匿名函数 func。这一步很关键,前面说过,只要闭包还在使用这些变量或常量,它们就会一直存在,不会被当作垃圾回收。也就是说,此时匿名函数中变量i的生命周期并没有结束,它与变量 nextInt 的生命周期一致。因此,会保持第 1 次执行后的 i=1,而不是 0,最终返回的结果是 2(i=1;i+=1)。

5) 第 3 次执行 fmt.Prinstln(nextInt()) 时,逻辑同第 2 次,此时的 i 为 2,故返回的结果是 3(i=2;i+=1)。

6) 当执行到 newInts := intSeq() 时,新的变量指向函数 intSeq,所以会为函数 intSeq 中的变量 i 重新分配一次内存空间。此时变量 i 的初始值为零值,匿名函数 func 赋值给函数变量 newInts。

7) 执行 fmt.Println(newInts()) 时,其逻辑与前面的步骤 3 相同,此时的 i=0,根据执行逻辑可知,返回的结果是 1。

请注意,在闭包情况下,变量 i 的生命周期不会因函数 intSeq 执行完而结束,变量 i 的生命周期与 main 函数中变量 nextInt 的生命周期一致。通常情况下,局部变量会因函数的调用而被创建,且会随着函数的执行结束而被销毁,但是闭包中外层函数的局部变量却是例外,因为内层函数还要继续使用它,所以它并不会因外层函数的执行结束而被销毁。

注意,闭包可能存在 Bug,可以使用工具 golint 对其进行检测。


如果一个函数 A 中的内层有匿名函数 func,该匿名函数 func 会去操作函数 A 外层中的局部变量 i,并且该外层的返回值就是这个内层匿名函数 func。在这种情况下,内层匿名函数 func 和外层函数的局部变量 i 就构成了闭包结构。可以将闭包理解为内部类,它由变量和方法构成,且只在变量被使用时初始化一次。

用闭包的方式来实现斐波那契数列,示例代码如下:
//以闭包的方式实现斐波那契数列的测试
func TestFibSimple(t *testing.T) {
    f := FibonacciSimple()
    for i := 0; i <= 100; i++ {
        fmt.Println(strconv.Itoa(i), ":", f())
    }
}

//以闭包的方式实现斐波那契数列
func FibonacciSimple() func() int  {
    a, b := 0, 1
    return func() int {
        a, b = b, a+b
        return a
    }
}

相关文章