介绍
《Go程序设计语言》
你只需要在调用普通函数或方法前加上关键字defer,就完成了defer所需要的语法。
当执行到该条语句时,函数和参数表达式得到计算,但直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行,不论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束。你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。
《Go入门指南》
允许我们推迟到函数返回之前(或者任意位置执行 return 语句之后)一刻才执行某个语句或者函数(为什么要在返回之后才执行这些语句?因为 return 语句同样可以包含一些操作,而不是单纯地返回某个值)
类似于其它面向对象语言中的 finall 语句,一般用于释放某些已分配的资源。
规则
- defer声明时,其后面的函数参数会被实时解析
- defer 执行顺序为FILO,栈操作
- defer 可以读取函数的有名返回值
规则一
defer 声明时,其后面的函数参数会被实时解析
package mainimport "fmt"func main() {var i int = 1defer fmt.Println("result1 ==> ", func() int {return i * 2 // 函数参数被实时解析}())i++fmt.Println("i: ", i)defer func() {fmt.Println("relult2 ==> ", i*2) // 函数体为引用}()i++fmt.Println("i: ", i)}
输出
D:\projects\gocode\studylib>go run d:\projects\gocode\studylib\deferdemo\main.goi: 2i: 3relult2 ==> 6result1 ==> 2
规则二
defer 执行顺序为FILO,栈操作
func main() {defer fmt.Print("!!!")defer fmt.Print("world")defer fmt.Print("hello")}
规则三
defer 可以读取函数的有名返回值
func fun1() (i int) {defer func() {i += 10 // return 之前,调用 i+=10}()return 0 // 返回 i = 0}func main() {fmt.Println("result2 ==>", fun1())}
输出
D:\projects\gocode\studylib>go run d:\projects\gocode\studylib\deferdemo\main.goresult2 ==> 10
return设计
go 语言中 return 返回流程: return语句设计为不是原子操作,
情况一: 有名返回值
func fun1() (i int) {defer func() {i++fmt.Println("func1 defer2:", i)}()defer func() {i++fmt.Println("func1 defer1:", i)}()return 0}func main() {fmt.Println("=========================================")// fmt.Println("fun1 return", fun1())fmt.Println("=========================================")fmt.Println("fun2 return", fun2())fmt.Println("=========================================")// fmt.Println("fun3 return", fun3())fmt.Println("=========================================")// fmt.Println("fun4 return", fun4())fmt.Println("=========================================")// n, _ := funcReturn("go")// fmt.Println(n)}
情况二:无名返回值
func fun2() int {var i int // i= 0defer func() {i++fmt.Println("func2 defer2: ", i)}() // 实际的调用推迟到包含 defer 语句的函数结束后才执行。defer func() {i++fmt.Println("func2 defer1: ", i)}() // 实际的调用推迟到包含 defer 语句的函数结束后才执行。return i}func main() {fmt.Println("=========================================")// fmt.Println("fun1 return", fun1())fmt.Println("=========================================")fmt.Println("fun2 return", fun2())fmt.Println("=========================================")// fmt.Println("fun3 return", fun3())fmt.Println("=========================================")// fmt.Println("fun4 return", fun4())fmt.Println("=========================================")// n, _ := funcReturn("go")// fmt.Println(n)}
输出结果
func2 defer1: 1func2 defer2: 2fun2 return 0
情况三:非原子操作
func f0() (r int) {t := 5defer func() {fmt.Printf("f0 defer t is :%d\n", t) //5fmt.Printf("f0 defer r is :%d\n", r) //5t = t + 5fmt.Printf("f0 defer r is :%d\n", r) //5}()return t// 1. r = t (t =5)// 5// 5// 5// return}
这里需要注意的是,return xxx语句并不是一条原子指令,defer 被插入到了 赋值 与 ret 之间。因此 return t应该被拆解程两部分,r = t 和 return。
情况四: 一个延迟调用可以修改包含此延迟调用的最内层函数的返回值
package mainimport "fmt"func Triple(n int) (r int) {defer func() {r += n // 修改返回值}()return n + n // <=> r = n + n; return}func main() {fmt.Println(Triple(5)) // 15}
估值
一个协程调用或者延迟调用的实参是在此调用发生时被估值的。更具体地说,
- 对于一个延迟函数调用,它的实参是在此调用被推入延迟调用堆栈的时候被估值的。
- 对于一个协程调用,它的实参是在此协程被创建的时候估值的。
- 一个匿名函数体内的表达式是在此函数被执行的时候才会被逐个估值的,不管此函数是被普通调用还是延迟/协程调用。
一个例子:
package mainimport "fmt"func main() {func() {for i := 0; i < 3; i++ {defer fmt.Println("a:", i)}}()fmt.Println()func() {for i := 0; i < 3; i++ {defer func() {fmt.Println("b:", i)}()}}()}
输出
D:\projects\gocode\studylib\deferdemo>go run main.go func1.go func2.goa: 2a: 1a: 0b: 3b: 3b: 3
第一个匿名函数中的循环打印出2、1和0这个序列,但是第二个匿名函数中的循环打印出三个3。因为第一个循环中的i是在fmt.Println函数被延迟调用的时候估的值,而第二个循环中的i是在第二个匿名函数调用的退出阶段估的值(此时循环变量i的值已经变为3)。 我们可以对第二个循环略加修改(使用两种方法),使得它和第一个循环打印出相同的结果。
