- 原文地址:Part 29: Defer
- 原文作者:Naveen 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)
}
Run in playgroud
以上是一个简单的程序,用于查找给定切片最大的数。largest
函数将int
切片作为参数,并输出该切片的最大数。largest
函数的第一行包含语句defer finished()
。这意味着在largest
函数返回之前将调用finished
函数。运行此程序,可以看到以下输出。
Started finding largest
Largest number in [78 109 2 563 300] is 563
Finished finding largest
largest
函数开始执行并打印上述输出的前两行。在它返回之前,defer
函数完成执行,并打印Finished finding largest
:)
defer
一个方法
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 ")
}
在上面的程序中,我们defer
了一个方法的调用,其余的代码是不难懂的。该程序输出,
Welcome John Smith
defer
的参数作用域
defer
的函数的参数是在执行defer
语句时传入的,在实际函数调用的时候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
语句实在第 12 行。a
是defer
函数printA
的参数。在第 13 行我们将a
的值更改为 10。该程序的输出,
value of a before deferred function call 10
value of a in deferred function 5
从上面的输出可以看到,尽管在执行defer
语句之后a
的值变为 10,但实际的defer
函数调用printA(a)
仍然打印 5。
多个defer
函数的调用顺序
当一个函数有多个defer
调用时,它们会被添加到栈中并以后进先出(LIFO)的顺序执行。
我们将编写一个小程序,使用一系列defer
来反向打印字符串。
package main
import (
"fmt"
)
func main() {
name := "Naveen"
fmt.Printf("Orignal String: %s\n", string(name))
fmt.Printf("Reversed String: ")
for _, v := range []rune(name) {
defer fmt.Printf("%c", v)
}
}
在上面的程序中,第 11 行开始的for range
循环迭代字符串并调用defer fmt.Printf("%c", v)输出字符。这些
defer`调用将被添加到栈中并以后进先出的顺序执行,因此字符串将以相反的顺序打印。该程序将输出,
Orignal String: Naveen
Reversed String: neevaN
defer
的实际用法
到目前为止我们看到的代码示例没有显示defer
的实际用法。在本节中,我们将研究defer
的一些实际用途。
defer
用于应该执行函数调用的地方,而不管代码流程如何???。让我们用一个使用WaitGroup
的例子来理解这一点。我们将首先编写程序而不使用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")
}
Run in playground
在上面的程序中,我们在第 8 行创建了一个rect
结构,第 13 行给rect
结构加上了area
方法用于计算矩形的面积。此方法检查矩形的长度和宽度是否小于 0。如果是这样,它会打印相应的消息,否则会打印矩形的面积。
main
函数创建了 3 个类型为rect
的变量r1
,r2
和r3
,将它们添加到rects
切片中。然后使用for range
循环迭代该切片,并将area
方法并发执行。 WaitGroup wg
用于保证所有Goroutines
执行完毕。WaitGroup
作为参数传递给area
方法,并在area
方法中调用wg.Done
,主要通知main
该Goroutine
已完成其工作。如果您仔细观察,可以看到这些调用恰好在area
方法返回之前发生。无论代码采用哪个条件分支执行,都应在方法返回之前调用wg.Done
,因此可以通过defer
来解决这种场景。
来用defer
重写上面的程序吧。
在下面的程序中,我们删除了上面程序中的 3 个wg.Done
,并将其替换为defer wg.Done()
,这将使代码更简洁易懂。
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
方法添加另一个处理分支。如果没有defer
对wg.Done
的调用,我们必须小心确保在这个新的处理分支中调用wg.Done
。但由于对wg.Done
的调用用了defer
,我们再也不用担心这种情况了。相似的应用场景应该还有很多,比如打开文件的关闭等等。但是需要注意的是,大量的使用defer
函数会导致程序运行效率变低。
有疑问加站长微信联系(非本文作者)