Go语言defer总结
前言:
defer是Go语言中的一个关键字(延迟调用),一般用于释放资源和连接、关闭文件、释放锁等。和defer类似的有java的finally和C++的析构函数,这些语句一般是一定会执行的(某些特殊情况后文会提到),不过析构函数析构的是对象,而defer后面一般跟函数或方法。
1、 多个defer语句,按先进后出的方式执行
package main import "fmt" func main() { var whatever [5]struct{} for i := range whatever { defer fmt.Println(i) } }
输出:
1 2 3 4 5 | 4 3 2 1 0 |
所有的defer语句会放入栈中,在入栈的时候会进行相关的值拷贝(也就是下面的“对应的参数会实时解析”)。
2、defer声明时,对应的参数会实时解析
简单示例:
1 2 3 4 5 6 7 8 9 | package main import "fmt" func main() { i := 1 fmt.Println( "i =" , i) defer fmt.Print(i) } |
输出:
1 2 | i = 1 1 |
defer后面的语句最后才会执行
辨析:defer后面跟无参函数、有参函数和方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 | package main import "fmt" func test(a int) { //无返回值函数 defer fmt.Println( "1、a =" , a) //方法 defer func (v int) { fmt.Println( "2、a =" , v)} (a) //有参函数 defer func () { fmt.Println( "3、a =" , a)} () //无参函数 a++ } func main() { test(1) } |
输出:
1 2 3 | 3、a = 2 2、a = 1 1、a = 1 |
解释:
方法中的参数a,有参函数中的参数v,会请求参数,直接把参数代入,所以输出的都是1。a++变成2之后,3个defer语句以后声明先执行的顺序执行,无参函数中使用的a现在已经是2了,故输出2。
3、可读取函数返回值(return返回机制)
defer、return、返回值三者的执行逻辑应该是:
return最先执行,return负责将结果写入返回值中;
接着defer开始执行一些收尾工作;
最后函数携带当前返回值(可能和最初的返回值不相同)退出。
当defer语句放在return后面时,就不会被执行。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | package main import "fmt" func f(i int) int{ return i defer fmt.Print( "i =" , i) return i+1 } func main() { f(1) } |
没有输出,因为return i之后函数就已经结束了,不会执行defer。
(1)无名返回值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package main import ( "fmt" ) func a() int { var i int defer func () { i++ fmt.Println( "defer2:" , i) }() defer func () { i++ fmt.Println( "defer1:" , i) }() return i } func main() { fmt.Println( "return:" , a()) } |
输出:
1 2 3 | defer1: 1 defer2: 2 return : 0 |
解释:
返回值由变量i赋值,相当于返回值=i=0。第二个defer中i++ = 1, 第一个defer中i++ = 2,所以最终i的值是2。但是返回值已经被赋值了,即使后续修改i也不会影响返回值。最终返回值返回,所以main中打印0。
(2)有名返回值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package main import ( "fmt" ) func b() (i int) { defer func () { i++ fmt.Println( "defer2:" , i) }() defer func () { i++ fmt.Println( "defer1:" , i) }() return i //或者直接写成return } func main() { fmt.Println( "return:" , b()) } |
输出:
1 2 3 | defer1: 1 defer2: 2 return : 2 |
解释:
这里已经指明了返回值就是i,所以后续对i进行修改都相当于在修改返回值,所以最终函数的返回值是2。
(3)函数返回值为地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package main import ( "fmt" ) func c() *int { var i int defer func () { i++ fmt.Println( "defer2:" , i) }() defer func () { i++ fmt.Println( "defer1:" , i) }() return &i } func main() { fmt.Println( "return:" , *(c())) } |
输出:
1 2 3 | defer1: 1 defer2: 2 return : 2 |
解释:
此时的返回值是一个指针(地址),这个指针=&i,相当于指向变量i所在的地址,两个defer语句都对i进行了修改,那么返回值指向的地址的内容也发生了改变,所以最终的返回值是2。
再看一个例子:
1 2 3 4 5 6 | func f() (r int) { defer func (r int) { r = r + 5 }(r) return 1 } |
最初返回值r的值是1,虽然defer语句中函数的参数名也叫r(这里我记作r’),但传参的时候相当于r‘=r(值传递),函数内的语句相当于r’=r‘+5,所以返回值r并没有被修改,最终的返回值仍是1。
4、defer与闭包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package main import "fmt" type Test struct { name string } func (t *Test) pp() { fmt.Println(t.name) } func main() { ts := []Test{{ "a" }, { "b" }, { "c" }} for _, t := range ts { defer t.pp() } } |
输出:
1 2 3 | c c c |
解释:
for结束时t.name=“c”,接下来执行的那些defer语句中用到的t.name的值均为”c“。
修改代码为:
package main import "fmt" type Test struct { name string } func pp(t Test) { fmt.Println(t.name) } func main() { ts := []Test{{"a"}, {"b"}, {"c"}} for _, t := range ts { defer pp(t) } }
输出:
c
b
a
解释:
defer语句中的参数会实时解析,所以在碰到defer语句的时候就把该时的t代入了。
再次修改代码:
package main import "fmt" type Test struct { name string } func (t *Test) pp() { fmt.Println(t.name) } func main() { ts := []Test{{"a"}, {"b"}, {"c"}} for _, t := range ts { tt := t println(&tt) defer tt.pp() } }
输出:
0xc000010200 0xc000010210 0xc000010220 c b a
解释:
:=用来声明并赋值,连续使用2次a:=1就会报错,但是在for循环内,可以看出每次tt:=t时,tt的地址都不同,说明他们是不同的变量,所以并不会报错。每次都有一个新的变量tt:=t,所以每次在执行defer语句时,对应的tt不是同一个(for循环中实际上生成了3个不同的tt),所以输出的结果也不相同。
5、defer用于关闭文件和互斥锁
文件:
func ReadFile(filename string) ([]byte, error) { f, err := os.Open(filename) if err != nil { return nil, err } defer f.close() return ReadAll() }
互斥锁:
var mu sync.Mutex var m = make(map[string]int) func lookup(key string) int { mu.Lock() defer mu.Unlock() return m[key] }
6、“解除”对所在函数的依赖
package main import "fmt" import "time" type User struct { username string } func (this *User) Close() { fmt.Println(this.username, "Closed !!!") } func main() { u1 := &User{"jack"} defer u1.Close() u2 := &User{"lily"} defer u2.Close() time.Sleep(10 * time.Second) fmt.Println("Done !") }
输出:
Done ! lily Closed !!! jack Closed !!!
解释:
defer后面跟无参函数,u1.Close()和u2.Close()要等sleep和fmt.Println(“Done !”)之后才可以执行,也就是在函数最终返回之前执行。
修改代码为:
package main import "fmt" import "time" type User struct { username string } func (this *User) Close() { fmt.Println(this.username, "Closed !!!") } func f(u *User) { defer u.Close() } func main() { u1 := &User{"jack"} f(u1) u2 := &User{"lily"} func() { defer u2.Close() }() time.Sleep(10 * time.Second) fmt.Println("Done !") }
输出:
jack Closed !!! lily Closed !!! Done !
这样的使用方式,似乎不太合理,但却有存在的必要性。大多数情况下,可以用于 u1,u2 之类非常消耗内存,或者cpu,其后执行时间过程且没有太多关联的情况。既保留了defer的功能特性,也满足范围精确控制的条件!(算是奇技淫巧吧😂)
7、defer与panic
(1)在panic语句后面的defer语句不被执行
func panicDefer() { panic("panic") defer fmt.Println("defer after panic") }
输出:
panic: panic goroutine 1 [running]: main.panicDefer() E:/godemo/testdefer.go:17 +0x39 main.main() E:/godemo/testdefer.go:13 +0x20 Process finished with exit code 2
defer 语句输出了内容。
Go中的panic类似其它语言中的抛出异常,panic后面的代码不再执行(panic语句前面的defer语句会被执行)。
8、调用os.Exit时defer不会被执行
func deferExit() { defer func() { fmt.Println("defer") }() os.Exit(0) }
当调用os.Exit()方法退出程序时,defer并不会被执行,上面的defer并不会输出。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
2018-10-17 git 合并远程分支