Golang全面深刻系列之 defer

 

前言

    你們都知道go語言的defer功能很強大,對於資源管理很是方便,可是若是沒用好,也會有陷阱哦。Go 語言中延遲函數 defer 充當着 try...catch 的重任,使用起來也很是簡便,那麼defer、return、返回值、panic 之間的執行順序是怎麼樣的呢,下面咱們就一點一點來揭開它的神祕面紗!話很少說了,來一塊兒看看介紹吧。函數

 

Defer介紹

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後執行(倒序執行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

 

defer / return / 返回值 / panic之間的執行順序

如今咱們分兩種狀況分析它們的執行順序:

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做用域

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

 

defer的實際用途

不管什麼業務或程序, 延遲都用在執行函數調用的地方。

舉例:

後期更新

相關文章
相關標籤/搜索