1. 程式人生 > >golang教程之Defer

golang教程之Defer

文章目錄

Defer

原文:https://golangbot.com/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的變數r1r2r3。然後將它們新增到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()的呼叫被推遲,我們不必擔心為此方法新增新的返回路徑。