Go語言函數的延遲調用(Deferred Code)詳解

先解釋一下這篇Blog延期的原因,本來已經準備好瞭全部內容,但是當我重新回顧實例三的時候,發現自己還是存在認知不足的地方,於是為瞭準確表述,查閱瞭大量的資料,重新編寫瞭第三部分,導致延期。感謝持續關註本筆記更新的朋友,後期我將逐步通過3-5分鐘視頻方式為大傢對筆記內容進行講解,幫助更多的朋友能夠快速掌握Go語言的基礎。

本節將介紹Go語言函數和方法中的延遲調用,正如名稱一樣,這部分定義不會立即執行,一般會在函數返回前再被調用,我們通過下面的幾個示例來瞭解一下延遲調用的使用場景。

基本功能

在以下這段代碼中,我們操作一個文件,無論成功與否都需要關閉文件句柄。這裡在三處不同的位置都調用瞭file.Close()方法,代碼顯得非常冗餘。

func ReadWrite() bool {
    file.Open("file")
    // Do your thing
    if failureX {
        file.Close()
        return false
    }

    if failureY {
        file.Close()
        return false
    }
    file.Close()
    return true
}

我們利用延遲調用來優化代碼。定義後的defer代碼,會在return之前返回,讓代碼顯得更加緊湊,且可讀性變強,對上面的代碼改造如下:

func ReadWrite() bool {
    file.Open("filename")
    // Define a defer code here
    defer file.Close()
    // Do your thing
    if failureX {
        return false
    }
    if failureY {
        return false
    }
    return true
}

示例一:延遲調用執行順序

我們通過這個示例來看一下延遲調用與正常代碼之間的執行順序

package main

import "fmt"

func TestDefer(x int) {
    defer fmt.Println("Defer code called")
    switch x {
    case 1:
        fmt.Println("Case 1 triggered!")
        return
    case 10:
        fmt.Println("Case 10 triggered!")
        return
    default:
        fmt.Println("Case default triggered!")
        return
    }
}

func main() {
    TestDefer(100)
    TestDefer(1)
    TestDefer(10)
}

先簡單分析一下代碼邏輯:

  • 首先定義瞭一個公共的TestDefer函數,這個函數接受一個整型的參數
  • 函數體內定義瞭defer部分,會輸出一句Defer code called
  • switch case會根據輸入的整型參數,輸出相應的trigger語句
  • 按照上面對延遲調用的分析,每次滿足case語句後,才會輸出Defer code called

從輸出中,我們可以觀察到如下現象:

  • 首次執行,default條件滿足,Case default triggered先輸出,再輸出defer內容
  • 第二次調用,1條件滿足,最後輸出defer內容
  • 第三次調用,10條件滿足,最後輸出defer內容

從這個實例中,我們很明顯觀察到,defer語句是在return之前執行

Case default triggered!
Defer code called
Case 1 triggered!
Defer code called
Case 10 triggered!
Defer code called

示例二:多defer使用方法

package main

import "fmt"

func TestDefer(x int) {
    defer fmt.Println("1st defined Defer code called")
    defer fmt.Println("2nd defined Defer code called")
    defer fmt.Println("3rd defined Defer code called")
    switch x {
    case 1:
        fmt.Println("Case 1 triggered!")
        return
    case 10:
        fmt.Println("Case 10 triggered!")
        return
    default:
        fmt.Println("Case default triggered!")
        return
    }
}

func main() {
    TestDefer(100)
}

仍然是相同的例子,但是在TestDefer中我們定義瞭三個defer輸出,根據LIFO原則,輸出的順序是3rd->2nd->1st,根據最後的結果,也是逆向向上執行defer輸出。

Case default triggered!
3rd defined Defer code called
2nd defined Defer code called
1st defined Defer code called

實例三:defer與局部變量、返回值的關系

就在整理這篇筆記的時候,發現瞭自己的認知誤區,主要是本節實例三中發現的,先來看一下英文的描述:

A defer statement pushes a function call onto a list. The list of saved calls is executed after the surrounding function returns. Defer is commonly used to simplify functions that perform various clean-up actions.

對於上面的這段話的理解:

defer定義的函數會被放入list中

存儲的defer函數會在周邊函數返回後執行

defer一般用於環境清理

原則一:defer函數的參數值,取決於defer函數調用時變量的值

package main

import "fmt"

func a() int {
    i := 0
    fmt.Printf("func i = %v\n", i)
    defer fmt.Printf("defer i = %v\n", i)
    i++
    fmt.Printf("func i = %v\n", i)
    defer fmt.Printf("defer after i++ = %v\n", i)
    return i
}

func main() {
    i := a()
    fmt.Printf("main i = %v\n", i)
}

下面是代碼執行輸出,我們來一起分析一下:

  • 在函數a中,定義瞭局部變量i
  • 在函數執行過程中進行瞭自增操作i++
  • 分別在i++前後,對i值進行瞭輸出,也就是我們下面輸出結果前兩行,與預期一致
  • 分別在i++前後,定義兩個defer語句,都是用fmt輸出i的值,輸出的順序與示例二的邏輯一致,先輸出的是defer after,再輸出defer
  • 根據原則一,在defer after的輸出中,由於i++完成自增,所以當時i的值已經變為瞭1,所以輸出為1
  • 同樣是根據原則一,在defer的輸出中,i並沒有進行自增,所以在當時情況下,i的值仍然為0,所以輸出為0
  • 最後返回的i值為1,主函數中輸出i的值為1
func i = 0
func i = 1
defer after i++ = 1
defer i = 0
main i = 1

原則二:defer可以讀取或修改顯示定義的返回值

package main

import "fmt"

func a() (i int) {
    fmt.Printf("func initial i = %v\n", i)
    defer func() {
        fmt.Printf("defer func initial i++ = %v\n", i)
        i++
        fmt.Printf("defer func after i++ = %v\n", i)
    }()
    fmt.Printf("func before return i = %v\n", i)
    return 10
}

func main() {
    i := a()
    fmt.Printf("main i = %v\n", i)
}

雖然在a()函數內,顯示的返回瞭10,但是main函數中得到的結果是defer函數自增後的結果,我們來分析一下代碼:

在a函數定義時,我們顯示的定義瞭返回變量i和類型int

在剛剛進入函數時,i的初始化值位0,返回前也是0

在最後的return時,直接返回瞭10

接著我們再來看defer函數執行情況,剛剛進入defer函數時,返回值i得到的值正是剛才返回的10

而在自增後,i的值變成瞭11

最後我們在主函數中,獲得的返回值也是11,印證瞭我們原則中的defer函數對於返回值的讀取和修改

func initial i = 0
func before return i = 0
defer func initial i++ = 10
defer func after i++ = 11
main i = 11

到此這篇關於Go語言函數的延遲調用(Deferred Code)詳解的文章就介紹到這瞭,更多相關Go 函數延遲調用內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: