【go】 Go语言中的 defer:从入门到理解底层机制(讲透版)

👨‍💻 关于作者:会编程的土豆

"不是因为看见希望才坚持,而是坚持了才看见希望。"

你好,我是会编程的土豆,一名热爱后端技术的Java学习者。

📚 正在更新中的专栏:

💕作者简介:后端学习者

defer 是 Go 里一个非常"有味道"的关键字。

很多人一开始只会用,但其实不理解执行顺序和坑点,很容易写错代码。

这篇直接帮你彻底搞懂。


一、defer 是什么?

一句话:

复制代码
defer = 延迟执行,在函数结束前执行

最简单例子

Go 复制代码
func main() {
    fmt.Println("start")

    defer fmt.Println("defer")

    fmt.Println("end")
}

输出

复制代码
start
end
defer

核心结论

复制代码
defer 一定在函数 return 之前执行

二、多个 defer 的执行顺序(重点)


示例

Go 复制代码
func main() {
    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3)
}

输出

复制代码
3
2
1

结论

复制代码
defer 是栈结构(后进先出 LIFO)

👉 类比:

复制代码
像压栈 → 出栈

三、defer + return(高频考点)


示例

Go 复制代码
func test() int {
    defer fmt.Println("defer")
    return 10
}

执行顺序(非常重要)

复制代码
1. 计算 return 的值
2. 执行 defer
3. 返回

四、经典陷阱:defer 修改返回值


示例1(无效修改)

Go 复制代码
func test() int {
    a := 10
    defer func() {
        a = 20
    }()
    return a
}

返回值

复制代码
10

为什么?

复制代码
return a 已经把值拷贝走了

示例2(有效修改)

Go 复制代码
func test() (a int) {
    a = 10
    defer func() {
        a = 20
    }()
    return
}

返回值

复制代码
20

关键区别

复制代码
✔ 命名返回值 → defer 可以修改
✔ 普通返回值 → defer 修改不了

五、defer + 参数(超级容易错)


示例

Go 复制代码
func main() {
    a := 10

    defer fmt.Println(a)

    a = 20
}

输出

复制代码
10

原因

复制代码
defer 在注册时就把参数"拷贝"了

对比:闭包写法

Go 复制代码
defer func() {
    fmt.Println(a)
}()

输出

复制代码
20

结论

复制代码
defer参数:立即求值
defer函数:延迟执行

六、defer 的典型应用场景


1. 关闭资源(最重要)

Go 复制代码
file, _ := os.Open("test.txt")
defer file.Close()

2. 解锁

Go 复制代码
mu.Lock()
defer mu.Unlock()

3. 计时

Go 复制代码
start := time.Now()
defer func() {
    fmt.Println(time.Since(start))
}()

七、defer 的底层理解(面试加分)


本质

复制代码
defer = 把函数压入一个栈

执行时机

复制代码
函数 return 前,按栈顺序执行

八、性能问题(进阶)


早期 Go

复制代码
defer 较慢

现在(Go 1.20+)

复制代码
优化后基本可以放心使用

九、常见坑总结(必须记住)


1️⃣ defer 顺序是反的

复制代码
后写先执行

2️⃣ 参数是立即计算

复制代码
defer fmt.Println(a)  // 立即拷贝

3️⃣ 命名返回值可以被修改

复制代码
func f() (a int)

4️⃣ defer 不要写在循环里(可能性能问题)


十、一句话总结

复制代码
defer = 注册一个"收尾操作",在函数退出前按栈顺序执行
相关推荐
高频因子挖掘机18 分钟前
量化回测应该保存不复权数据吗?从数据口径到收益计算的工程实践
后端·github·api
高频因子挖掘机1 小时前
实时行情为什么不应该逐只股票请求?批量查询与标的池管理实践
后端·github·api
程序猿DD7 小时前
OctaFuse Gateway 2.15.0:MCP 聚合、向量 Embeddings 与官方目录同步
后端·agent
余槐i9 小时前
PostgreSQL 函数索引报错 must be immutable:3种绕法与代价
数据库·后端·sql·postgresql·性能优化
需要8269 小时前
Spring Bean 生命周期:从实例化到销毁的完整钩子
java·后端·spring
死磕音视频10 小时前
让小模型做选择题:限定候选项并读取 logits 概率的入门实践(附 Python 代码)
开发语言·python·大模型·文本分类·transformers
凤山老林10 小时前
Spring Boot 接入国密 SM2/SM3/SM4 的一些实践记录
spring boot·后端·python
伯恩bourne12 小时前
Spring Boot Bean 注册的两种方式:@Component 家族 vs @Configuration + @Bean
java·spring boot·后端
张彦峰ZYF12 小时前
停顿短不等于延迟低:G1、ZGC 与低延迟服务的目标分野、代价账本与选型标定
后端·回收暂停、应用停止总时长·到达安全点、分配停滞·屏障吃吞吐、并发线程吃核心·并发窗口吃堆余量·分区式靠挑回收集兑现目标·染色指针式不支持压缩引用
pride.li12 小时前
第03章 Python 教程-基础语法:注释与变量
服务器·前端·后端·python·算法·计算机视觉