首頁 > 軟體

Go語言函數的延遲呼叫(Deferred Code)詳解

2022-07-12 10:00:02

先解釋一下這篇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 = %vn", i)
    defer fmt.Printf("defer i = %vn", i)
    i++
    fmt.Printf("func i = %vn", i)
    defer fmt.Printf("defer after i++ = %vn", i)
    return i
}

func main() {
    i := a()
    fmt.Printf("main i = %vn", 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 = %vn", i)
    defer func() {
        fmt.Printf("defer func initial i++ = %vn", i)
        i++
        fmt.Printf("defer func after i++ = %vn", i)
    }()
    fmt.Printf("func before return i = %vn", i)
    return 10
}

func main() {
    i := a()
    fmt.Printf("main i = %vn", 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 函數延遲呼叫內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com