# Defer

#### 什么是defer

`defer`翻译成中文是推迟的意思。`defer`语句用来推迟一个函数的执行，具体来说就是推迟到`defer`语句所在函数的最后。听起来有点晦涩难懂，我们通过一个例子来理解下。

```go
package main

import "fmt"

func finished() {
    fmt.Println("Finished finding largest")
}

func largest(nums []int) {
    defer finished()
    fmt.Println("Started finding largest")
    max := nums[0]
    for _, v := range nums {
        if v > max {
            max = v
        }
    }
    fmt.Println("Largest number in", nums, "is", max)
}

func main() {
    nums := []int{78, 109, 2, 563, 300}
    largest(nums)
}
```

这段程序比较简单，找到一个切片的最大值并打印。注意函数`largest`的第一行语句`defer finished()`，会推迟函数`finished()`的执行。`defer`所在的函数为`largest`，因此推迟到`largest`函数体的最后，即`return`之前。该程序输出如下：

```
Started finding largest
Largest number in [78 109 2 563 300] is 563
Finished finding largest
```

#### defer方法

前面的例子我们推迟了一个函数的执行。不过也可以推迟方法的执行。

```go
package main

import "fmt"

type person struct {
    firstName string
    lastName  string
}

func (p person) fullName() {
    fmt.Printf("%s %s", p.firstName, p.lastName)
}

func main() {
    p := person{
        firstName: "John",
        lastName:  "Smith",
    }
    defer p.fullName()
    fmt.Printf("Welcome ")
}
```

这里我们推迟了一个方法的执行`defer p.fullName()`。该程序比较简单，输出如下：

```
Welcome John Smith
```

#### defer函数/方法的参数

被`defer`执行的函数/方法的入参在`defer`语句执行时就已确定并计算好，而不是在函数/方法调用时。我们通过一个例子来理解下。

```go
package main

import "fmt"

func printA(a int) {
    fmt.Println("value of a in deferred function", a)
}
func main() {
    a := 5
    defer printA(a)
    a = 10
    fmt.Println("value of a before deferred function call", a)
}
```

这里`a`的初始值为`5`，然后执行`defer`语句，将`a`值传递给被`defer`的函数`printA`，这时入参就已经确定了，即便后面我们又把`a`的值更新为`10`，`printA`执行时的入参仍然是`5`。该程序输出如下：

```
value of a before deferred function call 10
value of a in deferred function 5
```

#### defer栈

如果一个函数内同时出现了多个`defer`，那么这些`defer`是按照栈结构来执行的，即先`defer`的后执行，后`defer`的先执行的顺序。下面我们通过这个特性来将一个字符串反转下：

```go
package main

import "fmt"

func main() {
    name := "Naveen"
    fmt.Printf("Original String: %s\n", string(name))
    fmt.Printf("Reversed String: ")
    for _, v := range []rune(name) {
        defer fmt.Printf("%c", v)
    }
}
```

这里的`for range`循环遍历字符串的每个字符，执行`defer fmt.Printf("%c", v)`语句，由于被`defer`的函数是栈顺序，因此打印的字符串是反转过来的。该程序输出结果为：

```
Original String: Naveen
Reversed String: neevaN
```

#### defer真实适用场景

下面我们看下`defer`真正适用的场景。我们先不使用`defer`来实现下面的程序。

```go
package main

import (
    "fmt"
    "sync"
)

type rect struct {
    length int
    width  int
}

func (r rect) area(wg *sync.WaitGroup) {
    if r.length < 0 {
        fmt.Printf("rect %v's length should be greater than zero\n", r)
        wg.Done()
        return
    }
    if r.width < 0 {
        fmt.Printf("rect %v's width should be greater than zero\n", r)
        wg.Done()
        return
    }
    area := r.length * r.width
    fmt.Printf("rect %v's area %d\n", r, area)
    wg.Done()
}

func main() {
    var wg sync.WaitGroup
    r1 := rect{-67, 89}
    r2 := rect{5, -67}
    r3 := rect{8, 9}
    rects := []rect{r1, r2, r3}
    for _, v := range rects {
        wg.Add(1)
        go v.area(&wg)
    }
    wg.Wait()
    fmt.Println("All go routines finished executing")
}
```

这里声明了结构体类型`rect`，即长方形类型，并在该结构体类型上定义了方法`area`用来计算面积。该方法内需要判断入参的合法性，即是否都是非负数。合法性判断完成后才打印输出长方形的面积。`area`方法内部有三个分支，`r.length < 0`、`r.width < 0`以及正常情况。但是每个分支在执行完对应逻辑时都需要调用`wg.Done()`来通知协程执行完毕，接着调用`return`退出方法的执行。这种场景非常适合用`defer`，因为我们无论执行哪个分支都需要在`return`前调用`wg.Done()`，因此可以将所有分支中的`wg.Done()`抽出来放到`defer`语句中。这样代码可读性更高，而且容易维护。 下面我们用`defer`重构下：

```go
package main

import (
    "fmt"
    "sync"
)

type rect struct {
    length int
    width  int
}

func (r rect) area(wg *sync.WaitGroup) {
    defer wg.Done()
    if r.length < 0 {
        fmt.Printf("rect %v's length should be greater than zero\n", r)
        return
    }
    if r.width < 0 {
        fmt.Printf("rect %v's width should be greater than zero\n", r)
        return
    }
    area := r.length * r.width
    fmt.Printf("rect %v's area %d\n", r, area)
}

func main() {
    var wg sync.WaitGroup
    r1 := rect{-67, 89}
    r2 := rect{5, -67}
    r3 := rect{8, 9}
    rects := []rect{r1, r2, r3}
    for _, v := range rects {
        wg.Add(1)
        go v.area(&wg)
    }
    wg.Wait()
    fmt.Println("All go routines finished executing")
}
```

该程序输出如下：

```
rect {8 9}'s area 72
rect {5 -67}'s width should be greater than zero
rect {-67 89}'s length should be greater than zero
All go routines finished executing
```

这里使用`defer`还有另外一个好处，假设我们后面又新增了一个判断分支，如果不是用`defer`，那么我们还得留心加上语句`wg.Done()`。因此这种场景还是使用`defer`更好，代码的可读性、可扩展性都比较好。


---

# 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://sunwenfei.gitbook.io/sunwenfei/golang/golang-ji-chu-jiao-cheng/defer.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.
