defer关键字、panic和recover实例分析

分类:编程技术 时间:2024-02-20 16:10 浏览:0 评论:0
0
本文向您介绍defer关键字、panic和recover的实例分析。内容非常详细。有兴趣的朋友可以参考一下。希望对大家有所帮助。

defer 关键字

defer 关键字允许将函数或语句延迟到函数语句块的末尾,即函数执行时即将退出,即使函数中途出错结束,即使有panic(),或者即使函数已经返回,被defer推迟的对象仍然会被执行。

其实defer的本质是,当函数中使用defer关键字时,会创建一个独立的defer栈帧,并将defer语句压入栈中,同时将defer语句压入栈中。相关变量也被复制到堆栈帧中(显然是按值)。因为栈是LIFO模式,所以先压栈,后执行。因为它是一个独立的栈帧,即使调用者函数返回或报错后,仍然可以进入 defer 栈帧执行。

例如:

func main() { a()} func a() { println("in a") defer b() // 将 b() 推入 defer println("leave a") 入栈 //b() 到这里才会被执行 } func b() { println("in b") println("leave b")}

上面会输出:

in aleave a in bleaving b

即使函数报错或者函数返回,defer对象也会在之前的最后一刻被执行函数退出。

func a() TYPE{ ...CODE... defer b() ...CODE... // 函数执行过程中发生错误 return args // 函数 b() 将被执行这里}

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

例如:

package main var x = 10 func main() { a()}func a() { println("start a:",x) // 输出 10 x = 20 defer b(x) // 压入堆栈,并按值将 20 复制到堆栈 x = 30 println("leave 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("leave 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("leave a:", x) // 输出 30 return x}

在这个defer对象中看到的是20,是一样的作为第一个延迟 b(x)。

原因是,如果 defer 是一个函数,它将直接在其定义位置计算参数和变量。按值复制按值复制,逐指针满足。因此,对于情况(1)和(3),x=20被复制到defer的定义位置处的延迟函数参数中,因此函数的内部操作始终是x的副本。第二种情况,它直接指向它看到的变量x=20。该变量是全局变量。当执行x=30时,其值将被修改。当延迟对象执行时,它指向的变量 x 的值已经被修改。

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

func a() int { println ( "start a:", x) // 输出 10 x = 20 { 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(后进先出)的方式执行,即,首先执行定义的 defer。

func main() { println("开始...") defer println("1") defer println("2") defer println("3") defer println("4") println ("end...")}

会输出:

start...end...4 3 2 1

有什么用推迟? ?一般用于善后操作,比如清理垃圾、释放资源、无论是否报错都执行defer对象等。在另一另一方面,defer 允许将这些后续操作与 start 语句放在一起,这大大提高了可读性和安全性。毕竟写完start语句就可以直接写defer.statement,并且永远不会忘记关闭、清理等操作。

比如打开文件和关闭文件的操作写在一起:

open() defer file.Close() ... 操作文件...

open() defer file.Close() ... 操作文件...

p>

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

打开和关闭文件锁、释放锁建立连接、释放连接结束、输出结束信息、清理垃圾(如临时文件)

panic()和recover()

panic()用于生成错误消息并终止当前goroutine。一般认为是退出panic()所在的函数,退出调用panic()所在函数的函数。例如,如果在 G() 中调用 F() 并且panic()是在F()中调用,那么F()将退出,G()也将退出。

注意,defer关键字延迟的对象是该函数的最后一次调用。即使发生恐慌,延迟对象也会被调用。

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

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 /呃。 go:14 +0x63main.main() E:/learning/err.go:8 +0x4c exit status 2

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

您可以使用recover()捕获panic()并恢复执行。 receive()用于捕获panic()错误,并返回该错误信息。但请注意,即使如果recover()捕获了panic(),则调用包含panic()函数的函数(即上面的G()函数)将会退出,所以如果recover()定义在G()中,那么G()将不会被执行(请参阅下面的一般格式)。

以下是panic()和recover()比较常见的格式:

func main() { G() // 下面的代码将执行...CODE IN MAIN ...} func G(){ defer func (){ if str := recovery(); str != nil { fmt.Println(str) } }() ...CODE IN G()... // F() 的调用必须在 defer 关键字之后 F() // 以下代码在此function 将不会被执行...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的定义必须在ic()发生之前的pan中。

例如,下面是一般格式的示例:

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() {如果 str := 恢复(); 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() 是一个内置函数(在内置包中)。 log包中还有一个Panic()函数。它调用Print()输出信息,然后调用panic()。 Go doc log Panic 一看就明白了:

$ go doc log Panic func Panic(v ...interface{}) Panic 相当于 Print() 后面调用了 panic()。

这里分享defer关键字、panic和recover的示例分析。希望以上内容能够对大家有所帮助,可以学到更多的知识。如果您觉得文章不错,可以分享出去,让更多的人看到。

1. 本站所有资源来源于用户上传或网络,仅作为参考研究使用,如有侵权请邮件联系站长!
2. 本站积分货币获取途径以及用途的解读,想在本站混的好,请务必认真阅读!
3. 本站强烈打击盗版/破解等有损他人权益和违法作为,请各位会员支持正版!
4. 编程技术 > defer关键字、panic和recover实例分析

用户评论