一、什么是 defer?
在Go语言中,defer用于注册延迟执行的函数。 这些被推迟的函数会在外层函数返回之前按照后进先出(LIFO)的顺序执行。
一句话总结: 👉 defer就是安排好“临走前要做的事”
二、defer 的基本语法
defer 函数调用
注意:
-
defer后面必须是一个函数调用,不能是函数定义!
-
defer语句会立刻计算参数,但延迟函数体的执行。
三、简单示例:基本用法
package main
import "fmt"
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
输出结果:
hello world
解释:
-
程序先输出"hello"。
-
main()结束前,执行defer的fmt.Println("world")。
四、defer的执行顺序
多个defer时,它们的执行顺序是栈结构(后进先出)。
package main
import "fmt"
func main() {
defer fmt.Println("first")
defer fmt.Println("second")
defer fmt.Println("third")
}
输出顺序:
third second first
总结口诀:
-
谁后
defer,谁先执行!
五、defer的参数求值时机
很多初学者容易误解:defer语句注册时就计算参数,而不是等到执行时才计算!
看例子:
package main
import "fmt"
func main() {
a := 10
defer fmt.Println("defer a:", a)
a = 20
fmt.Println("current a:", a)
}
输出:
current a: 20 defer a: 10
解释:
-
defer fmt.Println("defer a:", a)在注册时,a的值就是10。 -
后续a变了,不影响已注册的defer。
六、defer常见应用场景
1. 释放资源
比如关闭文件、释放数据库连接等。
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("test.txt")
if err != nil {
fmt.Println(err)
return
}
defer file.Close() // 确保函数结束前关闭文件
}
2. 解锁操作
mu.Lock() defer mu.Unlock()
无论函数中间出现什么异常,最后一定会解锁。
3. 捕获异常(panic恢复)
package main
import "fmt"
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
panic("Something went wrong")
}
七、defer性能影响
-
defer本身是有微小性能开销的(因为要保存调用信息)。
-
在高频、短小函数(如循环千万次)里,频繁使用defer可能影响性能。
-
Go 1.14+ 之后,官方优化了defer,性能开销大大降低了,但极限性能场景下仍要注意。
实际建议:
绝大多数业务场景可以放心使用
defer,优先保证代码正确性和简洁性。
八、defer搭配匿名函数使用
可以用匿名函数+defer,来灵活控制更多逻辑。
func main() {
fmt.Println("start")
defer func() {
fmt.Println("deferred block start")
fmt.Println("deferred block end")
}()
fmt.Println("end")
}
输出:
start end deferred block start deferred block end
九、defer修改返回值(常考!)
如果函数有具名返回值,defer可以修改它!
func calc() (res int) {
defer func() {
res += 100
}()
return 1
}
调用结果:
fmt.Println(calc()) // 输出 101
流程说明:
-
函数return时,返回值
res已经是1。 -
执行defer,把
res加了100。 -
最终返回101。
十、defer中的陷阱
1. 循环中defer常见坑
如果在循环里defer,会导致所有defer推到循环结束后一起执行。
for i := 0; i < 3; i++ {
defer fmt.Println(i)
}
输出:
2 1 0
每次的i值是注册时的值,不是执行时的值!
2. defer闭包变量捕获问题
如果defer里是闭包,要注意变量捕获!
for i := 0; i < 3; i++ {
defer func() {
fmt.Println(i)
}()
}
输出:
3 3 3
因为defer里的匿名函数引用了外层变量i,循环完后i已经变成3了!
✅ 正确写法:
for i := 0; i < 3; i++ {
v := i
defer func() {
fmt.Println(v)
}()
}
这样每次保存独立的v副本,输出:
2 1 0
👉 立即点击链接,开启你的全栈开发之路:Golang全栈开发完整课程
944






