> For the complete documentation index, see [llms.txt](https://denglj.gitbook.io/essential-go/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://denglj.gitbook.io/essential-go/19-defer/defer-in-depth.md).

# 深入defer

`defer`语句将函数标记为在当前函数的最后执行。

延迟调用就是在普通函数的前面加上`defer`关键字。

```go
defer someFunction()
```

包含`defer`语句的函数一旦要返回时，被`defer`标记的延迟函数就会被调用。延迟函数真正被调用的时机是，当包围函数：

* 执行返回语句
* 正常执行到最后
* 发生了恐慌

例子：

```go
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而返回）：

{% tabs %}
{% tab title="Go" %}

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

{% endtab %}

{% tab title="Output" %}

```
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
```

{% endtab %}
{% endtabs %}

延迟函数的参数的值，是它们在被解析求值时的值（而不是最后返回时的值）。

```go
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`调用可以在其返回之前修改函数的返回值。

```go
func plusOne(i int) (result int) {
	// 匿名函数之后必须加 () 进行调用
	defer func() { result++ }()

	// i 作为结果被返回, 它会被上面的延迟调用函数修改
	return i
}

func main() {
	fmt.Println(plusOne(1)) // 2
}
```

{% hint style="info" %}
**译者注**

**若要defer内部修改外部函数的返回值，则需要defer语句是匿名函数，外部函数的return值为具名返回值。**

之所以要求是匿名`defer`函数，是因为Golang不支持具名嵌套函数，则只有匿名嵌套函数才可以构成闭包。有了闭包之后，才可以在闭包内部访问外部函数的变量。

之所以要求外部函数是具名返回值才可以修改，是因为Golang的函数在`return`时，若`return`变量是匿名的，则该变量值的内存地址是另外申请的，`defer`语句在解析求值时并不知道该内存地址，而具名返回值的内存地址在函数解析之初就确定了，所以解析到`defer`时，可以知道该地址的值。
{% endhint %}
