大家好,我是二条,是一位从事后端开发的程序员。
上一篇,我们讲到了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")
}
上述代码履行的成果是:
1
2
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的定于次序履行。