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