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