首页 > 代码库 > 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