這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
前言
大家都知道go語言的defer功能很強大,對於資源管理非常方便,但是如果沒用好,也會有陷阱哦。Go 語言中延遲函數 defer 充當著 try...catch 的重任,使用起來也非常簡便,那麼defer、return、傳回值、panic 之間的執行順序是怎麼樣的呢,下面我們就一點一點來揭開它的神秘面紗!話不多說了,來一起看看介紹吧。
Defer介紹
defer語句用於函數在返回之前執行函數調用。這個定義可能看起來很複雜,但通過一個例子很容易理解。
package mainimport ("fmt")func finished() {fmt.Println("finished")}func largest() {defer finished()fmt.Println("largest()執行")}func main() {largest()}
也就是在func結束(return)之前執行的動作。無論定義在普通語句的前後。
defer同樣支援方法的調用。
package mainimport ("fmt")type person struct {firstName stringlastName string}func (p person) fullName() {fmt.Printf("%s %s",p.firstName,p.lastName)}func main() {p := person {firstName: "John",lastName: "Smith",}defer p.fullName()fmt.Printf("Welcome ")}
PS: defer聲明時會先計算確定參數的值,defer延遲執行的僅是其函數體
import ( "fmt")func printA(a int) { fmt.Println("value of a in deferred function", a)}func main() { a := 5 defer printA(a) a = 10 fmt.Println("value of a before deferred function call", a)}
當defer被聲明時, 其參數a值就會被即時解析,並不執行函數體內內容, 那麼後續的修改並不會影響當前參數值。
驗證結果:
package mainimport ("fmt""time")func main() {defer P(time.Now())time.Sleep(5*time.Second)fmt.Println("main ", time.Now())}func P(t time.Time) {fmt.Println("defer", t)fmt.Println("P ", time.Now())}
執行結果:
main 2018-03-16 14:43:25.10348 +0800 CST m=+5.003171200defer 2018-03-16 14:43:20.1033124 +0800 CST m=+0.003003600P 2018-03-16 14:43:25.142031 +0800 CST m=+5.041722200
defer後進先出
簡單的理解為先聲明的defer後執行(倒序執行defer語句)。
package mainimport ( "fmt")func main() { name := "Naveen" fmt.Printf("Orignal String: %s\n", string(name)) for _, v := range []rune(name) { defer fmt.Printf("%c", v) }}
輸出為: neevaN
defer / return / 傳回值 / panic之間的執行順序
現在我們分兩種情況分析它們的執行順序:
1. return前將傳回值賦值
2. 檢查是否有defer並執行
3. 最後return 攜帶傳回值退出函數
第一種: 匿名傳回值
package mainimport ("fmt")func main() {fmt.Println("a return:", a()) // 列印結果為 a return: 0}func a() int {var i intdefer func() {i++fmt.Println("a defer2:", i) // 列印結果為 a defer2: 2}()defer func() {i++fmt.Println("a defer1:", i) // 列印結果為 a defer1: 1}()return i}
輸出結果:
a defer1: 1a defer2: 2a return: 0
解釋: 匿名傳回值是在return之前被聲明( 鑒於類型原因,類型零值為0 ), defer無法訪問匿名的傳回值,因此傳回值是0, 而defer還是操作之前定義好的變數i。
試想:如果此處傳回值為指標類型,那麼輸出結果會不會有變化?
第二種:命名傳回值
package mainimport ("fmt")func main() {fmt.Println("a return:", a()) // 列印結果為 b return: 2}func a() (i int) {defer func() {i++fmt.Println("a defer2:", i) // 列印結果為 b defer2: 2}()defer func() {i++fmt.Println("a defer1:", i) // 列印結果為 b defer1: 1}()return i // 或者直接 return 效果相同}
輸出結果:
a defer1: 1a defer2: 2a return: 2
解釋: 命名傳回值是 在函式宣告的同時被聲明。因此defer可以訪問命名傳回值。return返回後的值其實是defer修改後的值。
defer範圍
defer在什麼環境下就不會執行?下面列舉幾個例子:
1. 當任意一條(主)協程發生 panic 時,會執行當前協程中 panic 之前已聲明的 defer;
func main() {fmt.Println("...")panic(e) // defer 不會執行defer fmt.Println("defer")}
2. 主動調用 os.Exit(int) 退出進程時,defer 將不再被執行。
func main() {fmt.Println("...")os.Exit(1) // defer 不會執行defer fmt.Println("defer")}
func main() {fmt.Println("...")defer fmt.Println("defer")os.Exit(1) // defer 不會執行}
3.在發生 panic 的(主)協程中,如果沒有一個 defer 調用 recover()進行恢複,則會在執行完之前已聲明的 defer 後,引發整個進程崩潰;
func main() {fmt.Println("...")defer fmt.Println("defer1")defer fmt.Println("defer2")defer fmt.Println("defer3")panic("error") // defer 會執行}
4. defer只對當前(主)協程有效
package mainimport ("fmt")func main() {fmt.Println("...")go func() { panic("err") }() // defer 執行defer fmt.Println("defer1")defer fmt.Println("defer2")defer fmt.Println("defer3")}
defer的實際用途
無論什麼業務或程式, 延遲都用在執行函數調用的地方。
舉例:
後期更新