go--函数调用--闭包

闭包定义

闭包是由函数和与其相关的引用环境组合而成的实体(即: 闭包=函数+引用环境)

闭包知识点

Go语言支持闭包
Go语言能通过escape analyze识别出变量的作用域,自动将变量在堆上分配。将闭包环境变量在堆上分配是Go实现闭包的基础。
返回闭包时并不是单纯返回一个函数,而是返回了一个结构体,记录下函数返回地址和引用的环境中的变量地址。

闭包结构体

闭包结构体
回到闭包的实现来,前面说过,闭包是函数和它所引用的环境。那么是不是可以表示为一个结构体呢:

type Closure struct {
    F func()() 
    i *int
}

事实上,Go在底层确实就是这样表示一个闭包的。让我们看一下汇编代码:

func f(i int) func() int {
    return func() int {
        i++
        return i
    }
}
MOVQ    $type.int+0(SB),(SP)
PCDATA    $0,$16
PCDATA    $1,$0
CALL    ,runtime.new(SB)    // 是不是很熟悉,这一段就是i = new(int)    
...    
MOVQ    $type.struct { F uintptr; A0 *int }+0(SB),(SP)    // 这个结构体就是闭包的类型
...
CALL    ,runtime.new(SB)    // 接下来相当于 new(Closure)
PCDATA    $0,$-1
MOVQ    8(SP),AX
NOP    ,
MOVQ    $"".func·001+0(SB),BP
MOVQ    BP,(AX)                // 函数地址赋值给Closure的F部分
NOP    ,
MOVQ    "".&i+16(SP),BP        // 将堆中new的变量i的地址赋值给Closure的值部分
MOVQ    BP,8(AX)
MOVQ    AX,"".~r1+40(FP)
ADDQ    $24,SP
RET    ,

其中func·001是另一个函数的函数地址,也就是f返回的那个函数。

考点

考点1:闭包引用环境

func f(i int) func() int {
    return func() int {
        i++
        return i
    }
}

函数f返回了一个函数,返回的这个函数,返回的这个函数就是一个闭包。这个函数中本身是没有定义变量i的,而是引用了它所在的环境(函数f)中的变量i。
c1 := f(0)
c2 := f(0)
c1() // reference to i, i = 0, return 1
c2() // reference to another i, i = 0, return 1
c1跟c2引用的是不同的环境,在调用i++时修改的不是同一个i,因此两次的输出都是1。函数f每进入一次,就形成了一个新的环境,对应的闭包中,函数都是同一个函数,环境却是引用不同的环境。

考点2: 闭包在循环语句中的引用环境

  • 例1
for i := 0; i < 3; i++ {
    func() {
        println(i) 
    }()
}

解答:
这段代码相当于

for i := 0; i < 3; i++ {
    f := func() {
        println(i) 
    }
    f()
}

这样就很清楚的能看出来最后的输出为 0,1,2

  • 例2
    正常代码:输出 0, 1, 2:
var dummy [3]int
for i := 0; i < len(dummy); i++ {
    println(i) // 0, 1, 2
}

复制代码然而这段代码会输出 3:

var dummy [3]int
var f func()
for i := 0; i < len(dummy); i++ {
    f = func() {
        println(i)
    }
}
f() // 3

把循环转换成这样的形式就容易理解了:

var dummy [3]int
var f func()
for i := 0; i < len(dummy); {
    f = func() {
        println(i)
    }
    i++
}
f() // 3

复制代码i 自加到 3 才会跳出循环,所以循环结束后 i 最后的值为 3
所以用 for range 来实现这个例子就不会这样:

var dummy [3]int
var f func()
for i := range dummy {
    f = func() {
        println(i)
    }
}
f() // 2

复制代码这是因为 for range 和 for 底层实现上的不同。

考点3: 闭包列表

  • 例1
var funcSlice []func()
for i := 0; i < 3; i++ {
    funcSlice = append(funcSlice, func() {
        println(i)
    })

}
for j := 0; j < 3; j++ {
    funcSlice[j]() // 3, 3, 3
}

复制代码输出序列为 3, 3, 3。
看了前面的例子之后这里就容易理解了:
这三个函数引用的都是同一个变量(i)的地址,所以之后 i 递增,解引用得到的值也会递增,所以这三个函数都会输出 3。

  • 例2
var funcSlice []func()
for i := 0; i < 3; i++ {
    func(i int) {
        funcSlice = append(funcSlice, func() {
            println(i)
        })
    }(i)

}
for j := 0; j < 3; j++ {
    funcSlice[j]() // 0, 1, 2
}

现在 println(i) 使用的 i 是通过函数参数传递进来的,并且 Go 语言的函数参数是按值传递的。
所以相当于在这个新的匿名函数内声明了三个变量,被三个闭包函数独立引用。原理跟第一种方法是一样的。
这里的解决方法可以用在大多数跟闭包引用有关的问题上

参考博客

https://juejin.im/post/5c850d035188257ec629e73e#heading-5
https://tiancaiamao.gitbooks.io/go-internals/content/zh/03.6.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容