【更多流程控制】1. defer語句

Go語言-defer語句

    與select語句同樣,Go語言中的defer語句也很是獨特,並且比前者有過之而無不及。defer語句僅能被放置在函數或方法中。它由關鍵字defer和一個調用表達式組成。注意,這裏的調用表達式所表示的既不能是對Go語言內建函數的調用也不能是對Go語言標準庫代碼包unsafe中的那些函數的調用。實際上,知足上述條件的調用表達式被稱爲表達式語句。請看下面的示例:函數

func readFile(path string) ([]byte, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close()
    return ioutil.ReadAll(file)
}

    函數readFile的功能是讀出指定文件或目錄(如下統稱爲文件)自己的內容並將其返回,同時當有錯誤發生時當即向調用方報告。其中,osioutil(導入路徑是io/ioutil)表明的都是Go語言標準庫中的代碼包。請注意這個函數中的倒數第二條語句。咱們在打開指定文件且未發現有錯誤發生以後,緊跟了一條defer語句。其中攜帶的表達式語句表示的是對被打開文件的關閉操做。注意,當這條defer語句被執行的時候,其中的這條表達式語句並不會被當即執行。它的確切的執行時機是在其所屬的函數(這裏是readFile)的執行即將結束的那個時刻。也就是說,在readFile函數真正結束執行的前一刻,file.Close()纔會被執行。這也是defer語句被如此命名的緣由。咱們在結合上下文以後就能夠看出,語句defer file.Close()的含義是在打開文件並讀取其內容後及時地關閉它。該語句能夠保證在readFile函數將結果返回給調用方以前,那個文件或目錄必定會被關閉。這其實是一種很是便捷和有效的保險措施。
   
    更爲關鍵的是,不管readFile函數正常地返回告終果仍是因爲在其執行期間有運行時恐慌發生而被剝奪了流程控制權,其中的file.Close()都會在該函數即將退出那一刻被執行。這就更進一步地保證了資源的及時釋放。
   
    注意,當一個函數中存在多個defer語句時,它們攜帶的表達式語句的執行順序必定是它們的出現順序的倒序。下面的示例能夠很好的證實這一點:spa

func deferIt() {
    defer func() {
        fmt.Print(1)
    }()
    defer func() {
        fmt.Print(2)
    }()
    defer func() {
        fmt.Print(3)
    }()
    fmt.Print(4)
}

    deferIt函數的執行會使標準輸出上打印出4321。請你們猜想下面這個函數被執行時向標準輸出打印的內容,並真正執行它以驗證本身的猜想。最後論證一下本身的猜想爲何是對或者錯的。code

func deferIt2() {
    for i := 1; i < 5; i++ {
        defer fmt.Print(i)
    }
}

    最後,對於defer語句,我還有兩個特別提示:
   
    1. defer攜帶的表達式語句表明的是對某個函數或方法的調用。這個調用可能會有參數傳入,好比:fmt.Print(i + 1)。若是表明傳入參數的是一個表達式,那麼在defer語句被執行的時候該表達式就會被求值了。注意,這與被攜帶的表達式語句的執行時機是不一樣的。請揣測下面這段代碼的執行:ci

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))
    }
}

    它在被執行以後,標準輸出上打印出1 2 3 4 40 30 20 10 。
   
    2. 若是defer攜帶的表達式語句表明的是對匿名函數的調用,那麼咱們就必定要很是警戒。請看下面的示例:資源

func deferIt4() {
    for i := 1; i < 5; i++ {
        defer func() {
            fmt.Print(i)
        }()
    }
}

    deferIt4函數在被執行以後標出輸出上會出現5555,而不是4321。緣由是defer語句攜帶的表達式語句中的那個匿名函數包含了對外部(確切地說,是該defer語句以外)的變量的使用。注意,等到這個匿名函數要被執行(且會被執行4次)的時候,包含該defer語句的那條for語句已經執行完畢了。此時的變量i的值已經變爲了5。所以該匿名函數中的打印函數只會打印出5。正確的用法是:把要使用的外部變量做爲參數傳入到匿名函數中。修正後的deferIt4函數以下:string

func deferIt4() {
    for i := 1; i < 5; i++ {
        defer func(n int) {
            fmt.Print(n)
        }(i)
    }
}

    請你們自行驗證一下它的正確性。io

 

總結:class

1. defer表達式在其外函數運行結束前的最後一刻執行。import

2.注意,當一個函數中存在多個defer語句時,它們攜帶的表達式語句的執行順序必定是它們的出現順序的倒序。匿名函數

(defer後的函數放在棧中,在函數運行到最後在逐條出棧運行)

 

擴展:打印出斐波那契數列的前10個數

package main

import (
    "fmt"
)

func main() {
	for i := 0; i < 10; i++ {
		fmt.Printf("%d ", fibonacci(i))
	}
}

func fibonacci(num int) int {
	if num == 0 {
		return 0
	}
	if num < 2 {
		return 1
	}
	return fibonacci(num-1) + fibonacci(num-2)
}


使用 defer語句,逆序打出上面的結果

package main

import (
    "fmt"
)

func main() {
    var r int
	for i := 0; i < 10; i++ {
	    r = fibonacci(i)
		fmt.Printf("%d ", r)
		defer func(n int){
		    fmt.Printf("%d ", n)
		}(r)
	}
}

func fibonacci(num int) int {
	if num == 0 {
		return 0
	}
	if num < 2 {
		return 1
	}
	return fibonacci(num-1) + fibonacci(num-2)
}

輸出結果以下

第一次輸出結果:0 1 1 2 3 5 8 13 21 34

第二次輸出結果:0 1 1 2 3 5 8 13 21 34 34 21 13 8 5 3 2 1 1 0
相關文章
相關標籤/搜索