面试常问之defer()的执行次序
情形1
代码语言:javascript复制package main
func main() {
defer print(123)
defer_call()
defer print(789) //panic之后的代码不会被执行
print("不会执行到这里")
}
func defer_call() {
defer func() {
print("打印前")
}()
defer func() {
print("打印中")
}()
defer print("打印后")
panic("触发异常")
defer print(666) //IDE会有提示: Unreachable code
}
结果为:
代码语言:javascript复制打印后打印中打印前123panic: 触发异常
goroutine 1 [running]:
main.defer_call()
/Users/shuangcui/explore/panicandrecover.go:19 0xe5
main.main()
/Users/shuangcui/explore/panicandrecover.go:6 0x51
可见:
- panic之后的defer()不会被执行
- panic之前的defer(),按照先进后出的次序执行,最后输出panic信息
(defer机制底层,是用链表实现的一个栈)
再如:
代码语言:javascript复制func main() {
fmt.Println(123)
defer fmt.Println(999)
subfunc()
}
func subfunc() {
defer fmt.Println(888)
for i := 0; i > 10; i {
fmt.Println("当前i为:", i)
panic("have a bug")
}
defer fmt.Println(456)
}
结果为:
代码语言:javascript复制123
456
888
999
defer会延迟到当前函数执行 return 命令前被执行, 多个defer之间按LIFO先进后出顺序执行
情形2 (在defer内打印defer之外的主方法里操作的变量)
代码语言:javascript复制package main
import "fmt"
func main() {
foo()
}
func foo() {
i := 0
defer func() {
//i--
fmt.Println("第一个defer", i)
}()
i
fmt.Println(" 1后的i:", i)
defer func() {
//i--
fmt.Println("第二个defer", i)
}()
i
fmt.Println("再 1后的i:", i)
defer func() {
//i--
fmt.Println("第三个defer", i)
}()
i
fmt.Println("再再 1后的i:", i)
i = i 666
fmt.Println(" 666后的i为:", i)
}
输出为:
代码语言:javascript复制 1后的i: 1
再 1后的i: 2
再再 1后的i: 3
666后的i为: 669
第三个defer 669
第二个defer 669
第一个defer 669
情形3 (在defer内外操作同一变量)
代码语言:javascript复制package main
import "fmt"
func main() {
foo()
}
func foo() {
i := 0
defer func() {
i--
fmt.Println("第一个defer", i)
}()
i
fmt.Println(" 1后的i:", i)
defer func() {
i--
fmt.Println("第二个defer", i)
}()
i
fmt.Println("再 1后的i:", i)
defer func() {
i--
fmt.Println("第三个defer", i)
}()
i
fmt.Println("再再 1后的i:", i)
i = i 666
fmt.Println(" 666后的i为:", i)
}
输出为:
代码语言:javascript复制 1后的i: 1
再 1后的i: 2
再再 1后的i: 3
666后的i为: 669
第三个defer 668
第二个defer 667
第一个defer 666
情形4! (发生了参数传递!---
传递参数给defer后面的函数, defer内外同时操作该参数)
代码语言:javascript复制package main
import "fmt"
func main() {
foo2()
}
func foo2() {
i := 0
defer func(k int) {
//k--
fmt.Println("第一个defer", k)
}(i)
i
fmt.Println(" 1后的i:", i)
defer func(k int) {
//k--
fmt.Println("第二个defer", k)
}(i)
i
fmt.Println("再 1后的i:", i)
defer func(k int) {
//k--
fmt.Println("第三个defer", k)
}(i)
i
fmt.Println("再再 1后的i:", i)
i = i 666
fmt.Println(" 666后的i为:", i)
}
输出为:
代码语言:javascript复制 1后的i: 1
再 1后的i: 2
再再 1后的i: 3
666后的i为: 669
第三个defer 2
第二个defer 1
第一个defer 0
如果取消三处k--
的注释, 输出为:
1后的i: 1
再 1后的i: 2
再再 1后的i: 3
666后的i为: 669
第三个defer 1
第二个defer 0
第一个defer -1
等同于:
代码语言:javascript复制package main
import "fmt"
func main() {
foo3()
}
func foo3() {
i := 0
defer f1(i)
i
fmt.Println(" 1后的i:", i)
defer f2(i)
i
fmt.Println("再 1后的i:", i)
defer f3(i)
i
fmt.Println("再再 1后的i:", i)
i = i 666
fmt.Println(" 666后的i为:", i)
}
func f1(k int) {
k--
fmt.Println("第一个defer", k)
}
func f2(k int) {
k--
fmt.Println("第二个defer", k)
}
func f3(k int) {
k--
fmt.Println("第三个defer", k)
}
defer指定的函数的参数在 defer 时确定,更深层次的原因是Go语言都是值传递。
情形5! (传递指针参数!---
传递参数给defer后面的函数, defer内外同时操作该参数)
代码语言:javascript复制package main
import "fmt"
func main() {
foo5()
}
func foo5() {
i := 0
defer func(k *int) {
fmt.Println("第一个defer", *k)
}(&i)
i
fmt.Println(" 1后的i:", i)
defer func(k *int) {
fmt.Println("第二个defer", *k)
}(&i)
i
fmt.Println("再 1后的i:", i)
defer func(k *int) {
fmt.Println("第三个defer", *k)
}(&i)
i
fmt.Println("再再 1后的i:", i)
i = i 666
fmt.Println(" 666后的i为:", i)
}
输出为:
代码语言:javascript复制 1后的i: 1
再 1后的i: 2
再再 1后的i: 3
666后的i为: 669
第三个defer 669
第二个defer 669
第一个defer 669
作如下修改:
代码语言:javascript复制package main
import "fmt"
func main() {
foo5()
}
func foo5() {
i := 0
defer func(k *int) {
(*k)--
fmt.Println("第一个defer", *k)
}(&i)
i
fmt.Println(" 1后的i:", i)
defer func(k *int) {
(*k)--
fmt.Println("第二个defer", *k)
}(&i)
i
fmt.Println("再 1后的i:", i)
defer func(k *int) {
(*k)--
fmt.Println("第三个defer", *k)
}(&i)
i
fmt.Println("再再 1后的i:", i)
i = i 666
fmt.Println(" 666后的i为:", i)
}
输出为:
代码语言:javascript复制 1后的i: 1
再 1后的i: 2
再再 1后的i: 3
666后的i为: 669
第三个defer 668
第二个defer 667
第一个defer 666
总结一下
即
- 如果传参进defer后面的函数(无论是闭包
func(){}(i)
方式还是子方法f(i)
方式,或是直接跟如fmt.Println(i)),defer回溯时均以当时传参时i的值去计算 - 反之,defer回溯时,以最后i的值带入计算;(参考下面的例子).
参考:
Go面试题答案与解析[1]
几种写法之间的归类与区别
代码语言:javascript复制package main
import "fmt"
func main() {
rs := foo6()
fmt.Println("in main func:", rs)
}
func foo6() int {
i := 0
defer fmt.Println("in defer :", i)
//defer func() {
// fmt.Println("in defer :", i)
//}()
i = 1000
fmt.Println("in foo:", i)
return i 24
}
输出为:
代码语言:javascript复制in foo: 1000
in defer : 0
in main func: 1024
如果改为:
代码语言:javascript复制package main
import "fmt"
func main() {
rs := foo6()
fmt.Println("in main func:", rs)
}
func foo6() int {
i := 0
//defer fmt.Println("in defer :", i)
defer func() {
fmt.Println("in defer :", i)
}()
i = 1000
fmt.Println("in foo:", i)
return i 24
}
输出为:
代码语言:javascript复制in foo: 1000
in defer : 1000
in main func: 1024
也可见,
代码语言:javascript复制defer fmt.Println("in defer :", i)
相当于
代码语言:javascript复制defer func(k int) {
fmt.Println(k)
}(i)
或
代码语言:javascript复制func f(k int){
fmt.Println(k)
}
这时的参数,都是传递时的值
而如
代码语言:javascript复制 defer func() {
fmt.Println("in defer :", i)
}()
这时的参数,为最后return之前那一刻的值
defer会影响返回值吗?
函数的return value 不是原子操作, 在编译器中实际会被分解为两部分:返回值赋值
和 return
。而defer刚好被插入到末尾的return前执行(即defer介于二者之间)。故可以在defer函数中修改返回值
package main
import (
"fmt"
)
func main() {
fmt.Println(doubleScore(0)) //0
fmt.Println(doubleScore(20.0)) //40
fmt.Println(doubleScore(50.0)) //50
}
func doubleScore(source float32) (rs float32) {
defer func() {
if rs < 1 || rs >= 100 {
//将影响返回值
rs = source
}
}()
rs = source * 2
return
//或者
//return source * 2
}
输出为:
代码语言:javascript复制0
40
50
再如:
代码语言:javascript复制func main() {
fmt.Println("foo return :", foo2())
}
func foo() map[string]string {
m := map[string]string{}
defer func() {
m["a"] = "b"
}()
return m
}
输出为:
代码语言:javascript复制foo return : map[a:b]
又如:
代码语言:javascript复制package main
import "fmt"
func main() {
fmt.Println("foo return :", foo())
}
func foo() int {
i := 0
defer func() {
i = 10086
}()
return i 5
}
输出为:
代码语言:javascript复制foo return : 5
若作如下修改:
代码语言:javascript复制
func foo() (i int) {
i = 0
defer func() {
i = 10086
}()
return i 5
}
则返回为:
代码语言:javascript复制foo return : 10086
return之后的语句先执行,defer后的语句后执行
将return value
拆解为两步: 确定value值,然后return..即如果return 后面是个方法或者复杂表达式,且有某个值i,会先计算.完成后defer再执行,如果defer里面也有对i的改动,是可以影响返回值的
(给函数返回值申明变量名, 这时, 变量的内存空间空间是在函数执行前就开辟出来的,且该变量的作用域为整个函数,return时只是返回这个变量的内存空间的内容,因此defer能够改变返回值)
defer不影响返回值,除非是map、slice和chan这三种引用类型,或者返回值定义了变量名
参考:
Golang研学:如何掌握并用好defer[2]--存疑("引用传递"那里明显错误)
Golang中的Defer必掌握的7知识点
参考资料
[1]
Go面试题答案与解析: https://yushuangqi.com/blog/2017/golang-mian-shi-ti-da-an-yujie-xi.html
[2]
Golang研学:如何掌握并用好defer: https://segmentfault.com/a/1190000019063371#comment-area