defer关键字

defer关键字可以让函数或语句延迟到函数语句块的最结尾时,即即将退出函数时执行,即便函数中途报错结束、即便已经panic()、即便函数已经return了,也都会执行defer所推迟的对象。

其实defer的本质是,当在某个函数中使用了defer关键字,则创建一个独立的defer栈帧,并将该defer语句压入栈中,同时将其使用的相关变量也拷贝到该栈帧中(显然是按值拷贝的)。因为栈是LIFO方式,所以先压栈的后执行。因为是独立的栈帧,所以即使调用者函数已经返回或报错,也一样能在它们之后进入defer栈帧去执行。

例如:

func main() {

a()

}

func a() {

println("in a")

defer b() // 将b()压入defer栈中

println("leaving a")

//到了这里才会执行b()

}

func b() {

println("in b")

println("leaving b")

}

上面将输出:

in a

leaving a

in b

leaving b

即便是函数已经报错,或函数已经return返回,defer的对象也会在函数退出前的最后一刻执行。

func a() TYPE{

...CODE...

defer b()

...CODE...

// 函数执行出了错误

return args

// 函数b()都会在这里执行

}

但注意,由于Go的作用域采用的是词法作用域,defer的定义位置决定了它推迟对象能看见的变量值,而不是推迟对象被调用时所能看见的值。

例如:

package main

var x = 10

func main() {

a()

}

func a() {

println("start a:",x) // 输出10

x = 20

defer b(x) // 压栈,并按值拷贝20到栈中

x = 30

println("leaving a:",x) // 输出30

// 调用defer延迟的对象b(),输出20

}

func b(x int) {

println("start b:",x)

}

比较下面的defer:

package main

var x = 10

func main() {

a()

}

func a() int {

println("start a:", x) // 输出10

x = 20

defer func() { // 压栈,但并未传值,所以内部引用x

println("in defer:", x) // 输出30

}()

x = 30

println("leaving a:", x) // 输出30

return x

}

上面defer推迟的匿名函数输出的值是30,它看见的不应该是20吗?先再改成下面的:

package main

var x = 10

func main() {

a()

}

func a() int {

println("start a:", x) // 输出10

x = 20

defer func(x int) {

println("in defer:", x) // 输出20

}(x)

x = 30

println("leaving a:", x) // 输出30

return x

}

这个defer推迟的对象中看见的却是20,这和第一种defer b(x)是相同的。

原因在于defer推迟的如果是函数,它直接就在它的定义位置处评估好参数、变量。该拷贝传值的拷贝传值,该指针相见的指针相见。所以,对于第(1)和第(3)种情况,在defer的定义位置处,就将x=20拷贝给了推迟的函数参数,所以函数内部操作的一直是x的副本。而第二种情况则是直接指向它所看见的x=20那个变量,则个变量是全局变量,当执行x=30的时候会将其值修改,到执行defer推迟的对象时,它指向的x的值已经是修改过的。

再看下面这个例子,将defer放进一个语句块中,并在这个语句块中新声明一个同名变量x:

func a() int {

println("start a:", x) // 输出10

x = 20

{

x := 40

defer func() {

println("in defer:", x) // 输出40

}()

}

x = 30

println("leaving a:", x) // 输出30

return x

}

上面的defer定义在语句块中,它能看见的x是语句块中x=40,它的x指向的是语句块中的x。另一方面,当语句块结束时,x=40的x会消失,但由于defer的函数中仍有x指向40这个值,所以40这个值仍被defer的函数引用着,它直到defer执行完之后才会被GC回收。所以defer的函数在执行的时候,仍然会输出40。

如果语句块内有多个defer,则defer的对象以LIFO(last in first out)的方式执行,也就是说,先定义的defer后执行。

func main() {

println("start...")

defer println("1")

defer println("2")

defer println("3")

defer println("4")

println("end...")

}

将输出:

start...

end...

4

3

2

1

defer有什么用呢?一般用来做善后操作,例如清理垃圾、释放资源,无论是否报错都执行defer对象。另一方面,defer可以让这些善后操作的语句和开始语句放在一起,无论在可读性上还是安全性上都很有改善,毕竟写完开始语句就可以直接写defer语句,永远也不会忘记关闭、善后等操作。

