【Go学习】理解Go语言中的函数闭包
2017-12-13 12:26
615 查看
【Go学习】理解Go语言中的函数闭包
闭包是什么?它是怎么产生的及用来解决什么问题呢。给出字面的定义先:闭包是由函数及其相关的引用环境组合而成的实体(即:闭包=函数+引用环境)。这个从字面上很难理解,至少我在刚接触这个概念的时候是没弄懂的,本文将结合实例代码进行解释。
函数是什么?
可能大家都知道:函数只是一段可执行代码,编译后就“固化”了,每个函数在内存中只有一份实例,得到函数的入口点便可以执行函数了。在函数式编程语言中,函数是一等公民(First class value:第一类对象,我们不需要像命令式语言中那样借助函数指针,委托操作函数),函数可以作为另一个函数的参数或返回值,可以赋给一个变量。函数可以嵌套定义,即在一个函数内部可以定义另一个函数,有了嵌套函数这种结构,便会产生闭包问题。如:
package main import "fmt" func exfunc(base int) func(int) int { return func(x int) int { return (base + x) } } func main() { myfunc := exfunc(10) fmt.Printf("%10d\n",myfunc(1)) myanotherfunc := exfunc(20) fmt.Printf("%10d\n",myanotherfunc(2)) }
运行结果:
在这段程序中,我们定义的匿名函数:
func(x int) int { return (base + x) }
是函数exfunc的内嵌函数,为方便后面的叙述,我们暂且称这个函数为insfunc,并且是exfunc函数的返回值。我们注意到一个问题:内嵌函数insfunc中引用到外层函数中的局部变量base,Go会这么处理这个问题呢?先让我们来看看这段代码的运行结果。当我们调用分别由不同的参数调用exfunc函数得到的函数时(myfunc(1),myanotherfunc(2)),得到的结果是隔离的,也就是说每次调用exfunc函数后都将生成并保存一个新的局部变量base。其实这里exfunc函数返回的就是闭包。
引用环境:
按照命令式语言的规则,exfunc函数只是返回了内嵌函数insfunc的地址,在执行insfunc函数时将会由于在其作用域内找不到base变量而出错。而在函数式语言中,当内嵌函数体内引用到体外的变量时,将会把定义时涉及到的引用环境和函数体打包成一个整体(闭包)返回。现在给出引用环境的定义就容易理解了:引用环境是指在程序执行中的某个点所有处于活跃状态的约束(一个变量的名字和其所代表的对象之间的联系)所组成的集合。闭包的使用和正常的函数调用没有区别。
由于闭包把函数和运行时的引用环境打包成为一个新的整体,所以就解决了函数编程中的嵌套所引发的问题。如上述代码段中,当每次调用exfunc函数时都将返回一个新的闭包实例,这些实例之间是隔离的,分别包含调用时不同的引用环境现场。不同于函数,闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
看到这里,大致应该对闭包有了浅显的认识,我们趁热打铁,再来看一个例子:
package main import "fmt" func exfunc() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } func main() { myfunc := exfunc() for i := 0;i<10;i++ { fmt.Printf("%10d\n",myfunc(i)) } }
运行结果:
0 1 3 6 10 15 21 28 36 45
按照我的理解,结果应该是如下这样:
0 1 2 3 4 6 6 7 8 9
反正我刚开始是没弄懂的,后面我修改了一下代码,加了些打印,让整个过程更加直观:
package main import "fmt" func exfunc() func(int) int { sum := 0 return func(x int) int { fmt.Printf("sum = %10d\n",sum) sum += x return sum } } func main() { myfunc := exfunc() for i := 0;i<10;i++ { fmt.Printf("myfunc(%d) = %4d\n",i,myfunc(i)) fmt.Println("_________________") } }
运行结果如下:
sum = 0 myfunc(0) = 0 _________________ sum = 0 myfunc(1) = 1 _________________ sum = 1 myfunc(2) = 3 _________________ sum = 3 myfunc(3) = 6 _________________ sum = 6 myfunc(4) = 10 _________________ sum = 10 myfunc(5) = 15 _________________ sum = 15 myfunc(6) = 21 _________________ sum = 21 myfunc(7) = 28 _________________ sum = 28 myfunc(8) = 36 _________________ sum = 36 myfunc(9) = 45 _________________
可以看到,sum的生命周期是跟接收exfunc()的变量myfunc的声明周期是一致的,并且sum的值,在每次调用
myfunc函数之后,都会改变,并且保存这个改变。这个很像C/C++中局部静态变量的作用,下面我们来一段简单的c代码:
#include <stdio.h> int exfunc(int x) { static int sum = 0; sum += x; return sum; } int main() { int i = 0; for(i=0;i<10;i++) { printf("exfunc(%d) = %4d\n",i,exfunc(i)); } return 0; }
运行结果如下:
exfunc(0) = 0 exfunc(1) = 1 exfunc(2) = 3 exfunc(3) = 6 exfunc(4) = 10 exfunc(5) = 15 exfunc(6) = 21 exfunc(7) = 28 exfunc(8) = 36 exfunc(9) = 45
可以看到跟我们上面用go闭包的结果一样,但是还是有区别的,我们再来稍微修改一下上面的代码:
package main import "fmt" func exfunc() func(int) int { sum := 0 return func(x int) int { fmt.Printf("sum = %10d\n",sum) sum += x return sum } } func main() { myfunc := exfunc() for i := 0;i<10;i++ { fmt.Printf("myfunc(%d) = %4d\n",i,myfunc(i)) fmt.Println("_________________") } fmt.Println("*****************") anfunc := exfunc() fmt.Printf("anfunc(%d) = %4d\n",10,anfunc(10)) }
sum = 0 myfunc(0) = 0 _________________ sum = 0 myfunc(1) = 1 _________________ sum = 1 myfunc(2) = 3 _________________ sum = 3 myfunc(3) = 6 _________________ sum = 6 myfunc(4) = 10 _________________ sum = 10 myfunc(5) = 15 _________________ sum = 15 myfunc(6) = 21 _________________ sum = 21 myfunc(7) = 28 _________________ sum = 28 myfunc(8) = 36 _________________ sum = 36 myfunc(9) = 45 _________________
*****************
sum = 0
anfunc(10) = 10
上面的结果可以看出,
区别就是C/C++中的静态变量不是独立的,每次调用这个函数,都会使用上次修改的结果,而闭包中的值对于每个闭包是独立存在的。
闭包把函数和运行时的引用环境打包成为一个新的整体,如上述代码段中,当每次调用exfunc函数时都将返回一个新的闭包实例,这些实例之间是隔离的,分别包含调用时不同的引用环境现场,比如上面代码中
myfunc这个闭包中sum的值经过几次运算后为36,而
anfunc这个闭包中sum的值为0。
闭包函数出现的条件:
1.被嵌套的函数引用到非本函数的外部变量,而且这外部变量不是“全局变量”。
2.嵌套的函数被独立了出来(被父函数返回或赋值 变成了独立的个体),
而被引用的变量所在的父函数已结束。
看到这么一句话,对闭包的理解,我觉得很透彻,可以作为本文的总结:
对象是附有行为的数据,而闭包是附有数据的行为。
相关文章推荐
- 理解Go语言中的函数闭包
- 举例讲解Go语言中函数的闭包使用
- Go语言学习四:函数和类
- Go语言学习笔记之函数(function)
- go语言笔记——append是内置的函数!!!new是一个函数!!!调试可以使用闭包,本质上是print调试,尼玛!
- 深入理解Go语言中的闭包
- Go语言学习(六)函数
- Go语言学习之函数(The way to go)
- Go语言学习笔记---函数
- Go语言学习笔记(三) [控制结构、内建函数]
- GO语言学习-匿名函数和闭包
- 你猜对了么?一个函数考察Go 语言闭包、defer的用法
- Go语言学习笔记(五) [函数]
- Go语言学习笔记 -- 内建函数
- Go语言学习笔记 -- 函数(2)
- Go语言学习笔记七: 函数
- 简单了解Go语言中函数作为值以及函数闭包的使用
- Go语言学习(六)函数
- GO语言学习-内建函数
- Go语言学习笔记(五) [函数]