首页 > Golang 阅读数:14

Go语言闭包

闭包并不是什么新奇的概念,它早在高级语言开始发展的年代就产生了。

1、闭包的概念

闭包(Closure)是词法闭包(Lexical Closure)的简称。是由函数和与其相关的引用环境组合而成的实体。在实现深约束时,需要创建一个能显式表示引用环境的东西,并将它与相关的子程序捆绑在一起,这样捆绑起来的整体被称为闭包。函数 + 引用环境 = 闭包。

闭包只是在形式和表现上像函数,但实际上不是函数。函数是一些可执行的代码,这些代码在函数被定义后就确定了,不会在执行时发生变化,所以一个函数只有一个实例。

闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。闭包在某些编程语言中被称为 Lambda 表达式。

函数本身不存储任何信息,只有与引用环境结合后形成的闭包才具有“记忆性”。函数是编译器静态的概念,而闭包是运行期动态的概念。

对象是附有行为的数据,而闭包是附有数据的行为。

2、闭包的优点

1) 加强模块化

闭包有益于模块化编程,便于以简单的方式开发较小的模块,从而提高开发速度和程序的可复用性。和没有使用闭包的程序相比,使用闭包可将模块划分得更小。

比如要计算一个数组中所有数字的和,只需要循环遍历数组,把遍历到的数字加起来就行了。如果现在要计算所有元素的积,又或者要打印所有的元素呢?解决这些问题都要对数组进行遍历,如果是在不支持闭包的语言中,程序员不得不一次又一次重复地写循环语句。而这在支持闭包的语言中是不必要的。这种处理方法多少有点像回调函数,不过要比回调函数写法更简单,功能更强大。

2) 抽象

闭包是数据和行为的组合,这使得闭包具有较好的抽象能力。

3) 简化代码

一个编程语言需要以下特性来支持闭包。
  • 函数是一阶值(First-class value,一等公民),即函数可以作为另一个函数的返回值或参数,还可以作为一个变量的值;
  • 函数可以嵌套定义,即在一个函数内部可以定义另一个函数;
  • 允许定义匿名函数;
  • 可以捕获引用环境,并把引用环境和函数代码组成一个可调用的实体。

接下来通过三个示例的对比来更清晰地了解闭包的作用。

【示例】没有使用闭包进行计数的代码。
package main
import "fmt"
func main() {
    for i := 0; i < 5; i++ {
        fmt.Printf("i=%d \t", i)
        fmt.Println(add2(i))
    }
}
func add2(x int) int {
    sum := 0
    sum += x
    return sum
}
运行结果如下:

i=0     0
i=1     1
i=2     2
i=3     3
i=4     4

由上面的示例可以看出,for 循环每执行一次,sum 都会清零,没有实现 sum 累加计数。

【示例】使用闭包函数实现计数器。
package main
import "fmt"
func main() {
    pos := adder()
    for i := 0; i < 10; i++ {
        fmt.Printf("i=%d \t", i)
        fmt.Println(pos(i))
    }
    fmt.Println("--------------------")
    for i := 0; i < 10; i++ {
        fmt.Printf("i=%d \t", i)
        fmt.Println(pos(i))
    }
}
func adder() func(int) int {
    sum := 0
    return func(x int) int {
        fmt.Printf("sum1=%d \t", sum)
        sum += x
        fmt.Printf("sum2=%d \t", sum)
        return sum
    }
}
运行结果如下:

i=0     sum1=0  sum2=0  0
i=1     sum1=0  sum2=1  1
i=2     sum1=1  sum2=3  3
i=3     sum1=3  sum2=6  6
i=4     sum1=6  sum2=10     10
i=5     sum1=10     sum2=15     15
i=6     sum1=15     sum2=21     21
i=7     sum1=21     sum2=28     28
i=8     sum1=28     sum2=36     36
i=9     sum1=36     sum2=45     45
--------------------
i=0     sum1=45     sum2=45     45
i=1     sum1=45     sum2=46     46
i=2     sum1=46     sum2=48     48
i=3     sum1=48     sum2=51     51
i=4     sum1=51     sum2=55     55
i=5     sum1=55     sum2=60     60
i=6     sum1=60     sum2=66     66
i=7     sum1=66     sum2=73     73
i=8     sum1=73     sum2=81     81
i=9     sum1=81     sum2=90     90

【示例】闭包使用案例。
package main
import "fmt"
func main() {
    myfunc := Counter()
    // fmt.Printf("%T\n", myfunc)
    fmt.Println("myfunc", myfunc)
    // 调用 myfunc 函数,i变量自增 1 并返回
    fmt.Println(myfunc())
    fmt.Println(myfunc())
    fmt.Println(myfunc())
    // 创建新的函数 nextNumber1,并查看结果
    myfunc1 := Counter()
    fmt.Println("myfunc1", myfunc1)
    fmt.Println(myfunc1())
    fmt.Println(myfunc1())
}
//计数器,闭包函数
func Counter() func() int {
    i := 0
    res := func() int {
        i += 1
        return i
    }
    // fmt.Printf("%T, %v \n", res, res)
    fmt.Println("Counter 中的内部函数:", res)
    return res
}
运行结果如下:

Counter 中的内部函数: 0x49ac50
myfunc 0x49ac50
1
2
3
Counter 中的内部函数: 0x49ac50
myfunc1 0x49ac50
1
2

由于闭包函数“捕获”了和它在同一作用域的其他常量和变量,所以当闭包在任何地方被调用,闭包都可以使用这些常量或者变量。它不关心这些变量是否已经超出作用域,只要闭包还在使用这些变量,这些变量就依然存在。