Defer in Golang がマスターしなければならない 7 つの知識ポイント

Golangで開発する場合、deferこの構文も必要な知識なのですが、関数が終了する前に実行されるということ以外に、注意すべき点はdeferありますか?

この記事でコンパイルされた defer の完全なシーンの使用法は、リンクから転送されます: https://learnku.com/articles/42255#7fa787

概要は次のとおりです。

  • 知っておくべきポイント 1: defer の実行順序
  • 知識ポイント 2: 先送りと先送り
  • 知っておくべきポイント3:関数の戻り値の初期化と遅延の間接的な影響
  • 心得ポイント4:有名な関数の戻り値が据え置き条件に合う
  • 知識ポイント 5: 遅延とパニックの出会い
  • 知識ポイント 6: defer に含まれるパニック
  • 知識ポイント 7: defer の下の関数パラメーターにはサブ関数が含まれています

知っておくべきポイント 1: defer の実行順序

複数の defer が表示される場合、それは「スタック」関係、つまり先入れ後出しの関係です。関数では、前に書いた defer は後ろに書いた defer よりも遅く呼び出されます。

サンプルコード


```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")
}

ここに画像の説明を挿入

出力結果:

C
B
A

知識ポイント 2: 先送りと先送り

サンプルコード

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()
}

実行結果は次のとおりです。

return func called
defer func called

結論は、return の後のステートメントが最初に実行され、defer の後のステートメントが後で実行されるということです。

知っておくべきポイント 3: 関数の戻り値の初期化

この知識ポイントはdefer自体には属しませんが、呼び出しシーンはdeferに関連しているため、deferが理解しなければならない知識ポイントの1つでもあります。

例: func DeferFunc1(i int) (t int) {}
where return value t int, this t は、関数の先頭で対応する型のゼロ値に初期化され、スコープは関数全体です。

ここに画像の説明を挿入

サンプルコード

package main

import "fmt"

func DeferFunc(i int) (t int) {
    
    

    fmt.Println("t = ", t)

    return 2
}

func main() {
    
    
    DeferFunc(10)
}

結果

t =  0

関数の戻り値の変数名が宣言されている限り、関数の初期化時に値 0 が割り当てられ、関数本体のスコープ内で可視になることが証明されています

心得ポイント4:有名な関数の戻り値が据え置き条件に合う

defer がない場合、関数の戻り値は実際には return と一致しますが、defer では異なります。

知識ポイント 2から、return を実行してから defer を実行することを学びました。したがって、return を実行した後、defer 内のステートメントを再度実行する必要があり、返されるはずの結果が変更される可能性があります

package main

import "fmt"

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

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

    return 1
}

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

returnButDefer()想定される戻り値は1ですが、戻り後はdeferの匿名関数関数で実行されるため、t=t*10が実行され、returnButDefer()上位main()層結果は10です

$ go run test.go
10

知識ポイント 5: 遅延とパニックの出会い

遅延を引き起こす可能性があるのは、return (または関数本体の最後まで) の会議とパニックの会議であることはわかっています。

知識ポイント 2によると、次のように defer が return を満たすことがわかっています。

ここに画像の説明を挿入

次に、パニックが発生したときに、このコルーチンの defer リストをトラバースして defer を実行します。defer の実行中: recover が発生したときに panic を停止し、recover に戻って実行を継続します。回復が検出されない場合、このコルーチンの遅延リストをトラバースした後、パニック メッセージが stderr にスローされます。

ここに画像の説明を挿入

A. defer はパニックに遭遇しますが、例外をキャッチしません
サンプル コード:

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 之后,永远执行不到") }()
}

結果

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

B. defer はパニックに遭遇し、例外をキャッチします
サンプルコード:

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 之后, 永远执行不到") }()
}

結果

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

defer の最大の機能は、 panic の後も有効であるため
、 defer は、異常な問題を回避するために、リソースの一部が閉じられることを保証できることです。

知識ポイント 6: defer に含まれるパニック

次のコードをコンパイルして実行するとどうなりますか?

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")
}

結果

defer panic

分析する

パニックは最後のものだけがrevoverでキャッチできます。
トリガーpanic("panic")後、遅延はスタックからポップアウトされ、順番に実行されます.panic("defer panic")最初に実行された遅延には例外ステートメントがあり、それは main の例外を上書きしpanic("panic")、最後に例外は 2 番目に実行された遅延によって捕捉されます.

知識ポイント 7: defer の下の関数パラメーターにはサブ関数が含まれています

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))
}

ここでは 4 つの関数があり、それらのインデックス番号はそれぞれ 1、2、3、および 4 です。

では、これら 4 つの関数の実行順序は何ですか? これには 2 つの defer があるため、defer はスタックに 2 回プッシュされます。高度なスタック 1 と最後のスタック 2 です。次に、function1 がスタックにプッシュされるときに、関数アドレスと関数パラメーターと共にスタックにプッシュされる必要があります.次に、function1 の 2 番目のパラメーターの結果を取得するには、最初に function3 を実行して計算する必要があります。 2 番目のパラメーターの場合、function3 が 2 番目のパラメーターになります。同様に、function2 をスタックにプッシュします。function4 を実行して、function2 の 2 番目のパラメーターの値を計算する必要があります。次に関数が終了し、最初に function2 をポップし、次に function1 をポップします。

したがって、シーケンスは次のとおりです。

プッシュ関数 1 を延期、関数アドレスをプッシュ、仮パラメータ 1、仮パラメータ 2 (関数 3 を呼び出し) –> print
3
function2 を呼び出す –> print 2
ポップ function1 を延期し、function1 を呼び出す –> print 1

3
4
2
1

練習: 面接の質問を延期する

以上のdeferの6つの知識ポイントを理解した上で、ネットで本当の疑問を検証してみましょう。

次のコードは何を出力しますか?

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()
}

運動分析

DeferFunc1


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

1. 入ってくる i に戻り値 t を代入し、この時点で t は 1 です
2. return ステートメントを実行して、t に t を代入します (何もしないことに等しい)
3. defer メソッドを実行し、t + 3 = 4 を設定します
4. 関数
t のスコープは関数全体であるため、4 を返し、変更は有効です。

DeferFunc2

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

1. 変数 t を作成し、それに値 1 を代入します
. 2. return ステートメントを実行します. 戻り値に t が代入されていることに注意してください. このとき、戻り値は 1 です (この戻り値は t ではありません). 3
. . defer メソッドを実行し、t + 3 = 4 を設定
4. 関数 return の戻り値 1 は、
次のコードに従っても理解できます。

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

上記のコードが戻るとき、それは t を result に代入することと同じです. defer が t の値を変更するとき、それは result に影響を与えません.

DeferFunc3

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

最初に return を実行し、戻り値 t を 2 に代入し
、defer メソッドを実行して t + 1 を返し
、最後に 3 を返す
DeferFunc4

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

1. 戻り値 t をゼロ値 0 に初期化する
2. 最初に defer の最初のステップを実行し、defer
の func 入力パラメーター t を 0 に割り当てます 3. defer の 2 番目のステップを実行し、defer をスタックにプッシュします
4. t を 1 に割り当てます
5. return 文を実行し、戻り値 t を 2 に代入します。
6. defer で
実行される func の入力パラメータがスタックにプッシュされた時点で代入されているため、defer、pop out、execute の 3 番目のステップを実行し、これは、この時点のパラメーターのフォームであるため、0 として出力されます。これに対応して、t の値が最後に 2 に変更されたため、別の 2 の
結果が出力されます。

4
1
3
0
2

おすすめ

転載: blog.csdn.net/weixin_42918559/article/details/128231779