美文网首页
Go教程第二十七篇:Defer

Go教程第二十七篇:Defer

作者: 大风过岗 | 来源:发表于2020-06-24 11:25 被阅读0次

Defer

本文是《Go系列教程》的第二十二篇文章。

什么是Defer?

defer/dɪˈfɜː(r)/ 意为:推迟,延期。

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类型的数组作为参数,并打印出该数组中的最大元素。largest函数的第一行是:defer finished()。这就意味着,finished()函数会在largest函数即将返回之前被调用。
运行此程序,会得到如下输出。

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

从上面的程序可以看出,程序会首先执行largest函数,并打印出前俩行的输出信息。在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 ")
}

在上面的程序中,我们延迟了一个方法调用,程序的其他部分都不言自明,我们就不解释了。运行此程序,输出如下:

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)

}

在上面的程序中,在程序的11行,我们把a初始为5。当defer语句执行的时候,此时a的值是5,因此,printA函数的参数就是5。之后,我们又把a的值变成10,并紧接着打印了a的值。
程序的输出如下:

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

从以上程序的输出,我们可以理解,虽然在defer语句执行之后,a的值从5变成10,但是真正实际调用printA的时候,打印的依旧是5。

defer堆栈

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

我们来写个程序,利用defer完成字符串的反转。

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

在上述程序中,在第11行的for循环中,我们迭代了一个字符串,并调用 defer fmt.Printf("%c",v)。这些延迟调用都会被加入到堆栈中。

image.png

上面的这个图表示出了延迟调用之后,堆栈的内容。堆栈是一种后进先出的数据结构。被最后压入堆栈的defer调用将会被首先执行。在本例中,efer fmt.Printf("%c", 'n')就会被首先执行,因此字符串就会以反转的书序打印出来。

程序的输出如下:

Original String: Naveen
Reversed String: neevaN

defer实战

上面我们看到的那些都不是实际开发中的用法。我们现在将讲述一下,在实际开发工作中,我们如何使用defer。

在函数调用的执行与代码流无关的时候,我们就应当使用Defer。我们用一个例子来理解一下,这是什么意思。我先写一个不使用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")
}

我们再使用defer重写此程序。

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

这就是使用defer的好处。我们说,如果使用一个新的if条件向area方法中添加了另一个返回路径。如果wg.Done没有延迟调用,我们就不得不非常小心,并确保我们是在这个新的返回路径中调用的wg.Done()。但是,由于wg.Done()调用被延迟了,因此,我们也就不用去担心向此方法中添加一条新的返回路径了。

感谢您的阅读,请留下您珍贵的反馈和评论。Have a good Day!

备注
本文系翻译之作原文博客地址

相关文章

  • Go教程第二十七篇:Defer

    Defer 本文是《Go系列教程》的第二十二篇文章。 什么是Defer? defer/dɪˈfɜː(r)/ 意为:...

  • Go教程第十七篇: 介绍并发

    Go教程第十七篇: 介绍并发 本文是《Go系列教程》的第十七篇文章。 Go是一个并发语言而不是一个并行语言。在讨论...

  • Go教程第七篇:Package

    Go教程第七篇:Package 本文是《Go系列教程》的第七篇文章。 什么是包、为什么使用包 ? 到目前为止,我们...

  • golang defer 特性

    defer.go

  • Go Defer

    Go Defer 如果函数里面有多条defer指令,他们的执行顺序是反序,即后定义的defer先执行。 defer...

  • Go教程第二十五篇:Go中的反射

    Go中的反射 本文是《Go系列教程》的第二十六篇文章。 反射是Go的高级特性之一。我将尽力讲解地简单些。 本教程具...

  • goLang异常处理

    defer defer是go提供的一种资源处理的方式。defer的用法遵循3个原则在defer表达式被运算的同时,...

  • Go language quick framework

    hello world structure syntax case defer go...

  • 异常

    1、error接口 2、defer 你可以在Go函数中添加多个defer语句,当函数执行到最后时,这些defer语...

  • go defer

    函数体执行结束后,按照调用顺序的反向,逐个执行 即时函数发生严重错误也会执行 支持匿名函数的调用 常用于自愿清理、...

网友评论

      本文标题:Go教程第二十七篇:Defer

      本文链接:https://www.haomeiwen.com/subject/tacrfktx.html