Defer
什么是defer
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
的第一行语句defer finished()
,会推迟函数finished()
的执行。defer
所在的函数为largest
,因此推迟到largest
函数体的最后,即return
之前。该程序输出如下:
Started finding largest
Largest number in [78 109 2 563 300] is 563
Finished finding largest
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 p.fullName()
。该程序比较简单,输出如下:
Welcome John Smith
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)
}
这里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
的先执行的顺序。下面我们通过这个特性来将一个字符串反转下:
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
来实现下面的程序。
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
重构下:
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
更好,代码的可读性、可扩展性都比较好。
Last updated
Was this helpful?