您的位置:首页 > 编程语言 > Go语言

【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语言 闭包 函数