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的示例分析。希望以上内容能够对大家有所帮助,可以学到更多的知识。如果您觉得文章不错,可以分享出去,让更多的人看到。
2. 本站积分货币获取途径以及用途的解读,想在本站混的好,请务必认真阅读!
3. 本站强烈打击盗版/破解等有损他人权益和违法作为,请各位会员支持正版!
4. 编程技术 > defer关键字、panic和recover实例分析