【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 = 注册一个"收尾操作",在函数退出前按栈顺序执行
相关推荐
鶴哥只手遮天几秒前
《Redis 8.10.3 深度剖析》系列 · 第 1 章·全景与源码地图:版本谱系、构建体系、全局状态与启动链路
redis·后端·开源
用户40031504335几秒前
《从 skynet.timeout 到回调执行:Skynet 分层时间轮源码剖析》
后端
一条Go5 分钟前
四个进程,一把锁:Alioth 的告警评测为什么不需要消息队列
后端·设计模式·架构
用户19961435599616 分钟前
插件是怎么被安全装进系统的
后端
Pniubi29 分钟前
Java为什么要分堆和栈
java·开发语言
愚农搬码1 小时前
自研表单引擎、开源组件二开和商业产品,企业应该如何选择?
后端
用户298698530141 小时前
Python 自动化处理 Word 页眉页脚的几种典型场景
后端·python·api
小小晓.1 小时前
Qt 对话框、QLineEdit 与布局管理实战详解
开发语言·qt
愚农搬码1 小时前
一款企业级电子表单系统应该具备哪些功能?
后端
炸薯条!2 小时前
博客系统测试报告
开发语言·lua