Go语言学习
  • README
  • Go 基础
    • go语言介绍
    • go语言主要特性
    • go内置类型和函数
    • init函数和main函数
    • 下划线
    • iota
    • 字符串
    • 数据类型:数组与切片
    • 数据类型:byte、rune与字符串
    • 变量的5种创建方式
    • 数据类型:字典
    • 指针
    • 数据类型:指针
    • 类型断言
    • 流程控制:defer延迟执行
    • 异常机制:panic和recover
    • 函数
    • go依赖管理
    • go中值传递、引用传递、指针传递区别
  • 标准库
    • Go net/http包
  • 数据结构
    • 哈希表
      • 为什么对 map 的 key 或 value 进行取址操作是不允许的
  • Gin
    • gin 快速开始
    • gin-swagger用法
  • Go 进阶
    • Go 指针
    • Go 中的 GC 演变是怎样的?
    • Go 的堆和栈
  • 面向对象
    • make 和 new 的区别
    • new(T) 和 &T{} 有什么区别?
  • 并发编程
    • Channel
    • Go语言 CSP 并发模型
    • GMP 模型原理
      • GMP 模型为什么要有 P ?
    • Go 协程池(goroutine pool)
    • Go语言常见的并发模式
    • Go并发实践:主动停止goroutine
  • 最佳实践
    • 发布Go语言模块
  • 软件包
    • 常用的GoLang包工具
    • Go的UUID生成
    • 现代化的命令行框架Cobra
    • 配置解析神器Viper
    • Go发送邮件gomail
    • Go反射框架Fx
    • NSQ消息队列的使用
    • Go爬虫框架colly
    • grpc-go 的安装和使用
Powered by GitBook
On this page
  • 延迟调用
  • defer特性
  • 即时求值的变量快照
  • 多个defer 反序调用
  • defer 与 return 孰先孰后
  • return 的实现逻辑
  • defer、 return、返回值三者顺序
  • 为什么要有 defer

Was this helpful?

  1. Go 基础

流程控制:defer延迟执行

延迟调用

defer是Go语言中的延迟执行语句。

用来添加函数结束时执行的代码,常用于释放某些已分配的资源、关闭数据库连接、断开socket连接、解锁一个加锁的资源。

  • Go语言机制担保一定会执行defer语句中的代码

  • 其它语言中也有类似的机制,比如Java、C#语言里的finally语句

  • defer 的用法很简单,只要在后面跟一个函数的调用,就能实现将这个xxx函数的调用,延迟到当前函数执行完后再执行。

如下示例

import "fmt"

func myfunc() {
    fmt.Println("B")
}

func main() {
    defer myfunc()
    fmt.Println("A")
}

输出如下

A
Bgo

defer特性

1. 关键字 defer 用于注册延迟调用。
2. 这些调用直到 return 前才被执。因此,可以用来做资源清理。
3. 多个defer语句,按先进后出的方式执行。
4. defer语句中的变量,在defer声明时就决定了。

defer用途:

1. 关闭文件句柄
2. 锁资源释放
3. 数据库连接释放

即时求值的变量快照

使用 defer 只是延时调用函数,此时传递给函数里的变量,不受到后续程序的影响

比如下面的例子

import "fmt"

func main() {
    name := "go"
    defer fmt.Println(name) // 输出: go,只是延时执行,不会输出python

    name = "python"
    fmt.Println(name)      // 输出: python
}

输出如下

python
go

多个defer 反序调用

当在一个函数里使用了 多个defer,那么这些defer 的执行函数是如何的呢? 如下示例:

import "fmt"

func main() {
    name := "go"
    defer fmt.Println(name) // 输出: go

    name = "python"
    defer fmt.Println(name) // 输出: python

    name = "java"
    fmt.Println(name)
}

输出如下:

java
python
go

可见 ,多个defer 是反序调用的,类似栈一样,后进先出

defer 与 return 孰先孰后

代码示例:

import "fmt"

var name string = "go"

func myfunc() string {
    defer func() {
        name = "python"
    }()

    fmt.Printf("myfunc 函数里的name:%s\n", name)
    return name
}

func main() {
    myname := myfunc()
    fmt.Printf("main 函数里的name: %s\n", name)
    fmt.Println("main 函数里的myname: ", myname)
}

输出如下

myfunc 函数里的name:go
main 函数里的name: python
main 函数里的myname:  go

来一起理解一下这段代码,第一行很直观,name 此时还是全局变量,值还是go

第二行也不难理解,在 defer 里改变了这个全局变量,此时name的值已经变成了 python

重点在第三行,为什么输出的是 go ?

原因是defer 是return 后才调用的。所以在执行 defer 前,myname 已经被赋值成 go 了。 结论: defer 与 return 孰先孰后?return先执行,defer后执行

return 的实现逻辑

1、第一步给返回值赋值(若是有名返回值直接赋值,匿名返回值 则 先声明再 赋值) ; 2、第二步调用RET返回指令并传入返回值,RET会检查是否存在defer语句,若存 在就先逆序插播 defer语句 ; 3、最后 RET 携带返回值退出函数 。

defer、 return、返回值三者顺序

defer、 return、返回值 三者的执行顺序是 : return 最先给返回值赋值;接着 defer 开始执行一些收尾工作;最后 RET 指令携带返回值退出函数

为什么要有 defer

用来添加函数结束时执行的代码,常用于释放某些已分配的资源、关闭数据库连接、断开socket连接、解锁一个加锁的资源

若是没有 defer,你可以写出这样的代码

func f() {
    r := getResource()  //0,获取资源
    ......
    if ... {
        r.release()  //1,释放资源
        return
    }
    ......
    if ... {
        r.release()  //2,释放资源
        return
    }
    ......
    if ... {
        r.release()  //3,释放资源
        return
    }
    ......
    r.release()     //4,释放资源
    return
}

使用了 defer 后,代码就显得简单直接,不管你在何处 return,都会执行 defer 后的函数。

func f() {
    r := getResource()  //0,获取资源

    defer r.release()  //1,释放资源
    ......
    if ... {
        ...
        return
    }go
    ......
    if ... {
        ...
        return
    }
    ......
    if ... {
        ...
        return
    }
    ......
    return
}

defer 是先进后出

这个很自然,后面的语句会依赖前面的资源,因此如果先前面的资源先释放了,后面的语句就没法执行了。go

Previous类型断言Next异常机制:panic和recover

Last updated 2 years ago

Was this helpful?