小猿圈分享Go面試必考題目之defer篇

2021-09-23 10:14:40 字數 3724 閱讀 8598

下面程式分別輸出什麼?

func f1()

}func f2() ()}}

func f3() (i)}}

func f4() int ()

return t

}func f5() (r int) ()

return 0

}func f6() (r int) ()

return t

}func f7() (r int) ®

return 1

}同樣的,我們先不急著作答,我們先來看看defer的講解。

defer是什麼及用途

defer是什麼?

defer 就像它的字面意思一樣,就是延遲執行的意思,但是需要注意的是 defer 只能作用於函式,像變數賦值defer i = 10這種編譯是會報錯的。

defer函式的執行順序

被 defer 的函式會放入乙個棧中,所以是先進後出的執行順序,而被 defer 的函式在 return 之後執行。

清理釋放資源

當我們開啟乙個檔案時,用完之後我們需要 close 這個檔案,否則會導致檔案描述符洩露,不用 defer 的**一般是這樣寫的:

func copyfile(dstname, srcname string) (written int64, err error)

dst, err := os.create(dstname)

if err != nil

written, err = io.copy(dst, src)

dst.close()

src.close()

return

}

上面**檔案都close了,但這裡有乙個問題:如果os.open(srcname)成功了,然後在os.create(dstname)發錯誤了,這時的 src 卻沒有 close ,這樣就會導致src的檔案描述符洩露了。

修復這個問題也很簡單,我們在os.create(dstname)發生錯誤的時候,將src close掉就行了。但是問題又來了如果是多個檔案同時開啟,那這段**將會非常的臃腫,而且會很容易的漏掉一些檔案的 close。

使用defer可以完美的解決這個問題。

func copyfile(dstname, srcname string) (written int64, err error)

defer src.close()

dst, err := os.create(dstname)

if err != nil

defer dst.close()

return io.copy(dst, src)

}

上面**中,只需在檔案操作成功的時候呼叫 defer close 就可以了,而且檔案的 open 和 close 放在一起也不容易漏掉。

執行recover

被 defer 的函式在 return 之後執行,這個時機點正好可以捕獲函式丟擲的 panic,因而defer 的另乙個重要用途就是執行 recover ,而 recover 也只有在 defer 中才會起作用。

func test()

}()panic(「error」)

}另外需要注意的是,recover 要放在 panic 點的前面,一般放在函式的起始的位置就可以了。

defer與return的關係

go 的函式返回值是通過堆疊返回的,這也是實現了多返回值的方法。看以下**:

// foo.go

package main

func foo() (int, int)

func main()

生成的彙編**如下:

$ go build -gcflags 『-l』 -o foo foo.go

$ go tool objdump -s 「main.foo」 foo

text main.foo(sb) /users/kltao/code/go/src/example/foo.go

bar.go:6 0x104ea70 48c744240801000000 movq $0x1, 0x8(sp)

bar.go:6 0x104ea79 48c744241002000000 movq $0x2, 0x10(sp)

bar.go:6 0x104ea82 c3 ret

也就是說 return 語句不是原子操作,它被拆成了兩步

rval = *** // 返回值賦值給rval

ret // 函式返回

而 defer 語句就是在這兩條語句之間執行,也就是

rval = *** // 返回值賦值給rval

defer_func // 執行defer函式

ret // 函式返回

上面的題目中,還涉及到另外乙個知識點,那就是閉包。

簡單來說,go 語言中的閉包就是在函式內引用函式體之外的資料,這樣就會產生一種結果,雖然資料定義是在函式外,但是在函式內部運算元據也會對資料產生影響。看下面的例子:

func foo()

fmt.println(i) // 輸出2

}上面的 i 就是乙個閉包引用,當匿名函式執行時,i 也會被修改。

題目解析

下面統一將rval稱為函式最終return的變數值

func f1()

}// 因為defer的呼叫是先進後出的順序

// 所以輸出:5, 4, 3, 2, 1

func f2() ()}}

// 上面說到,i是乙個閉包引用

// 所以當執行defer時,i已經是5了

// 所以輸出:5,5,5,5,5

func f3() (i)}}

// go的函式引數是值拷貝,所以這是普通的函式傳值

// 所以輸出:5,4,3,2,1

func f4() int ()

return t

}// 注意:f4函式的返回值是沒有宣告變數的

// 所以t雖然是閉包引用,但返回值rval不是閉包引用

// 可以拆解為

// rval = t

// t++

// return rval

// 所以輸出是5

func f5() (r int) ()

return 0

}// 注意:f5函式的返回值是有宣告變數的

// 所以返回值r是閉包引用

// 可以拆解為

// r = 0

// rval = r

// r++

// return rval

// 所以輸出:1

func f6() (r int) ()

return t

}// 這裡t雖然是閉包引用,但返回值r不是閉包引用

// 可以拆解為

// r = t

// rval = r

// t = t + 5

// return rval

// 所以輸出:5

func f7() (r int) ®

return 1

}// 因為匿名函式的引數也是r,所以相當於是

// 匿名函式的引數r = r + 5,不影響外部

// 所以輸出:1

做這種defer的題,需要注意返回值是否為閉包引用。

總結謹記defer和return執行的順序

注意返回值是否為閉包引用

今天的分享就到這裡,不知道大家有沒有學會呢,記得收藏或者記到小本本上哦,希望大家都能找到自己喜歡合適的工作哦,小猿圈-it自學人的小圈子。

小猿圈分享6個 JavaScript 小技巧(下)

nee necountry us state new yourk 複製 6.物件 6.1 使用解構刪除不必要屬性 有時候你不希望保留某些物件屬性,也許是因為它們包含敏感資訊或僅僅是太大了 just too big 你可能會列舉整個物件然後刪除它們,但實際上只需要簡單的將這些無用屬性賦值給變數,然後把...

python小猿 小猿圈python學習 內建函式

python的len為什麼你可以直接用?肯定是直譯器啟動時就定義好了 每個函式的作用我都幫你標好了 abs 求絕對值 all return true if bool x is true for all values x in the iterable.if the iterable is empty...

小猿圈IT自學分享 自學程式設計需要克服的困難

隨著網際網路行業的蓬勃發展,越來越多的年輕人選擇改行做程式設計師,有些人選擇報名學習班,也有一些人選擇自學,因為現在網上的學習資料太多了,但是自學的話也不是那麼簡單的,需要客服很多困難,也有很多阻擋學習的因素,下邊就是小猿圈給大家總結的幾點自學程式設計需要克服的困難 1.充足的時間 如果你正在自學程...