golang教程之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
函式將int slice
作為引數,並輸出最大數量的輸入切片。largest
函式的第一行包含語句defer finished()
。 這意味著在largest
函式返回之前將呼叫finished()
函式。 執行此程式,您可以看到以下輸出列印。
Started finding largest
Largest number in [78 109 2 563 300] is 563
Finished finding largest
largest
finished
執行並列印文字完成查詢最大:)
延遲方法
延遲不僅限於函式。 延遲方法呼叫也是完全合法的。 讓我們寫一個小程式來測試它。
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)
}
在上面的程式中,a初始化為5。當在第12行中執行延遲語句時,a的值是5,因此這將是延遲的printA函式的引數。 我們將a的值更改為第10行,下一行列印a的值。 該程式輸出,
value of a before deferred function call 10
value of a in deferred function 5
從上面的輸出可以理解,儘管在執行延遲語句之後a的值變為10,但實際的延遲函式呼叫printA(a)仍然列印5。
棧的延遲
當一個函式有多個延遲呼叫時,它們會被新增到堆疊中並以後進先出(LIFO)順序執行。
我們將編寫一個小程式,使用一堆延遲來反向列印字串。
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)
}
}
在上面的程式中,for迴圈迭代字串並呼叫第12行中的fmt.Printf(“%c”,v)。這些延遲呼叫將被新增到堆疊中並以後進先出順序執行,因此字串將以相反的順序列印。 該程式將輸出,
Orignal String: Naveen
Reversed String: neevaN
延遲使用
到目前為止我們看到的程式碼示例沒有顯示defer
的實際用法。 在本節中,我們將研究延遲的一些實際用途。
Defer
用於應該執行函式呼叫的地方,而不管程式碼流程如何。 讓我們用一個使用WaitGroup的程式的例子來理解這一點。 我們將首先編寫程式而不使用延遲,然後我們將修改它以使用延遲並理解延遲是多麼有用。
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
方法計算矩形的面積。此方法檢查矩形的長度和寬度是否小於零。如果是這樣,它會列印相應的訊息,否則會列印矩形的面積。
main
函式建立了3個型別為rect的變數r1
,r2
和r3
。然後將它們新增到rects
切片中。然後使用for range
迴圈迭代該切片,並將area
方法稱為當前併發Goroutine。WaitGroup wg
用於確保主函式被阻止,直到所有Goroutines完成執行。此WaitGroup作為引數傳遞給area
方法,area
方法中呼叫wg.Done()
通知主函式Goroutine已完成其工作。如果您仔細注意,可以看到這些呼叫恰好在area
方法返回之前發生。無論程式碼流採用何種路徑,都應在方法返回之前呼叫wg.Done()
,因此可以通過單個延遲呼叫有效地替換這些呼叫。
讓我們使用defer重寫上面的程式。
在下面的程式中,我們刪除了上述程式中的3個wg.Done()
呼叫,並將其替換為第14行中的單個延遲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
在上述程式中使用延遲還有一個優點。 假設我們使用新的if
條件向area
方法新增另一個返回路徑。 如果沒有延遲對wg.Done()
的呼叫,我們必須小心並確保在這個新的返回路徑中呼叫wg.Done()
。 但由於對wg.Done()
的呼叫被推遲,我們不必擔心為此方法新增新的返回路徑。