关于defer的解释有很多,今天我从我自己的角度来说说defer
问题 1.什么是defer?
在一个函数执行到return一步的时候,实际执行的并不仅仅是return这一个操作,他不是原子的,解析golang代码的汇编语言可以发现,在计算机底层把return分为了多步去执行。比如return 1
// 伪代码
reval = 1
defer_func
ret
很明显在函数结束的最后的时候执行了defer
问题 2.defer有什么作用?
defer的作用在于函数执行完毕之后无论如何也会执行defer中的代码。举个例子
func function(){
file1,err := os.Open("anyfile.txt")
file2,err := os.Open("anyfile2.txt")
.......
defer file1.Close()
defer file2.Close()
return
}
这样就保证了文件一定能被关闭。无论函数遇到panic等等的异常错误,都会执行defer。
(重要)问题 3. defer的原理和几种使用方法?
关于底层的原理主要是defer之后的代码堆栈与正常的不同,详细的过程我引用别人的一段话
每次defer语句执行的时候,会把函数“压栈”,函数参数会被拷贝下来;当外层函数(非代码块,如一个for循环)退出时,defer函数按照定义的逆序执行;如果defer执行的函数为nil, 那么会在最终调用函数的产生panic.defer语句并不会马上执行,而是会进入一个栈,函数return前,会按先进后出的顺序执行。也说是说最先被定义的defer语句最后执行。先进后出的原因是后面定义的函数可能会依赖前面的资源,自然要先执行;否则,如果前面先执行,那后面函数的依赖就没有了。
在defer函数定义时,对外部变量的引用是有两种方式的,分别是作为函数参数和作为闭包引用。作为函数参数,则在defer定义时就把值传递给defer,并被cache起来;作为闭包引用的话,则会在defer函数真正调用时根据整个上下文确定当前的值。
接下来我讲讲使用方法(重要)
1.使用闭包的情况下
func main(){
b := 1
defer func() {
fmt.Println(b)
}()
b ++
return
}
输出:2
解释:看代码可知,defer后的匿名函数使用了闭包,什么是闭包?,你可以理解为函数使用了函数内未定义的外部变量.
在return之后,defer执行了打印变量b的代码,这时的b是使用的上层函数的b,所以b为2.
2.不使用闭包1。
func main(){
b := 1
defer func(num int) {
fmt.Println(num)
}(b)
b ++
return
}
输出:1
解释:为什么这次的输出为1?因为在函数压栈的过程中(也就是顺序执行 执行到defer那一行时候)
b就被作为参数传入了defer定义的匿名函数 只不过没执行罢了。待return的时候执行defer后的代码的时候,
直接就把之前压栈的时候传入的参数b给输出了,defer后面几行的代码是否改变了b 对defer并没有影响。
3.不使用闭包2
func main(){
b := []int{1,2,3}
defer func(num []int) {
fmt.Println(num)
}(b)
b[0] = 2
return
}
输出:{2,2,3}
解释:为什么这次的输出为2? 很简单,即使在defer后的匿名函数中,也有‘值传递和引用传递’的区别,
切片b是一个引用类型。