一、什么是 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全栈开发完整课程