本文为译文,希望在大家学习的过程中能带给大家帮助😘
原文地址 Golang's "defer" Explained (With Examples)
If you just want to see the code, you can find the complete examples on Github
基础用法
defer关键字指示一个函数在其包裹函数执行完成之后再执行。
package main
import "fmt"
func main() {
// When we add `defer` before a function, that function is executed
// after the surrounding function completes
defer fmt.Println("this is printed once the function completes")
fmt.Println("this is printed first")
fmt.Println("this is printed second")
}
输出:
// this is printed first
// this is printed second
// this is printed once the function completes
函数的作用域范围
我们需要注意:defer只是作用于被包裹的函数内。我们看如下的例子
package main
import "fmt"
func main() {
defer fmt.Println("this is printed once main completes")
greet()
fmt.Println("this is printed after greet is called")
}
func greet() {
// 当greet函数执行完成后,这个函数会立刻执行
defer fmt.Println("printed after the first greeting")
fmt.Println("first greeting")
}
输出如下:
// first greeting
// printed after the first greeting
// this is printed after greet is called
// this is printed once main completes
执行序列(调用栈)
当我们的函数中存在多个deferred,它们将会被存贮然后以堆栈的顺序执行。即先进后出。
func myFunction() {
defer fmt.Println("first")
defer fmt.Println("second")
defer fmt.Println("third")
fmt.Println("starting myFunction...")
// ...
}
输出
starting myFunction...
third
second
first
每次,每个函数被推迟后,他就会被塞入一个栈中。一旦包裹函数执行完成,被推迟的函数就会被弹出。
使用带有panic的defer
在go中如果一个函数发生错误后就会产生panic,这种情况下函数会立刻退出,其后边所有的语句都不会被执行。
假如defer的函数在panic发生之前,那么defer的函数仍会在函数退出之前被调用。
package main
import "fmt"
func main() {
defer fmt.Println("this is printed once the function panics")
fmt.Println("this is printed before panic")
panic("something went wrong")
fmt.Println("this should not be printed")
}
// this is printed before panic
// this is printed once the function panics
// panic: something went wrong
// goroutine 1 [running]:
// main.main()
// /Users/soham/src/github.com/sohamkamani/go-defer-example/defer-with-panic.go:11 +0xe4
// exit status 2
实际上,我们可以使用内置的recover函数来判断是否发生了panic.
这个只能在derfer函数内完成,我们需要明确的说明,当panic发生的时候需要做些什么
package main
import "fmt"
func main() {
defer func() {
// we can use the recover function inside the deferred function
// to tell if it was called after `panic` was called
if r := recover(); r != nil {
fmt.Println("recovered from panic: ", r)
}
}()
panic("something went wrong")
fmt.Println("this should not be printed")
}
// this is printed before panic
// recovered from panic: something went wrong
argument evaluation
一般包裹函数执行完成后会执行derfer的函数,但是defer函数的参数会立刻执行。 defer函数的传入参数在定义时就已经明确,不论传入的参数是变量、表达式、函数语句,都会先计算出计算出实参结果,再随defer语句入栈
package main
import "fmt"
// i is a package level variable
var i int
// here, we increment the value of i every time this
// function is called
func count() int {
i++
return i
}
func main() {
// `count` is called here first
defer fmt.Println("deferred count:", count())
fmt.Println("current count:", count())
}
current count: 2
deferred count: 1
package main
import "fmt"
var i int
func count() int {
i++
return i
}
func main() {
defer printCount()
fmt.Println("current count:", count())
}
// Now, count is evaluated inside `printCount`, so it will
// only be called when the deferred function executes
func printCount() {
fmt.Println("count:", count())
}
current count: 1
count: 2
Do a test.what would be the output of this code.
func main() {
fmt.Println(zeroOrOne())
}
func zeroOrOne() (i int) {
defer func() {
i++
}()
return i
}
1
在函数返回之前,推迟的函数会被执行。
上面的例子中,包裹函数zeroOrone执行完成后,变量i会被增加。
何时该用defer
defer比较像java python中的finally块。在尝试运行一些无论成功或者失败的代码后,代码块都会被执行。
当你在代码执行完成后需要做一些清洗的工作的时候,这个能力就比较重要了。例如
- 在一个文件打开后然后关闭它。
- 在运行一个test后然后reset它。
额外的,你可以处理任何panic在其发生之后。
defer主要我们应该注意的问题在于argument evaluation&order of execution
你之前有使用过defer么,面临了哪些问题,可以在留言区告诉我。我们可以一块探索讨论。