跳到主要内容

defer语句

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

9.1、defer的用法

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

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

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

// 打开文件
file_obj,err:=os.Open("满江红")
if err != nil {
fmt.Println("文件打开失败,错误原因:",err)
}

// 关闭文件
defer file_obj.Close()
// 操作文件

9.2、多个defer执行顺序

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

fmt.Println("test01")

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

9.3、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中的函数调用。需要格外注意的是,这一拷贝拷贝的是那一刻函数的值和参数的值。注册之后再修改函数值或参数值时,不会生效。

9.4、defer执行时机

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

rval = xxx
ret

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

rval = xxx
defer_func
ret rval

defer x = 100
x := 10
return x // rval=10. x = 100, ret rval

经典面试题:

package main

import "fmt"

func f1() int {
i := 5
defer func() {
i++
}()
return i
}

func f2() *int {
i := 5
defer func() {
i++
fmt.Printf(":::%p\n", &i)
}()

fmt.Printf(":::%p\n", &i)
return &i
}

func f3() (result int) {
defer func() {
result++
}()
return 5 // result = 5;ret result(result替换了rval)
}

func f4() (result int) {
defer func() {
result++
}()
return result // ret result变量的值
}

func f5() (r int) {
t := 5
defer func() {
t = t + 1
}()
return t // ret r = 5 (拷贝t的值5赋值给r)
}

func f6() (r int) {
fmt.Println(&r)
defer func(r int) {
r = r + 1
fmt.Println(&r)
}(r)
return 5
}

func f7() (r int) {
defer func(x int) {
r = x + 1
}(r)
return 5
}

func main() {
// println(f1())
// println(*f2())
// println(f3())
// println(f4())
// println(f5())
// println(f6())
// println(f7())
}
提示

在命名返回方式中,最终函数返回的就是命名返回变量的值,因此,对该命名返回变量的修改会影响到最终的函数返回值!