【go语言】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好玩吧,上面的例子有的来源自于网络上其他人的博客。





     本文转自qingkechina 51CTO博客,原文链接:http://blog.51cto.com/qingkechina/1895048,如需转载请自行联系原作者


相关文章
|
17天前
|
Go
go语言中的数据类型
go语言中的数据类型
13 0
|
22天前
|
Go 开发者
掌握Go语言:Go语言结构体,精准封装数据,高效管理实体对象(22)
掌握Go语言:Go语言结构体,精准封装数据,高效管理实体对象(22)
|
22天前
|
安全 Go
掌握Go语言:Go语言通道,并发编程的利器与应用实例(20)
掌握Go语言:Go语言通道,并发编程的利器与应用实例(20)
|
22天前
|
存储 缓存 安全
掌握Go语言:Go语言中的字典魔法,高效数据检索与应用实例解析(18)
掌握Go语言:Go语言中的字典魔法,高效数据检索与应用实例解析(18)
|
4天前
|
数据采集 存储 Go
使用Go语言和chromedp库下载Instagram图片:简易指南
Go语言爬虫示例使用chromedp库下载Instagram图片,关键步骤包括设置代理IP、创建带代理的浏览器上下文及执行任务,如导航至用户页面、截图并存储图片。代码中新增`analyzeAndStoreImage`函数对图片进行分析和分类后存储。注意Instagram的反爬策略可能需要代码适时调整。
使用Go语言和chromedp库下载Instagram图片:简易指南
|
22天前
|
存储 安全 Go
掌握Go语言:Go语言类型转换,无缝处理数据类型、接口和自定义类型的转换细节解析(29)
掌握Go语言:Go语言类型转换,无缝处理数据类型、接口和自定义类型的转换细节解析(29)
|
23小时前
|
人工智能 Go 调度
掌握Go并发:Go语言并发编程深度解析
掌握Go并发:Go语言并发编程深度解析
|
23小时前
|
SQL 关系型数据库 MySQL
Golang数据库编程详解 | 深入浅出Go语言原生数据库编程
Golang数据库编程详解 | 深入浅出Go语言原生数据库编程
|
1天前
|
Go 开发者
Golang深入浅出之-Go语言流程控制:if、switch、for循环详解
【4月更文挑战第21天】本文介绍了Go语言中的流程控制语句,包括`if`、`switch`和`for`循环。`if`语句支持简洁的语法和初始化语句,但需注意比较运算符的使用。`switch`语句提供多分支匹配,可省略`break`,同时支持不带表达式的形式。`for`循环有多种形式,如基本循环和`for-range`遍历,遍历时修改原集合可能导致未定义行为。理解并避免易错点能提高代码质量和稳定性。通过实践代码示例,可以更好地掌握Go语言的流程控制。
10 3
Golang深入浅出之-Go语言流程控制:if、switch、for循环详解
|
1天前
|
Go
Golang深入浅出之-Go语言函数基础:定义、调用与多返回值
【4月更文挑战第21天】Go语言函数是代码组织的基本单元,用于封装可重用逻辑。本文介绍了函数定义(包括基本形式、命名、参数列表和多返回值)、调用以及匿名函数与闭包。在函数定义时,注意参数命名和注释,避免参数顺序混淆。在调用时,要检查并处理多返回值中的错误。理解闭包原理,小心处理外部变量引用,以提升代码质量和可维护性。通过实践和示例,能更好地掌握Go语言函数。
14 1
Golang深入浅出之-Go语言函数基础:定义、调用与多返回值