去:匿名函数

时间:2016-05-29 12:37:23

标签: go

以下是我一直试图理解的代码:

package main

import (
    "fmt"
)

func squares() func() int {
    var x int
    return func() int {
        x = x + 2
        return x * x
    }
}

func main() {
    f := squares()
    fmt.Println(f())
    fmt.Println(f())
    fmt.Println(f())
    fmt.Println(squares()())
    fmt.Println(squares()())
    fmt.Println(squares()())
}

我们得到的结果:

4
16
36
4
4
4

我的问题是:为什么xfmt.Println(squares()())的价值保持不变?

4 个答案:

答案 0 :(得分:6)

短版

每次拨打<Style.Triggers> <Trigger Property="IsSelected" Value="True"> <Setter Property="Panel.ZIndex" Value="99"/> </Trigger> </Style.Triggers> 时,您都在构建新的闭包。

这就像用面向对象语言构建一个新的Counter对象一样:

squares

......而不是:

new Counter().increment(); // 4
new Counter().increment(); // 4

更长的版本

在您的函数中,c = new Counter(); c.increment(); // 4 c.increment(); // 16 声明了一个局部变量var x int

x

对于任何函数,局部变量仅在函数内部可见。如果在不同的上下文中调用函数,则每个调用都有一组可由本地符号(此处为func squares() func() int { var x int return func() int { x = x + 2 return x * x } } )寻址的内存存储。返回函数时会发生的情况是,代码范围内当前可见的任何绑定都与函数一起保存,然后称为闭包。

闭包可以保持状态,就像对象一样。因此,你的闭包可以引用在创建它时可见的局部变量,即使你转移引入局部变量的块(幸运的是,GC在这里跟踪与这些变量相关的内存)。

定义x时,会创建一个新的闭包。每次调用它时,都会修改内部f变量引用的相同位置。但是如果你创建了新的闭包并且每次都调用它们,那么你就不会看到相同的副作用,因为每个x都在内存中命名了不同的位置。

答案 1 :(得分:2)

当您使用闭包函数调用第二个变量时获得相同结果的原因是范围规则的结果。由新的自调用或匿名函数创建的所有函数值捕获并共享相同的变量 - 可寻址的存储位置,而不是它在该特定时刻的值。

匿名函数引入了一个新的词法块,它共享与函数值指向的逻辑地址相同的逻辑地址,因此每次调用该函数而不将函数封装到新范围时,它将共享相同的逻辑地址。这就是为什么你会在很多地方看到以这种方式调用匿名函数的原因:

for i := range mymap {
        func(n int) { 
             fmt. Println(n) 
        }(i) // note the scope
}

要解决您的问题,一种方法是使用指针变量,这样您就可以绝对确定将共享分配给相同内存地址的变量。以下是更新且有效的代码:

package main

import (
    "fmt"
)

func squares() func(x *int) int {
    return func(x *int) int {
        *x = *x + 2
        return *x * *x
    }
}

func main() {
    f := squares()
    x := 0
    fmt.Println(f(&x))
    fmt.Println(f(&x))
    fmt.Println(f(&x))
    fmt.Println(squares()(&x))
    fmt.Println(squares()(&x))
    fmt.Println(squares()(&x))
}

Go Playground

另一种方法是将变量x公开为全局变量。这将保证您每次运行匿名函数时都不会创建新变量x

答案 2 :(得分:1)

这里有一个不同的视角来回答你的问题。

首先分解squares()函数,以了解发生了什么:

func squares() func() int {

上面定义了一个名为squares的函数,返回另一个类型为func() int的函数(并返回int,但不是squares()这里的重点)。

先将它钻入脑中:它返回一个函数。

现在,让我们看看当我们致电 var x int 时会发生什么:

x

那就是它。它定义了一个变量x,默认值为每个Go规范的值。

好的,现在我们在范围内有一个名为 return func() int { x = x + 2 return x * x } 的变量,它的值为0.现在,我们返回一个函数:

x

如果我们之前没有定义var x int,那么这将是一个构建错误,因为必须定义x。但是,它是在前一个范围中定义的。

使用闭包允许定义另一个范围,该范围使用先前范围中的变量。在这种情况下var x int

此外,您还可以修改闭包(先前作用域中的变量)。在这种情况下,我们正在修改先前范围中定义的先前f := squares()

暂时抓住这些想法,让我们运行一些代码......

squares()

在这里,我们运行var x int,将func()定义为零并返回f() x fmt.Println(f()) fmt.Println(f()) fmt.Println(f()) ,可以对f()执行更多操作。

var x int

由于我们继续重用f(),这会将x的范围保持在堆栈上,只要您拥有此f()变量,该堆栈就会保留。因此,var x int = 0继续保留其修改后的值并进行修改。

有了所有这些知识,您问题的答案很简单:如果您不保留范围,如上所述的squares()定义,那么您定义一个新的{{1} } fmt.Println(squares()()) 的每次邀请:

squares()

这会在每次通话时调用新的var x int = 0。因此,新的squares()()

因为每次调用一个新的正方形,所以在每次调用时,使用var x int = 0调用的正方形返回函数的输出始终在global $wpdb; $vypis = $wpdb->get_results ("SELECT * FROM hodnoceni", ARRAY_A); foreach ($vypis as $film) { class $film['nazev'] extends WP_Widget { 上运行。

答案 3 :(得分:1)

仅仅因为(1)x是匿名函数的捕获闭包,(2)int类型的默认值是0。因此,每次调用它时,您都会看到相同的输出。

让我们像这样重写函数squares

func squares(initialValue int) func() int {
    var x int
    x = initialValue
    return func() int {
        x = x + 2
        return x * x
    }
}

现在为此:

func main() {
    f := squares(0)
    fmt.Println(f())
    fmt.Println(f())
    fmt.Println(f())
    fmt.Println(squares(0)())
    fmt.Println(squares(0)())
    fmt.Println(squares(0)())
}

我们会看到确切的输出!因为我们正在使用x初始化0。如果我们使用1作为x的初始值:

func main() {
    f := squares(1)
    fmt.Println(f())
    fmt.Println(f())
    fmt.Println(f())
    fmt.Println(squares(1)())
    fmt.Println(squares(1)())
    fmt.Println(squares(1)())
}

我们会看到这个结果:

9
25
49
9
9
9

正如您所看到的,它只是x的初始值,当未明确初始化时,它将具有默认值零。

相关问题