例如,打开文件,关闭文件的操作写在一起:

open()

defer file.Close()

... 操作文件 ...

以下是defer的一些常用场景:

打开关闭文件

锁定、释放锁

建立连接、释放连接

作为结尾输出结尾信息

清理垃圾(如临时文件)

panic()和recover()

panic()用于产生错误信息并终止当前的goroutine,一般将其看作是退出panic()所在函数以及退出调用panic()所在函数的函数。例如,G()中调用F(),F()中调用panic(),则F()退出,G()也退出。

注意,defer关键字推迟的对象是函数最后调用的,即使出现了panic也会调用defer推迟的对象。

例如,下面的代码中,main()中输出一个start main之后调用a(),它会输出start a,然后就panic了,panic()会输出panic: panic in a,然后报错,终止程序。

func main() {

println("start main")

a()

println("end main")

}

func a() {

println("start a")

panic("panic in a")

println("end a")

}

执行结果如下:

start main

start a

panic: panic in a

goroutine 1 [running]:

main.a()

E:/learning/err.go:14 +0x63

main.main()

E:/learning/err.go:8 +0x4c

exit status 2

注意上面的end a和end main都没有被输出。

可以使用recover()去捕获panic()并恢复执行。recover()用于捕捉panic()错误,并返回这个错误信息。但注意,即使recover()捕获到了panic(),但调用含有panic()函数的函数(即上面的G()函数)也会退出,所以如果recover()定义在G()中,则G()中调用F()函数之后的代码都不会执行(见下面的通用格式)。

以下是比较通用的panic()和recover()的格式:

func main() {

G()

// 下面的代码会执行

...CODE IN MAIN...

}

func G(){

defer func (){

if str := recover(); str != nil {

fmt.Println(str)

}

}()

...CODE IN G()...

// F()的调用必须在defer关键字之后

F()

// 该函数内下面的代码不会执行

...CODE IN G()...

}

func F() {

...CODE1...

panic("error found")

// 下面的代码不会执行

...CODE IN F()...

}

可以使用recover()去捕获panic()并恢复执行。但以下代码是错误的:

func main() {

println("start main")

a()

println("end main")

}

func a() {

println("start a")

panic("panic in a")

// 直接放在panic后是错误的

panic_str := recover()

println(panic_str)

println("end a")

}

之所以错误,是因为panic()一出现就直接退出函数a()和main()了。要想recover()真正捕获panic(),需要将recover()放在defer的推迟对象中,且defer的定义必须在panic()发生之前。

例如,下面是通用格式的示例:

package main

import "fmt"

func main() {

println("start main")

b()

println("end main")

}

func a() {

println("start a")

panic("panic in a")

println("end a")

}

func b() {

println("start b")

defer func() {

if str := recover(); str != nil {

fmt.Println(str)

}

}()

a()

println("end b")

}

以下是输出结果:

start main

start b

start a

panic in a

end main

注意上面的end b、end a都没有被输出,但是end main输出了。

panic()是内置的函数(在包builtin中),在log包中也有一个Panic()函数,它调用Print()输出信息后,再调用panic()。go doc log Panic一看便知:

$ go doc log Panic

func Panic(v ...interface{})

Panic is equivalent to Print() followed by a call to panic().

