go语言中defer使用指南

目录

1.使用场景

2.执行顺序

3.for循环中的defer及defer中的闭包陷阱

4.defer与返回值的关系

5.总结


1.使用场景

在编程的时候,经常需要打开一些资源,比如数据库连接、文件、锁等,这些资源需要在用完之后释放掉,否则会造成内存泄漏。

在 Go 中 defer 一般用于异常处理、资源释放、文件关闭、解锁互斥量等操作。有一个编码好习惯就是,在初始化资源后,可以在后面紧跟一个defer函数取释放资源。

2.执行顺序

defer执行顺序是后进先出(底层是由链栈实现的)。

在函数内,defer与return的执行顺序是:defer在函数内return执行之后、函数退出之前执行。

3.for循环中的defer及defer中的闭包陷阱

闭包:一个匿名函数捕获了外部变量,就形成了闭包

在 Go 语言 中,如果在循环内执行 defer 语句,每次循环中执行的 defer 语句都会将其延迟的函数调用压入栈中。所有的 defer 调用都会在循环所在的函数返回之前按照后进先出(LIFO) 顺序执行。

还有超级重要,即在 defer 声明时 ,函数的参数会立即求值(这针对的是有参数传入的函数),而不是等到延迟调用时才求值。

重要的事情说三遍,defer 声明时函数的参数会立即求值,deferzai 声明时函数的参数会立即求值,defer 声明时 函数的参数会立即求值!!!

什么是函数的参数会立即求值?

栗子1:

vbnet 复制代码
package main

import "fmt"

func main() {
    for i := 0; i < 3; i++ {
        defer func(val int) {    // 闭包,捕获了外部变量i
            fmt.Println(val) // 打印传递的值
        }(i) // 在这里立即捕获当前的 i 值,
    }
    fmt.Println("Loop ended")
}

输出结果:

Go 复制代码
Loop ended
2
1
0

栗子2:

Go 复制代码
package main

import "fmt"

func main() {
    for i := 0; i < 3; i++ {
        defer func() {   // 闭包,捕获了外部变量i
            fmt.Println(i) // 直接引用 i,持有的是引用,而非具体的值
        }()
    }
    fmt.Println("Loop ended")
}

输出结果:

Go 复制代码
Loop ended
3
3
3

造成这种结果的原因:

闭包捕获的是引用,而非具体的值!!!这一点很重要

栗子1:通过参数传递固定 i 的值,每个匿名函数保存的是当前循环中 i 的值。因此,输出结果是 2, 1, 0。

栗子2:匿名函数捕获的是循环变量 i 的引用 ,所有匿名函数访问的都是同一个变量 i,而此时 i 的值已经是 3。因此,输出结果是 3, 3, 3。

4.defer与返回值的关系

栗子3:

Go 复制代码
func foo() int {    //返回值未命名
    i := 0
    defer func() { i++ }()
    return i
}    // 返回0

栗子4:

Go 复制代码
func bar() (i int) {    // 返回值命名了
    i = 0
    defer func() { i++ }()
    return i
}    // 返回1

造成这种结果的原因是:

defer可以修改命名返回值,不能修改匿名返回值!!!

  • 在foo函数中,由于使用的是匿名返回值 ,i的值在return i的时候就已经确定为0。即使随后的defer函数将i增加到1,这个改变不会反映在已经确定的返回值上,所以foo函数返回0。

  • 在bar函数中,由于使用了命名返回值i ,defer函数能够访问并修改这个命名返回值。因此,在return i之后但函数真正返回之前,defer函数将i从0增加到1,导致bar函数最终返回1。

5.总结

  1. defer 一般用于异常处理、资源释放、文件关闭、解锁互斥量
  2. defer 在函数内return执行之后、函数退出之前执行
  3. defer 声明时函数的参数会立即求值(针对有参数传入的函数)
  4. 闭包捕获的是引用,而非具体的值
  5. defer 可以修改命名返回值,不能修改匿名返回值
相关推荐
weixin_531094698 分钟前
heap_4内存管理
java·开发语言·算法
疯狂的维修14 分钟前
关于用C#写电机起保停控制
开发语言·c#
谁在黄金彼岸1 小时前
nginx服务化五套方案原理解剖与选型
后端
谁在黄金彼岸1 小时前
WinSW在Win7上失败真相-实测与修复
后端
花间相见1 小时前
【后端开发|Redis进阶01】—— Redis分布式锁全解:从SETNX到Redisson看门狗,再到RedLock
后端·面试
llqbzllll1 小时前
A2A 1.0 到底和 MCP 差在哪:从 Agent Card、Task 到 Java 最小互操作
后端
花间相见1 小时前
【后端开发|JVM基础01】—— Java垃圾回收全解:从分代内存模型到各收集器的分代职责
后端
geovindu1 小时前
rust: Abstract Factory pattern
开发语言·后端·设计模式·rust·抽象工厂模式
pe7er1 小时前
Spring Boot 日志最佳实践:接入、分级、异常记录与滚动拆分
后端
“AI国潮设计-小江”1 小时前
《Python+SDXL实战:用ControlNet批量生成“英歌舞麻将糕”IP,附自动化脚本与商用思路》
开发语言·人工智能·python·prompt·aigc