Go—–使用defer的陷阱
defer关键字用来推迟执行某个语句或函数到任意位置执行return语句之后。常用在:文件流的关闭、解锁一个加锁的资源、关闭数据库链接等。函数中存在defer语句时的执行顺序是:先return后执行defer语句,最后才结束函数调用。以下面几个测试作为例子进行讲解。
小测试
下面的几个test结果会是多少呢?先自己思考下结果,答案在文章后面。
teet1
package main
import "fmt"
func main() {
i := 0
defer fmt.Println(i)
i++
return
}
test2
package main
import "fmt"
func main() {
fmt.Println(test())
}
func test() (result int) {
defer func() {
result++
}()
return 0
}
test3
package main
import "fmt"
func main(){
arr := [5]string{"Go", "Java", "JavaScript", "c++", "Python"}
for _, value := range arr {
defer fmt.Printf("%s -->",value)
}
fmt.Println("从我开始执行")
}
test4
package main
import "fmt"
func main() {
arr := [5]string{"Go", "Java", "JavaScript", "c++", "Python"}
for _, value := range arr {
defer func() {
fmt.Printf("%s -->",value)
}()
}
fmt.Println("从我开始执行")
}
test5
package main
import "fmt"
func main() {
i:=0
defer func(i int) {
for ;i<5 ; i++ {
fmt.Printf("%d -->",i)
}
}(i)
i++
}
defer示例答案及原理解析
test1答案
结果为“0”,而不是“1”。原因在Go文档中有对defer的解释:Each time a “defer” statement executes, the function value and parameters to the call are evaluated as usual and saved a new, but the actual function is not invoked。
也就是说在程序还没运行defer的函数之前,传入函数的参数值已经得到了保存,只是没有调用而已。因此,打印函数中的参数i值为0,而不是加1后的值。
test2答案
运行结果为1,而不是0。原因是defer语句是在return语句之前执行的,而且return语句不是原子的。“return XXX”语句其实可以改写成两句:
result = 1
return
因此函数返回的实际过程是这样的:先给返回值赋值,然后调用defer表达式,最后才是返回到调用函数中。defer表达式可能会在设置函数返回值之后,在返回到调用函数之前,修改返回值,使最终的函数返回值与你想象的不一致。
test3答案
运行结果为
从我开始执行
Python -->c++ -->JavaScript -->Java -->Go -->
原因在于当有多个defer被注册时,它们会以出栈的顺序执行。
test4答案
运行结果为
从我开始执行
Python -->Python -->Python -->Python -->Python -->
原因在于defer函数执行时,for循环已经结束,此时的value为最后一次循环的值,即Python,因此,当执行defer函数时,只是对Python的循环打印。
test5答案
运行结果为”0 –>1 –>2 –>3 –>4 –>“,而不是”1 –>2 –>3 –>4 –>“,具体原因和test1的原理一样,在匿名函数中传入的参数i在defer函数未执行之前,就已经得到了保存。