Golang 中 defer、return、返回值之间执行顺序,想不通哇,求大哥哥们帮帮忙。 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
Flygar
V2EX    Go 编程语言

Golang 中 defer、return、返回值之间执行顺序,想不通哇,求大哥哥们帮帮忙。/h1>
  •  
  •   Flygar 2018-05-21 23:56:16 +08:00 2922 次点击

    这是一个创建于 2703 天前的主题,其中的信息可能已经有所发展或是发生改变。
    func a() { i := 0 i++ //defer 执行阶段处于 return 之后,函数返回之前 defer fmt.Println("defer", i) //那这个为什么输出为 1 ?不应该是 2 吗 i++ fmt.Println("i=", i) return } 

    这是为啥子呢?

    第 1 条附言    2018-05-22 01:44:21 +08:00

    感谢各位,给的链接我也看了(我没看懂),有些地方不清楚。 这个地方不清楚
    defer声明时会先计算确定参数的值,defer推迟执行的仅是其函数体。
    下面这个函数返回值是7,defer的打印也是7;
    我明明在defer func()前明确了ret = 1;
    不应该函数返回值是2,defer的打印也是2吗?

    func b() (ret int) { ret = 1 defer func() { ret++ fmt.Printf("defer %d\n", ret) }() return 6 } 
    第 2 条附言    2018-05-22 22:07:16 +08:00

    我明白啦
    感谢14L,18L两位大哥,牛逼!贼鸡儿强!

    //Output: 5 5 5 5 5 //defer 表达式中的 i 是对 for 循环中 i 的引用。到最后,i 加到 5,故最后全部打印 5。 for i := 0; i < 5; i++ { defer func() { fmt.Println(i) }() } //Output: 4 3 2 1 0 //如果将 i 作为参数传入 defer 表达式中,在传入最初就会进行求值保存,只是没有执行延迟函数而已。 for i := 0; i < 5; i++ { defer func(idx int) { fmt.Println(idx) }(i) // 传入的 i,会立即被求值保存为 idx } 
    lujjjh
        1
    lujjjh  
       2018-05-22 00:09:12 +08:00   2
    https://golang.org/doc/effective_go.html#defer

    > The arguments to the deferred function (which include the receiver if the function is a method) are evaluated when the defer executes, not when the call executes.
    guoer
        2
    guoer  
       2018-05-22 00:17:18 +08:00   1
    zwh2698
        3
    zwh2698  
       2018-05-22 00:21:49 +08:00 via Android   1
    遇到 defer 就把后面的语句压栈,等执行完了,再执行栈中的语句,类似 c++栈对象的析构
    msg7086
        4
    msg7086  
       2018-05-22 00:25:14 +08:00   2
    defer 当然是取当前环境的变量了。
    你想想,比如你开个循环读取 10 个文件,把关闭全部 defer 了,函数退出的时候是关闭这 10 个文件,还是把最后那个文件关闭 10 次?
    timothyye
        5
    timothyye  
       2018-05-22 00:45:29 +08:00   1
    之前写过一篇 blog,希望能帮到你

    《 Golang 中 defer 的那些事》 https://xiaozhou.net/something-about-defer-2014-05-25.html
    xrlin
        6
    xrlin  
       2018-05-22 01:18:42 +08:00   1
    Flygar
        7
    Flygar  
    OP
       2018-05-22 01:46:07 +08:00
    楼上的老哥们。大嘎好,我系渣渣灰。
    boboliu
        8
    boboliu  
       2018-05-22 07:13:07 +08:00 via Android
    关于 append1 中的问题,请参考: https://blog.golang.org/defer-panic-and-recover 中 3. Deferred functions may read and assign to the returning function's named return values. 这一段。

    That's a feature.
    Binb
        9
    Binb  
       2018-05-22 08:01:43 +08:00 via Android
    不是因为 0 加 1 等于 1 吗。。。这个例子没体现 defer..
    goofool
        10
    goofool  
       2018-05-22 09:23:07 +08:00
    函数是值传递的,你怎么操作原来的值,拷贝都不会变啊!
    wweir
        11
    wweir  
       2018-05-22 09:32:26 +08:00 via Android
    一直疑惑,defer 的栈帧是寄存在什么地方的,和普通函数一样?
    xrlin
        12
    xrlin  
       2018-05-22 10:02:52 +08:00
    ```go
    func b() (ret int) {
    ret = 1
    defer func() {
    ret++
    fmt.Printf("defer %d\n", ret)
    }()
    return 6
    }
    ```
    在 return 6 这条语句执行时其实是先把 6 赋值给 ret,然后执行 defer,再设置 RET 标记,然后 return,所以输出 7。
    baoanlol
        13
    baoanlol  
       2018-05-22 10:10:14 +08:00
    @xrlin 多谢解惑!
    Shakeitin
        14
    Shakeitin  
       2018-05-22 10:11:06 +08:00   1
    defer 后直接调用函数时,参数是值传递的,所以在第一个例子中,i 的值就已经被固定为 1 了

    但是在调用闭包时,无论是否 defer,只要变量不是通过参数列表传递给闭包,而是通过闭包的自动捕获变量拿到了这个变量,效果都是直接引用了这个变量本身
    在第二个例子中的顺序就是
    1: 为 ret 赋值为 6
    2: 执行 defer 中的闭包,ret 被赋值为 7 并输出
    3: b() 函数返回

    如果第二个例子是如下这样的话,在 }(ret) 这一行,形参就已经被确定了,就会 defer 输出 1,函数返回 6

    func b() (ret int) {
    ret = 1
    defer func(x int) {
    x++
    fmt.Printf("defer %d\n", x)
    }(ret)
    return 6
    }
    Shakeitin
        15
    Shakeitin  
       2018-05-22 10:13:09 +08:00
    defer 输出 2 打错了
    baoanlol
        16
    baoanlol  
       2018-05-22 10:24:48 +08:00
    正好借楼问一句,我是 scala 和 java 那边过来在自己研究 fabric,然后你们有没有觉得 go 对 error handler 用起来特别麻烦呢?调用什么 api 都会返回 2 个参数,一个实际值,一个 err (比如 x,err=f()),然后每次都要写一段 if (err != nil) ,完全不如 java 那边直接 try catch 所有传递的 exception 来的简单方便。。。。
    xrlin
        17
    xrlin  
       2018-05-22 10:30:13 +08:00
    @baoanlol 你可以试试直接 panic,然后外层调用者捕捉 panic,err 校验和 exception 也是各有优劣吧。
    gnaggnoyil
        18
    gnaggnoyil  
       2018-05-22 11:09:27 +08:00
    这让我感觉 C++的 lambda 中显式指定 capture 的做法真是无比英明的设计……
    deepzz
        19
    deepzz  
       2018-05-22 18:12:26 +08:00   1
    Flygar
        20
    Flygar  
    OP
       2018-05-22 22:08:19 +08:00
    @Shakeitin @deepzz 牛逼!我整明白了!再次表示感谢!
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1031 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 36ms UTC 22:57 PVG 06:57 LAX 15:57 JFK 18:57
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86