1.简介
defer是Go语言中的延迟调用函数,它是在函数真正返回之前立即执行,一般用于资源回收、捕捉异常操作,其实现原理是:
1)在对应语句打上标签,告诉系统,先不要调用、先执行入栈操作,待函数退栈时在一并调用;
2)当defer语句入栈时,会将函数地址、函数实参一同入栈,如果实参是表达式、函数语句,则会优先运行;
3)函数退栈时,无论是正常调用结束还是触发panic,都会调用之前入栈的defer语句,若有多个defer语句时,则按照FILO顺序执行调用;
2.使用
下面介绍defer语句的使用特点。
2.1延迟调用
defer是延迟调用的,是在函数退栈前才被调用的,如:
```go
func main() {
defer fmt.Println("A")
fmt.Println("B")
}```
输出顺序:B A
2.2生效时机
defer语句只有在被声明、入栈的前提下,才会有效,否则,函数退栈时不会被调用,如:
```go
func main() {
defer fmt.Println("A")
return
defer fmt.Println("B") // 由于提前return,此处,defer语句失效
}```
输出顺序:A
2.3后于return语句
defer语句是延迟调用的,但和return语句细比较,return语句先执行,defer语句后执行,如:
```go
func NewTest() int {
fmt.Println("B")
return 0
}
func Test() int {
defer fmt.Println("A")
return NewTest() // 先执行该语句
}
func main() {
Test()
}```
输出顺序:B A
需要注意的是,此处先执行的是return语句,但还没进入函数退栈、修改寄存器的处理步骤。
2.4执行顺序
多个defer语句的执行顺序是FILO,如:
```go
func main() {
defer fmt.Println("A")
defer fmt.Println("B")
fmt.Println("C")
}
输出顺序:C B A
func test() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
}```
2.5嵌套
当defer发生嵌套时,内层的defer也是按照FILO的顺序执行,并且不影响到外层执行顺序,如:
```go
func main() {
defer fmt.Println("A")
defer func() { // defer整体执行顺序保持不变,依据是:
defer fmt.Println("B") // fmt.Println("E")
fmt.Println("C") // func() {}()
defer fmt.Println("D") // fmt.Println("A")
}() // 而// func() {}()内部会继续按照FILO的顺序执行
defer fmt.Println("E")
}```
输出顺序:E C D B A
2.6传参
之前举例中,defer语句中要么没有实参,要么实参取值明确,但在实际调用中,参数传递情况是未知的,如果传的是变量、表达式、函数语句,则会计算出实参结果,在随defer语句入栈。
2.6.1普通变量
defer语句传参中,实参是普通变量,此时,普通变量的取值,就会被压入栈中,如:
```go
func main() {
for index := 0; index < 5; index++{
defer fmt.Printf("%d ", index)
}
}```
输出顺序:4 3 2 1 0
2.6.2指针变量
defer语句传参中,实参是指针变量,此时,指针变量的地址会被压入栈中,而入栈时变量的取值是啥,已经不关注了,关注的是出栈执行时,指针变量的取值,如:
```go
func main() {
for index := 0; index < 5; index++{
defer func(index *int){fmt.Printf("%d ", *index)}(&index)
}
}```
输出顺序:5 5 5 5 5
2.6.3函数语句
defer语句传参中,实参是函数语句,此时,会先执行函数,并将函数的返回值压入栈中,如:
```go
func main() {
f := func(num int)int{
fmt.Printf("%d ", num)
return 10 * num
}
for index := 0; index < 5; index++{
defer fmt.Printf("%d ", f(index)) // 第一循环时,先执行函数f(0),然后,将函数返回值入栈
}
fmt.Println("")
}```
输出顺序:0 1 2 3 4 40 30 20 10 0
2.7函数返回值
defer语句会影响到函数返回值的结果,此处要先讲一个知识点,对于有返回的函数,return语句执行的处理就是给返回值赋值,如:
```go
func Test() (val int) {
return 2 // 此处执行的处理是 val = 2
}```
2.7.1defer语句操作返回值
defer语句操作函数返回值,此时,返回值的取值会被修改,如:
```go
func Test(val int) (rst int) {
defer func() {
rst += val // 在执行defer语句,对 rst 执行加运算,即 rst = 20 + 10,最终返回值rst取值为30
}()
return 20 // 先执行return语句,对返回值变量rst执行赋值操作,即 rst = 20
}
func main() {
fmt.Println(Test(10))
}```
输出顺序:30
再举个例子,修改返回值是指修改函数语句中的返回值,而不是return语句中的变量,如:
```go
func Test(val int) int { // 返回是int类型,系统会先生成一个临时变量,假设名字是 tmp
rst := val
defer func() {
rst += 20 // 在执行defer语句,对 rst 执行加运算,即 rst = 20 + 10,最终返回值rst取值为30,但tmp还是10
}()
return rst // 执行return语句,即 tmp = rst、 tmp = 10
}
func main() {
fmt.Println(Test(10))
}```
输出顺序:10
2.8配合使用
2.8.1闭包
该种情况是指defer语句的函数体用到外面变量的情况,类似闭包调用,此时,入栈的只有函数地址,不涉及到变量,所以出栈执行时,变量取的是出栈时的值,如:
```go
func main() {
for index := 0; index < 5; index++{
defer func() {fmt.Printf("%d ", index)}()
}
}```
输出顺序:5 5 5 5 5
2.8.2panic
defer常用于异常捕捉,与panic、recover配合使用,处理规则是:defer先与panic声明,当有多个panic时,只有最后一个panic生效,并且recover也捕捉最后一个panic,如:
```go
func main() {
defer func() {
if err := recover(); err != nil{
fmt.Println(err)
}else {
fmt.Println("fatal")
}
}()
defer func() {
panic("defer panic")
}()
panic("main panic")
}```
输出顺序是:defer panic
3.注意
1)defer延迟调用的方式导致其存在浪费内存空间的问题,因为系统需要将defer延迟调用的内容先存下来、然后等函数退栈时再调用,所以defer也要按需使用;
有疑问加站长微信联系(非本文作者)