Golang 中关于闭包的坑

所谓闭包是指内层函数引用了外层函数中的变量或称为引用了自由变量的函数,其返回值也是一个函数,了解过的语言中有闭包概念的像 js,python,golang 都类似这样。

python 中的闭包可以嵌套函数,像下面这样:

def make_adder(addend):
    def adder(augend):
        return augend + addend
    return adder

转化成 golang 代码则像下面这样:

func outer(x int) func(int) int{
    func inner(y int) int{
        return x + y
    }
    return inner
}

当然这是错误的,golang 中是不能嵌套函数的,但是可以在一个函数中包含匿名函数,完整示例像下面这样:

package main

import (
    "fmt"
)

func outer(x int) func(int) int {
    return func(y int) int {
        return x + y
    }
}

func main() {
    f := outer(10)
    fmt.Println(f(100))
}

看了一段时间 golang 后,对于 golang 中的闭包可能出现的坑大致有下面几个。

1,for range 中使用闭包

一个示例:

func main() {
    s := []string{"a", "b", "c"}
    for _, v := range s {
        go func() {
            fmt.Println(v)
        }()
    }
    select {}    // 阻塞模式
}                          

// 嗯,结果应该是 a,b,c 吧

来看看结果:

运行结果

输出的结果不期而然,大家的结果也不一定和我相同。

对比下面的改进:

func main() {
    s := []string{"a", "b", "c"}
    for _, v := range s {
        go func(v string) {
            fmt.Println(v)
        }(v)   //每次将变量 v 的拷贝传进函数
    }
    select {}
}

所以结果当然是:

"a"
"b"
"c"

由于使用了 go 协程,并非顺序输出。

解释:也不用多解释了吧,在没有将变量 v 的拷贝值传进匿名函数之前,只能获取最后一次循环的值,这是新手最容易遇到的坑。

2,函数列表使用不当

package main

import (
    "fmt"
)

func test() []func() {
    var s []func()

    for i := 0; i < 3; i++ {
        s = append(s, func() {  //将多个匿名函数添加到列表
            fmt.Println(&i, i)
        })
    }

    return s    //返回匿名函数列表
}
func main() {
    for _, f := range test() {  //执行所有匿名函数
        f()
    }
}

运行结果:

运行结果

解决方法:

package main

import (
    "fmt"
)

func test() []func() {
    var s []func()

    for i := 0; i < 3; i++ {
        x := i                  //复制变量
        s = append(s, func() {
            fmt.Println(&x, x)
        })
    }

    return s
}
func main() {
    for _, f := range test() {
        f()
    }
}

解释:每次 append 操作仅将匿名函数放入到列表中,但并未执行,并且引用的变量都是 i,随着 i 的改变匿名函数中的 i 也在改变,所以当执行这些函数时,他们读取的都是环境变量 i 最后一次的值。解决的方法就是每次复制变量 i 然后传到匿名函数中,让闭包的环境变量不相同。

若是你对闭包理解了,也可以利用闭包来修改全局变量:

package main                   

import (
    "fmt"
)                              

var x int = 1                  

func main() {
    y := func() int {
        x += 1
        return x
    }()
    fmt.Println("main:", x, y)
} 

// 结果:    main: 2 2

3,延迟调用

defer 调用会在当前函数执行结束前才被执行,这些调用被称为延迟调用,
defer 中使用匿名函数依然是一个闭包。

package main

import "fmt"

func main() {
    x, y := 1, 2

    defer func(a int) {
        fmt.Printf("x:%d,y:%d\n", a, y)  // y 为闭包引用
    }(x)      // 复制 x 的值

    x += 100
    y += 100
    fmt.Println(x, y)
}

输出结果:

101 102
x:1,y:102

总结:从形式上看,匿名函数都是闭包。闭包的使用非常灵活,上面仅是几个比较简单的示例,不当的使用容易产生难以发现的 bug,当出现意外情况时,首先检查函数的参数,声明可以接收参数的匿名函数,这些类型的闭包问题也就引刃而解了。

作者:田飞雨
链接:https://www.jianshu.com/p/fa21e6fada70
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

原文地址:https://www.cnblogs.com/sky1130/p/11011078.html

时间: 2024-10-10 03:20:36

Golang 中关于闭包的坑的相关文章

Redis 在Golang中使用遇到的坑

