一、闭包 Closure
闭包 Closure 在某些语言如 Java、Python 或者 Ruby 中也被成为 Lambda 表达式或者匿名函数。
闭包是引用了自由变量的 匿名函数
,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境也不会释放或者删除,在闭包中可以继续使用这个变量,也就是说闭包就等于 函数+引用环境
。
在 Go 编程 | 连载 23 - 函数实现接口 中,我们知道函数可以通过定义一个函数类型来像结构体一样实例化;函数本身不存储任何信息,只有与引用环境结合后形成的闭包才具有“记忆性”。
函数是编译期静态的概念,而闭包是运行期动态的概念。
闭包可以对作用域上变量的引用进行修改,修改引用的变量就会对变量进行实际修改。
package main import "fmt" func main(){ info := "Go" // 将一个匿名函数赋值给 f 变量 f1 := func(){ // 修改 info 变量的值 info = "Elixir" } // 调用匿名函数 f1() fmt.Println(info) stark := make(map[string]interface{}) stark["name"] = "Tony Stark" stark["age"] = 33 f2 := func() { stark["address"] = "NYC" } f2() fmt.Println(stark) } 复制代码
执行上述代码,输出结果如下:
Elixir map[address:NYC age:33 name:Tony Stark] 复制代码
二、闭包实现生成器
被捕获到比包中的变量让函数本身有了 “记忆”,闭包中的逻辑可以修改捕获的变量的值,变量就会跟随闭包的声明周期一直存在,闭包本身就如同变量一样有了 “记忆”。
package main import "fmt" func main() { // 创建一个累加器,初始值为 1 outputSum := OutputSum(1) fmt.Println(outputSum()) fmt.Printf("%p\n", outputSum) // 创建一个累加器,初始值为 10 outputSum2 := OutputSum(10) fmt.Println(outputSum2()) fmt.Printf("%p\n", outputSum2) } // 返回值是一个 func() int 类型既一个返回 int 类型的函数 func OutputSum(v int) func() int { return func() int { v ++ return v } } 复制代码
执行上述代码,输出结果如下:
2 0x108b1a0 11 0x108b180 复制代码
创建的两个函数实例虽然包含的变量不同,但其实是指向同一块内存,闭包会在运行时动态修改变量值。
闭包这种记忆的特性可以用于实现类似工厂模式的生成器。
package main import "fmt" func main() { genScore := GenScore("Stark") // 返回分数信息 name, score := genScore() fmt.Println(name, score) } func GenScore(name string) func()(string, int) { // score score := 90 return func() (string, int){ return name, score } } 复制代码
执行上述代码,输出结果如下:
Stark 90 复制代码
上述代码中 GenScore 返回值将 name 和 score 两个变量引入匿名函数,从而形成闭包。
闭包还具有一定的封装性,函数外部无法直接访问修改这些匿名函数中引用的变量,这与面向对象中的封装特性相似。