Go教程第二十七篇:Defer
Defer
本文是《Go系列教程》的第二十二篇文章。
什么是Defer?
defer/dɪˈfɜː(r)/ 意为:推迟,延期。
Defer语句可用于延迟一个函数调用。即:Defer语句所在的函数会先执行,并在其返回之前,调用defer声明的函数。
这个定义可能看起来有点复杂,但是如果用一个例子来理解它的话,就会变得很简单。
示例
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类型的数组作为参数,并打印出该数组中的最大元素。largest函数的第一行是:defer finished()。这就意味着,finished()函数会在largest函数即将返回之前被调用。
运行此程序,会得到如下输出。
Started finding largest
Largest number in [78 109 2 563 300] is 563
Finished finding largest
从上面的程序可以看出,程序会首先执行largest函数,并打印出前俩行的输出信息。在largest函数返回之前,我们的延迟函数finished开始执行,并打印出文本:Finished finding largest。
被延迟的函数
Defer并不局限于函数。对于方法调用我们同样可以进行defer操作。我们写段程序测试下。
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 ")
}
在上面的程序中,我们延迟了一个方法调用,程序的其他部分都不言自明,我们就不解释了。运行此程序,输出如下:
Welcome John Smith
参数求值
延迟函数的参数是在defer语句执行时计算的,而不是在实际的函数调用完成之后才进行的。
我们写段程序来理解下。
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行,我们把a初始为5。当defer语句执行的时候,此时a的值是5,因此,printA函数的参数就是5。之后,我们又把a的值变成10,并紧接着打印了a的值。
程序的输出如下:
value of a before deferred function call 10
value of a in deferred function 5
从以上程序的输出,我们可以理解,虽然在defer语句执行之后,a的值从5变成10,但是真正实际调用printA的时候,打印的依旧是5。
defer堆栈
当一个函数中有多个defer调用时,它们就会被压入堆栈中,并以LIFO(后进先出)的顺序执行。
我们来写个程序,利用defer完成字符串的反转。
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循环中,我们迭代了一个字符串,并调用 defer fmt.Printf("%c",v)。这些延迟调用都会被加入到堆栈中。

上面的这个图表示出了延迟调用之后,堆栈的内容。堆栈是一种后进先出的数据结构。被最后压入堆栈的defer调用将会被首先执行。在本例中,efer fmt.Printf("%c", 'n')就会被首先执行,因此字符串就会以反转的书序打印出来。
程序的输出如下:
Original String: Naveen
Reversed String: neevaN
defer实战
上面我们看到的那些都不是实际开发中的用法。我们现在将讲述一下,在实际开发工作中,我们如何使用defer。
在函数调用的执行与代码流无关的时候,我们就应当使用Defer。我们用一个例子来理解一下,这是什么意思。我先写一个不使用defer的例子,之后我们再使用defer改写。来理解defer的用途。
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")
}
我们再使用defer重写此程序。
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 {-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没有延迟调用,我们就不得不非常小心,并确保我们是在这个新的返回路径中调用的wg.Done()。但是,由于wg.Done()调用被延迟了,因此,我们也就不用去担心向此方法中添加一条新的返回路径了。
感谢您的阅读,请留下您珍贵的反馈和评论。Have a good Day!
备注
本文系翻译之作原文博客地址