美文网首页随笔-生活工作点滴
不是我吹,你可能连defer都不清楚

不是我吹,你可能连defer都不清楚

作者: LinkinStar | 来源:发表于2019-07-05 14:53 被阅读0次

前言

在golang中,对于defer,我之前的理解就是和java中的finally代码块一样,没什么难度,但是吧,当我最近看的一些神奇的问题,我就发现原来并非想的那么简单。

先举个栗子

package main

import "fmt"

func main() {
    fmt.Println(DeferFunc1(1))
    fmt.Println(DeferFunc2(1))
    fmt.Println(DeferFunc3(1))
    DeferFunc4()
}

func DeferFunc1(i int) (t int) {
    t = i
    defer func() {
        t += 3
    }()
    return t
}

func DeferFunc2(i int) int {
    t := i
    defer func() {
        t += 3
    }()
    return t
}

func DeferFunc3(i int) (t int) {
    defer func() {
        t += i
    }()
    return 2
}

func DeferFunc4() (t int) {
    defer func(i int) {
        fmt.Println(i)
        fmt.Println(t)
    }(t)
    t = 1
    return 2
}

请问这段代码输出的结果是什么?


答案见文末


如果你看完答对了,那么请直接点击右上角的关闭按钮,如果你答错了,你可以继续往下看了。
下面会一步步介绍,到底为什么结果会是这样

基础知识

函数的返回值初始化

如 : func DeferFunc1(i int) (t int) {
其中返回值t int,这个t会在函数起始处被初始化为对应类型的零值并且作用域为整个函数。

defer的执行顺序

虽然这边没有提及,但是还是要说一下,因为很多人学习defer的时候都会用到,就是当多个defer出现的时候,它是一个“栈”的关系,也就是先进后出。一个函数中,写在前面的defer会比写在后面的defer调用的晚。

defer与return谁先谁后

return先,defer后
这个可能会让人怀疑,后面会详细解释。

函数的返回与return

在没有defer的情况下,其实函数的返回就是与return一致的,但是有了defer就不一样了。
函数的返回其实是有两个步骤的,第一个当执行到return语句的时候

func DeferFunc3(i int) (t int) {
    defer func() {
        t += i
    }()
    return 2
}

这个时候会先将返回值t赋值为2,然后执行defer,完成之后才会真正返回外部调用者。

defer调用的三步走

这个就是今天的重头戏了,defer这个语法其实一共有三个步骤。

  1. 将defer方法中的参数进行赋值。
  2. 将defer压入栈中。
  3. 当return或者是panic的时候依次出栈执行。
    后面会用实际的例子说明具体执行的情况。

解释

有了上面的所有知识点,其实你就应该能明白上面输出的结果了。如果还不明白就看看下面的分析解释吧。

DeferFunc1

func DeferFunc1(i int) (t int) {
    t = i
    defer func() {
        t += 3
    }()
    return t
}

首先上面是第一个方法

  1. 将返回值t赋值为传入的i,此时t为1
  2. 执行return语句将t赋值给t(等于啥也没做)
  3. 执行defer方法,将t + 3 = 4
  4. 函数返回 4
    因为t的作用域为整个函数所以修改有效。

DeferFunc2

func DeferFunc2(i int) int {
    t := i
    defer func() {
        t += 3
    }()
    return t
}

第二个方法

  1. 创建变量t并赋值为1
  2. 执行return语句,注意这里是将t赋值给返回值,此时返回值为1(这个返回值并不是t)
  3. 执行defer方法,将t + 3 = 4
  4. 函数返回返回值1

可能这里就有点难理解了,修改一下代码你就明白了

func DeferFunc2(i int) (result int) {
    t := i
    defer func() {
        t += 3
    }()
    return t
}

上面的代码return的时候相当于将t赋值给了result,当defer修改了t的值之后,对result是不会造成影响的。

DeferFunc3

func DeferFunc3(i int) (t int) {
    defer func() {
        t += i
    }()
    return 2
}
  1. 首先执行return将返回值t赋值为2
  2. 执行defer方法将t + 1
  3. 最后返回 3

DeferFunc4

func DeferFunc4() (t int) {
    defer func(i int) {
        fmt.Println(i)
        fmt.Println(t)
    }(t)
    t = 1
    return 2
}

这个分析的步骤要详细一些

  1. 初始化返回值t为零值 0
  2. 首先执行defer的第一步,赋值defer中的func入参t为0
  3. 执行defer的第二步,将defer压栈
  4. 将t赋值为1
  5. 执行return语句,将返回值t赋值为2
  6. 执行defer的第三步,出栈并执行
    因为在入栈时defer执行的func的入参已经赋值了,此时它作为的是一个形式参数,所以打印为0;相对应的因为最后已经将t的值修改为2,所以再打印一个2

总结

看完,有的人肯定又要出来搞事了,说这个在实际中不会遇到的,实际中谁写这么蠢的代码。但是其实某些时候非常重要,当我们需要在defer中返回一些错误信息的时候,并且需要将这些信息给到调用者的时候,就需要注意变量的作用域以及执行顺序所带来的差异。

而且正因为这样的执行顺序,在实际中要记住:
defer 最大的功能是 panic 后依然有效
所以defer可以保证你的一些资源一定会被关闭,从而避免一些异常出现的问题。

参考例子来源于网络,自己做了修改和结合:
https://stackoverflow.com/questions/52718143/is-golang-defer-statement-execute-before-or-after-return-statement

答案

4
1
3
0
2

相关文章

  • 不是我吹,你可能连defer都不清楚

    前言 在golang中,对于defer,我之前的理解就是和java中的finally代码块一样,没什么难度,但是吧...

  • 别再一知半解的用defer

    你是不是觉得defer很简单、很好用,但也许你掉坑里了都不知道! 这篇文章不介绍defer的常用功能,而是介绍你在...

  • 卢克:可能你连备胎都不是...

    在跟女孩相处的时候,有些兄弟不知道自己是否已经成为备胎。 有备胎肯定就有主胎,备胎就是除了主胎之外的第二或者第三人...

  • 气液增压缸怎么排油?

    气液增压缸怎么排油?对于这个问题,可能很多用户都不是很清楚,而连增压缸产品怎么放油都不清楚,再后面的加液压油等操作...

  • 秘密

    今天包饺子时,总能从婆婆口中得知些不为人知的秘密! 婆婆说可能连老公都不是很清楚! 婆婆竟然告诉我她有...

  • 科普帖:你知道超人为啥是男的嘛?

    连烟都不会吸,你还是不是男的?” “连酒都不喝,你还是不是男的?” “连……都不……,你还是不是男的?” 这样的话...

  • 别太自信,你可能连备胎都不是!

    你遇到过这种情况吗?曾经暧昧过的人黑不提白不提意外失踪?你原以为缘分已尽就这样吧,他又突然联系你?于是,你又重新燃...

  • 创业VS考编

    迷茫的时候,你连自己吃什么都不知道? 迷茫的时候,你连自己的内心深处所想都不清楚呢? 迷茫的时候,你连自己都不知道...

  • 2018-05-08

    可能有时候,最无奈的就是不知道不了解不清楚你在讲什么,于是你误会我我误会你,次数多了,我连解释都不想解释,了解也...

  • 你连备胎都不是

    年会节目统筹组成员,隔着好几个座位大喊,“嘿,你记得明天把时间排出来,我们核对控场问题。” 那位被选为控场的同事反...

网友评论

    本文标题:不是我吹,你可能连defer都不清楚

    本文链接:https://www.haomeiwen.com/subject/jlhehctx.html