在使用 Golang 开发程序时,我们可能会经常使用 defer 关键字,实现资源的回收,以及完成“收尾”工作。
执行顺序
若函数中有多个 defer ,执行顺序为 先进后出 ,可理解为栈。
package main
import (
"fmt"
)
func main() {
for i := 0; i < 3; i++ {
defer fmt.Println(i)
}
}
// 2
// 1
// 0
参数确定的时机
defer 在声明时会先计算确定其参数的值,推迟执行的仅是其函数体。
package main
import "fmt"
func print(a int) {
fmt.Println("value in deferred function", a)
}
func main() {
a := 5
defer print(a)
a = 10
fmt.Println("value before deferred function call", a)
}
// value before deferred function call 10
// value in deferred function 5
在实际开发中,defer 的使用经常伴随着闭包和匿名函数的使用。
package main
import "fmt"
func main() {
for i := 0; i < 3; i++ {
// i 在每次循环中,都被确定其对应的值:0,1,2
defer fmt.Println(i)
}
}
// 2
// 1
// 0
package main
import "fmt"
func main() {
for i := 0; i < 3; i++ {
// 由于函数体只是被延迟执行,故在执行 defer 时,
// i 的值为 3
defer func() {
fmt.Println(i)
}()
}
}
// 3
// 3
// 3
return
在调用 return 时,会完成以下的事情:
- 对返回值赋值
- 调用 defer 表达式
- 退出函数,并将返回值给调用函数
package main
import (
"fmt"
)
// 匿名返回值
func func_naked_return() int {
defer func() {
fmt.Println("由于无法获取到匿名返回值变量,故无法对其进行修改")
}()
fmt.Println("对返回值赋值")
return 1
}
// 匿名返回值
func func_naked_return_pointer() *int {
var r int
defer func() {
r++
fmt.Println("由于返回类型为指针,故可通过修改 r 间接修改匿名返回值")
}()
fmt.Println("对返回值赋值")
return &r
}
// 命名返回值
func func_named_return() (r int) {
defer func() {
fmt.Println("在调用 defer 表达式时,已完成对返回值的赋值 ", r)
r = 2
}()
fmt.Println("对返回值赋值")
return 1
}
func main() {
fmt.Println("退出函数,将返回值给调用函数 ", func_naked_return())
fmt.Println("-------")
fmt.Println("退出函数,将返回值给调用函数 ", func_named_return())
fmt.Println("-------")
fmt.Println("退出函数,将返回值给调用函数 ", *func_naked_return_pointer())
}
// 对返回值赋值
// 由于无法获取到匿名返回值变量,故无法对其进行修改
// 退出函数,将返回值给调用函数 1
// -------
// 对返回值赋值
// 在调用 defer 表达式时,已完成对返回值的赋值 1
// 退出函数,将返回值给调用函数 2
// -------
// 对返回值赋值
// 由于返回类型为指针,故可通过修改 r 间接修改匿名返回值
// 退出函数,将返回值给调用函数 1
- 匿名返回值变量无法被 defer 表达式所获取,故 defer 无法直接修改匿名返回值。但在返回值为指针类型的情况下,可通过修改指针所指向的值,实现间接修改匿名返回值
作用域
- 当任意一条(主)协程发生 panic 时,会执行当前协程中 panic 之前已声明的 defer
- 主动调用
os.Exit(int)时,defer 将不执行 - 在发生 panic 的(主)协程中,若没有一个 defer 调用
recover()进行修复,则在执行完之前已声明的 defer 后,进程崩溃 - defer 只对当前的(主)协程有效