- Go语言defer 秀才 2025年2月14日 大约 8 分钟 Go语言 Go语言 golang go基础语法 defer 此页内容 1. defer关键字 2. defer执行顺序 3. defer的使用场景 3.1 资源的释放 3.2 配合recover一起处理panic 4. defer与return 1. defer关键字 defer 顾名思义,延迟。它是go语言中的一个关键字,主要用在函数或方法前面,作用是用于函数和方法的延迟调用,在语法上, defer 与普通的函数调用没有什么区别。 在使用上非常简单,只需要弄清楚以下几点即可: 延迟的函数的什么时候被调用? 函数 return 的时候 发生 panic 的时候 延迟调用的语法规则 defer 关键字后面表达式必须是函数或者方法调用 延迟内容不能被括号括起来 2. defer执行顺序 上一小节说到 defer 关键字后面的函数调用会在函数 return 或者发生 panic 的时候执行,这个在单个 defer 的时候很好理解,但当一个函数中有多个 defer 的时候,他们的顺序是怎么样的呢? defer 语句的执行顺序是先进后出LIFO。下面请看具体例子:
package main
import "fmt"
func defer1() {
fmt.Println("defer1")
}
func defer2() {
fmt.Println("defer2")
}
func defer3() {
fmt.Println("defer3")
}
func main() {
defer defer1()
defer defer2()
defer defer3()
}
运行结果:
defer3
defer2
defer1
可以看到执行顺序跟栈是一样的,先调用,后执行 3. defer的使用场景 通过前面的小节我们知道了 defer 关键字主要是用于延迟调用,那么什么场景下需要我们用到延迟调用了,有过Go语言基础的同学在一些代码中经常看到 defer 关键字。 defer 关键字一般用在以下两个场景中 3.1 资源的释放 通过 defer 延迟调用机制,我们可以简洁优雅处理资源回收问题,从而避免在复杂的代码逻辑情况下,遗漏相关的资源回收问题,用的比较多的就是类似网络连接,数据库连接,以及文件句柄的资源的释放。 看看下面一个复制文件的函数:
func CopyFile(dstFile, srcFile string) (wr int64, err error) {
src, err := os.Open(srcFile)
if err != nil {
return
}
dst, err := os.Create(dstFile)
if err != nil {
return
}
wr, err = io.Copy(dst, src)
dst.Close()
src.Close()
return
}
仔细看这段代码,其实是有问题的,比如当地6行执行失败,程序直接返回了,但我们并没有关闭前面打开的文件资源src,这样就造成了资源的浪费。 那么用 defer 关键字,我们可以怎么做呢?
func CopyFile(dstFile, srcFile string) (wr int64, err error) {
src, err := os.Open(srcFile)
if err != nil {
return
}
defer src.Close()
dst, err := os.Create(dstFile)
if err != nil {
return
}
defer dst.Close()
wr, err = io.Copy(dst, src)
return wr, err
}
只要我们正确打开了某个资源,比如 src 和 dst ,没返回 err 的情况下,都可以用 defer 延迟调用来关闭资源,注意,这是 go 语言中非常常见的一种资源关闭方式。 3.2 配合recover一起处理panic defer 另一个常用的地方就是在处理程序 panic 的时候,关于程序的异常捕获我们将在下一个小节讲到,这里大家可以先了解一下, go 语言中用 panic 来抛出异常,用 recover 来捕获异常,所以当我们的程序出现异常的时候,我们需要知道是发生了什么异常的时候,就可以用 defer recover 来捕获异常
package main
import "fmt"
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println(r)
}
}()
a := 1
b := 0
fmt.Println("result:", a/b)
}
运行结果:
result: runtime error: integer divide by zero
可以看到,程序并没有输出 result ,这是因为我们尝试对一个除数为0的数做除法,这是不允许的,所以程序回panic,但我们用 defer 在程序发生 panic 的时候捕获了这个异常,打印出异常信息: runtime error: integer divide by zero 。 4. defer与return 前面第一小节我们介绍过 defer 函数的执行是在 return 的时候,那么在具体一点,在 return 的时候, defer 具体做了什么?又会带来什么结果?这是一个非常值得探讨的问题,也是面试官在面试中经常会问的问题,往往通过这个问题就可一看出一个面试者对 go 语言掌握的扎不扎实。 例子1:
package main
import "fmt"
func deferRun() {
var num = 1
defer fmt.Printf("num is %d", num)
num = 2
return
}
func main(){
deferRun()
}
运行结果:
num is 1
为什么? 延迟函数 defer fmt.Printf("num is %d", num) 的参数 num 在 defer 语句出现的时候就已经确定, num=1 ,所以不管后面怎么修改 a 的值,最终调用 defer 函数传递给 defer 函数的参数已经固定是 1 了,不会再变化。 例子2:
package main
import "fmt"
func main() {
deferRun()
}
func deferRun() {
var arr = [4]int{1, 2, 3, 4}
defer printArr(&arr)
arr[0] = 100
return
}
func printArr(arr *[4]int) {
for i := range arr {
fmt.Println(arr[i])
}
}
运行结果:
100
2
3
4
为什么? 通过前一个地址,我们知道在defer出现的时候,参数已经确定,但是这里传递的是地址,地址没变,但是地址对应的内容被修改了,所以输出会被修改。 例子3:
package main
import "fmt"
func main() {
res := deferRun()
fmt.Println(res)
}
func deferRun() (res int) {
num := 1
defer func() {
res++
}()
return num
}
运行结果:
2
为什么? 这是一个非常经典的例子,要想准确的的只程序的执行结果,需要我们对函数return的执行有一个细致的了解。其实函数的return并非一个原子操作,return的过程可以被分解为以下三步: 设置返回值 执行defer语句 将结果返回 所以,在本例中,第一步是将 result 的值设置为 num ,此时还未执行 defer , num 的值是 1 ,所以 result 被设置为 1 ,然后再执行 defer 语句将 result+1 ,最终将 result 返回,所以会打印出 2 。 例子4:
package main
import "fmt"
func main() {
res := deferRun()
fmt.Println(res)
}
func deferRun() int {
var num int
defer func() {
num++
}()
return 1
}
运行结果:
1
为什么? 本例和前面的区别返回值是匿名的,但是我们可以同样运用上面的思路,自己创建一个返回值,这里假设为 res ,运用前面的思路分析,第一步将 res 设置为1,第二步执行 defer 将 num+1 ,第三步将 res 返回,所以最终结果是 1 。 例子5:
package main
import "fmt"
func main() {
res := deferRun()
fmt.Println(res)
}
func deferRun() int {
num := 1
defer func() {
num++
}()
return num
}
运行结果:
1
同样的思路不难分析:自己创建一个返回值,这里假设为 res ,第一步将 res 设置为 num ,所以 res 的值为 1 ,第二步执行 defer 将 num+1 ,此时 num 为 2 ,但是 res 为 1 ,第三步将 res 返回,所以最终结果是 1 。 例子6:
package main
import "fmt"
func main() {
res := deferRun()
fmt.Println(res)
}
func deferRun() (res int) {
num := 1
defer func() {
num++
}()
return num
}
运行结果:
1
不难分析运行结果还是1,同样的三步分析法,因为 defer 改变的是 num 的值,而不是改变的 res 的值,所以结果不会变,不过 defer 函数里变为 res++ ,那么结果就是 2 了。 所以,当我们碰到defer与return确定最终的返回值,可以总结为以下两点: defer 定义的延迟函数的参数在 defer 语句出时就已经确定下来了 return 不是原子级操作,执行过程是: 设置返回值—>执行 defer 语句—>将结果返回 关注秀才公众号: IT杨秀才 ,领取精品学习资料 公众号后台回复: Go面试 ,领取Go面试题库PDF 公众号后台回复: Go学习 ,领取Go必看书籍 公众号后台回复: 大模型 ,领取大模型学习资料 公众号后台回复: 111 ,领取架构学习资料 公众号后台回复: 26届秋招 ,领取26届秋招企业汇总表
🔥 配套实战项目,拆得开、跑得起、能写进简历 多 Agent 编排 + RAG 混合检索 · 31 篇深度教程 + 50+ 面试题 点击查看 DevSupport AI 实战项目 → 在 GitHub 上编辑此页 上次编辑于: 2026/6/24 23:41:22 贡献者: Percygu , Your Name , gupeng 上一页 15. Go语言error 下一页 17. Go语言异常捕获