深入defer

defer语句将函数标记为在当前函数的最后执行。
延迟调用就是在普通函数的前面加上defer关键字。
defer someFunction()
包含defer语句的函数一旦要返回时,被defer标记的延迟函数就会被调用。延迟函数真正被调用的时机是,当包围函数:
  • 执行返回语句
  • 正常执行到最后
  • 发生了恐慌
例子:
func logExit(name string) {
fmt.Printf("Function %s returned\n", name)
}
func main() {
fmt.Println("First main statement")
defer logExit("main") // defer语句的位置无关紧要
fmt.Println("Last main statement")
}
如果一个函数中有多个延迟语句,它们将形成一个栈。最后一个defer是在闭包函数返回之后第一个执行的,随后依次调用先前的defer(下方例子会因panic而返回):
Go
Output
func logNum(i int) {
fmt.Printf("Num %d\n", i)
}
func main() {
defer logNum(1)
fmt.Println("First main statement")
defer logNum(2)
defer logNum(3)
panic("panic occurred")
fmt.Println("Last main statement") // 不会被打印
// 下面不会执行,因为执行流程永远不会到达此行
defer logNum(4)
}
First main statement
Num 3
Num 2
Num 1
panic: panic occurred
goroutine 1 [running]:
main.main()
/tmp/src963563549/main.go:17 +0x152
exit status 2
延迟函数的参数的值,是它们在被解析求值时的值(而不是最后返回时的值)。
package main
import (
"fmt"
)
func logNum(i int) {
fmt.Printf("Num %d\n", i)
}
func main() {
i := 1
defer logNum(i) // deferred function call: logNum(1)
fmt.Println("First main statement")
i++ //i=2
defer logNum(i) // deferred function call: logNum(2)
i++ // i = 3
defer logNum(i * i) // deferred function call: logNum(9)
}
---------Output---------
First main statement
Num 9
Num 2
Num 1
如果一个函数有具名返回值,在该函数中的匿名defer调用可以在其返回之前修改函数的返回值。
func plusOne(i int) (result int) {
// 匿名函数之后必须加 () 进行调用
defer func() { result++ }()
// i 作为结果被返回, 它会被上面的延迟调用函数修改
return i
}
func main() {
fmt.Println(plusOne(1)) // 2
}
译者注
若要defer内部修改外部函数的返回值,则需要defer语句是匿名函数,外部函数的return值为具名返回值。
之所以要求是匿名defer函数,是因为Golang不支持具名嵌套函数,则只有匿名嵌套函数才可以构成闭包。有了闭包之后,才可以在闭包内部访问外部函数的变量。
之所以要求外部函数是具名返回值才可以修改,是因为Golang的函数在return时,若return变量是匿名的,则该变量值的内存地址是另外申请的,defer语句在解析求值时并不知道该内存地址,而具名返回值的内存地址在函数解析之初就确定了,所以解析到defer时,可以知道该地址的值。