Golang 中 defer 的使用

Golang 中有一个 defer 关键词。来讲一些操作延迟,这个关键词通常用于关闭资源等等。

defer 的使用

func test(srcName string) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()
}

这里如果打开文件出错,在 err != nil 的情况下返回,如果没有 defer 这一行,这个打开的文件就无法被关闭。这种用法也很类似 java 里的 finally。

defer 的特性

defer 关键词有几个特别的地方。

defer 表达式的参数在定义时就被明确

package main

import "fmt"

func main() {
    a()
    b()
}

func a() {
    i := 0
    defer fmt.Println("a: ", i)
    i++
    return
}

func b() {
    i := 0
    i++
    defer fmt.Println("b: ", i)
    return
}

// output
// a:  0
// b:  1

多个 defer 的执行顺序是先进后出

package main

import "fmt"

func main() {
    a()
}

func a() {
    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3)
    defer fmt.Println(4)
}

// output
// 4
// 3
// 2
// 1

我的理解是,在 defer 被定义的时候 ,表达式会放入一个栈结构中,然后执行这些 defer 表达式的时候就是先进后出了。

defer 表达式可以修改函数的有命名的返回值

package main

import "fmt"

func main() {
    fmt.Println(a())
}

func a() (i int) {
    i = 0
    defer func() { i++ }()
    return 1
}

// output
// 2

这里的结果也许让人很奇怪。首先返回的变量名为 i,在 return 语句里相当于把这个变量值 设置为了 1。然后执行了 defer 的表达式,defer 将这个值又加上了 1,所以最后返回的结果是 2。

注意:这里只是在有名返回值的情况,匿名返回值没有这样的。

defer 和 return 的关系

从上面的第三点就可以看出来,defer 和 return 真正返回之前修改了返回值。

return 的执行过程:

执行 return 语句的时候,首先会给返回参数赋值(有名返回值直接赋值,匿名返回值先声明返回参数再赋值),例如前面第三个例子最后 return 1 就是把 1 赋值给返回参数 i。

调用 RET 返回指令,RET 会检查是否有 defer 的栈存在,有就执行 defer 语句。

RET 携带返回值退出函数。

所以这里也说明有名返回值会被 defer 修改是因为有名返回值的返回参数在函数声明的同时就被声明了。defer 可以访问到,而匿名函数值是在 return 执行的时候才声明返回参数,defer 操作的参数与其无关。

这里有一篇文章,对于这个问题分析的很好。Golang中defer、return、返回值之间执行顺序的坑