go mysql recover_Go基础系列:defer、panic和recover相关推荐

  1. Mysql数据库基础系列(二):表结构、键值

    表结构 约束条件 查看约束条件 mysql> desc 库名.表名; 字段名----| 类型---------------| 空------ |键值-- |默认值---- |额外设置 | 设置约 ...

  2. mysql 开发基础系列22 SQL Model(带迁移事项)

    一.概述 与其它数据库不同,mysql 可以运行不同的sql model 下, sql model 定义了mysql应用支持的sql语法,数据校验等,这样更容易在不同的环境中使用mysql. sql ...

  3. mysql 开发基础系列18 存储过程和函数(下)

    1. 光标的使用(游标) 在存储过程和函数中可以使用光标对结果集进行循环的处理,光标使用包括光标的声明,open ,fetch,close. 下面在存储过程中使用一个光标, 这个举例中光标里的逻辑不重 ...

  4. mysql 开发基础系列19 触发器

    触发器是与表有关的数据库对象,触发器只能是针对创建的永久表,而不能是临时表. 1.1 创建触发器 -- 语法: CREATE TRIGGER trigger_name trigger_time tri ...

  5. Mysql数据库基础第八章:窗口函数和公用表表达式(CTE)

    Mysql数据库基础系列 软件下载地址 提取码:7v7u 数据下载地址 提取码:e6p9 mysql数据库基础第一章:(一)数据库基本概念 mysql数据库基础第一章:(二)mysql环境搭建 mys ...

  6. panic和recover的使用规则

    转自:https://www.cnblogs.com/vikings-blog/p/7109519.html 转自个人博客 chinazt.cc 在上一节中,我们介绍了defer的使用. 这一节中,我 ...

  7. MYSQL高效基础学习系列1

    MYSQL高效基础学习系列1 目录 数据库基本概念 操作库,表和记录 数据类型 为字段指定的约束条件 目录 数据库基本概念 1.数据库服务器:运行数据库管理软件的计算机 2.数据库管理软件:mysql ...

  8. mysql bdb版本_深入理解mysql之BDB系列(1)---BDB相关基础知识

    深入理解mysql之BDB系列(1) ---BDB相关基础知识 作者:杨万富 一:BDB体系结构 1.1.BDB体系结构 BDB总体的体系结构如图1.1所看到的,包括五个子系统(见图1.1中相关数). ...

  9. 深入理解mysql之BDB系列(1)---BDB相关基础知识

        深入理解mysql之BDB系列(1) ---BDB相关基础知识 作者:杨万富 一:BDB体系结构 1.1.BDB体系结构 BDB整体的体系结构如图1.1所示,包含五个子系统(见图1.1中相关数 ...

  10. mysql bdb_深入了解mysql它BDB系列(1)---BDB基础知识

    深入了解mysql它BDB系列(1) ---BDB关基础知识 作者:杨万富 一:BDB体系结构 1.1.BDB体系结构 BDB总体的体系结构如图1.1所看到的,包括五个子系统(见图1.1中相关数).1 ...

最新文章

  1. 亚马逊科学家Nikko Ström:将人工智能助理日常化的梦想照进现实
  2. python表单防重复提交_防止表单重复提交的几种策略
  3. 哲学家就餐 java_java模拟哲学家就餐问题
  4. adobe aem_AEM中的单元测试(大声思考)
  5. 解决Office 2016客户端如何同SharePoint Server2016安装在一起
  6. 修改shell提示符的显示格式
  7. 性能是.NET Core的一个关键特性
  8. Python+socket实现TCP套接字服务端自由限速
  9. JS 获取指定URL的时间
  10. Webpack4 学习笔记六 多页面配置和devtool
  11. 2022年Python最新面试题汇总及答案
  12. Oracle数据库模糊查询
  13. 【进阶】python写一个小猫
  14. 微信小程序开发者代码管理中,不想要的项目怎么删除
  15. Tableau 中国最美八条骑行线路(一)线路地图
  16. 《崩坏3》评测:游戏设计中整体性和利用率分析(下)
  17. 数学分析 - 定积分(待修改)
  18. swiper跳转指定slides,其他路由跳转swiper指定slides
  19. 重磅榜单 | 国内云服务企业估值50强,【友盟+】位列其中。
  20. hmm念什么_HMM是什么意思

热门文章

  1. hread.interrupt()到底意味着什么
  2. varchar,char,varchar2,mybatis查询无返回
  3. 容量规划的一些探讨与实践
  4. Entity Framework之问题收集
  5. 收集WebDriver的执行命令和参数信息
  6. 实现全排列的另一种方法(续)
  7. Delphi 中的MD5实现方法及delphi2009和delphi2010中用法
  8. Android2.1--如何在android模拟器上安装与删除.APK文件
  9. Silk codec的一些资料
  10. 【深度语义匹配模型 】原理篇一:表示型