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、返回值之间执行顺序,想不通哇,求大哥哥们帮帮忙。

  •  
  •   Flygar · 2018-05-21 23:56:16 +08:00 · 2386 次点击
    这是一个创建于 2159 天前的主题,其中的信息可能已经有所发展或是发生改变。
    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   ·   我们的愿景   ·   实用小工具   ·   4945 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 09:41 · PVG 17:41 · LAX 02:41 · JFK 05:41
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.