defer语句是go语言提供的一种用于注册延迟调用的机制,是go语言中一种很有用的特性。
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()
2、多个defer执行顺序
当一个函数中有多个defer语句时,会按 defer 定义的顺序逆序执行,也就是说最先注册的defer函数调用最后执行 => 先到后得
fmt.Println("test01")
defer fmt.Println("test02")
fmt.Println("test03")
defer fmt.Println("test04")
fmt.Println("test05")
// 执行结果
test01
test03
test05
test04
test02
3、defer的拷贝机制
当执行defer语句时,函数调用不会马上发生,会先把defer注册的函数及其变量拷贝到 defer 栈中保存,直到函数 return 前才执行 defer 中的函数调用。需要格外注意的是,这一拷贝,拷贝的那一刻函数的值和参数的值。注册之后再修改函数值或参数值时,不会生效。
// 案例1
foo := func() {
fmt.Println("hello sgg1")
}
defer foo() // hello sgg1 => foo函数已经被copy到defer 栈中了
foo = func() {
fmt.Println("hello sgg2")
}
// 案例2
x := 10
defer func(a int) {
fmt.Println(a) // 10
}(x) // 先把 x := 10 作为值copy 进去了,a和x不是引用的同一内存空间
x++
// 闭包函数(闭包里和外层引用了同一块内存空间)
x := 10
defer func() {
fmt.Println(x) // 11
}()
x++
4、defer执行时机
在Go语言的函数 return 语句不是原子操作,而是被拆成了两步。
rval = xxx
ret
而 defer 语句就是在这两条语句之间执行,也就是
rval = xxx
defer_func
ret
案例:
package main
import "fmt"
func f1() int {
i := 5
defer func() {
i++
}()
return i //reval = i; defer func => i = 6; ret => return reval
}
func f2() *int {
i := 5
defer func() {
i++
fmt.Printf(":::%p",&i)
}()
fmt.Printf(":::%p",&i)
return &i //reval = &i;defer func => i = 6; ret => return reval
}
func f3() (result int) {
defer func() {
result++
}()
return 5 // ret (result = 5) => result = 5;defer result= 6 ; ret
}
func f4() (result int) {
defer func() {
result++
}()
return result // ret result变量的值 => result = 0;defer result= 1; ret
}
func f5() (r int) {
t := 5
defer func() {
t = t + 1
}()
return t // ret r = 5(拷贝t的值5赋值给r) => r = 5; defer; return r
}
func f6() (r int) {
defer func(r int) {
r = r + 1 // r的作用域是内置函数
}(r) // 0
return 5 // r = 5; defer r = 1; ret r = 5
}
func f7() (r int) {
defer func(x int) {
r = x + 1 // r 作用域是 f7 函数
}(r)
return 5 // r = 5; defer r = 1; ret r = 1
}
func main() {
// println(f1())
// println(*f2())
// println(f3())
// println(f4())
// println(f5())
// println(f6())
// println(f7())
}
在命名返回方式中,最终函数返回的就是命名返回变量的值,因此,对该命名返回变量的修改会影响到最终的返回值!