Go的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)
}

Run in playground

上面是一个简单的程序,用于查找给定切片的最大数量。该largest函数接受一个int切片作为参数并打印该切片的最大数量。函数的第一行包含语句defer finished()。这意味着该finished()函数将在函数返回之前被调用。运行该程序,您可以看到打印出以下输出。

Started finding largest  
Largest number in [78 109 2 563 300] is 563  
Finished finding largest  

函数开始执行并打印上述输出的前两行。在它返回之前,我们的延迟函数finished会执行并打印文本Finished finding largest

延迟方法

Defer 不仅仅限于函数。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 ")  
}

Run in playground

在上面的程序中,我们第21行调用了Defer。 程序的其余部分是不言自明的。该程序输出,

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)

}

Run in playground

在上面的程序中,a的值最初为5。 当第 12行执行 defer 语句时。a的值是5,因此这将是被当做printA延迟函数的参数。我们将第 1 3行中的值更改a为 10。该程序输出,

value of a before deferred function call 10  
value of a in deferred function 5  

从上面的输出可以理解,虽然执行 defer 语句后的a值发生了变化10,但实际的延迟函数调用printA(a)仍然打印5

延迟堆栈

当一个函数有多个延迟调用时,它们会被压入堆栈并按后进先出(LIFO)顺序执行。

我们将编写一个小程序,使用延迟堆栈反向打印字符串。

package main

import (  
    "fmt"
)

func main() {  
    name := "Naveen"
    fmt.Printf("Original String: %s\n", string(name))
    fmt.Printf("Reversed String: ")
    for _, v := range name {
        defer fmt.Printf("%c", v)
    }
}

Run in playground

在上面的程序中,使用for range循环迭代字符串并调用defer fmt.Printf("%c", v)这些延迟调用将被添加到堆栈中。

延迟堆栈

上图表示添加 defer 调用后堆栈的内容。堆栈是后进先出的数据结构。最后压入堆栈的 defer 调用将首先被拉出并执行。在这种情况下defer fmt.Printf("%c", 'n'),将首先执行,因此字符串将以相反的顺序打印。

该程序将输出,

Original String: Naveen  
Reversed String: neevaN  

延迟的实际使用

到目前为止,我们看到的代码示例并未显示 defer 的实际用途。在本节中,我们将研究 defer 的一些实际用途。

Defer 用于无论代码流如何都应该执行函数调用的地方。让我们通过使用WaitGroup的程序示例来理解这一点。我们将首先编写不使用 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")
}

Run in playground

在上面的程序中,我们创建了一个rect结构体和area的方法。此方法检查矩形的长度和宽度是否小于零。如果是,则打印相应的消息,否则打印矩形的面积。

main函数创建 3 个类型为rect的变量。然后将它们添加到rects 的切片中。 然后使用循环迭代该切片,并在第 37 行中将area方法作为并发调用。37. WaitGroup用于确保 main 函数被阻塞,直到所有 Goroutines 执行完毕。此 WaitGroup 作为参数传递给方法,并且调用wg.Done()方法通知主函数 Goroutine 已完成其工作。如果您仔细观察,您会发现调用*wg.Done()*发生在区域方法返回之前。

无论代码流采用的路径如何,都应在方法返回之前调用 wg.Done(),因此这些调用可以有效地由多个调用替换单个调用

在下面的程序中,我们删除了wg.Done()上面程序中的 3 个调用,并将其替换为defer 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")
}

Run in playground

该程序输出,

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  

在上面的程序中使用 defer 还有一个优点。假设我们area使用新if条件向该方法添加另一个返回路径。如果调用wg.Done()没有延迟,我们必须小心并确保我们调用wg.Done()这个新的返回路径。但由于调用wg.Done()被Defer,我们不必担心向该方法添加新的返回路径。

本教程到此结束。祝你有美好的一天。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值