Go语言defer

函数中被defer关键字声明的语句会被延迟执行,延迟到函数结束之前才执行。

首先对于函数中的return语句,它是由两步组成,而非一个原子操作:

  return=赋值给返回值+返回

func f1(x int) int {  //x=5
	defer func() {
		x++
	}()
	return x
}

  如果传入的参数是5,这个函数的返回值是5,在函数最后返回的时候,首先创建一个匿名变量作为返回值,并且将x的值赋值给这个匿名变量,然后defer的延迟执行函数将x+1,但是此时已经和返回值没有关系了,因为x和返回值是两个变量,最后就是执行RET返回,所以一个函数的return语句是分两步执行,然后defer声明的语句则延迟到这两句之间执行

func f1(x int) (y int) {  //x=5
	defer func() {
		y++
	}()
	return x
}

  如果这个时候传入的参数是5,这个函数的返回值是6,此时我们已经将返回值由匿名变量显示的变为变量y,所以执行的过程为首先将x的赋值给y,此时y的值是5,然后执行defer声明的语句,将y++,所以返回值这个变量的值变为了6,最后执行RET返回,因此最后的返回值是6.

func f1() (y int) {
	defer func(y int) {
		y++
	}(5)
	return y
}

  这个函数的返回值是0,首先返回值y的类型是int,默认值是0,所以在返回的时候首先给y赋值为0,然后执行defer声明的函数,此时采用的是值传递,所以这个函数中的y其实是函数返回值的那个y的副本,他们是两个变量,所以此时修改y不能对函数返回值的那个y起作用,最后再返回。

func f1() (y int) {
	defer func(y *int) {
		*y++
	}(&y)
	return 5
}

  这个函数最后的返回值是6,过程如下,首先对函数的返回值y赋值为5,然后执行defer声明的函数,这个函数的入口参数是一个指针,所以采用的是地址传递而不是值传递,上面那个函数就是把返回值y的地址传入了defer的函数,所以最后修改了返回值y的值,y+1,所以最后返回值是6

  然后再看一个比较复杂的例子,这个例子说明了在对defer的语句进行压栈的时候,会保存当前的状态:

func calc(index string, a, b int) int {
	ret := a + b
	fmt.Println(index, a, b, ret)
	return ret
}

func main() {
	a, b := 1, 2
	// a=1
	// b=2
	defer calc("1", a, calc("10", a, b))
	// defer的函数中还调用了函数,所以在对这条语句压栈的时候,会先执行内层的calc函数
	// calc("10",1,2)==>输出"10" 1 2 3
	// 然后对defer语句当前的状态压栈,当前a=1
	// 所以压栈的语句为:
	// defer calc("1",1,3)
	a = 0
	// a=0
	defer calc("2", a, calc("20", a, b))
	// defer的函数中还调用了函数,所以在对这条语句压栈的时候,会先执行内层的calc函数
	// calc("20",0,2)==>输出"20" 0 2 2
	// 然后对defer语句当前的状态压栈,当前a=0
	// 所以压栈的语句为:
	// defer calc("2",0,2)
	b = 1
	// b=1
	// 然后执行压栈的defer语句
	// calc("2",0,2)==>输出"2" 0 2 2
	// calc("1",1,3)==>输出"1" 1 3 4
	// 所以最后的输出结果是:
	// "10" 1 2 3
	// "20" 0 2 2
	// "2" 0 2 2
	// "1" 1 3 4
}

  从上面的代码可以看出两点,defer声明的函数内部如果还有函数调用(比如参数是另一个函数),那么会首先执行那个函数,并且得到一个结果,然后将这个结果压栈,另一点就是对于defer函数的变量,会在defer被压栈的时候保存这些变量当前的值,defer语句被压栈以后,再修改这些变量的值,并不会对defer语句中的这些变量产生影响。

  defer和panic与recover

  recover必须和defer搭配使用,defer必须定义在panic之前

  当函数遇到panic语句的时候会退出,如果没有recover()语句,那么整个程序就会崩溃退出,在执行panic语句退出之前,会首先执行在panic语句之前定义的defer,如果在这个defer语句中有recover()函数,那么执行的结果就是recover()恢复错误之前的现场,并且仅仅退出这个函数,但是整个程序不会崩溃,而仅仅是这个函数退出了不再执行,但是整个程序依旧继续向后执行:

  

func f1() {
	fmt.Println("1")
}
func f2() {
	panic("there is a serious error")
	fmt.Println("2")
}
func f3() {
	fmt.Println("3")
}

func main() {
	f1()
	f2()
	f3()
}

  此时这段代码的输出为:

Go语言defer_第1张图片

 

 输出了panic的错误信息以后,整个程序直接崩溃,而不再执行main函数后面的信息

func f1() {
	fmt.Println("1")
}
func f2() {
	defer func() {
		err := recover()    
    //recover()函数恢复了错误,结果是f2这个函数退出,整个程序不退出
		if err != nil {
			fmt.Println("recover in function 2")
		}
	}()
	panic("there is a serious error")
	fmt.Println("2")
}
func f3() {
	fmt.Println("3")
}

func main() {
	f1()
	f2()
	f3()
}    

  所以在上面的代码中只有f2函数退出了,后面的f3函数仍然继续执行,整个程序不会异常崩溃。结果如下:

Go语言defer_第2张图片

 

你可能感兴趣的:(Go语言defer)