defer关键字、panic和recover的示例分析

技术defer关键字、panic和recover的示例分析这篇文章给大家介绍defer关键字、panic和recover的示例分析,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。defer关键字def

本文介绍了延迟关键字、恐慌和恢复的实例分析。内容很详细,有兴趣的朋友可以参考一下,希望对你有帮助。

defer关键字

delay关键字可以将函数或语句延迟到函数语句块的末尾,也就是说,它将在函数即将退出时执行。即使函数在中间以错误结束,即使它已经死机(),即使函数已经返回,延迟的对象也将被执行。

实际上,delay的本质是在函数中使用delay关键字时,会创建一个独立的delay堆栈框架,并将delay语句推入堆栈,同时将它使用的相关变量复制到堆栈框架中(显然是通过值复制的)。因为堆栈是后进先出模式,所以先按堆栈,然后再执行。因为它是一个独立的堆栈框架,所以即使调用方函数返回或报告了错误,它也可以进入延迟堆栈框架,在它们之后执行。

例如:

func main(){ a()} func a(){ println(' in a ')延迟b()//将b()推入延迟堆栈。println('离开a') //B () {println('在B ')在此之前不会执行println。

以上将输出:

在a区在b区

即使函数报告了错误或者函数已经返回,延迟的对象也将在函数退出之前的最后时刻执行。

func a()类型.密码.辩护人(b).密码.//函数执行了一个错误。返回参数//函数b()将在此处执行}

但是请注意,因为Go的作用域是词法作用域,所以defer的定义位置决定了它在调用defer对象时可以看到的变量值,而不是它在调用defer对象时可以看到的值。

例如:

package var x=10 func main(){ a()} func a(){ println(' start a: ',x)//Output 10 x=20 delay b(x)//堆叠,并按值将20复制到堆栈,x=30 println('离开a : ',x)//output 30 //object b()(调用defer delay,Output 20 } funcb(x int){ println(' start b : ',x)}

比较以下延迟:

package var x=10 func main(){ a()} func a()int { println(' start a : ',x)//Output 10 x=20 defer func() {//堆叠,但未传递任何值。因此,内部参考xprintln ('inderfer3360 ',x)//输出30} () x=30println('离开a: ',x)//输出30 return x}

上面延迟的匿名函数的输出值是30。不应该是20吗?然后将其更改为以下内容:

package var X=10 func main(){ a()} func a()int { println(' start a : ',X)//output 10x=20 defer func(X int){ println(' indeder 3360 ',X)//output 20 }(X)X=30 println(' exit a : ',x)//output 30 return x}

您在这个延迟对象中看到的是20,与第一个延迟的b (x)相同。

原因在于defer推迟的如果是函数,它直接就在它的定义位置处评估好参数、变量。该拷贝传值的拷贝传值,该指针相见的指针相见.因此,对于情况(1)和(3),在defer的定义位置,x=20被复制到defer函数参数中,所以函数的内部操作总是x的副本,在第二种情况下,它直接指向它看到的变量x=20,然后变量就是全局变量。当x=30被执行时,它的值将被修改。当延迟的对象被执行时,它所指向的x的值已经被修改。

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

func a()int { println(' start a : ',x)//Output 10x=20 { x 3360=40 deferfunc(){ println(' indefer 3360 ',x)//Output 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 mainstart apanic: panic in agoroutine 1 [running]:main.a()        E:/learning/err.go:14 +0x63main.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 apanic 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().

关于defer关键字、panic和recover的示例分析就分享到这里了,希望

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/126954.html

(0)

相关推荐

  • oracle插入clob字段出现问题怎么办

    技术oracle插入clob字段出现问题怎么办这篇文章将为大家详细讲解有关oracle插入clob字段出现问题怎么办,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。1.用insert语句

    攻略 2021年11月12日
  • 四时田园杂兴题目意思,四时田园杂兴的全部意思

    技术四时田园杂兴题目意思,四时田园杂兴的全部意思四时田园杂兴古诗的意思是:一树树梅子变得金黄,杏子也越长越大了;荞麦花一片雪白,油菜花倒显得稀稀落落四时田园杂兴题目意思。白天长了,篱笆的影子随着太阳的升高变得越来越短,没

    生活 2021年10月23日
  • 怎么打出拼音声调,怎么打出汉语拼音声调符号

    技术怎么打出拼音声调,怎么打出汉语拼音声调符号打开WORD后怎么打出拼音声调,选择“插入→符号”,在符号→子集”选项里选择“进格的修饰字符”,里面就有汉语的声调符号.如图所示. 拼音声调
    拼音声调是指普通话中的声调,通

    生活 2021年10月21日
  • ibatisresultclass有哪几种类型(ibatis result查询空值怎么处理)

    技术如何进行iBATIS ResultMap基础的浅析本篇文章为大家展示了如何进行iBATIS ResultMap基础的浅析,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。iBAT

    攻略 2021年12月19日
  • C++怎么定义析构函数

    技术C++怎么定义析构函数这篇文章主要讲解了“C++怎么定义析构函数”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++怎么定义析构函数”吧!如果一个类需要明确的销毁动作

    攻略 2021年11月29日
  • java中怎么去掉List集合中重复的元素

    技术java中怎么去掉List集合中重复的元素本篇内容介绍了“java中怎么去掉List集合中重复的元素”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希

    攻略 2021年10月29日