延迟使用澄清

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

Defer usage clarification

问题

假设我有以下函数:

func printNumbers() {
    var x int

    defer fmt.Println(x)

    for i := 0; i < 5; i++ {
        x++
    }
}

根据规范中的说明:

每次执行“defer”语句时,函数值和调用的参数会像平常一样被评估并保存,但实际的函数不会被调用。

显然,当函数执行结束时,将打印出零。但是,如果我想打印出变量x的最终值,我该怎么办?

我想到了以下解决方案:

func printNumbers() {
    var x int

    printVal := func() {
        fmt.Println(x)
    }

    defer printVal()

    for i := 0; i < 5; i++ {
        x++
    }
}

所以我想知道是否有更好的方法来解决这个问题。

英文:

Let's assume I have the following function

func printNumbers(){
 var x int

 defer fmt.Println(x)

 for i := 0; i &lt; 5; i++{
  x++
 }
}

As it is said in the specification:

> Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked.

Obviously, zero will be printed out when the function execution ends.
But what should I do if I want to print out the final value of variable x?

I've come up with the following solution:

func printNumbers(){
  var x int

  printVal := func(){
    fmt.Println(x)
  }

  defer printVal()

  for i := 0; i &lt; 5; i++{
    x++
  }
}

So I wonder if there is a better way to resolve this problem.

答案1

得分: 8

通常重要的是,x 不能是你延迟执行的函数的参数,因为它们在执行 defer 时就会被求值。

1) 使用匿名函数

下面是使用匿名函数的解决方案:

defer func() { fmt.Println(x) }()

在这里,x 不是延迟执行的匿名函数的参数,因此它不会被求值。只有当匿名函数被执行并调用 fmt.Println() 时才会被求值。

2) 使用指针

使用指向 x 的指针(如 &x)可以工作,因为只有地址会被求值,最终指向的值当然是 5。但是这种方法的问题是 fmt.Println() 不会打印指向的值,而是打印指针本身。

但是为了演示它的工作原理,可以看看这个辅助函数:

func Print(i *int) {
    fmt.Println(*i)
}

并使用它:

defer Print(&x) // 最后会打印 5

3) 使用自定义类型

这与指针解决方案类似,但不需要辅助函数。但是需要编写自己的 String() 方法:

type MyInt int

func (m *MyInt) String() string {
    return strconv.Itoa(int(*m))
}

并使用它:

var x MyInt

defer fmt.Println(&x)

for i := 0; i < 5; i++ {
    x++
}

当执行 defer 语句时,只有指针会被求值(x 的地址,类型为 *MyInt)。由于类型 *MyInt 实现了 fmt.Stringerfmt.Println() 将调用它的 String() 方法。

4) 包装

这也类似于指针解决方案,而且它甚至不会像你期望的那样仅打印 5

问题 #2 的原因是 fmt.Println() 会打印指针而不是指向的值(我们通过自己的 Print() 函数解决了这个问题)。然而,还有其他类型类似于指针,fmt.Println() 会打印它们的内容。

所以让我们将变量包装到一个切片中,看看会发生什么:

x := []int{0}

defer fmt.Println(x)

for i := 0; i < 5; i++ {
    x[0]++
}

输出:

[5]

看到 5 的原因是切片是一个描述符。当 defer 被求值时,会复制切片(在执行时将传递给 fmt.Println()),但它引用相同的底层数组。

还要注意,如果指针是指向结构体、数组、切片、映射的指针,fmt.Println() 会打印指向的内容,所以下面的代码也可以工作:

x := struct{ i int }{}

defer fmt.Println(&x)

for i := 0; i < 5; i++ {
    x.i++
}

并打印:

&{5}
英文:

Generally what is important is x cannot be a parameter of the function you are deferring, because they are evaluated when defer is executed.

1) With Anonymous Function

Here's a solution using an anonymous function:

defer func() { fmt.Println(x) }()

Here x is not a parameter of the deferred anonymous function, so it will not be evaluated. Only when the anonymous function is executed and it calls fmt.Println().

2) With Pointer

Using a pointer (like &amp;x) pointing to x would work, because only the address is evaluated, and the pointed value at the end will be 5 of course. The problem with this is that fmt.Println() will not print the pointed value but the pointer itself.

But to demonstrate its working, see this helper function:

func Print(i *int) {
	fmt.Println(*i)
}

And using it:

defer Print(&amp;x) // Will print 5 at the end

3) With custom type

This is similar to the pointer solution, but doesn't need a helper function. But it does need you to write your String() method:

type MyInt int

func (m *MyInt) String() string {
	return strconv.Itoa(int(*m))
}

And using it:

var x MyInt

defer fmt.Println(&amp;x)

for i := 0; i &lt; 5; i++ {
	x++
}

When defer statement is executed, only the pointer will be evaluated (address of x, type of *Myint). And since the type *MyInt implements fmt.Stringer, fmt.Println() will call its String() method.

4) Wrapping

This is also similar to the pointer solution, and this will not even print exactly just 5 as you expect, but:

Problem with #2 was that fmt.Println() will print the pointer and not the pointed value (which we solved with our own Print() function). However there are other types which are similar to pointers and fmt.Println() will print their content.

So let's wrap the variable into a slice, and see what happens:

x := []int{0}

defer fmt.Println(x)

for i := 0; i &lt; 5; i++ {
	x[0]++
}

Prints:

[5]

Reason for seeing 5 is that a slice is a descriptor. When defer is evaluated, a copy is made of the slice (that will be passed to fmt.Println() when it gets executed) but it refers to the same underlying array.

Also note that fmt.Println() prints the pointed content if the pointer is a pointer to a struct, array, slice, maps, so the following code also works:

x := struct{ i int }{}

defer fmt.Println(&amp;x)

for i := 0; i &lt; 5; i++ {
	x.i++
}

And prints:

&amp;{5}

答案2

得分: 5

如果defer语句有参数,它们会在defer语句所在行进行求值;下面的代码片段演示了这一点,其中defer语句将打印0:

func printNumber() {
   i := 0
   defer fmt.Println(i) // 将打印0
   i++
   return
}

如果你想要推迟执行一个语句或函数,直到封闭的(调用的)函数结束,你可以使用匿名函数作为defer语句。下面是一个更新后的示例:

func printNumbers() {
	x := 0
	defer func() { fmt.Println(x) }()
	for i:=0; i < 5; i++ {
		x++;
	}
	return
}

点击这里查看示例代码。

英文:

If the defer has arguments they are evaluated at the line of the defer-statement; this is illustrated in the following snippet, where the defer will print 0:

func printNumber() {
   i := 0
   defer fmt.Println(i) // will print 0
   i++
   return
}

You can use an anonymous function as a defer statement if you want to postpone the execution of a statement or a function until the end of the enclosing (calling) function. Here is an updated example:

func printNumbers() {
	x := 0
	defer func() { fmt.Println(x) }()
	for i:=0; i &lt; 5; i++ {
		x++;
	}
	return
}

http://play.golang.org/p/YQGQ_8a0_9

huangapple
  • 本文由 发表于 2015年7月14日 18:48:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/31404471.html
匿名

发表评论

匿名网友

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

确定