首页 > 代码库 > wait,wait for me
wait,wait for me
去年输出了一系列golang的编码文章,但总感觉有话没讲,但又不清楚具体是什么,所以本文以随笔为主。
我们知道函数的调用其实就是一个入栈和出栈的动作:
main() --> normal()
如果用这个表示调用,那么在堆栈中就是把函数normal()的入口地址push,当函数normal()执行完毕后,堆栈pop函数normal()地址,同时返回到main()的调用处。
试想当执行函数normal()时出现异常时,会有什么情况发生呢?
package main
import (
"fmt"
)
func normal(a int64) int64 {
var b int64 = 0
return a / b
}
func main() {
var a int64 = 64
fmt.Println("a/b= ", normal(a))
}
你肯定想,这会抛错呀~
是的,U are right,会抛出panic: runtime error: integer divide by zero.
进一步想,如果var b int64 = 0不是简单的赋值,而是一块内存的分配,不幸的是,刚分配完内存就抛异常了,那么该内存就永远没有被释放的机会。
如何解决?其它语言有:try、catch、finally等关键字。
golang采用了defer关键字,该关键字用于告诉程序:“wait,wait,我做点事情之后,你再退出本次调用”。
func normal(a int64) int64 {
defer fmt.Println("wait, wait for me.")
var b int64 = 0
return a / b
}
修改normal()函数,在函数体内增加defer再运行,就会发现在抛异常之前把“wait, wait for me.”打印出来了。
这表示在函数normal()被调用之后,64/0遇到了问题,此时golang会抛出panic前,defer说等等,等我打印点东西后,你再抛。
【defer语法】:
defer 表达式
func normal(a int64) int64 {
defer func() {
fmt.Println("panic will be throwen.")
}()
var b int64 = 0
return a / b
}
当表达式是一个匿名函数时,一定要记得后面追加(),这表示是一个表达式 :)
【defer使用场景】:
defer一般使用在函数体开始,或者紧跟着申请资源的语句后面
不建议把defer放到函数体的后面。修改一下上面的示例:
func normal(a int64) {
var b int64 = 0
fmt.Println("a/b= ", a/b)
defer func() {
fmt.Println("panic will be throwen.")
}()
}
func main() {
var a int64 = 64
normal(a)
}
此时的defer已无意义,所以"panic will be throwen."不会被打印出来。
【多个顺序defer】:
被调用函数中若有多个顺序defer,则先会出现“先定义后执行”现象
func main() {
defer fmt.Println("0")
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
defer fmt.Println("4")
fmt.Println("Test multi defers")
}
执行结果为:
Test multi defers
4
3
2
1
0
想想这很自然,从堆栈来看,越是后面定义的defer越是处于堆栈的栈顶。
该代码可以精简为:
func main() {
for i := 0; i < 5; i++ {
defer fmt.Println(i)
}
fmt.Println("Test multi defers")
}
【defer表达式中存在函数调用】:
defer语句被执行的时候,传递给延迟函数的参数都会被求值,但是延迟函数调用表达式并不会在此时被求值。
感觉这句话比较绕口,不好难理解?先看一个例子:
func history(date string) string { // 打印"2016 will be history",并返回"2017"字符串
s := date + " will be history."
fmt.Println(s)
return "2017"
}
func future(date string) string { // 打印"2017 will be coming",并返回"2017 will be coming"字符串
s := date + " will be coming."
fmt.Println(s)
return s
}
func main() {
defer future(history("2016"))
fmt.Println("It‘s the Spring Festival now.")
}
对照着defer future(history("2016"))理解一下“传递给延迟函数的参数都会被求值,但是延迟函数调用表达式并不会在此时被求值”。
延迟函数:future()
延迟函数的参数:history("2016")
由于延迟函数的参数会被求值,即history("2016")会被执行,所以会先指印出“2016 will be history”,同时延迟函数变为future("2017"),它要求被延迟执行。
从而该程序执行结果为:
2016 will be history.
It‘s the Spring Festival now.
2017 will be coming.
感觉“defer语句被执行的时候,传递给延迟函数的参数都会被求值,但是延迟函数调用表达式并不会在此时被求值”这语句已经理解了,请再看下面的例子:
func main() {
for i := 0; i < 5; i++ {
defer func() {
fmt.Println(i)
}()
}
fmt.Println("Test multi defers")
}
它的运行结果为:
Test multi defers
5
5
5
5
5
是不是有点懵逼了?
对照着defer func(){
fmt.Println(i)
}()
理解一下“传递给延迟函数的参数都会被求值,但是延迟函数调用表达式并不会在此时被求值”
延迟函数表达式:
func() {
fmt.Println(i)
}()
在defer语句被执行时,该表达式并不会被求值,即被执行,i值你自己玩吧,所以等循环完成之后i值变为5,再打印出“Test multi defers”,函数马上要return时,这5个defer分别说:“wait, wait for me.”。
于是第5个defer表达式被执行,打印i值(这时i值为5),所以打印出:
5
于是第4个defer表达式被执行,打印i值(这时i值为5),所以打印出:
5
于是第3个defer表达式被执行,打印i值(这时i值为5),所以打印出:
5
于是第2个defer表达式被执行,打印i值(这时i值为5),所以打印出:
5
于是第1个defer表达式被执行,打印i值(这时i值为5),所以打印出:
5
从而出现该结果 :)
接下来咋玩?
func main() {
for i := 0; i < 5; i++ {
defer func(n int) {
fmt.Println(n)
}(i)
}
fmt.Println("Test multi defers")
}
再理解"defer语句被执行的时候,传递给延迟函数的参数都会被求值,但是延迟函数调用表达式并不会在此时被求值"一下:
当i=0时
延迟函数调用表达式:func(n int) { fmt.Println(n) }(i)
延迟函数的参数:n
延迟函数调用表达式不会被求值,但延迟函数的参数i会被求值,所以n值变为0
当i=1时
延迟函数调用表达式:func(n int) { fmt.Println(n) }(i)
延迟函数的参数:n
延迟函数调用表达式不会被求值,但延迟函数的参数i会被求值,所以n值变为1
依次类推,从而最终执行结果为:
Test multi defers
4
3
2
1
0
defer好玩吧,上面的例子有的来源自于网络上其他人的博客。
本文出自 “青客” 博客,请务必保留此出处http://qingkechina.blog.51cto.com/5552198/1895048
wait,wait for me