用八个demo搞懂Go语言defer的五大特性

冬浩小哥_2508

冬浩小哥_2508

2023-04-23

1747人浏览

转载

在 go 语言中使用 defer 关键字可以将代码延迟到函数结束之前执行。在开发中,我们经常使用defer关键字完成善后工作,如关闭打开的文件描述符、关闭连接以及释放资源等。

func demo0() {
    fileName := "./test.txt"
    f, _ := os.OpenFile(fileName, os.O_RDONLY, 0)
    defer f.Close()

    contents, _ := ioutil.ReadAll(f)
    fmt.Println(string(contents))}

defer关键字一般紧跟在打开资源代码的后面,防止后续忘记释放资源,defer 声明的代码实际上要等到函数结束之前才会被执行。defer 虽然简单易用,但如果忽略了它的特性,就会在开发中面临困惑。于是,我总结了 defer 的五大特性,通过 8 个demo逐步介绍 defer 的特性。

特性1:多个 defer 时的调用顺序:先进后出

使用多个 defer 关键字时,先被声明的 defer 语句后被调用。类似于“栈”先进后出的特性,defer 的这一特性也很好理解,先被打开的资源,可能会被后续代码依赖,所以要后释放才安全。

func demo1() {
    for i := 0; i <h2>特性2:作用域为当前函数,不同函数下拥有不同的 defer 栈</h2><p>运行 demo2 ,从结果中可以看出,第一个匿名函数和第二个匿名函数的 defer 执行顺序没有关系。<br>defer 作用域仅为当前函数,在当前函数最后执行,所以不同函数下拥有不同的 defer 栈。</p><pre class="brush:php;toolbar:false;">func demo2() {
    func() {
        defer fmt.Println(1)
        defer fmt.Println(2)
    }()

    fmt.Println("=== 新生代农民工啊 ===")

    func() {
        defer fmt.Println("a")
        defer fmt.Println("b")
    }()}// 2// 1// === 新生代农民工啊 ===// b// a

特性3:defer 后的函数形参在声明时确认(预计算参数)

运行 demo3_1 ,根据结果,我们可以得出:defer 在声明时,就已经确认了形参n的值,而不是在执行时确认的;所以,后续变量 num 无论如何改变都不影响 defer 的输出结果。

func demo3_1() {
    num := 0
    defer func(n int) {
        fmt.Println("defer:", n)
    }(num)
    // 等同 defer fmt.Println("defer:", num)

    for i := 0; i <p>运行 demo3_2,为什么这里 defer 的最终输出的结果会和变量 num 相同?因为这里使用的是指针。<br>defer <strong>声明时</strong>,已经确认了形参p指针的指向地址,指向变量 num;后续变量 num 发生改变。所以在 defer <strong>执行时</strong>,输出的是p指针指向的变量num的当前值。</p><pre class="brush:php;toolbar:false;">func demo3_2() {
    num := 0
    p := &num    defer func(p *int) {
        fmt.Println("defer:", *p)
    }(p)

    for i := 0; i <p>再看一下 demo3_3,defer 打印的变量并没有通过函数参数传入,在defer<strong>执行时</strong>,才获取的”全局变量”num,所以 defer 输出结果与变量num一致。</p><pre class="brush:php;toolbar:false;">func demo3_3() {
    num := 0
    defer func() {
        fmt.Println("defer:", num)
    }()

    for i := 0; i <h2>特性4:return 与 defer 执行顺序:return 先 defer 后</h2><p>运行 demo4_1,可以发现 defer、return 都是在函数最后执行,但 return 先于 defer 执行;</p><pre class="brush:php;toolbar:false;">func demo4_1() (int, error) {
    defer fmt.Println("defer")
    return fmt.Println("return")}// return// defer

这一点从输出结果上显而易见,但当 return、defer 的执行顺序和**函数返回值**“相遇”时,又将会产生许多复杂的场景。
在 demo4_2 中,函数使用命名返回值,最终输出结果为7。其中经历了这几个过程:

  1. (首先)变量 num 作为返回值,初始值为0;

  2. (其次)随后变量 num 被赋值为 10;

  3. (然后)return 时,变量 num 作为返回值被重新赋值为 2;

  4. (接着)defer 在 return 后执行,拿到变量 num 进行修改,值为7;

    Go语言(Golang)1.26.0
    Go语言(Golang)1.26.0

    Go语言(Golang)1.26.0版本官方下载,版本号 1.26.0,适合旧项目维护、兼容性测试和指定版本开发环境搭建。

    下载
  5. (最后)变量 num 作为返回值,最终函数返回结果为7;

    func demo4_2() (num int) {
     num = 10
     defer func() {
         num += 5
     }()
    
     return 2}// 7

再来看一个例子。
在 demo4_3 中,函数使用匿名返回值,最终结果输出为2。其中经历的过程是这样的:

  1. 进入函数,此时返回值变量并未创建;

  2. 创建变量 num,赋值为 10;

  3. return 时创建函数返回值变量,并赋值为2;这个返回值变量你可以把它看成匿名变量,或者是变量 a、b、c、d……,但它就不是变量 num;

  4. defer 时,无论怎样修改变量 num,都与函数返回值无关;

  5. 所以,最终的函数返回结果为2;

    func demo4_3() int {
     num := 10
     defer func() {
         num += 5
     }()
    
     return 2}// 2

    特性5:发生 panic 时,已声明的 defer 会出栈执行

    运行 demo5_1,可以看到当出现 panic 时,会触发已经声明的 defer 出栈执行,随后在再 panic,而在 panic 之后声明的 defer 将得不到执行。

    func demo5_1() {
     defer fmt.Println(1)
     defer fmt.Println(2)
     defer fmt.Println(3)
    
     panic("没点赞异常") // 触发defer出栈执行
    
     defer fmt.Println(4) // 得不到执行}

    正是利用这个特性,在 defer 中可以通过 recover 捕获 panic,防止程序崩溃。

    func demo5_2() {
     defer func() {
         if err := recover(); err != nil {
             fmt.Println(err, "问题不大")
         }
     }()
    
     panic("没点赞异常") // 触发defer出栈执行
    
     // ...}

    附

    完整代码:github.com/newbugcoder/learngo/tre...

golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

go语言

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
Tailwind CSS 响应式布局怎么写
Tailwind CSS 响应式布局怎么写

Tailwind CSS 响应式布局通常从移动端样式写起,再用 sm、md、lg 等前缀调整不同屏幕宽度下的网格、间距和导航。本专题整理断点配置、Grid 与 Flex 自适应布局及常见溢出问题,帮助你逐步完成多设备适配。

2026.11.03

0

19

Tailwind CSS 常用类名怎么记
Tailwind CSS 常用类名怎么记

Tailwind CSS 类名很多,先按布局、间距、文字、颜色和状态分组记忆,再通过官方文档检索具体写法。本专题结合类名排序、动态拼接失效、自定义实用类等问题,帮助你建立常用类名的速查方法。

2026.11.03

0

12

码上飞入口地址合集
码上飞入口地址合集

本专题汇总了码上飞(CodeFlying)的官方入口地址合集,涵盖官网主站及网页版在线使用入口,支持注册登录后直接在线体验零代码AI应用生成。同时整理了APP端下载渠道与小程序入口,助你快速访问平台,通过自然语言描述需求即可生成应用、小程序或网站。

2026.11.03

0

10

Uvicorn 报错与问题解决教程
Uvicorn 报错与问题解决教程

本专题汇总了Uvicorn服务器常见问题与报错解决的教程合集,涵盖启动失败、端口占用、模块导入错误、事件循环冲突及热重载失效等典型故障的排查思路。同时整理了多进程与Worker配置、SSL/HTTPS证书报错、跨域与代理配置等实战解决方案,助你快速定位并解决Uvicorn运行中的各类问题。

2026.11.02

20

14

Uvicorn使用教程
Uvicorn使用教程

本专题汇总了Uvicorn使用教程与启动运行指南,涵盖其作为Python ASGI服务器的定位与特点,以及命令行启动(uvicorn main:app --reload)、参数配置(host/port/workers)、程序内嵌运行与Gunicorn结合部署等核心用法。同时整理了热重载调试、日志配置与生产环境部署优化等技巧,助你快速掌握Uvicorn的运行与配置。

2026.11.02

20

17

Uvicorn安装配置教程
Uvicorn安装配置教程

本专题汇总了Uvicorn的安装配置教程与部署指南,涵盖通过pip安装Uvicorn、启动FastAPI/Starlette应用、开发模式热重载(--reload)及多进程工作模式配置等核心操作。同时整理了结合Gunicorn的生产部署方案、HTTPS与反向代理配置及性能调优技巧,助你快速完成Uvicorn服务的安装与上线部署。

2026.11.02

20

17

Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

2026.10.28

120

16

Vite 如何搭配 Vue.js
Vite 如何搭配 Vue.js

Vite搭配Vue.js专题汇集创建Vue 3项目、配置开发环境、路径别名、Vue插件、代理、多环境构建与调试的已审核文章,帮助读者完成从初始化到日常开发的配置。

2026.10.28

100

15

Vite 项目目录怎么看
Vite 项目目录怎么看

Vite项目目录专题汇集入口文件、src源码、public静态资源、路径别名、项目根目录和构建输出目录相关的已审核文章,帮助读者理解各文件夹的用途与引用方式。

2026.10.28

300

10

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程