【Go学习】理解Go语言中的函数闭包

【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))

}

运行结果:
图1

在这段程序中,我们定义的匿名函数:

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 

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