1.从lua脚本传回到go那边的数字是string类型 2.hincrby 返回当前值的计算结果(即存放到redis中的值)3.hmset 成功返回:{ok=OK}(坑) 4.hget 一个空的值,会返回nil?还是0?(在lua里面返回false,直接返回到go中会报err错:“redis:nil”)5.hset 一个不存在的key,返回什么呢?即设置失败返回什么错误?(会创建出这个key来,并返回1,如果这个已key存在,则返回0[设值仍然会成功] 6.hmset 返回的始终是 ok:OK

golang中创建logger时候踩过的坑

golang中创建logger时候踩过的坑 错误的代码 package main import ( "fmt" "io" "log" "os" ) var logger *log.Logger func init(){ fmt.Println("创建日记录日志文件") f,err:=os.OpenFile("./Log.log",os.O_WRONLY|os.O_CREATE|os.O_

Golang中多用途的defer

defer顾名思义就是延迟执行,那么defer在Golang中该如何使用以及何时使用呢? A "defer" statement invokes a function whose executionis deferred to the moment the surrounding function returns, Golang的官方时这么定义的. 1.那么在什么情况下会调用defer延迟过的函数呢? 从文档中可以知道主要有两种情况: 当函数执行了return 语句后 当函数处于pan

javascript中的闭包、模仿块级作用域和私有变量

闭包是指有权访问另一个函数作用域中的变量的函数.创建闭包的常见方式为:在一个函数内部创建另一个函数. "当某个函数被调用时,会创建一个执行环境(execution context)及相应的作用域链.然后,使用arguments和其他命名参数的值来初始化函数的活动对象(activation object).但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象出于第三位.....直至作用域链终点的全局执行环境." function creawteCompariso

javascript中的闭包解析

学习javaScript已经有一段时间了,在这段时间里,已经感受到了JavaScript的种种魅力,这是一门神奇的语言,同时也是一门正在逐步完善的语言,相信在大家的逐步修改中,这门语言会逐步的完善下去,在上一篇随笔中,和大家分享了JavaScript中独有的类中的继承方式,今天呢,就跟大家分享一下我这几天一直在搞,却还是搞的不是很透彻的闭包问题,对于一个初学者而言,JavaScript中的闭包无疑是一个难点,而且也是我们必须要掌握的一个重点,那么今天我就跟大家分享一下我在学习闭包的时候的感悟以及

Golang 中使用多维 map

http://tnt.wicast.tk/2015/11/02/golang-multiple-dimension-map/ Golang 的 XML/JSON 解析库乍看使用起来很方便,只要构造一样结构的 Struct 就可以一下子导入到变量中去了.其实手工构造 Struct 非常容易出现结构偏差,而且最要命的是 Unmarshal() 执行的时候不是严格导入所以没有任何报错. 于是这两天写了一个给 Golang 用的 XML to Struct 生成器,希望能一劳永逸. 不过在制作过程中有遇

Javascript中的闭包(转载)

前面的话: 闭包,是 javascript 中重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,你很难从定义去理解它.下面是作者从作用域链慢慢讲到闭包以及在后面提到了一些闭包的高级用法.下面大家一起来学习Javascript中的闭包. 谈一谈JavaScript作用域链 当执行一段JavaScript代码(全局代码或函数)时,JavaScript引擎会创建为其创建一个作用域又称为执行上下文(Execution Context),在页面加载后会

golang中省略返回值造成内存泄漏

我已经两次因为不恰当的省略go中的函数返回值,一次造成MySql的too many connection错误,一次造成严重的内存泄漏.所以在这里大家分享一下这个问题和解决办法,也提醒自己以后不要再犯类似的错了. 众所周知,go中的函数可以返回多个值.但很多时候我们并不需要所有的值,而且go中定义了一个变量必须使用才可以,不然会报错.所以对于不需要的返回值,一般的操作方法就是省略: for _,value := range slice{ //.... } 一个典型就是上面的range.range可

Golang 中操作 Mongo Update 的方法

Golang 和 MongoDB 中的 ISODate 时间交互问题 2018年02月27日 11:28:43 独一无二的小个性 阅读数:357 标签: GolangMongoDB时间交互时间转换 更多 个人分类: MongoDBGolang 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u010649766/article/details/79385948 MongoDB 中有一种时间格式数据 ISODate,参考如下:  如果在 Golan