若是一个panic是无心间引起的,其中的值只能由Go语言运行时系统给定,可是当使用painc函数有意引起一个panic时,却能够自行指定其包含的值。函数
举个栗子spa
package main func main() { s1 := []int{0, 1, 2, 3, 4} e5 := s1[5] _ = e5 }
运行上面的代码,会抛出panic3d
panic: runtime error: index out of range goroutine 1 [running]: //Id为1的goroutine在此panic被引起时正在运行 main.main() /Users/haolin/GeekTime/Golang_Puzzlers/src/puzzlers/article19/q0/demo47.go:5 +0x3d //此行代码在其所属源码文件中的行数,以及源码文件的绝对路径, +03d是计数偏移量,用处不大。 exit status 2 //以退出状态码2结束运行,通常状态不为0时表示程序非正常退出
某个函数中的某行代码引起了一个panic后,初始的panic详情会被创建起来,而且该程序的控制器会当即今后行代码转移到调用其所属函数的那行代码上(调用栈中的上一级),此行代码所属函数的执行随即终止。紧接着,控制权并不会在此有片刻停留,它又会当即转移至上一级的调用代码处,反方向传播直至最外层函数(go函数,对于主goroutine来讲就是main函数)。可是控制器也不会停留在那里,而是被Go语言运行时系统收回。随后程序奔溃并终止运行,承载程序此次运行的进程也会随之死亡并消失。与此同时,在这个控制器传播过程当中,panic详情会积累和完善,并在程序终止以前打印出来。code
//main函数调用了caller1函数,caller1函数调用了caller2函数 goroutine 1 [running]: main.caller2() /Users/haolin/GeekTime/Golang_Puzzlers/src/puzzlers/article19/q1/demo48.go:22 +0x91 main.caller1() /Users/haolin/GeekTime/Golang_Puzzlers/src/puzzlers/article19/q1/demo48.go:15 +0x66 main.main() /Users/haolin/GeekTime/Golang_Puzzlers/src/puzzlers/article19/q1/demo48.go:9 +0x66 exit status 2
在调用panic函数,把某个值做为参数传给该函数blog
因为panic函数的惟一一个参数时空接口类型的,因此从语法上讲,它能够接受任何类型的值。但最好传入errir类型的错误值,或者其余能够被有效序列化(能够更易读地去表示形式转换)的值。使程序崩溃时,panic包含的那个值字符串表示形式会被打印出来接口
Go语言的内建函数recover专用于恢复panic。recover函数无需任何参数,而且会返回一个空接口类型的值。若是用法正确,这个值实际上就是即将恢复的panic包含的值,而且若是这个panic是因咱们调用panic函数引起的,那么该值同时也会是这次调用panic函数时,传入的参数值副本。队列
上面强调用法正确,那什么是不正确的用法?进程
package main import ( "fmt" "errors" ) func main() { fmt.Println("Enter function main.") // 引起 panic。 panic(errors.New("something wrong")) p := recover() fmt.Printf("panic: %s\n", p) fmt.Println("Exit function main.") }
在上面这个函数中先经过调用panic函数引起一个panic,紧接着想经过调用recover函数恢复这个panic。但这个recover函数不会起到任何做用,由于panic一旦发生,控制权会沿着调用栈反方向传播,因此在panic函数调用以后的代码,根本没有执行的机会。字符串
那若是把recover函数的代码提早呢?即先调用recover函数,再调用panic函数。这样也不行,由于在调用recover函数时未发生panic,那么该函数就不会作任何事情,只会返回一个nil。源码
那怎样才是正确的作法呢?
这就要用到defer语句。defer语句是被用来延迟执行代码的。延迟到该语句所在的函数即将执行结束的那一刻,不管结束执行的缘由是什么,即便致使它执行结束的缘由是一个panic,所以联用defer语句和recover函数调用,可以恢复一个已经发生的panic
package main import ( "fmt" "errors" ) func main() { fmt.Println("Enter function main.") defer func(){ fmt.Println("Enter defer function.") if p := recover(); p != nil { fmt.Printf("panic: %s\n", p) } fmt.Println("Exit defer function.") }() // 引起 panic。 panic(errors.New("something wrong")) fmt.Println("Exit function main.") }
尽可能把defer语句写在函数体开始处,由于在引起panic的语句以后的全部语句,都不会有任何执行机会。只有这样defer函数中的recover函数调用才会拦截,并恢复defer语句所属的函数,及其调用代码中发生的全部panic
若是一个函数中有多条defer语句,那么那几个defer函数调用的执行顺序是怎样的?
在同一个函数中,defer函数调用的执行顺序和它们所属的defer语句的出现顺序彻底相反。当一个函数即将结束执行时,写在最下面的defer函数调用会最早执行,其次是写在它上边的与它距离最近的defer函数调用,以此类推
package main import "fmt" func main() { defer fmt.Println("first defer") for i := 0; i < 3; i++ { defer fmt.Printf("defer in for [%d]\n", i) } defer fmt.Println("last defer") } //运行结果 last defer defer in for [2] defer in for [1] defer in for [0] first defer
若是for语句中包含一条defer语句,那这条defer语句执行次数,就取决于for语句迭代次数。而且同一条defer语句每被执行一次,其中的defer调用就会产生一次,并且这些函数调用一样不会被当即执行。在defer执行时,Go语言会把它携带的defer函数及其参数值另行存储到一个先进后出的队列,至关于一个栈。在须要执行某个函数中defer函数调用时,Go语言会先拿到对应的队列,而后从该队列中一个一个取出defer函数及其参数值,并逐个执行调用