golang的defer踩坑彙總

原文鏈接:http://www.zhoubotong.site/post/50.html

defer語句用於延遲函數調用,每次會把一個函數壓入棧中,函數返回前再把延遲的函數取出並執行。延遲函數可以有參數:

  • 延遲函數的參數在defer語句出現時就已確定下來(傳值的就是當前值)

  • return先賦值(對於命名返回值),然後執行defer,最後函數返回

  • 延遲函數執行按後進先出順序執行

  • 延遲函數可操作主函數的變量名返回值(修改返回值)

  • defer後面的表達式可以是func或者是method的調用,如果defer的函數爲nil,則會panic

日常開發中,使用不當很容易造成意外的“坑”。下面我整理了下常規使用場景下,defer的問題可能的踩坑彙總。

釋放資源

defer 語句正好是在函數退出時執行的語句,所以使用 defer 能非常方便地處理資源釋放、句柄關閉等問題。

package main
import (
	"fmt"
	"os"
)
func fileSize(filename string) int64 {
	f, err := os.Open(filename)
	if err != nil {
		return 0
	}
	// 延遲調用Close, 此時Close不會被調用
	defer f.Close()
	info, err := f.Stat()
	if err != nil {
		// defer機制觸發, 調用Close關閉文件
		return 0
	}
	size := info.Size()
	// defer機制觸發, 調用Close關閉文件
	return size
}
func main() {
	fmt.Println(fileSize("demo.txt"))
}

變量捕獲

defer中的變量會被提前捕獲,後續的修改不會影響到已捕獲的值,舉個例子:

package main
import (
	"fmt"
)
func main() {
	i := 0
	defer fmt.Println("Defer運行值:", i)
	i = 10 // 這裏雖然修改了值,但是不會影響上面的i值
	fmt.Println("最後輸出值:", i)
}

結果defer語句中打印的值是修改前的值。:

最後輸出值: 10

Defer運行值: 0

變量名返回值

在defer中修改具體變量名返回值時,會影響到函數的實際返回值,繼續舉個例子:

package main

import (
    "fmt"
)

func ShowDefer() {
    fmt.Println("最後輸出值:", deferValue())
}
func deferValue() (ret int) { // 注意這裏返回res變量值
    ret = 0

    defer func() { // 會直接修改棧中對應的返回值
        ret += 10
        fmt.Println("Defer 運行值:", ret)
    }()
    ret = 2
    fmt.Println("Ret重置值:", ret)
    return //返回的ret最後是 其實是本次2+上面的ret+=10的結果
}

func main() {
    ShowDefer()
}

//Ret重置值: 2
//Defer 運行值: 12
//最後輸出值: 12

非變量名返回值

當函數爲非具體名返回值時,defer無法影響返回值(因在return時,對應返回值已存入棧中),繼續舉個例子:

package main

import (
    "fmt"
)

func ShowDefer() {
    fmt.Println("最後輸出值:", deferValue())
}
func deferValue() int { // 非命名變量返回
    ret := 0
    defer func() {
    ret += 10
    fmt.Println("Defer 運行值:", ret)
}()
    ret = 2
    return ret // 這裏直接返回ret2
}

func main() {
    ShowDefer()
}

//Defer 運行值: 12
//最後輸出值: 2

經過上面的實踐理解,我們來看下下面的筆試題:

筆試題一

package main

import "fmt"

func f() (result int) {
    defer func() {
    result *= 7
}()
    return 3
}
func main() {
    fmt.Println(f())
}

問題解析:這裏return先給result賦值爲3,之後執行defer,result變爲21,最後返回21。

筆試題二

package main

import "fmt"

func f() int {
    result := 3
    defer func() {
        result *= 7
    }()
    return result
}

func main() {
    fmt.Println(f())
} 

問題解析:這裏return確定返回值3,之後defer才修改result,最後函數返回return確定的返回值3。

筆試題三

package main

import "fmt"
// 多個defer
func multiDefer() {
    for i := 3; i > 0; i-- {
    defer func(n int) {
        fmt.Print(n, " ")
    }(i)
    }

    for i := 33; i > 30; i-- {
        defer fmt.Print(i, " ")
    }
}

func main() {
    multiDefer()
}  

問題解析:多個defer函數,按順序逆序執行,這裏輸出31 32 33 1 2 3 。

筆試題四

package main

import "fmt"

var fun func() string

func main() {
    fmt.Println("hello monkey")
    defer fun()
} 

問題解析:由於這裏的defer指定的func爲nil,所以會panic 。

筆試題五

package main

import "fmt"

func main() {
    for i := 3; i > 0; i-- {
        defer func() {
            fmt.Print(i, " ")
            }()
    }
} 

問題解析:這裏是極度容易踩坑的地方,由於defer這裏調用的func沒有參數,等執行的時候,i已經爲0(按3 2 1逆序,最後一個i=1時,i--的結果最後是0),所以這裏輸出3個0 。

如果還不太好理解?

package main

import "fmt"

func main() {
    for i := 3; i > 1; i-- { // 循環滿足條件的是 3 2,
        defer func() { // 因爲func 沒有參數,defer運行最後i--即 2-- 結果爲 1
            fmt.Print(i, " ") // 循環2次 結果均爲 1
        }()
    }
}//輸出 1 1  

按照常規的思維理解應該是這樣:

package main

import "fmt"

func main() {
    for i := 3; i > 0; i-- {
    defer func(i int) {
        fmt.Print(i, " ")
        }(i)
    }
} 
感興趣的朋友可以細細品下。


  

  

  

  

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章