【问题标题】:Why I get 0 and 1 in the following golang code example with defer为什么我在下面的 golang 代码示例中使用 defer 得到 0 和 1
【发布时间】:2019-05-02 10:37:28
【问题描述】:

调用 defer 对以两种不同方式声明的变量产生不同的结果

package main

import (
    "fmt"
)

func c(i int) int {
    defer func() { i++ }()
    return i
}

func c1() (i int) {
    defer func() { i++ }()
    return i
}

func c2() (i int) {
    defer func() { i++ }()
    return 2
}

func main() {
    fmt.Println(c(0)) // Prints 0
    fmt.Println(c1()) // Prints 1
    fmt.Println(c2()) // Prints 3 Thank you icza
}

https://play.golang.org/p/gfnnCZ--DkH

【问题讨论】:

  • 感谢来自#general@golang slack 的 Johan Brandhorst 提供的参考 golang.org/ref/spec#Defer_statements "也就是说,如果周围函数通过显式返回语句返回,则在设置任何结果参数后执行延迟函数该 return 语句,但在函数返回其调用者之前"
  • golang.org/ref/spec#Defer_statements "如果周围函数通过显式 return 语句返回,则延迟函数在 任何结果参数由该 return 语句设置之后执行,但 函数返回给它的调用者之前。" ... "如果延迟函数是一个函数字面量,并且周围的函数具有在字面量范围内的命名结果参数,则延迟函数可以在返回结果参数之前访问和修改它们。"

标签: go deferred


【解决方案1】:

在第一个示例中,i 是一个(传入)参数。在return 语句中,返回值被计算,延迟函数在此之后运行,增加i 对返回值没有影响。

在第二个示例中,i 是结果参数的名称。在return 语句中,您显式返回值i,然后将其分配给返回值i(这是一个无操作)。但是延迟函数可以修改返回“变量”的值,如果他们这样做了,就会对实际返回的值产生影响。

如果我们再添加一个例子,这就更清楚了:

func c2() (i int) {
    defer func() { i++ }()
    return 2
}

此函数将返回3,因为return 2 语句会将2 分配给i,然后延迟函数将增加此值,因此返回值将是3。在Go Playground 上试试这个。相关部分来自Spec: Return statements:

指定结果的“return”语句在执行任何延迟函数之前设置结果参数。

一般来说,如果函数(或方法)具有命名结果参数,则返回值将始终是这些变量的值,但一定不要忘记return 语句可能会为这些结果参数分配新值,并且它们可以通过延迟函数return 语句之后修改。

Spec: Defer statements中提到了这个:

例如,如果延迟函数是 function literal 并且周围的函数有 named result parameters 在字面量内,则延迟函数可以在返回结果参数之前访问和修改结果参数。

博文Defer, Panic and Recover中也提到过:

延迟函数可以读取并分配给返回函数的命名返回值。

还有Effective Go: Recover:

如果doParse 发生紧急情况,恢复块会将返回值设置为nil——延迟函数可以修改命名的返回值。

查看相关问题:How to return a value in a Go function that panics?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-07-04
    • 2018-03-22
    • 1970-01-01
    • 2013-01-13
    • 1970-01-01
    • 1970-01-01
    • 2014-03-19
    相关资源
    最近更新 更多