# 深入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 %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://denglj.gitbook.io/essential-go/19-defer/defer-in-depth.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
