Golang中的defer关键字|青训营笔记

135 阅读2分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记。

Go语言中的defer语句十分常用,其的作用是延迟函数或方法调用语句的执行至函数结束时,我们可以利用defer语句作一些资源的收尾工作,比如:

func A() {
    ...
    f, err := os.Open(filename)
    if err != nil { ... }
    defer f.close()
    ...
}

文件资源便会在函数A()执行完后得到释放,通过defer语句我们的编程更加方便,不用刻意记住资源的释放,同时也使代码的可读性上升了。

执行顺序

defer语句的执行顺序不同于普通的语句,它们会被压入栈中延迟执行,因此执行顺序符合后进先出的规律。

在青训营入营笔试中就有一道选择题考到这个点,题目如下:

func main(){
    if true {
        defer fmt.Printf("1")
    } else {
        defer fmt.Printf("2")
    }
    defer fmt.Printf("3")
}

结果为31,因为打印3的语句在后,因此执行时顺序在前,而打印2的语句所在if分支未被执行,因此在函数收尾时其中的defer语句也不会被执行。

与return的关系

我们使用golang编程时经常会遇到在有返回值的函数中用到defer语句,有时候defer语句跟返回值相关,那么什么情况下defer语句会影响到返回值呢?

以下有两个例子:

1.
func A() int {
    i := 0
    defer func(){ i++ }()
    return i
}
2.
func B() (i int) {
    defer func(){ i++ }()
    return i
}

使用如下main函数:
func main(){
    fmt.Println("A(): ", A())
    fmt.Println("B(): ", B())
}

运行结果如图:

image.png

原理在于:函数结束时,return语句首先对返回值进行赋值,然后执行被defered的语句,最后将返回值带出函数体。

在A()函数中,return会创建一个int类型的匿名变量,将i的值赋给它,那么defer的i++只能影响i而不能影响将要返回的匿名变量,故结果仍为0。

在B()函数中,return的变量在原本已声明为i,那么defer语句的i++则会影响到将要被返回的变量i,因此结果是1。