[TOC] # 闭包 ## 简介 **作用:缩小变量作用域,减少对全局变量的污染** 闭包又是什么?你可以想象一下,在一个函数中存在对外来标识符的引用。所谓的外来标识符,既不代表当前函数的任何参数或结果,也不是函数内部声明的,它是直接从外边拿过来的 ![](https://box.kancloud.cn/ac6a5657ad6e6e2edbae512c7788dea6_1342x662.png) 一个函数捕获了和他在同一个作用域的其他常量和变量.这就意味着当闭包被调用的时候,不管在程序什么地方调用,闭包能够使用这些常量或者变量. 它不关心这些捕获了的变量和常量是否已经超出了作用域,所以只有闭包还在使用他,这些变量就还会存在. 在go里面,所有的匿名函数都是闭包 ~~~ func main() { a := 10 str := "mike" //匿名函数,没有函数名字,函数定义没有调用 f1 := func() { fmt.Println("a = ", a) fmt.Println("str = ", str) } //调用 f1() //给一个函数类型起别名 type FuncType func() //函数没有参数没有返回值 //声明变量 var f2 FuncType f2 = f1 f2() //定义匿名函数,同时调用 func() { fmt.Printf("a = %d, str = %s\n", a, str) }() //后面的()代表调用此匿名函数 //带参数的匿名函数 f3 := func(i, j int) { fmt.Printf("a = %d, str = %s\n", a, str) } f3(1, 2) //有参数有返回值 x, y := func(i, j int) (max, min int) { if i > j { max = i min = j } else { max = j min = i } return }(10, 20) fmt.Print(x, y) } ~~~ **闭包以引用的方式捕获外部变量** ~~~ func main() { a := 10 str := "nike" func() { a = 666 str = "go" fmt.Printf("内部: a = %d, str = %s\n", a, str) }() //()代表直接调用 fmt.Printf("外部: a = %d, str =%s\n", a, str) } ~~~ 输出 ~~~ 内部: a = 666, str = go 外部: a = 666, str =go ~~~ **闭包保存变量** ~~~ func test02() func() int { var x int //没有初始化,值为0 return func() int { x++ return x * x } } func main() { //返回函数类型 f := test02() fmt.Println(f()) fmt.Println(f()) fmt.Println(f()) } ~~~ ## 使用 > Go 函数可以是一个闭包。闭包是一个函数值,它引用了函数体之外的变量。 这个函数可以对这个引用的变量进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。 例如,函数 adder 返回一个闭包。每个返回的闭包都被绑定到其各自的 sum 变量上。 在上面例子中(这里重新贴下代码,和上面代码一样): ~~~ package main import "fmt" func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } func main() { pos, neg := adder(), adder() for i := 0; i < 10; i++ { fmt.Println( pos(i), neg(-2*i), ) } } ~~~ 如pos := adder()的adder()表示返回了一个闭包,并赋值给了pos,同时,这个被赋值给了pos的闭包函数被绑定在sum变量上,因此pos闭包函数里的变量sum和neg变量里的sum毫无关系。 `func adder() func(int) int`的`func(int) int`表示adder()的输出值的类型是func(int) int这样一个函数 没有闭包的时候,函数就是一次性买卖,函数执行完毕后就无法再更改函数中变量的值(应该是内存释放了);有了闭包后函数就成为了一个变量的值,只要变量没被释放,函数就会一直处于存活并独享的状态,因此可以后期更改函数中变量的值(因为这样就不会被go给回收内存了,会一直缓存在那里)。 比如,实现一个计算功能:一个数从0开始,每次加上自己的值和当前循环次数(当前第几次,循环从0开始,到9,共10次),然后\*2,这样迭代10次: 没有闭包的时候这么写: ~~~ func abc(x int) int { return x * 2 } func main() { var a int for i := 0; i < 10; i ++ { a = abc(a+i) fmt.Println(a) } } ~~~ 如果用闭包可以这么写: ~~~ func abc() func(int) int { res := 0 return func(x int) int { res = (res + x) * 2 return res } } func main() { a := abc() for i := 0; i < 10; i++ { fmt.Println(a(i)) } } ~~~ 2种写法输出值都是: ~~~ 0 2 8 22 52 114 240 494 1004 2026 ~~~ 从上面例子可以看出闭包的3个好处: 1. 不是一次性消费,被引用声明后可以重复调用,同时变量又只限定在函数里,同时每次调用不是从初始值开始(函数里长期存储变量) 这有点像使用面向对象的感觉,实例化一个类,这样这个类里的所有方法、属性都是为某个人私有独享的。但比面向对象更加的轻量化 2. 用了闭包后,主函数就变得简单了,把算法封装在一个函数里,使得主函数省略了a=abc(a+i)这种麻烦事了 3. 变量污染少,因为如果没用闭包,就会为了传递值到函数里,而在函数外部声明变量,但这样声明的变量又会被下面的其他函数或代码误改。 关于闭包的第一个好处,再啰嗦举个例子 1. 若不用闭包,则容易对函数外的变量误操作(误操作别人),例: ~~~ var A int = 1 func main() { foo := func () { A := 2 fmt.Println(A) } foo() fmt.Println(A) } ~~~ 输出: ~~~ 2 1 ~~~ 如果手误将A := 2写成了A = 2,那么输出就是: ~~~ 2 2 ~~~ 即会影响外部变量A 2. 为了将某一个私有的值传递到某个函数里,就需要在函数外声明这个值,但是这样声明会导致这个值在其他函数里也可见了(别人误操作我),例: ~~~ func main() { A := 1 foo := func () int { return A + 1 } B := 1 bar := func () int { return B + 2 } fmt.Println(foo()) fmt.Println(bar()) } ~~~ 输出: ~~~ 2 3 ~~~ 在bar里是可以对变量A做操作的,一个不小心就容易误修改变量A **结论:函数外的变量只能通过参数传递进去,不要通过全局变量的方式的渠道传递进去,当函数内能读取到的变量越多,出错概率(误操作)也就越高。**