7 knowledge points that Defer in Golang must master

When developing with Golang, deferthis syntax is also necessary knowledge, but besides knowing that it is executed before a function exits, deferis there anything else that needs to be paid attention to?

The full scene usage of defer compiled in this article is transferred from the link: https://learnku.com/articles/42255#7fa787

The outline is as follows:

  • Knowledge point 1: Execution order of defer
  • Knowledge point 2: defer and return which comes first
  • Knowledge point 3: the indirect influence of function return value initialization and defer
  • Knowledge point 4: The return value of a famous function meets the defer situation
  • Knowledge point 5: defer meets panic
  • Knowledge point 6: defer contains panic
  • Knowledge point 7: The function parameters under defer contain sub-functions

Knowledge point 1: The execution order of defer

When multiple defers appear, it is a "stack" relationship, that is, first in, last out. In a function, the defer written in the front will be called later than the defer written in the back.

sample code


```go
package main

import "fmt"

func main() {
    defer func1()
    defer func2()
    defer func3()
}

func func1() {
    fmt.Println("A")
}

func func2() {
    fmt.Println("B")
}

func func3() {
    fmt.Println("C")
}

insert image description here

Output result:

C
B
A

Knowledge point 2: defer and return which comes first

sample code

package main

import "fmt"

func deferFunc() int {
    
    
    fmt.Println("defer func called")
    return 0
}

func returnFunc() int {
    
    
    fmt.Println("return func called")
    return 0
}

func returnAndDefer() int {
    
    

    defer deferFunc()

    return returnFunc()
}

func main() {
    
    
    returnAndDefer()
}

The execution result is:

return func called
defer func called

The conclusion is: the statement after return is executed first, and the statement after defer is executed after

Knowledge point 3: function return value initialization

This knowledge point does not belong to defer itself, but the calling scene is related to defer, so it is also one of the knowledge points that defer must understand.

For example: func DeferFunc1(i int) (t int) {}
where the return value t int, this t will be initialized to a zero value of the corresponding type at the beginning of the function and the scope is the entire function.

insert image description here

sample code

package main

import "fmt"

func DeferFunc(i int) (t int) {
    
    

    fmt.Println("t = ", t)

    return 2
}

func main() {
    
    
    DeferFunc(10)
}

result

t =  0

It proves that as long as the variable name of the return value of the function is declared, it will be assigned a value of 0 when the function is initialized, and it will be visible in the scope of the function body.

Knowledge point 4: The return value of a famous function meets the defer situation

In the absence of defer, the return of the function is actually consistent with return, but it is different with defer.

​ We learned from knowledge point 2 that return first and then defer, so after executing return, the statement in defer must be executed again, and the result that should have been returned can still be modified.

package main

import "fmt"

func returnButDefer() (t int) {
    
      //t初始化0, 并且作用域为该函数全域

    defer func() {
    
    
        t = t * 10
    }()

    return 1
}

func main() {
    
    
    fmt.Println(returnButDefer())
}

​ The returnButDefer()supposed return value is 1, but after the return, it is executed by the anonymous func function of defer, so t=t*10 is executed, and the result returnButDefer()returned to the upper main()layer is 10

$ go run test.go
10

Knowledge point 5: defer meets panic

​ We know that what can trigger defer is meeting return (or function body to the end) and meeting panic.

​ According to knowledge point 2 , we know that defer meets return as follows:

insert image description here

Then, when a panic occurs, traverse the defer list of this coroutine and execute the defer. During the execution of defer: stop panic when encountering recover, and return to recover to continue execution. If recover is not encountered, after traversing the defer list of this coroutine, a panic message is thrown to stderr.

insert image description here

A. defer encounters a panic, but does not catch the exception
Sample code:

package main

import (
    "fmt"
)

func main() {
    
    
    defer_call()

    fmt.Println("main 正常结束")
}

func defer_call() {
    
    
    defer func() {
    
     fmt.Println("defer: panic 之前1") }()
    defer func() {
    
     fmt.Println("defer: panic 之前2") }()

    panic("异常内容")  //触发defer出栈

    defer func() {
    
     fmt.Println("defer: panic 之后,永远执行不到") }()
}

result

defer: panic 之前2
defer: panic 之前1
panic: 异常内容
//... 异常堆栈信息

B. defer encounters a panic and catches an exception
Sample code:

package main

import (
    "fmt"
)

func main() {
    
    
    defer_call()

    fmt.Println("main 正常结束")
}

func defer_call() {
    
    

    defer func() {
    
    
        fmt.Println("defer: panic 之前1, 捕获异常")
        if err := recover(); err != nil {
    
    
            fmt.Println(err)
        }
    }()

    defer func() {
    
     fmt.Println("defer: panic 之前2, 不捕获") }()

    panic("异常内容")  //触发defer出栈

    defer func() {
    
     fmt.Println("defer: panic 之后, 永远执行不到") }()
}

result

defer: panic 之前2, 不捕获
defer: panic 之前1, 捕获异常
异常内容
main 正常结束

The biggest function of defer is that it is still valid after panic
, so defer can guarantee that some of your resources will be closed, so as to avoid some abnormal problems.

Knowledge point 6: defer contains panic

What happens when you compile and execute the following code?

package main

import (
    "fmt"
)

func main()  {
    
    

    defer func() {
    
    
       if err := recover(); err != nil{
    
    
           fmt.Println(err)
       }else {
    
    
           fmt.Println("fatal")
       }
    }()

    defer func() {
    
    
        panic("defer panic")
    }()

    panic("panic")
}

result

defer panic

analyze

panic only the last one can be caught by revover. After
triggering , the defers are popped out of the stack and executed in sequence. There will be an exception statement in the first executed defer, which will overwrite the exception in main , and finally the exception will be caught by the second executed defer.panic("panic")panic("defer panic")panic("panic")

Knowledge point 7: The function parameters under defer contain sub-functions

package main

import "fmt"

func function(index int, value int) int {
    
    

    fmt.Println(index)

    return index
}

func main() {
    
    
    defer function(1, function(3, 0))
    defer function(2, function(4, 0))
}

​ Here, there are 4 functions, and their index numbers are 1, 2, 3, and 4 respectively.

So what is the order of execution of these 4 functions? There are two defers in it, so the defer will be pushed to the stack twice, the advanced stack 1, and the last stack 2. Then, when function1 is pushed onto the stack, it needs to be pushed into the stack together with the function address and function parameters. Then in order to get the result of the second parameter of function1, it is necessary to execute function3 first to calculate the second parameter, then function3 will be the second parameter. one executes. Similarly, push function2 onto the stack, you need to execute function4 to calculate the value of the second parameter of function2. Then the function ends, pop function2 first, and then pop function1.

So the sequence is as follows:

defer push function1, push function address, formal parameter 1, formal parameter 2 (call function3) –> print 3
defer push function2, push function address, formal parameter 1, formal parameter 2 (call function4) –> print 4
defer pop function2, call function2 –> print 2
defer pop function1, call function1 –> print 1

3
4
2
1

Practice: Defer interview questions

After understanding the above 6 knowledge points of defer, let's verify the real questions on the Internet.

What does the following code output?

package main

import "fmt"

func DeferFunc1(i int) (t int) {
    
    
    t = i
    defer func() {
    
    
        t += 3
    }()
    return t
}

func DeferFunc2(i int) int {
    
    
    t := i
    defer func() {
    
    
        t += 3
    }()
    return t
}

func DeferFunc3(i int) (t int) {
    
    
    defer func() {
    
    
        t += i
    }()
    return 2
}

func DeferFunc4() (t int) {
    
    
    defer func(i int) {
    
    
        fmt.Println(i)
        fmt.Println(t)
    }(t)
    t = 1
    return 2
}

func main() {
    
    
    fmt.Println(DeferFunc1(1))
    fmt.Println(DeferFunc2(1))
    fmt.Println(DeferFunc3(1))
    DeferFunc4()
}

Exercise Analysis

DeferFunc1


func DeferFunc1(i int) (t int) {
    
    
    t = i
    defer func() {
    
    
        t += 3
    }()
    return t
}

1. Assign the return value t to the incoming i, and t is 1 at this time
2. Execute the return statement to assign t to t (equal to doing nothing)
3. Execute the defer method, and set t + 3 = 4
4. Function Returns 4
because the scope of t is the entire function so the modification is valid.

DeferFunc2

func DeferFunc2(i int) int {
    
    
    t := i
    defer func() {
    
    
        t += 3
    }()
    return t
}

1. Create a variable t and assign it a value of 1.
2. Execute the return statement. Note that t is assigned to the return value. At this time, the return value is 1 (this return value is not t).
3. Execute the defer method, and set t + 3 = 4
4. The return value 1 of the function return
can also be understood according to the following code

func DeferFunc2(i int) (result int) {
    
    
    t := i
    defer func() {
    
    
        t += 3
    }()
    return t
}

When the above code returns, it is equivalent to assigning t to result. When defer modifies the value of t, it will not affect result.

DeferFunc3

func DeferFunc3(i int) (t int) {
    
    
    defer func() {
    
    
        t += i
    }()
    return 2
}

First execute return, assign the return value t to 2
and execute the defer method to return t + 1
and finally return 3
DeferFunc4

func DeferFunc4() (t int) {
    
    
    defer func(i int) {
    
    
        fmt.Println(i)
        fmt.Println(t)
    }(t)
    t = 1
    return 2
}

1. Initialize the return value t to zero value 0
2. First execute the first step of defer, assign func input parameter t in defer to 0
3. Execute the second step of defer, push defer to the stack
4. Assign t to 1
5. Execute the return statement, and assign the return value t to 2.
6. Execute the third step of defer, pop out and execute
because the input parameter of the func executed by defer has been assigned when it is pushed into the stack, and it is a form at this time parameter, so it is printed as 0; correspondingly, because the value of t has been modified to 2 at the end, so another 2
result is printed

4
1
3
0
2

Guess you like

Origin blog.csdn.net/weixin_42918559/article/details/128231779