一、defer用法

defer 是golang中独有的流程控制语句,用于延迟指定语句的运行时机,运行于函数的内部,当他所属函数运行完之后它才会被调用。

例如以下使用defer的代码:

func main() {
    defer fmt.Println("HelloDefer")
    fmt.Println("HelloWorld")
}

输出结果为:

HelloWorld
HelloDefer

它会先打印出HelloWorld ,然后再打印出HelloDefer。一个函数中如果有多个defer ,运行顺序和函数中的调用顺序相反,因为它们都是被写在了栈中:

func deferTest(){
    defer fmt.Println("HelloDefer1")
    defer fmt.Println("HelloDefer2")
    fmt.Println("HelloWorld")
}

运行结果:

fmt.Println("HelloDefer2")
fmt.Println("HelloDefer1")
fmt.Println("HelloWorld")

二、defer和return

在包含有return语句的函数中,defer的运行顺序位于return之后,但是defer所运行的代码片段会生效:

func deferReturn() int {
    i := 1

    defer func() {
        fmt.Println("Defer")
        i += 1
    }()

    return func() int {
        fmt.Println("Return")
        return i
    }()
}

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

运行结果:

Return
Defer
1

这里很明显就能看到defer是在return之后运行的!但是有一个问题是defer里执行了语句i += 1 ,按照这个逻辑的话返回的i 值应该是2 而不是1 。这个问题是由于return的运行机制导致的:return返回一个对象时,如果返回类型不是指针或者引用类型,那么return返回的就不是这个对象本身,而是这个对象的副本。也就是说,实际上当defer执行的时候,return已经把值返回了,返回的是一个副本值,此时defer对值的修改就无效了。

如果返回的是引用值,defer的改动是有效的。我们可以把返回值得int类型改成*int:

func deferAndReturn() *int {
    i := 1
    defer func() {
        fmt.Println("Defer:\t", i, &i)
        i += 1
    }()
    return func() *int {
        fmt.Println("Return:\t", i, &i)
        return &i
    }()
}

func main() {
    x := deferAndReturn()
    fmt.Println("main:\t", *x, x)
}

程序的输出为:

Return:  1 0xc000014060
Defer:   1 0xc000014060
main:    2 0xc000014060

可以看到,在return和defer执行前,i的值都是1,并且地址一样。返回到main函数后,i的地址也是和函数体的一样,但是值是2了,这就说明defer中对i的修改生效了。

三、defer和panic

panic会在defer运行完之后才把恐慌扩散到其他函数:

func deferPanic(){
    defer fmt.Println("HelloDefer")
    panic("Hey, I'm panic")
}

结果:

HelloDefer  //会先输出defer中的语句
panic: Hey, I'm panic

goroutine 1 [running]:
main.deferPanic()
        /Users/maqian/code/go/src/awesomeProject/defer/defer.go:21 +0xb9
main.main()
        /Users/maqian/code/go/src/awesomeProject/defer/defer.go:29 +0x82

四、defer和for循环

不要在defer内使用外部变量,可能会造成一些意想不到的错误:

func deferTest() {
    for i := 0; i < 5; i++ {
        defer func() {
            fmt.Printf("%d", i)
        }()
    }
}

它的输出的结果是55555 ,原理很简单,因为defer会在for循环运行完后才会调用,for循环运行完时i的值为5,所以打印的i值会是55555。

正确的做法是不要在defer函数中使用共享变量,而是手动传入参数:

func deferTest() {
    for i := 0; i < 5; i++ {
        defer func(i int) {
            fmt.Printf("%d", i)
        }(i)
    }
}

此时就会打印出43210而不是55555了。

最后修改:2020 年 02 月 22 日
如果觉得我的文章对你有用,请随意赞赏