理解Go语言中的defer

117 阅读3分钟

defer语句是go语言提供的一种用于注册延迟调用的机制,是go语言中一种很有用的特性。

defer 用法

defer语句注册了一个函数调用,这个调用会延迟到defer语句所在的函数执行完毕后执行,所谓执行完毕是指该函数执行了return语句、函数体已执行完最后一条语句或函数所在协程发生了恐慌。

fmt.Println("test01") 
defer fmt.Println("test02") 
fmt.Println("test03")

编程经常会需要申请一些资源,比如数据库连接、打开文件句柄、申请锁、获取可用网络连接、申请内存空间等,这些资源都有一个共同点那就是在我们使用完之后都需要将其释放掉,否则会造成内存泄漏或死锁等其它问题。但操作完资源忘记关闭释放是正常的,而defer可以很好解决这个问题

// 打开文件 file_obj,err:=os.Open("1.txt") 
if err != nil { 
    fmt.Println("文件打开失败",err) 
} 
// 关闭文件 defer file_obj.Close() 
// 操作文件

多个defer执行顺序

当一个函数中有多个defer语句时,会按defer定义的顺序逆序执行,也就是说最先注册的defer函数调用最后执行。

fmt.Println("test01") 
defer fmt.Println("test02") 
fmt.Println("test03") 
defer fmt.Println("test04") 
fmt.Println("test05")

defer拷贝机制

// 案例1
foo := func() {
    fmt.Println("I am function foo1")
}
defer foo()
foo = func() {
    fmt.Println("I am function foo2")
}

// 案例2
x := 10
defer func(a int) {
    fmt.Println(a)
}(x)    
x++

// 案例3
x := 10
defer func() {
    fmt.Println(x)   // 保留x的地址
}()
x++

当执行defer语句时,函数调用不会马上发生,会先把defer注册的函数及变量拷贝到defer栈中保存,直到函数return前才执行defer中的函数调用。需要格外注意的是,这一拷贝拷贝的是那一刻函数的值和参数的值。注册之后再修改函数值或参数值时,不会生效。

defer的执行时机

在Go语言的函数 return 语句不是原子操作,而是被拆成了两步

rval = xxx 
ret

而 defer 语句就是在这两条语句之间执行,也就是

rval = xxx 
defer_func 
ret rval

defer面试题

package main

import "fmt"

func f1() int {
    i := 5
    defer func() {
        i++
    }()
    return i
}  //  retVal = 5  执行defer使得 i = 6 return retVal 返回的值并没有收到i++的影响
func f2() *int {

    i := 5
    defer func() {
        i++
        fmt.Printf(":::%p\n", &i)
    }()
    fmt.Printf(":::%p\n", &i)
    return &i
}  // retVal = i的地址  执行defer使得 i= 6 return retVal 返回的值仍然是i的地址 ,*f2()的值为6

func f3() (result int) {
    defer func() {
        result++
    }()
    return 5 // 可以看到此时函数有变量参数作为返回值,因此 return时的逻辑为:  result的值为5,执行defer,result为6,再return result ,因此最终返回6
}

func f4() (result int) {
    defer func() {
        result++
    }()
    return result // retVal = result ,执行defer result = 1, 最后return retVal = 1
}

func f5() (r int) {
    t := 5
    defer func() {
        t = t + 1
    }()
    return t // ret r = 5 (拷贝t的值5赋值给r)  // retVal = r = 5, 执行defer使得t=6, 因此return的值仍为5
}

func f6() (r int) {
    fmt.Println(&r)
    defer func(r int) {
        r = r + 1
        fmt.Println(&r)
    }(r)
    return 5  // ret r = 5  执行defer,defer中影响到的r是defer函数内部的r, return ret 最终返回的仍是5
}

func f7() (r int) {
    defer func(x int) {
        r = x + 1
    }(r)
    return 5  // ret r = 5, 执行defer,r = 1,return ret最终为1
}

func main() {

    println(f1())  // 5
    println(*f2()) // 6
    println(f3())  // 6
    println(f4())  // 1
    println(f5())  // 5
    println(f6())  // 5
    println(f7())   // 1

}