导言
- 原文链接: Part 29: Defer
- If translation is not allowed, please leave me in the comment area and I will delete it as soon as possible.
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
函数 用于寻找切片中的最大值。lastest
函数 的第一行是 defer finished()
— 这表示在 lastest
函数 结束前,调用 finished
函数。
运行这个程序,你会得到如下输出:
Started finding largest
Largest number in [78 109 2 563 300] is 563
Finished finding largest
在上面的输出中,前 2
句是 largest
函数 打印的,而最后一句是 largest
函数 退出前,finished
函数 打印的。
延迟 方法的执行
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 ")
}
在上面程序的第 22
行,我们延迟了方法的执行。
程序输出如下:
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)
}
在第 12
行定义 defer
语句 时,此时 a
等于 5
,所以 被延迟函数printA
的参数就为 5
。
在第 13
行,我们将 a
改为 10
,并在下一行输出 a
。
最终,程序输出如下:
value of a before deferred function call 10
value of a in deferred function 5
从上面可以看出:在 defer
语句 定义后,将 a
改为 10
并不会影响 printA
函数 的参数。
延迟栈
当函数内定义多个 defer
时,这些 defer
会被推入栈中,并以 后进先出 的顺序执行。
接下来,我们来写个程序吧,它的功能是:使用 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)
}
}
在上面的程序中,for range
循环 遍历了整个字符串,并调用 defer fmt.Printf("%c", v)
语句。
这些 defer
会形成一个栈,内部如下:
栈是后进先出的数据结构,所以最晚入栈的 defer
最早执行。
对应于上面的例子,此时 defer fmt.Printf("%c", 'n')
会最先执行。
程序输出如下:
Original String: Naveen
Reversed String: neevaN
defer
的实际用途
这一节,我们来谈谈 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")
}
从上面的代码中,你会发现:在 area
函数结束前,我们都调用了 wg.Done()
。
接下来,我们使用 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")
}
在上面的程序中,我们移除了 3
句 wg.Done()
,转而用 1
句 defer wg.Done()
替代。这使得代码更简单易懂。
程序输出如下:
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
还有一个好处。
好处是:假设我们想给 area
方法 添加新的 if
判断。此时,如果没有使用 defer
,我们每次都要在 if
语句块 的最后添加 wg.Done()
,而如果使用 defer
,我们就不用这样做了。
这就是 defer
了~
祝你愉快~
原作者留言
优质内容来之不易,您可以通过该 链接 为我捐赠。
最后
感谢原作者的优质内容。
欢迎指出文中的任何错误。