你們都知道go語言的defer功能很強大,對於資源管理很是方便,可是若是沒用好,也會有陷阱哦。Go 語言中延遲函數 defer 充當着 try...catch 的重任,使用起來也很是簡便,那麼defer、return、返回值、panic 之間的執行順序是怎麼樣的呢,下面咱們就一點一點來揭開它的神祕面紗!話很少說了,來一塊兒看看介紹吧。函數
defer語句用於函數在返回以前執行函數調用。這個定義可能看起來很複雜,但經過一個例子很容易理解。spa
package main import ( "fmt" ) func finished() { fmt.Println("finished") } func largest() { defer finished() fmt.Println("largest()執行") } func main() { largest() }
也就是在func結束(return)以前執行的動做。不管定義在普通語句的先後。指針
defer一樣支持方法的調用。code
package main import ( "fmt" ) type person struct { firstName string lastName 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 main import ( "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.003171200 defer 2018-03-16 14:43:20.1033124 +0800 CST m=+0.003003600 P 2018-03-16 14:43:25.142031 +0800 CST m=+5.041722200
簡單的理解爲先聲明的defer後執行(倒序執行defer語句)。string
package main import ( "fmt" ) func main() { name := "Naveen" fmt.Printf("Orignal String: %s\n", string(name)) for _, v := range []rune(name) { defer fmt.Printf("%c", v) } }
輸出爲: neevaNit
如今咱們分兩種狀況分析它們的執行順序:
1. return前將返回值賦值
2. 檢查是否有defer並執行
3. 最後return 攜帶返回值退出函數
第一種: 匿名返回值
package main import ( "fmt" ) func main() { fmt.Println("a return:", a()) // 打印結果爲 a return: 0 } func a() int { var i int defer 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: 1 a defer2: 2 a return: 0
解釋: 匿名返回值是在return以前被聲明( 鑑於類型緣由,類型零值爲0 ), defer沒法訪問匿名的返回值,所以返回值是0, 而defer仍是操做以前定義好的變量i。
試想:若是此處返回值爲指針類型,那麼輸出結果會不會有變化?
第二種:命名返回值
package main import ( "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: 1 a defer2: 2 a return: 2
解釋: 命名返回值是 在函數聲明的同時被聲明。所以defer能夠訪問命名返回值。return返回後的值實際上是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 main import ( "fmt" ) func main() { fmt.Println("...") go func() { panic("err") }() // defer 執行 defer fmt.Println("defer1") defer fmt.Println("defer2") defer fmt.Println("defer3") }
不管什麼業務或程序, 延遲都用在執行函數調用的地方。
舉例:
後期更新