美文网首页
Go语言中的闭包理解

Go语言中的闭包理解

作者: 码二哥 | 来源:发表于2020-02-05 10:32 被阅读0次

    参考

    http://c.biancheng.net/view/59.html
    https://www.jianshu.com/p/faf7ef7fbcf8
    https://www.calhoun.io/5-useful-ways-to-use-closures-in-go/

    关键点

    希望通过下面的关键词,实现目的:能够快速回忆理解复习 知识点:

    1、闭包 = 匿名函数 + 引用环境

    2、函数,是编译器静态的概念

    3、闭包,是运行期动态的概念

    4、闭包的本质什么?要解决什么问题
    可以从以下几个方面去理解:

    • 将函数增加记忆功能,

    • 函数的视角去看,将函数运行结束后状态能够保存下来

    • 引用环境的视角去看,对同一块内存,用同一个函数 操作后的结果,可以操作一次,也可以操作多次

    • 闭包很适合迭代场景下的使用,可以迭代一次,也可以迭代多次

    • 引用环境,可以认为是中间态,或者说,运行态 , 或者说 一个变量,经过匿名函数处理后,会产生不同的,就是不同的状态

    • 还可以从另外一个角度去分析:一个函数内部修改了某个变量(内存),等这个函数执行完毕后,这个变量还在,下次再次执行这个函数的时候,可以继续获取到当前的变量,即执行这个函数的时候,都是在上次的执行结果之上进行的;那么,这个变量,肯定是在函数外部定义的,然后,对这个变量和函数进行封装成一个函数,那么整体就是闭包了。递归操作,就有这种特性
      含有以上特征时,可以考虑使用闭包

    1、什么闭包?

    简单的说,就是

    闭包 = 匿名函数 + 引用环境

    同一个函数与不同引用环境的组合,可以形成不同的实例,如下图所示:

    闭包与函数引用

    2、函数是否可以存储状态,或者说,存储信息?

    一个函数类型就像结构体一样,可以被实例化,
    函数本身不存储任何信息,只有与引用环境结合后的闭包,才具有"记忆性";

    调用函数时会创建内存,
    但是,当函数调用结束后,内存释放掉了,
    因此, 函数本身不具备存储任何信息的能力

    3、什么是 引用环境

    由于闭包把函数和运行时的引用环境打包成为一个新的整体, 所以就解决了函数编程中的前嵌套所引发的问题。
    当每次调用包含闭包的函数时都将返回一个新的闭包实例,这些实例之间是隔离的, 分别包含调用时不同的引用环境现场。
    不同于函数,闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

    对引用环境的说明

    4、闭包的本质是什么呢?

    可以从以下几个方面去理解:
    1、将函数增加记忆功能,

    2、从函数的视角去看,将函数运行结束后的状态能够保存下来

    3、从引用环境的视角去看,对同一块内存,用同一个函数 操作后的结果,可以操作一次,也可以操作多次

    4、闭包很适合迭代场景下的使用,可以迭代一次,也可以迭代多次

    5、引用环境,可以认为是中间态,或者说,运行态
    含有以上特征时,可以考虑使用闭包

    5、什么场景下使用闭包呢?

    5.1、例子 1:计算一个切片的和

    需求分析:
    一个切片是有至少一个元素以上构成的,那么切片的和是一个累加的过程,
    既然是累加的过程就会有 中间过程,中间态,
    把中间过程(中间态)看做是一个引用环境,

    package main
    
    import "fmt"
    
    func sum() func(int) int  {
        var num int
    
        return func(temp int) int {
            num += temp
            return num
        }
    }
    
    func main() {
        data := []int{
            2,3,4,1,5,1,
        }
        s := sum()
        var result int
        for _, value := range data{
            result = s(value)
        }
    
        fmt.Printf("result = %d\n", result)
    
    }
    
    
    image

    5.2、例子 2:计算一下某个函数调用次数

    package main
    
    import "fmt"
    
    func createApi(str string) {
        fmt.Printf("createApi recevice str =\t%s\n", str)
    }
    
    func countFunc(f func(string)) func(string) int {
        var count int
    
        return func(str string) int {
            f(str)
            count++
            return count
        }
    }
    
    func main() {
        cf := countFunc(createApi)
    
        cf("hello")
        cf("world")
    
        result := cf("Golang")
    
        fmt.Printf("call createApi num %d", result)
    }
    
    
    image

    5.3、例子 3: 斐波那契数列

    如:
    1,2,3,5,8,13,21,34,……

    package main
    
    import "fmt"
    
    func makeFibGen() func() int {
        f0 := 0
        f1 := 1
    
        return func() int {
            f1, f0 = (f0 + f1), f1
    
            return f0
        }
    }
    
    func main() {
        gen := makeFibGen()
    
        for i := 0; i < 10; i++ {
            fmt.Printf("%d ", gen())
        }
    }
    
    
    image

    6、闭包中遇到

    请参考下面的博客:
    https://www.jianshu.com/p/fa21e6fada70
    https://blog.csdn.net/chunyuan314/article/details/81247746
    https://www.jianshu.com/p/faf7ef7fbcf8
    https://blog.csdn.net/chunyuan314/article/details/81247746

    问题核心是:

    • 闭包中的匿名函数,在引用环境时,不是拷贝,而是引用

    • for循环会很快执行结束,而协程还未开始,导致协程在获取for循环给的变量时,拿到的都是 最后一个变量

    • 解决措施,

      • 在创建协程后,需要添加阻塞,如休息一小段时间

      • 传递for循环的变量时,先复制给 一个临时变量,而临时变量属于值拷贝,从而协程拿到了属于自己的变量

      • 还可以使用通道,如 https://blog.csdn.net/li_101357/article/details/80196650

    相关文章

      网友评论

          本文标题:Go语言中的闭包理解

          本文链接:https://www.haomeiwen.com/subject/gnijxhtx.html