golang的defer踩坑汇总
原文链接:http://www.zhoubotong.site/post/50.html
defer语句用于延迟函数调用,每次会把一个函数压入栈中,函数返回前再把延迟的函数取出并执行。延迟函数可以有参数:
-
延迟函数的参数在defer语句出现时就已确定下来(传值的就是当前值)
-
return先赋值(对于命名返回值),然后执行defer,最后函数返回
-
延迟函数执行按后进先出顺序执行
-
延迟函数可操作主函数的变量名返回值(修改返回值)
-
defer后面的表达式可以是func或者是method的调用,如果defer的函数为nil,则会panic
日常开发中,使用不当很容易造成意外的“坑”。下面我整理了下常规使用场景下,defer的问题可能的踩坑汇总。
释放资源
defer 语句正好是在函数退出时执行的语句,所以使用 defer 能非常方便地处理资源释放、句柄关闭等问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package main import ( "fmt" "os" ) func fileSize(filename string) int64 { f, err := os.Open(filename) if err != nil { return 0 } // 延迟调用Close, 此时Close不会被调用 defer f.Close() info, err := f.Stat() if err != nil { // defer机制触发, 调用Close关闭文件 return 0 } size := info.Size() // defer机制触发, 调用Close关闭文件 return size } func main() { fmt.Println(fileSize( "demo.txt" )) } |
变量捕获
defer中的变量会被提前捕获,后续的修改不会影响到已捕获的值,举个例子:
1 2 3 4 5 6 7 8 9 10 | package main import ( "fmt" ) func main() { i := 0 defer fmt.Println( "Defer运行值:" , i) i = 10 // 这里虽然修改了值,但是不会影响上面的i值 fmt.Println( "最后输出值:" , i) } |
结果defer语句中打印的值是修改前的值。:
最后输出值: 10
Defer运行值: 0
变量名返回值
在defer中修改具体变量名返回值时,会影响到函数的实际返回值,继续举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | package main import ( "fmt" ) func ShowDefer() { fmt.Println( "最后输出值:" , deferValue()) } func deferValue() (ret int) { // 注意这里返回res变量值 ret = 0 defer func () { // 会直接修改栈中对应的返回值 ret += 10 fmt.Println( "Defer 运行值:" , ret) }() ret = 2 fmt.Println( "Ret重置值:" , ret) return //返回的ret最后是 其实是本次2+上面的ret+=10的结果 } func main() { ShowDefer() } //Ret重置值: 2 //Defer 运行值: 12 //最后输出值: 12 |
非变量名返回值
当函数为非具体名返回值时,defer无法影响返回值(因在return时,对应返回值已存入栈中),继续举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | package main import ( "fmt" ) func ShowDefer() { fmt.Println( "最后输出值:" , deferValue()) } func deferValue() int { // 非命名变量返回 ret := 0 defer func () { ret += 10 fmt.Println( "Defer 运行值:" , ret) }() ret = 2 return ret // 这里直接返回ret2 } func main() { ShowDefer() } //Defer 运行值: 12 //最后输出值: 2 |
经过上面的实践理解,我们来看下下面的笔试题:
笔试题一
1 2 3 4 5 6 7 8 9 10 11 12 13 | package main import "fmt" func f() (result int) { defer func () { result *= 7 }() return 3 } func main() { fmt.Println(f()) } |
问题解析:这里return先给result赋值为3,之后执行defer,result变为21,最后返回21。
笔试题二
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | package main import "fmt" func f() int { result := 3 defer func () { result *= 7 }() return result } func main() { fmt.Println(f()) } |
问题解析:这里return确定返回值3,之后defer才修改result,最后函数返回return确定的返回值3。
笔试题三
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package main import "fmt" // 多个defer func multiDefer() { for i := 3; i > 0; i-- { defer func (n int) { fmt.Print(n, " " ) }(i) } for i := 33; i > 30; i-- { defer fmt.Print(i, " " ) } } func main() { multiDefer() } |
问题解析:多个defer函数,按顺序逆序执行,这里输出31 32 33 1 2 3 。
笔试题四
1 2 3 4 5 6 7 8 9 10 | package main import "fmt" var fun func () string func main() { fmt.Println( "hello monkey" ) defer fun() } |
问题解析:由于这里的defer指定的func为nil,所以会panic 。
笔试题五
1 2 3 4 5 6 7 8 9 10 11 | package main import "fmt" func main() { for i := 3; i > 0; i-- { defer func () { fmt.Print(i, " " ) }() } } |
问题解析:这里是极度容易踩坑的地方,由于defer这里调用的func没有参数,等执行的时候,i已经为0(按3 2 1逆序,最后一个i=1时,i--的结果最后是0),所以这里输出3个0 。
如果还不太好理解?
1 2 3 4 5 6 7 8 9 10 11 | package main import "fmt" func main() { for i := 3; i > 1; i-- { // 循环满足条件的是 3 2, defer func () { // 因为func 没有参数,defer运行最后i--即 2-- 结果为 1 fmt.Print(i, " " ) // 循环2次 结果均为 1 }() } } //输出 1 1 |
按照常规的思维理解应该是这样:
1 2 3 4 5 6 7 8 9 10 11 | package main import "fmt" func main() { for i := 3; i > 0; i-- { defer func (i int) { fmt.Print(i, " " ) }(i) } } |
感兴趣的朋友可以细细品下。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具