什么是闭包?

一个持有外部环境变量的函数就是闭包(可以描述为引用了自由变量 的函数或者描述为闭包是指内层函数引用了外层函数中的变量) 。理解闭包通常有以下几个关键点:

  • 函数
  • 自由变量
  • 环境

举个例子:

func a(x, y int) {  //函数a的作用域则是环境
  fmt.Println(x, y) //此处x,y均不是自由变量
  func b() {
    fmt.println(x, y) //在内部函数b中,x,y相对于b是自由变量
  }()
  //无论b最终是否会作为返回值被函数a返回,b本身都已经形成了闭包
}

解释: 函数b因为捕获了外部作用域(环境)中的变量x,y,因此形成了闭包。而变量x, y并不属于函数b,所以在概念里被称为「自由变量」。

golang闭包的坑

浅坑

浏览下面的代码,然后写出返回的结果:

package main

import (
    "fmt"
)

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

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

返回结果为:110(很好理解,不做解释,不懂的继续往下看即可理解)

  1 package main                                                                                                         
  2  
  3 import (
  4     "fmt"
  5 )
  6  
  7 func outer(x int) (func(int) int, *int) {
  8     return func(y int) int {
  9         return x + y
 10     }, &x
 11 }
 12  
 13 func main() {
 14     f, x := outer(10)
 15     fmt.Printf("x's value: %d, %p\n", *x, x)
 16     *x = 20
 17     fmt.Println(f(100))
 18 }

返回结果为:

x's value: 10, 0xc42000e218
120

此处就会产生疑问,outer函数内赋值的变量x,为什么在main函数中能够修改?

答:使用逃逸分析就可以很清晰的解释这个问题。首先,go在一定程度上消除了堆和栈的区别,因为go 在编译的时候进行逃逸分析来决定一个对象放栈上还是堆上,不逃逸的对象放栈上,可能逃逸的放堆上 。其次,go开启逃逸分析分析有两种选择,一是可以在编译的时候使用go build --gcflags=-m main.go ,二是在运行的时候使用

go run -gcflags '-m -l' main.go (-l 是为了取消自动内联)。最后,看下上述代码的逃逸分析:

分析结果:

➜  go run -gcflags '-m -l' test.go
# command-line-arguments
./test.go:8: func literal escapes to heap
./test.go:8: func literal escapes to heap
./test.go:9: &x escapes to heap
./test.go:7: moved to heap: x
./test.go:10: &x escapes to heap
./test.go:15: *x escapes to heap
./test.go:15: x escapes to heap
./test.go:17: f(100) escapes to heap
./test.go:15: main ... argument does not escape
./test.go:17: main ... argument does not escape
x's value: 10, 0xc42007e018
120

结论 :通过逃逸分析发现,变量x逃逸到堆上,所有在main函数中能够访问并修改。所以最终打印的值为120。

深坑

当defer遇到闭包就是深坑,defer调用会在当前函数执行结束前才被执行,defer中使用匿名函数依然是一个闭包。

package main

import "fmt"

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

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

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

输出结果:

101 102
x:1,y:102

原因: main函数会在defer匿名函数打印之前打印101 102,这一点无需解释;defer匿名函数中a的打印值为1,是因为函数在定义的时候就拷贝了x变量给a,y的打印值为102是因为闭包引用了外部main函数自由变量l的原因。

参考资料


版权声明:本文为phantom_111原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/phantom_111/article/details/79405092