推迟使用说明

时间:2015-07-14 10:48:05

标签: go deferred

我们假设我有以下功能

func printNumbers(){
 var x int

 defer fmt.Println(x)

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

正如specification中所述:

  

每次执行“延迟”语句时,将像往常一样评估调用的函数值和参数,并重新保存,但不会调用实际函数。

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

我提出了以下解决方案:

func printNumbers(){
  var x int

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

  defer printVal()

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

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

2 个答案:

答案 0 :(得分:6)

一般来说重要的是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) // Will print 5 at the end

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}

答案 1 :(得分:3)

如果延迟有参数,则在延迟语句的行中对它们进行评估;这在下面的代码片段中说明,其中defer将打印0:

self.viewController.check()

如果要将语句或函数的执行推迟到封闭(调用)函数的末尾,则可以使用匿名函数作为延迟语句。这是一个更新的例子:

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

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