Defer
Q7nl1s admin

什么是Defer?

Defer 语句用于在存在 Defer 语句的周围函数返回之前执行一个函数调用。这个定义看起来很复杂,但通过一个例子就可以很简单地理解。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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函数接收一个int slice作为参数,并打印出这个slice的最大数字。最大函数的第一行包含defer finished()语句。这意味着finished()函数将在largest函数返回之前被调用。运行这个程序,你可以看到打印出以下输出:

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

largest的函数开始执行并打印出上述输出的前两行。而在它返回之前,我们的deferred(延迟)函数finished执行并打印出文字Finished finding largest :)

Deferred methods(递延方法)

延迟并不只限于函数。延迟一个方法的调用也是完全合法的。让我们写一个小程序来测试一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 ")
}

在上面的程序中,我们在第22行延迟了一个方法调用。该程序的其余部分是不言自明的。这个程序的输出:

1
Welcome John Smith  

参数评估

延迟函数的参数在执行延迟语句时被评估,而不是在实际函数调用时被评估。

让我们通过一个例子来理解这一点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)

}

在上面的程序的第11行,我们定义了一个初始值为5a。当defer语句在第12行被执行时,a的值是5,因此这将是被延迟的printA函数的参数。我们在第13行将a的值改为10。下一行打印出a的值,该程序输出:

1
2
value of a before deferred function call 10  
value of a in deferred function 5

从上面的输出可以了解到,尽管在执行了延迟语句后a的值变成了10,但实际延迟的函数调用printA(a)仍然打印5

延迟器的堆栈

当一个函数有多个defer调用时,它们会被推到一个堆栈中,并以后进先出(LIFO)的顺序执行。

我们将写一个小程序,使用defers堆栈反向打印一个字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)
}
}

在上面的程序中,第11行的for range循环对字符串进行了迭代,并对其进行了处理。迭代字符串在第12行调用defer fmt.Printf("%c", v)。这些延迟的调用将被添加到一个堆栈中。

defer_0

上面的图片代表了添加延迟调用后的堆栈内容。栈是一个后进先出的数据结构。最后被推到堆栈中的延迟调用将被拉出并首先执行。在这种情况下,defer fmt.Printf("%c", 'n')将首先被执行,因此字符串将以相反的顺序被打印。

这个程序将输出。

1
2
Original String: Naveen  
Reversed String: neevaN

defer的实际使用

到目前为止,我们看到的代码样本并没有显示defer的实际用途。在本节中,我们将研究defer的一些实际用途。

defer用于应该执行函数调用的地方,而不考虑代码流的情况。让我们以一个使用WaitGroup的程序为例来理解这一点。我们将首先编写不使用defer的程序,然后修改程序以使用defer,并理解defer的作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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")
}

在上面的程序中,我们在第8行创建了一个rect结构,在第13行创建了一个计算rest面积的area方法。这个方法检查矩形的lengthwidth是否小于零。如果是这样,它将打印出相应的信息,否则将打印出rest的面积。

主函数创建了3个rest类型的变量r1r2r3。然后它们被添加到第34行的rest切片中。然后使用for range循环对这个片断进行迭代,并在第37行以并发的Goroutine方式调用area方法。WaitGroup wg被用来确保main函数被阻塞,直到所有的Goroutine执行完毕。这个WaitGroup被作为一个参数传递给area方法,area方法在第16、21和26行中调用wg.Done()来通知main函数,Goroutine已经完成了它的工作。wg.Done()应该在方法返回之前被调用,无论代码流的路径如何,因此这些调用可以有效地被一个defer调用所取代。

让我们用defer重写上面的程序。

在下面的程序中,我们删除了上述程序中的3个wg.Done()调用,并在第14行用一个defer wg.Done()调用代替了它。这使得代码更加简单易懂。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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")
}

这个程序输出。

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

在上述程序中使用defer还有一个好处。假设我们用一个新的if条件给area方法添加另一个返回路径。如果对wg.Done()的调用没有被defer,我们必须小心谨慎,确保在这个新的返回路径中调用wg.Done()。但由于对wg.Done()的调用被defer了,我们就不需要担心为这个方法添加新的返回路径。

这样我们就到了本教程的结尾。祝你有个愉快的一天。

 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View