golang之闭包

什么是闭包?

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

  • 函数
  • 自由变量
  • 环境

举个例子:

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的原因。

参考资料

### Golang闭包的概念 在 Go 语言中,闭包是一个强大的概念,允许函数捕获并记住其创建时所在的作用域内的变量。这意味着即使该函数在其原始作用域之外执行,仍然可以访问这些外部变量[^2]。 闭包不仅限于简单的函数定义;实际上,在 Go 中,当一个匿名函数引用了其外层作用域中的变量时,这个组合就构成了闭包。这种机制让开发者可以在不改变全局命名空间的情况下共享数据,并且有助于构建更加模块化和易于维护的应用程序[^4]。 ### 如何使用闭包 #### 创建简单闭包 下面的例子展示了如何在一个函数内定义另一个带有对外部变量引用的匿名函数: ```go func createAdder(base int) func(int) int { return func(inc int) int { base += inc // 修改外部作用域中的 `base` 变量 return base } } ``` 在这个例子中,`createAdder()` 函数接收一个整数参数 `base` 并返回一个新的函数——此新函数每次调用都会增加传入值到 `base` 上面去[^1]。 #### 返回闭包 Go 实现闭包的方式是通过将闭包环境变量存储在堆上分配的空间里,而不是仅仅返回普通的函数指针。因此,当从一个函数返回一个闭包时,实际上是返回了一个包含了函数本身及其依赖的所有自由变量的数据结构[^3]。 #### 结合回调使用的闭包 由于闭包能够携带额外的信息进入新的上下文中,这使得它们非常适合用来作为回调函数传递给其他方法或库函数。例如: ```go package main import ( "fmt" ) // 定义处理数据的方法,接受切片和回调函数作为输入 func processData(data []int, callback func(int)) { for _, v := range data { callback(v * 2) } } func main() { dataSet := []int{1, 2, 3, 4, 5} processData(dataSet, func(x int) { fmt.Printf("%d ", x) // 输出双倍后的数值序列 }) } ``` 上述代码片段说明了怎样利用闭包来简化逻辑表达的同时保持良好的灵活性与扩展性[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值