golang教程之Defer

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wyy626562203/article/details/83411472

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()的调用被推迟,我们不必担心为此方法添加新的返回路径。

猜你喜欢

转载自blog.csdn.net/wyy626562203/article/details/83411472