Go的第八课——闭包

闭包

基本介绍

闭包就是一个函数与其相的引用环境组合的一个整体(实体)

例子

给一个这样的累加器的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

func Addupper() func(int) int {
var n int = 10
return func(x int) int {
n = n + x
return n
}
}

func main() {
f := Addupper()
fmt.Println(f(1))
fmt.Println(f(2))
}
/*
11
13
*/

在 Addupper 中定义了一个 n 变量返回了一个匿名函数,匿名函数的作用就是和外部的 n 相加然后返回。

这个匿名函数和外部的变量 n 就构成了一个闭包,我们调用 Addupper 相当于是得到了一个对象,这个 n 就是对象的属性,返回的结果就是对象的方法。

一开始看着确实不太明白,因为想不明白为什么这个 n 每次的值都在,因为自己的想法是这个 n 是局部变量,然后呢局部变量的生命周期只存在于调用的时候,调用结束即销毁。

实验

经过自己的测试之后终于是发现一个大问题,go 语言好像局部变量好像根本不在栈上,群友解释是大概因为 go 有垃圾回收机制,一旦检测到指针引用丢失的时候马上回收这一块内存,如果还在引用那就不进行释放。

我用了如下的测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

func test() {
var n int
fmt.Printf("ptr:%p\n", &n)
}

func main() {

test()
test()
test()
test()

}
/*
ptr:0xc00001c098
ptr:0xc00001c0b8
ptr:0xc00001c0e0
ptr:0xc00001c0e8
*/

结果发现都不一致,因为讲道理,这样反复升栈平栈肯定这个局部变量都会存在同一个位置。

比如经典的 C 语言

我最初的想法是想看看为什么这个 n 它不会被覆盖也不会被释放,和其它函数比起来是不是因为返回的匿名函数包含了这个局部变量导致它没有定义在栈上面所以内存一直没有被回收导致的结果,然后就发现好像 go 的局部变量都不存在栈上面。

先按这么理解吧,也不清楚具体情况,反正目前唯一能说服我的结论也就只能是这样了。

然后问了下群友,大猫爷给出了这样的答复

咱也不懂这个苛刻的条件,反正按照说法应该就不是在栈上面了。

官方解释

因为返回的函数指针引用了局部变量,所以在函数指针存在的期间,被引用的局部变量也一致有效

那么我多次调用这个闭包函数,它返回的对象将不一样,分开算的,调用这个函数可以理解为一个对象的实例化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import "fmt"

func Addupper() func(int) int {
var n int = 10
return func(x int) int {
n = n + x
return n
}
}

func main() {
f := Addupper()
fmt.Println(f(2))
fmt.Println(f(3))

f = Addupper()
fmt.Println(f(2))
fmt.Println(f(3))

}
/*
12
15
12
15
*/