深入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而返回):

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)
}

延迟函数的参数的值,是它们在被解析求值时的值(而不是最后返回时的值)。

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时,可以知道该地址的值。

最后更新于