前言
大家都知道go语言的defer功能很强大,对于资源管理非常方便,但是如果没用好,也会有陷阱哦。Go 语言中延迟函数 defer 充当着 try...catch 的重任,使用起来也非常简便,那么defer、return、返回值、panic 之间的执行顺序是怎么样的呢,下面我们就一点一点来揭开它的神秘面纱!话不多说了,来一起看看介绍吧。
Defer介绍
defer语句用于函数在返回之前执行函数调用。这个定义可能看起来很复杂,但通过一个例子很容易理解。
package main
import (
"fmt"
)
func finished() {
fmt.Println("finished")
}
func largest() {
defer finished()
fmt.Println("largest()执行")
}
func main() {
largest()
}
也就是在func结束(return)之前执行的动作。无论定义在普通语句的前后。
defer同样支持方法的调用。
package main
import (
"fmt"
)
type person struct {
firstName string
lastName string
}
func (p person) fullName() {
fmt.Printf("%s %s",p.firstName,p.lastName)
}
func main() {
p := person {
firstName: "John",
lastName: "Smith",
}
defer p.fullName()
fmt.Printf("Welcome ")
}
PS: defer声明时会先计算确定参数的值,defer推迟执行的仅是其函数体
import (
"fmt"
)
func printA(a int) {
fmt.Println("value of a in deferred function", a)
}
func main() {
a := 5
defer printA(a)
a = 10
fmt.Println("value of a before deferred function call", a)
}
当defer被声明时, 其参数a值就会被实时解析,并不执行函数体内内容, 那么后续的修改并不会影响当前参数值。
验证结果:
package main
import (
"fmt"
"time"
)
func main() {
defer P(time.Now())
time.Sleep(5*time.Second)
fmt.Println("main ", time.Now())
}
func P(t time.Time) {
fmt.Println("defer", t)
fmt.Println("P ", time.Now())
}
执行结果:
main 2018-03-16 14:43:25.10348 +0800 CST m=+5.003171200
defer 2018-03-16 14:43:20.1033124 +0800 CST m=+0.003003600
P 2018-03-16 14:43:25.142031 +0800 CST m=+5.041722200
defer后进先出
简单的理解为先声明的defer后执行(倒序执行defer语句)。
package main
import (
"fmt"
)
func main() {
name := "Naveen"
fmt.Printf("Orignal String: %s\n", string(name))
for _, v := range []rune(name) {
defer fmt.Printf("%c", v)
}
}
输出为: neevaN
defer / return / 返回值 / panic之间的执行顺序
现在我们分两种情况分析它们的执行顺序:
1. return前将返回值赋值
2. 检查是否有defer并执行
3. 最后return 携带返回值退出函数
第一种: 匿名返回值
package main
import (
"fmt"
)
func main() {
fmt.Println("a return:", a()) // 打印结果为 a return: 0
}
func a() int {
var i int
defer func() {
i++
fmt.Println("a defer2:", i) // 打印结果为 a defer2: 2
}()
defer func() {
i++
fmt.Println("a defer1:", i) // 打印结果为 a defer1: 1
}()
return i
}
输出结果:
a defer1: 1
a defer2: 2
a return: 0
解释: 匿名返回值是在return之前被声明( 鉴于类型原因,类型零值为0 ), defer无法访问匿名的返回值,因此返回值是0, 而defer还是操作之前定义好的变量i。
试想:如果此处返回值为指针类型,那么输出结果会不会有变化?
第二种:命名返回值
package main
import (
"fmt"
)
func main() {
fmt.Println("a return:", a()) // 打印结果为 b return: 2
}
func a() (i int) {
defer func() {
i++
fmt.Println("a defer2:", i) // 打印结果为 b defer2: 2
}()
defer func() {
i++
fmt.Println("a defer1:", i) // 打印结果为 b defer1: 1
}()
return i // 或者直接 return 效果相同
}
输出结果:
a defer1: 1
a defer2: 2
a return: 2
解释: 命名返回值是 在函数声明的同时被声明。因此defer可以访问命名返回值。return返回后的值其实是defer修改后的值。
defer作用域
defer在什么环境下就不会执行?下面列举几个例子:
1. 当任意一条(主)协程发生 panic 时,会执行当前协程中 panic 之前已声明的 defer;
func main() {
fmt.Println("...")
panic(e) // defer 不会执行
defer fmt.Println("defer")
}
2. 主动调用 os.Exit(int)
退出进程时,defer 将不再被执行。
func main() {
fmt.Println("...")
os.Exit(1) // defer 不会执行
defer fmt.Println("defer")
}
func main() {
fmt.Println("...")
defer fmt.Println("defer")
os.Exit(1) // defer 不会执行
}
3.在发生 panic 的(主)协程中,如果没有一个 defer 调用 recover()
进行恢复,则会在执行完之前已声明的 defer 后,引发整个进程崩溃;
func main() {
fmt.Println("...")
defer fmt.Println("defer1")
defer fmt.Println("defer2")
defer fmt.Println("defer3")
panic("error") // defer 会执行
}
4. defer只对当前(主)协程有效
package main
import (
"fmt"
)
func main() {
fmt.Println("...")
go func() { panic("err") }() // defer 执行
defer fmt.Println("defer1")
defer fmt.Println("defer2")
defer fmt.Println("defer3")
}
defer的实际用途
无论什么业务或程序, 延迟都用在执行函数调用的地方。
举例:
后期更新