大家好,我是二条,是一位从事后端开发的程序员。
上一篇,咱们讲到了Go中的字符串为什么不能被批改,这一篇来总结defer语句中的几个暗藏的细节。
对于Go中的defer,是做什么的?执行程序是怎么样的?置信学过Go语言的同学,曾经不在生疏,明天就来讲讲其中须要把握的几个知识点。
要讲到这几个知识点,还是大抵总结一下defer这个内置关键字
。
1、defer是一种提早解决机制,是在函数进行return之前进行执行。
2、defer是采纳栈的形式执行,也就是说先定义的defer后执行,后定义的defer最先被执行。
正因为defer具备这种机制,能够用在函数返回之前,敞开一些资源。例如在某些操作中,连贯了MySQL、Redis这样的服务,在函数返回之前,就能够应用defer语句对连贯进行敞开。就相似oop语言中的 finally
操作一样,不论产生任何异样,最终都会被执行。
其语法格局也十分的简略。
package main
import "fmt"
func main() {
function1()
}
func function1() {
fmt.Printf("1")
defer function2()
fmt.Printf("2")
}
func function2() {
fmt.Printf("3")
}
上述代码执行的后果是:
2
1
3
上面就来总结这六个小知识点:
1、defer 的执行程序。 采纳栈的形式执行,先定义后执行。
2、defer 与 return 谁先谁后。return 之后的语句先执行,defer 后的语句后执行。
3、函数的返回值初始化与 defer 间接影响。defer中批改了返回值,理论返回的值是依照defer批改后的值进行返回。
4、defer 遇见 panic。依照defer的栈程序,输入panic触发之前定义好的defer。
5、defer 中蕴含 panic。依照defer的栈程序,输入panic触发之前的defer。并且defer中会接管到panic信息。
6、defer 下的函数参数蕴含子函数。会先进行子函数的后果值,而后在依照栈的程序进行输入。
defer的执行程序是什么样的
对于这个问题,后面的示例代码也提到过了,采纳栈的程序执行。在定义时,压入栈中,执行是从栈中获取。
defer与return谁先谁后
先来看如下一段代码,最终的执行后果是怎么样的。
func main() {
fmt.Println(demo2())
}
func demo2() int {
defer func() {
fmt.Println("2")
}()
return func() int {
fmt.Println("1")
return 4
}()
}
运行上述代码,失去的后果是:
1
2
4
可能你会有一个疑难❓ ,既然都提到了defer是在函数返回之前执行,为什么还是先输入1,而后在输入2呢?对于defer的定义,就是在函数返回之前执行
。这一点毋庸置疑,必定是在return之前执行。须要留神的是,return 是非原子性的,须要两步,执行前首先要失去返回值 (为返回值赋值),return 将返回值返回调用处。defer 和 return 的执行程序是先为返回值赋值,而后执行 defer,而后 return 到函数调用处。
函数的返回值初始化与defer间接影响
同样的形式,咱们先看一段代码,猜想一下最终的执行后果是什么。
func main() {
fmt.Println(demo3())
}
func demo3() (a int) {
defer func() {
a = 3
}()
return 1
}
上诉代码,最终的运行后果如下:
3
跟上第2个知识点相似,函数在return之前,会进行返回值赋值,而后在执行defer语句,最终在返回后果值。
1、在定义函数demo3()时,为函数设置了一个int类型的变量a,此时int类型初始化值默认是0。
2、定义一个defer语句,在函数return之前执行,匿名函数中对返回变量a进行了一次赋值,设置 a=3。
3、此时执行return语句,因为return语句是执行两步操作,先为返回变量a执行一次赋值操作,将a设置为3。紧接着执行defer语句,此时defer又将a设置为3。
4、最终return进行返回,因为第3步的defer对a进行了从新赋值。因而a就变成了3。
5、最初main函数打印后果,打印的其实是defer批改之后的值。
如果将变量a的申明放回到函数外部申明呢,其运行的后果会依据return的值进行返回。
func main() {
fmt.Println(demo7())
}
func demo7() int {
var a int
defer func(a int) {
a = 10
}(a)
return 2
}
上述的最终后果返回值如下:
10
2
为什么会产生两种不同的后果呢?这是因为,这是因为产生了值拷贝景象。在执行defer语句时,将参数a传递给匿名函数时进行了一个值拷贝的过程。因为值拷贝是不会影响原值,因而匿名函数对变量a进行了批改,不会影响函数内部的值。当然传递一个指针的话,后果就不一样了。在函数定义时,申明的变量能够了解为一个全局变量,因而defer或者return对变量a进行了批改,都会影响到该变量上。
defer遇见panic。
panic是Go语言中的一种异常现象,它会中断程序的执行,并抛出具体的异样信息。既然会中断程序的执行,如果一段代码中产生了panic,最终还会调用defer语句吗?
func main() {
demo4()
}
func demo4() {
defer func() {
fmt.Println("1")
}()
defer func() {
fmt.Println("2")
}()
panic("panic")
defer func() {
fmt.Println("3")
}()
defer func() {
fmt.Println("4")
}()
}
运行上述代码,最终失去的后果如下:
╰─ go run defer.go
2
1
panic: panic
goroutine 1 [running]:
main.demo4()
从下面的后果不难看出,尽管产生了panic异样信息,还是输入了defer语句中的信息,这阐明panic的产生,还是会执行defer操作。那为什么前面的两个defer没有被执行呢。这是因为pani的产生,会中断程序的执行,因而后续的代码基本没有拿到执行权。
当函数中产生了panic异样,会马上停止以后函数的执行,panic之前定义的defer都会被执行
,所有的 defer 语句都会保障执行并把控制权交还给接管到 panic 的函数调用者。这样向上冒泡直到最顶层,并执行(每层的) defer,在栈顶处程序解体,并在命令行中用传给 panic 的值报告谬误状况:这个终止过程就是 panicking。
defer中蕴含panic
上一个知识点提到了,程序中尽管产生了panic,然而在panic之前定义的defer语句,还是会被执行。要想在defer中获取到具体的panic信息,须要应用 recover()
进行获取。
func main() {
demo5()
}
func demo5() {
defer func() {
fmt.Println("1")
if err := recover(); err != nil {
fmt.Println(err)
}
}()
defer func() { fmt.Println("2") }()
panic("panic")
defer func() { fmt.Println("defer: panic 之后, 永远执行不到") }()
}
上述代码执行的后果如下:
2
1
panic
这个(recover)内建函数被用于从 panic 或 谬误场景中复原:让程序能够从 panicking 从新取得控制权,进行终止过程进而恢复正常执行。
defer下的函数参数蕴含子函数
对于这种场景,可能大家很少遇见,也不是很分明理论的调用逻辑。先来看一段代码。
func main() {
demo6()
}
func function(index int, value int) int {
fmt.Println(index)
return index
}
func demo6() {
defer function(1, function(3, 0))
defer function(2, function(4, 0))
}
上诉代码最终执行的后果是:
3
4
2
1
其执行的逻辑是:
1、执行第1个defer时,压入defer栈中,该defer会执行一个function的函数,在函数返回之前执行。
2、因为该函数中又蕴含了一个函数(子函数),Go语言解决的机制是,先执行该子函数。
3、执行完子函数,接着再执行第2个defer语句。此时,第2个defer中也有一个子函数,依照第2点的逻辑,这个子函数会被间接执行。
4、定义完defer语句之后,此时完结该函数的调用。所有被定义的defer语句,依照栈程序进行输入。
因而能够得出的论断是,当defer中存在子函数时,子函数会依照defer定义的语句程序,优先执行。defer最外层的逻辑,则依照栈的程序执行。。
总结
对于defer的应用,是非常简单的。这里须要留神几点。
1、defer是在函数返回之前执行,defer的执行程序是优先于return。return的执行是一个两步操作,先对return返回的值进行赋值,而后执行defer语句,最初将后果进行返回给函数的调用者。
2、即便函数内产生了panic异样,panic之前定义的defer依然会被执行。
3、defer中存在子函数,子函数会依照defer的定于程序执行。
发表回复