私たちの仕事でもよく使われるGo言語の特徴であり、面接官も好む知識ポイントでもあるdeferキーワードを、今回はこの記事を通して徹底的にマスターしていきます。
面接質問資料を無料でダウンロードするにはここをクリックしてください
Go 言語の初心者から熟練者まで、ここをクリックして無料でダウンロードしてください。
記事ディレクトリ
defer2 つの主要な機能
defer は golang のキーワードであり、主に次の 2 つの大きな特徴があります。
- 遅延呼び出し: 現在の関数の実行が完了した後に呼び出しを実行します。
func f1(){
defer fmt.Println("hello world")
fmt.Println("hello defer!")
}
出力結果:
$ go run main.go
hello defer!
hello world
後入れ先出し: 複数の遅延関数がある場合、実行順序は後入れ先出しになります。
func f2(){
defer fmt.Println("hello 1!")
defer fmt.Println("hello 2!")
defer fmt.Println("hello 3!")
fmt.Println("hello defer!")
}
出力結果
$ go run main.go
hello defer!
hello 3!
hello 2!
hello 1!
延期と返却の実行順序
延期と返却の実行順序は面接でよく検討される点であり、道教者はよく理解する必要があります。
まず、例を挙げて、次の状況でのコードの出力を見てみましょう。
func f1() (r int){
defer func(){
r++
}()
return 0
}
func f2() (r int) {
t:=5
defer func() {
t = t+5
}()
return t
}
func f3() (r int) {
defer func(r int) {
r = r+5
}(r)
return 0
}
func main(){
fmt.Println(f1())
fmt.Println(f2())
fmt.Println(f3())
}
友達が最初に答えを考えてから、後で見ることをお勧めします。
$ go run main.go
1
5
0
同じ道士の張三さんはこう思った、「くそー、冗談でしょ? 0、10、5 じゃないの…」
さて、それらを 1 つずつ分析しましょう。
ここでは、まず return ステートメントの実行順序を理解する必要があります。
return ステートメント自体はアトミック命令ではなく、次のように、最初に戻り値に値を代入してから戻ります。
func f4() (r int) {
return 1
}
//执行过程:
r:=1 //赋值
ret //执行返回
defer 式が含まれている場合、関数の戻りプロセスは次のようになります。
最初に戻り値に値を代入し、次に defer 式を呼び出し、最後に結果を返します。
つまり、f1() の場合
func f1() (r int){
defer func(){
r++
}()
return 0
}
//执行过程:
r:=0 //赋值
r++ //defer
ret //r=1
f2の場合
func f2() (r int) {
t:=5
defer func() {
t = t+5
}()
return t
}
//执行过程
t:=5
r:=t
t=t+5 //defer
ret //r=5
f3() の場合、遅延中に渡されるパラメータ r は実際には値のコピーです。
したがって、defer の r を変更しても戻り値の結果には影響せず、r を t に置き換えても結果は同じ、つまり以下と同等であることを理解するのに役立ちます。
func f3() (r int) {
defer func(t int) {
t = t+5
}(r)
return 0
}
//执行过程
r:=0
t = r, t = t +5 //defer
ret // r=0
延期の適用シナリオ
シナリオ 1: リソースの解放
ファイルを開くなど、コード内でリソースを使用する場合、解放し忘れたり、論理エラーが原因でリソースが閉じられなくなることがよくあります。この時点で defer を使用すると、このリソース リークを回避できます。まず次のコードを見てみましょう。
file,_ := os.Open("test.txt")
//process为业务逻辑处理
if err:=process(file);err!=nil {
return
}
file.Close()
上記のコードには重大な問題があり、err!=nil を直接返すと、リソースをクローズする file.close() ステートメントが実行されず、リソース リークが発生します。
また、一連のビジネスロジックの処理を書いた後、リソースをクローズするのを忘れやすく、リソース漏洩が発生します。したがって、リソースの適用が成功するたびに自動的にクリーンアップするために defer を追加するという 1 つの原則を心に留めておく必要があります。関数の戻り数に関係なく、リソースは正しく解放されます。
正しい書き込みロジックは次のとおりです。
file,_ := os.Open("test.txt")
defer file.Close()
//process为业务逻辑处理
if err:=process(file);err!=nil {
return
}
シナリオ 2: 例外のキャプチャ
Golang のプログラムの例外処理には、try catch はありませんが、panic とcover があります。プログラムでパニックが発生した場合、時間内に回復しないとサービスが直接ハングアップして重大な結果を引き起こすため、通常は例外をキャッチするために回復を使用します。
func main(){
defer func(){
if ok:=recover();ok!=nil{
fmt.Println("recover")
}
}()
panic("error")
}
上記の 2 つのシナリオはよく理解しておく必要がありますが、もちろん、defer の機能を使用して、コード トレース、関数パラメーターと戻り値の記録などをエレガントに実装することもできます。
# シナリオ 3: コードの追跡
プログラムが関数に入る、または関数から出る情報を追跡することによって、この関数が実行されるかどうかをテストします。
func main(){
f1()
f2()
}
func f1(){
defer trace_leave(trace_enter("f1()"))
fmt.Println("f1()程序逻辑")
}
func f2(){
defer trace_leave(trace_enter("f2()"))
fmt.Println("f2()程序逻辑")
}
func trace_enter(msg string) string{
fmt.Println("enter: ",msg)
return msg
}
func trace_leave(msg string) {
fmt.Println("leave: ",msg)
}
出力は次のとおりです。
$go run main.go
enter: f1()
f1()程序逻辑
leave: f1()
enter: f2()
f2()程序逻辑
leave: f2()
シナリオ 4: 関数のパラメーターと戻り値を出力する
特定の関数の実行結果が期待どおりではありません。defer を使用すると、デバッグ ステートメントを複数の場所に出力する代わりに、関数のパラメーターと戻り値を 1 つのステップで出力できます。
func main(){
func1("hello")
}
func func1(str string) ( res string) {
defer func() {
fmt.Printf("func1(%s) = %s", str, res)
}()
res = fmt.Sprintf("%s, jack!",str)
return
}
出力結果:
$go run main.go
func1(hello) = hello, jack!
面接のポイントまとめ
- 遅延の 2 つの主な特徴
- 延期と返却の実行順序
- 延期の適用シナリオ
追記
この記事で紹介した面接テクニックについて質問がある場合は、コメント欄に返信して、一緒に学び、一緒に進歩していきましょう!
公開アカウント [Jiandao Programming] をフォローすると、毎日バックエンド技術面接ポイントがあります