sunwenfei
  • 关于我
  • Golang
    • Goglang基础教程【译】
      • 介绍
        • 安装
        • Hello World
      • 变量、基本类型以及常量
        • 变量
        • 基本类型
        • 常量
      • 函数和包
        • 函数
        • 包
      • 条件、循环流程控制语句
        • if else条件语句
        • switch语句
        • 循环语句
      • 数组、切片、变参函数
        • 数组(Array)
        • 切片(Slice)
        • 变参函数
      • 其他数据类型
        • 映射(Map)
        • 字符串
      • 指针、结构体和方法
        • 指针
        • 结构体
        • 方法
      • 面向对象编程
        • 结构体 vs 类
        • 组合 vs 继承
        • 接口
        • 多态
      • 并发
        • 并发介绍
        • 协程(goroutine)
        • 管道(channel)
        • 带缓存的管道(buffered channel)
        • 协程池
        • 管道选择器(select)
        • 互斥锁(Mutex)
      • Defer
      • 一等公民函数
      • 反射
      • 错误
        • 错误处理
        • 自定义错误类型
        • panic和recover
      • 文件读写
        • 读文件
        • 写文件
    • Golang面向对象编程
    • Golang函数式编程
    • Golang并发编程
    • Golang web服务编程
    • Golang数据结构与算法
  • Shell编程
    • Find命令
  • JavaScript
    • browser
    • Node.JS
    • Deno
  • TypeScript
  • HTTP
    • 【译】通过信鸽理解HTTPS交互原理
  • React
    • React16
      • Hooks
        • 使用React Hooks拉取数据
  • 移动端开发
    • 原生
    • Flutter
    • ReactNative
    • 小程序
  • 前端测试
Powered by GitBook
On this page

Was this helpful?

  1. Golang
  2. Goglang基础教程【译】

Defer

什么是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的第一行语句defer finished(),会推迟函数finished()的执行。defer所在的函数为largest,因此推迟到largest函数体的最后,即return之前。该程序输出如下:

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

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

这里我们推迟了一个方法的执行defer p.fullName()。该程序比较简单,输出如下:

Welcome John Smith

defer函数/方法的参数

被defer执行的函数/方法的入参在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,然后执行defer语句,将a值传递给被defer的函数printA,这时入参就已经确定了,即便后面我们又把a的值更新为10,printA执行时的入参仍然是5。该程序输出如下:

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

defer栈

如果一个函数内同时出现了多个defer,那么这些defer是按照栈结构来执行的,即先defer的后执行,后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)
    }
}

这里的for range循环遍历字符串的每个字符,执行defer fmt.Printf("%c", v)语句,由于被defer的函数是栈顺序,因此打印的字符串是反转过来的。该程序输出结果为:

Original String: Naveen
Reversed String: neevaN

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

这里声明了结构体类型rect,即长方形类型,并在该结构体类型上定义了方法area用来计算面积。该方法内需要判断入参的合法性,即是否都是非负数。合法性判断完成后才打印输出长方形的面积。area方法内部有三个分支,r.length < 0、r.width < 0以及正常情况。但是每个分支在执行完对应逻辑时都需要调用wg.Done()来通知协程执行完毕,接着调用return退出方法的执行。这种场景非常适合用defer,因为我们无论执行哪个分支都需要在return前调用wg.Done(),因此可以将所有分支中的wg.Done()抽出来放到defer语句中。这样代码可读性更高,而且容易维护。 下面我们用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 {5 -67}'s width should be greater than zero
rect {-67 89}'s length should be greater than zero
All go routines finished executing

这里使用defer还有另外一个好处,假设我们后面又新增了一个判断分支,如果不是用defer,那么我们还得留心加上语句wg.Done()。因此这种场景还是使用defer更好,代码的可读性、可扩展性都比较好。

Previous互斥锁(Mutex)Next一等公民函数

Last updated 5 years ago

Was this helpful?