我模仿了有关闭包的在线教程,并编写了以下代码.

func foo1() func() {
    xValue := 1
    x := &xValue
    defer func() {
        xValue = 2
    }()
    return func() {
        *x = *x + 1
        fmt.Printf("foo1 val = %d\n", *x)
    }
}

func main() {

    f1 := foo1()
    f1()
    f1()
    f1()
}

我很困惑,在执行f1 := foo1()之后,变量xValue似乎应该被循环使用,所以使用*x应该是错误的,但上面的代码没有错误,执行时被罚款,这给出了输出

foo1 val = 3
foo1 val = 4
foo1 val = 5

因此,我想知道除了指针本身之外,闭包是否还持有指针的值,或者是GO语言的垃圾收集机制导致xValue不被删除吗?

推荐答案

在GO中,闭包引用它所关闭的任何变量(地址).引用the language reference条:

函数文字是闭包:它们可以引用周围函数中定义的变量.然后,这些变量在周围的函数和函数文本之间共享,只要它们是可访问的,它们就会一直存在.

因此,在您的示例中:

  1. f1 := foo1()

    1. 使xValue变量存在(编译器很可能会将其分配到堆上).它将从其类型0的零值开始.
    2. 使变量x存在,并为其分配地址xValue.
    3. 运行defer红色闭包,并将值2赋给xValue.
    4. 返回结束变量x的闭包.

    后一点可能有点棘手:因为返回的闭包引用了变量x,所以编译器保证即使在返回foo之后变量也存在.由于x包含地址xValue(因此是对xValue的实时引用),因此该地址也仍然存在,并且不能被垃圾收集. 使用相同的转义分析方法,编译器保证xValue从声明它的函数的返回中幸存下来.

  2. 您执行返回的闭包,该闭包通过指向它的指针修改xValue--这里没有魔术发生.另外两个电话也是这样做的.

总而言之,您可能被您的C++知识绊倒了,在C++中,函数中声明的任何变量一旦从该函数返回控制就不再存在,因此存在于该函数之外的任何对它的引用都变得无效.在GO中,情况并非如此:在这方面,语言被明确定义为安全的:如果从函数调用向外部世界返回(或以其他方式传递)对任何变量的引用,则编译器确保任何变量具有适当的分配,以在创建该变量的函数调用中幸存下来.

Go相关问答推荐

Go安装成功但没有输出简单的Hello World

在多个 struct 体中重用 Go 中的函数

Fizz对Gin的OpenAPI生成器正在重命名类型

「GORM错误」不支持的数据类型:&[],不正确的模式

使用 httptest 对 http 请求进行单元测试重试

是否可以在调试期间在 VSCode 中预览 github.com/shopspring/decimal 值?

如何在 `hashicorp / terraform-exec` 中将 `ApplyConfig` 传递给 `tf.Apply()`?

golang yaml 马歇尔网址

使用模拟在 golang 中测试我的界面.专门测试调用同级函数的 1 个函数

Go:如何在将 float64 转换为 float32 时判断精度损失

如何为导入的嵌入式 struct 文字提供值?

在 Go GRPC 服务器流式拦截器上修改元数据

如何编写一个以字符串或错误为参数的通用函数?

我相信我正确地在 sRGB 和线性 RGB 之间进行了转换,那么为什么深色的结果看起来更糟呢?

vs 代码调试 go 测试不通过标志

Golang Echo Labstack 如何在模板视图中调用函数/方法

Golang 使用 docker 将敏感数据作为参数传递

在 golang 中联合一个接口和类型

Terraform 自定义提供程序 - 数据源架构

不能使用 *T 类型的变量作为参数类型