Go闭包变量作用域

huangapple go评论85阅读模式
英文:

Go closure variable scope

问题

我正在阅读《CreateSpace An Introduction to Programming in Go 2012》的第86页,我发现了这个邪恶的魔法:

func makeEvenGenerator() func() uint {
    i := uint(0)

    return func() (ret uint) {
        ret = i
        i += 2
        return
    }
}

// 这是它的调用方式
nextEven := makeEvenGenerator()
fmt.Println(nextEven())
fmt.Println(nextEven())
fmt.Println(nextEven())

1)为什么i没有重置?
2)nextEven()返回的是uint类型,还是Println聪明到可以处理任何类型?

英文:

I'm reading 'CreateSpace An Introduction to Programming in Go 2012'

and on page 86 I found this evil magic

func makeEvenGenerator() func() uint {
    i := uint(0)

    return func() (ret uint) {
        ret = i
        i += 2
        return
    }
}

// here's how it's called
nextEven := makeEvenGenerator()
fmt.Println(nextEven())
fmt.Println(nextEven())
fmt.Println(nextEven())
  1. Why is i not resetting ?
  2. is nextEven() returning and uint or is Println so smart that it can work with everything ?

答案1

得分: 15

为了清晰起见,我将为这两个函数分配名称:

func makeEvenGenerator() func() uint { // 将其称为“工厂函数”
    i := uint(0)

    return func() (ret uint) { // 将其称为“闭包函数”
        ret = i
        i += 2
        return
    }
}

工厂函数返回闭包函数——在Go中,函数是一等公民,也就是说它们可以作为右值表达式,例如:

f := func() { fmt.Println("f was called") }

f() // 输出 "f was called"

在你的代码中,闭包函数包裹了工厂函数的上下文,这被称为“词法作用域”。这就是为什么变量 i 在闭包函数内部可用,它不是 i 的副本,而是对 i 本身的引用。

闭包函数使用了一个名为 ret 的“命名返回值”。这意味着在闭包函数内部,你将隐式地声明 ret,并且在 return 的位置,ret 的值将被返回。

这行代码:

ret = i

i 的当前值赋给 ret。它不会改变 i 的值。然而,这行代码:

i += 2

将改变 i 的值,以便在下次调用闭包函数时使用。

这里有一个我为你编写的小闭包示例。它可能没有太大的实际用途,但在我看来很好地说明了闭包的作用域、目的和用法:

package main

import "fmt"

func makeIterator(s []string) func() func() string {
    i := 0
    return func() func() string {
        if i == len(s) {
            return nil
        }
        j := i
        i++
        return func() string {
            return s[j]
        }
    }
}

func main() {

    i := makeIterator([]string{"hello", "world", "this", "is", "dog"})

    for c := i(); c != nil; c = i() {
        fmt.Println(c())
    }

}
英文:

For the sake of clarity, I'll assign names to both functions:

func makeEvenGenerator() func() uint { // call this "the factory"
    i := uint(0)

    return func() (ret uint) { // call this "the closure"
        ret = i
        i += 2
        return
    }
}

The factory returns the closure – functions are first class citizens in Go i.e. they can be right-hand expressions, for example:

f := func() { fmt.Println("f was called"); }

f() // prints "f was called"

In your code, the closure wraps over the context of the factory, this is called lexical scoping. This is why the variable i is available inside the closure, not as a copy but as a reference to i itself.

The closure uses a named return value called ret. What this means is that inside the closure you'll have implicitly declared ret and at the point of return, whatever value ret has will be returned.

This line:

ret = i

will assign the current value of i to ret. It will not change i. However, this line:

i += 2

will change the value of i for the next time the closure is called.

<hr>

Here you'll find a little closure example I wrote together for you. It's not extremely useful but illustrates the scope, purpose and use of closures pretty well in my opinion:

package main

import &quot;fmt&quot;

func makeIterator(s []string) func() func() string {
	i := 0
	return func() func() string {
		if i == len(s) {
			return nil
		}
		j := i
		i++
		return func() string {
			return s[j]
		}
	}
}

func main() {

	i := makeIterator([]string{&quot;hello&quot;, &quot;world&quot;, &quot;this&quot;, &quot;is&quot;, &quot;dog&quot;})

	for c := i(); c != nil; c = i() {
		fmt.Println(c())
	}

}

答案2

得分: 8

  1. 为什么i没有重置?

在Go语言中,闭包通过引用来捕获变量。这意味着内部函数持有对外部作用域中i变量的引用,每次调用它都会访问同一个变量。

  1. nextEven()返回的是uint类型,还是Println聪明到可以处理任何类型?

fmt.Println()(以及fmt.Print()fmt.Fprint()等)可以处理大多数类型。它以"默认格式"打印其参数。这与使用fmt.Printf()%v动词打印的内容相同。

英文:

> 1) Why is i not resetting ?

Closures in Go capture variables by reference. That means the inner function holds a reference to the i variable in the outer scope, and each call of it accesses this same variable.

> 2) is nextEven() returning and uint or is Println so smart that it can
> work with everything ?

fmt.Println() (along with fmt.Print(), fmt.Fprint(), etc.) can work most types. It prints its arguments in the "default format". It is the same thing that is printed using fmt.Printf() using the %v verb.

答案3

得分: -1

闭包中的变量既不受代码段的限制,也不受上下文的限制。

英文:

The variable in closure is free from neither code segment nor context.

huangapple
  • 本文由 发表于 2013年11月13日 23:12:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/19957323.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定