一、defer 和 return
1、defer和return的顺序
defer是在return之后执行的,但是在写代码的时候,defer要写在return之前,否则defer在return后不会执行;
defer什么时候会执行:函数执行了return语句、运行到函数结尾自动返回、对应的goroutine panic
if err!=nil{
return
}
defer func(){
fmt.Println("defer")
}()
//此处return后,defer不会执行;
defer会将待执行函数加入到一个类似栈的地方,等return之后在来执行这个栈里面的函数;
「所以return之后的defer是没在栈里的;」
2、defer和带名返回值
如果函数的返回值是「无名的」,则go语言会在执行return的时候会执行一个「类似创建一个临时变量」作为保存return值的动作;
对于「有名返回值」的函数,由于返回值在函数定义的时候已经将该变量进行定义,在执行return的时候会「先执行返回值保存操作」,「而后续的defer函数会改变这个返回值」(虽然defer是在return之后执行的,但是由于使用的函数定义的变量,所以执行defer操作后对该变量的修改会影响到return的值)
func main() {
fmt.Println("test1: ",test1().Error())
fmt.Println("test3: ",test3().Error())
//运行结果
//test1: this is test2's error (错了,返回的err不是test1的)
//test3: this is test3's error
//总结:defer会对带名返回值产生修改
}
func test1() (err error) {
defer func() {
//这里会把原来的err覆盖掉
err = test2()
}()
//运行到某处,报err了,然后return,执行defer
err = errors.New("this is test1's error")
return
}
func test2() (err error) {
err = errors.New("this is test2's error")
return
}
func test3() error {
var err error
defer func() {
//不会覆盖原来的err
err = test2()
}()
//运行到某处,报err了,然后return,执行defer
err = errors.New("this is test3's error")
return err
}
//有名返回值的另一个陷阱
func test6()(err error){
if true{
//有时候在多返回值的情况下,使用:=时,会不小心新建一个同名的err变量,就会出现问题了
err:=errors.New(" this is test6's error")
fmt.Println(err)
//return err
}
return
}
如果把if的return去掉,那么上层得到的只是一个nil,因为if内的err是一个新的变量,和返回值没关系;
所以为了将err返回给上层,要在if里直接return err;
或者:
err1:=errors.New(" this is test6's error")
err = err1
3、defer执行匿名函数
func main() {
test4()
test5()
//运行结果:
//num = 20
//num = 10
}
func test4(){
num := 10
defer func(){
//这里的num相当于全局变量,当num被修改时,再来执行defer,使用的num值是已经被修改过了的
fmt.Println("num = ",num)
}()
num = 20
return
}
func test5(){
num := 10
defer func(n int){
//这里通过函数参数来接收num,能够保存num的值,当num被修改后再来执行defer,还是能够拿到最开始的num值
fmt.Println("num = ",n)
}(num)
num = 20
return
}
4、返回值被隐藏
func test(i int) (err error) {
if i == 0 {
//正常,这里新声明了一个err,但是return时是给返回值的err赋值
//err := fmt.Errorf("the i is equal 0")
//return err
//正常,这是直接给返回值的err赋值
//err = fmt.Errorf("the i is equal 0")
//return
//错误,err is shadowed during return(返回时err被隐藏)
//这里新声明了一个err,所以return时,返回值的err仍然是空的;提示err被隐藏;
//不止是error类型会,其他类型也会;
err := fmt.Errorf("the i is equal 0")
fmt.Print(err)
return
}
return
}
//之所以这样是因为出现了局部变量和返回值同名的情况,且return时是直接return,这样局部变量就被隐藏了;
//所以,在多返回值且使用短声明时,注意是否会声明新的和返回值用名的变量
//如:result,err:=test()
//那么什么时候,多返回值会不小心声明了新的变量?
//这和变量的作用域有关,如果在新的作用域里,很可能就会产生新的变量
func test(i int) ( err error) {
//这里的err是返回值的err,编译正常。因为是同个作用域
j ,err:= test1()
fmt.Println(j,err)
if i == 0 {
//if内部是新的作用域,所以这里的err是新的变量了。编译会报错,err is shadowed during return
j ,err:= test1()
fmt.Println(j,err)
return
}
return
}
5、变量作用域
- 「全局变量:」 定义:函数外部定义的变量都属于全局变量;全局变量声明必须以 var 开头 生效范围:当前 package 内,如果想要其他 package 访问,全局变量以大写开头
- 「参数变量:」 定义:函数调用时传递的变量 生效范围:函数范围(但是有可能会可变传入参数的值,取决于传递的参数的类型)
- 「局部变量:」 定义:在函数或者在语义块(if/for等)中定义的变量 生效范围:定义在函数里面则在整个函数范围有效; 定义在语义块,则在整个语义块生效
//作用域
函数内,if内,for内,都是新的作用域
注意作用域的优先级(当变量名相同时,会优先使用当前作用域的变量)
func Test2(){
str := "local var"
if true {
str := "block var"
fmt.Println(str) //优先使用当前作用域的变量
}
fmt.Println(str)
}
//参数变量和局部变量
两者区别:参数变量的赋值是在函数外部完成的(调用函数时);参数变量声明后可以不使用,局部变量声明不使用编译错误
大部分情况下,参数变量和局部本地变量是一样的
//全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑;
//函数的参数、reciever、返回值都拥有当前函数的作用域;
二、panic和recover
panic
- panic会停止当前goroutine的正常执行。
- 当函数F调用panic时,函数F被立即停止,然后运行所有在F函数中的defer函数,然后F返回到调用他的函数。
- 对于调用者G,F函数的行为就像panic一样,会终止G的执行并运行G中所defer函数,然后继续返回,此过程会持续到当前goroutine的最顶层,然后当前goroutine异常退出。
- panic可以通过内置的recover来捕获。
recover
- recover用来管理含有panic行为的goroutine,
recover运行在defer函数中
,获取panic抛出的错误值,并将程序恢复成正常执行的状态。 - 如果在defer函数之外调用recover,那么recover不会停止并且捕获panic错误。
- 如果goroutine中没有panic,那么recover的返回值也是nil。由此可见,recover的返回值表示当前goroutine是否有panic行为
- recover用来管理含有panic行为的goroutine,
注意:
- 如果一直没有recover,抛出的panic到当前goroutine最上层函数时,协程直接异常终止。
- recover都是在当前的goroutine里进行捕获的,这就是说,对于创建goroutine的外层函数,如果goroutine内部发生panic并且内部没有用recover,外层函数是无法用recover来捕获的,
这样会造成整个程序崩溃
。 注意:协程panic了,会导致整个程序崩溃。我写HandleFunc时,并没有写recover啊,为什么有时候panic程序不会崩溃?因为http的底层帮你recover了; - recover返回的是interface{}类型而不是go中的 error 类型
func main() {
fmt.Println("我是main")
go test()
time.Sleep(5 * time.Second)
fmt.Println("我是main")
}
func test() {
defer func() {
err := recover()
if err != nil {
fmt.Println("捕捉到panic:",err)
}
}()
fmt.Println("我是协程")
time.Sleep(3 * time.Second)
panic("test_panic!")
}
本文使用 mdnice 排版