Go開發(fā)工程師
未來3-5年企業(yè)高性能項目不可替代的語言,從基礎到項目實戰(zhàn)再到重構,真正從入門到精通
與select
語句一樣,Go語言中的defer
語句也非常獨特,而且比前者有過之而無不及。defer
語句僅能被放置在函數(shù)或方法中。它由關鍵字defer
和一個調(diào)用表達式組成。注意,這里的調(diào)用表達式所表示的既不能是對Go語言內(nèi)建函數(shù)的調(diào)用也不能是對Go語言標準庫代碼包unsafe
中的那些函數(shù)的調(diào)用。實際上,滿足上述條件的調(diào)用表達式被稱為表達式語句。請看下面的示例:
func readFile(path string) ([]byte, error) { file, err := os.Open(path) if err != nil { return nil, err } defer file.Close() return ioutil.ReadAll(file) }
函數(shù)readFile
的功能是讀出指定文件或目錄(以下統(tǒng)稱為文件)本身的內(nèi)容并將其返回,同時當有錯誤發(fā)生時立即向調(diào)用方報告。其中,os
和ioutil
(導入路徑是io/ioutil
)代表的都是Go語言標準庫中的代碼包。請注意這個函數(shù)中的倒數(shù)第二條語句。我們在打開指定文件且未發(fā)現(xiàn)有錯誤發(fā)生之后,緊跟了一條defer
語句。其中攜帶的表達式語句表示的是對被打開文件的關閉操作。注意,當這條defer
語句被執(zhí)行的時候,其中的這條表達式語句并不會被立即執(zhí)行。它的確切的執(zhí)行時機是在其所屬的函數(shù)(這里是readFile
)的執(zhí)行即將結束的那個時刻。也就是說,在readFile
函數(shù)真正結束執(zhí)行的前一刻,file.Close()
才會被執(zhí)行。這也是defer
語句被如此命名的原因。我們在結合上下文之后就可以看出,語句defer file.Close()
的含義是在打開文件并讀取其內(nèi)容后及時地關閉它。該語句可以保證在readFile
函數(shù)將結果返回給調(diào)用方之前,那個文件或目錄一定會被關閉。這實際上是一種非常便捷和有效的保險措施。
更為關鍵的是,無論readFile
函數(shù)正常地返回了結果還是由于在其執(zhí)行期間有運行時恐慌發(fā)生而被剝奪了流程控制權,其中的file.Close()
都會在該函數(shù)即將退出那一刻被執(zhí)行。這就更進一步地保證了資源的及時釋放。
注意,當一個函數(shù)中存在多個defer
語句時,它們攜帶的表達式語句的執(zhí)行順序一定是它們的出現(xiàn)順序的倒序。下面的示例可以很好的證明這一點:
func deferIt() { defer func() { fmt.Print(1) }() defer func() { fmt.Print(2) }() defer func() { fmt.Print(3) }() fmt.Print(4) }
deferIt
函數(shù)的執(zhí)行會使標準輸出上打印出4321
。請大家猜測下面這個函數(shù)被執(zhí)行時向標準輸出打印的內(nèi)容,并真正執(zhí)行它以驗證自己的猜測。最后論證一下自己的猜測為什么是對或者錯的。
func deferIt2() { for i := 1; i < 5; i++ { defer fmt.Print(i) } }
最后,對于defer
語句,我還有兩個特別提示:
1. defer
攜帶的表達式語句代表的是對某個函數(shù)或方法的調(diào)用。這個調(diào)用可能會有參數(shù)傳入,比如:fmt.Print(i + 1)
。如果代表傳入?yún)?shù)的是一個表達式,那么在defer
語句被執(zhí)行的時候該表達式就會被求值了。注意,這與被攜帶的表達式語句的執(zhí)行時機是不同的。請揣測下面這段代碼的執(zhí)行:
func deferIt3() { f := func(i int) int { fmt.Printf("%d ",i) return i * 10 } for i := 1; i < 5; i++ { defer fmt.Printf("%d ", f(i)) } }
它在被執(zhí)行之后,標準輸出上打印出1 2 3 4 40 30 20 10
。
2. 如果defer
攜帶的表達式語句代表的是對匿名函數(shù)的調(diào)用,那么我們就一定要非常警惕。請看下面的示例:
func deferIt4() { for i := 1; i < 5; i++ { defer func() { fmt.Print(i) }() } }
deferIt4
函數(shù)在被執(zhí)行之后標出輸出上會出現(xiàn)5555
,而不是4321
。原因是defer
語句攜帶的表達式語句中的那個匿名函數(shù)包含了對外部(確切地說,是該defer
語句之外)的變量的使用。注意,等到這個匿名函數(shù)要被執(zhí)行(且會被執(zhí)行4次)的時候,包含該defer
語句的那條for
語句已經(jīng)執(zhí)行完畢了。此時的變量i
的值已經(jīng)變?yōu)榱?code class="marker">5。因此該匿名函數(shù)中的打印函數(shù)只會打印出5
。正確的用法是:把要使用的外部變量作為參數(shù)傳入到匿名函數(shù)中。修正后的deferIt4
函數(shù)如下:
func deferIt4() { for i := 1; i < 5; i++ { defer func(n int) { fmt.Print(n) }(i) } }
請大家自行驗證一下它的正確性。
命令源碼文件index.go中代碼的功能是打印出斐波那契數(shù)列的前10個數(shù),即:0 1 1 2 3 5 8 13 21 34
。請把第 9 行的fmt.Printf
函數(shù)調(diào)用語句修改成一條defer
語句,使得該文件被執(zhí)行之后標準輸出上會出現(xiàn):
0 1 1 2 3 5 8 13 21 34 34 21 13 8 5 3 2 1 1 0
顯然,這是關于斐波那契數(shù)列的一段回文。提示一下,這需要利用前面講到的defer
語句的幾個特殊行為。尤其是最后的“特別提示”中講到的一些內(nèi)容。
第9行的語句應該被修改成:
defer func(n int) { fmt.Printf("%d ", n) }(func() int { n := fibonacci(i) fmt.Printf("%d ", n) return n }())
請驗證,完成請求
由于請求次數(shù)過多,請先驗證,完成再次請求
打開微信掃碼自動綁定
綁定后可得到
使用 Ctrl+D 可將課程添加到書簽
舉報