什么是defer
defer
翻译成中文是推迟的意思。defer
语句用来推迟一个函数的执行,具体来说就是推迟到defer
语句所在函数的最后。听起来有点晦涩难懂,我们通过一个例子来理解下。
Copy 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
之前。该程序输出如下:
Copy Started finding largest
Largest number in [78 109 2 563 300] is 563
Finished finding largest
defer方法
前面的例子我们推迟了一个函数的执行。不过也可以推迟方法的执行。
Copy 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()
。该程序比较简单,输出如下:
defer函数/方法的参数
被defer
执行的函数/方法的入参在defer
语句执行时就已确定并计算好,而不是在函数/方法调用时。我们通过一个例子来理解下。
Copy 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
。该程序输出如下:
Copy value of a before deferred function call 10
value of a in deferred function 5
defer栈
如果一个函数内同时出现了多个defer
,那么这些defer
是按照栈结构来执行的,即先defer
的后执行,后defer
的先执行的顺序。下面我们通过这个特性来将一个字符串反转下:
Copy 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
的函数是栈顺序,因此打印的字符串是反转过来的。该程序输出结果为:
Copy Original String: Naveen
Reversed String: neevaN
defer真实适用场景
下面我们看下defer
真正适用的场景。我们先不使用defer
来实现下面的程序。
Copy 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
重构下:
Copy 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" )
}
该程序输出如下:
Copy 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
更好,代码的可读性、可扩展性都比较好。