Go變數作用域精講及程式碼實戰

techlead_krischang發表於2024-06-11

關注作者,復旦AI博士,分享AI領域與雲服務領域全維度開發技術。擁有10+年網際網路服務架構、AI產品研發經驗、團隊管理經驗,同濟本復旦碩博,復旦機器人智慧實驗室成員,國家級大學生賽事評審專家,發表多篇SCI核心期刊學術論文,阿里雲認證的資深架構師,專案管理專業人士,上億營收AI產品研發負責人。


精講Go語言中區域性作用域、全域性作用域、塊作用域、包作用域、函式作用域的定義、記憶體管理和併發使用,提供豐富示例,幫助讀者編寫高效、安全的程式碼。

file

1. 變數的作用域概述

在程式設計中,變數的作用域(Scope)定義了變數在程式中的可見性和生命週期。理解變數的作用域對於編寫健壯且可維護的程式碼至關重要。Go語言(簡稱Go)提供了幾種不同的作用域型別,使得開發者可以靈活地控制變數的可見範圍和生命週期。本章節將詳細概述Go語言中變數的各種作用域,幫助讀者更好地理解和應用這些概念。

1.1 作用域的型別

在Go語言中,主要有以下幾種作用域型別:

作用域型別 描述 示例
區域性作用域 變數在函式或程式碼塊內部宣告,僅在該函式或程式碼塊內可見。 func main() { var x int = 10 }
全域性作用域 變數在包級別宣告,在同一包內的所有檔案中都可見。 var y int = 20
塊作用域 變數在程式碼塊(例如迴圈或條件語句)內部宣告,僅在該程式碼塊內可見。 for i := 0; i < 10; i++ { var z int = i }
函式作用域 函式內的變數,僅在函式體內可見。 func foo() { var a int = 30 }
包作用域 包級別的變數宣告,在整個包範圍內可見。 package main; var b int = 40

1.2 作用域的可見性和生命週期

不同作用域型別決定了變數的可見性和生命週期:

  1. 區域性作用域

    • 可見性:區域性變數僅在宣告它們的函式或程式碼塊內可見。
    • 生命週期:區域性變數的生命週期從它們被宣告開始,到函式或程式碼塊執行完畢為止。
  2. 全域性作用域

    • 可見性:全域性變數在同一包內的所有檔案中都可見。
    • 生命週期:全域性變數在程式啟動時被分配記憶體,並在程式結束時釋放。
  3. 塊作用域

    • 可見性:塊作用域的變數僅在相應的程式碼塊內可見。
    • 生命週期:塊作用域的變數從程式碼塊開始執行到結束時結束。
  4. 函式作用域

    • 可見性:函式作用域的變數僅在函式體內可見。
    • 生命週期:函式作用域的變數從函式呼叫開始到函式返回時結束。
  5. 包作用域

    • 可見性:包作用域的變數在整個包範圍內可見。
    • 生命週期:包作用域的變數在包被載入時初始化,並在程式結束時釋放。

1.3 作用域與記憶體管理

不同作用域的變數在記憶體管理上也有所不同:

  • 區域性變數:通常分配在棧上,函式或程式碼塊執行完畢後自動釋放。
  • 全域性變數:通常分配在堆上,直到程式結束時才釋放。
  • 塊變數:與區域性變數類似,通常分配在棧上,塊執行完畢後釋放。
  • 函式變數:類似於區域性變數,在棧上分配並在函式結束後釋放。
  • 包變數:與全域性變數類似,通常在堆上分配,直到程式結束。

1.4 作用域的實際應用

理解不同作用域的應用場景對於編寫高效程式碼至關重要:

  • 區域性變數適用於臨時儲存和區域性計算,避免全域性變數的命名衝突。
  • 全域性變數適用於跨函式共享資料,但要小心避免資料競爭和不必要的記憶體佔用。
  • 塊變數適用於迴圈和條件判斷中的臨時資料儲存。
  • 函式變數適用於封裝函式內部邏輯,保證變數的私有性和安全性。
  • 包變數適用於包內共享資料,實現模組化設計。

透過合理使用不同作用域,開發者可以有效管理變數的生命週期和可見性,提高程式碼的可維護性和效能。

1.5 作用域的常見問題與除錯技巧

處理變數作用域時,可能遇到以下常見問題:

  • 變數遮蔽:內層作用域的變數名與外層作用域相同,導致外層變數被遮蔽。
  • 作用域汙染:不合理使用全域性變數,導致命名衝突和意外修改。
  • 生命週期管理:誤用區域性變數和全域性變數,導致記憶體洩漏或效能問題。

除錯技巧包括:

  • 使用偵錯程式逐步檢查變數的值和生命週期。
  • 利用編譯器警告和錯誤資訊,及時發現作用域問題。
  • 編寫單元測試,驗證不同作用域下變數的行為。

2. 區域性作用域

區域性作用域是指變數在函式或程式碼塊內部宣告,其作用範圍僅限於該函式或程式碼塊。理解區域性作用域對於編寫安全、高效且可維護的程式碼至關重要。在本章節中,我們將詳細探討區域性作用域的定義、記憶體管理及在併發環境中的使用。

2.1 區域性作用域的定義

區域性變數是在函式或程式碼塊內部宣告的變數。它們只能在宣告它們的作用範圍內訪問,離開該範圍後,這些變數將不再可見。區域性變數的作用域通常較小,生命週期也較短,這使得它們在使用時非常高效。

  1. 函式內部的區域性變數

    • 這些變數在函式體內宣告,僅在函式體內可見。它們的生命週期從函式呼叫開始,到函式返回時結束。
    • 示例:
    func main() {
        var x int = 10
        fmt.Println("x in main:", x) // 輸出: x in main: 10
    }
    
  2. 程式碼塊內部的區域性變數

    • 這些變數在程式碼塊(如條件語句、迴圈語句)內部宣告,僅在該程式碼塊內可見。它們的生命週期從程式碼塊開始執行,到程式碼塊結束時結束。
    • 示例:
    func main() {
        if true {
            var y int = 20
            fmt.Println("y in if block:", y) // 輸出: y in if block: 20
        }
        // fmt.Println("y outside if block:", y) // 編譯錯誤: y 未定義
    }
    
  3. 巢狀作用域

    • 區域性作用域可以巢狀,一個函式或程式碼塊內部可以包含多個巢狀的程式碼塊,每個程式碼塊都有自己的區域性變數。
    • 示例:
    func main() {
        var x int = 10
        if x > 5 {
            var y int = 20
            if y > 15 {
                var z int = 30
                fmt.Println("z in nested if block:", z) // 輸出: z in nested if block: 30
            }
            // fmt.Println("z outside nested if block:", z) // 編譯錯誤: z 未定義
        }
        // fmt.Println("y outside if block:", y) // 編譯錯誤: y 未定義
    }
    

區域性變數的優點

  1. 避免命名衝突:由於區域性變數的作用範圍有限,它們不會與全域性變數或其他函式的區域性變數發生命名衝突。
  2. 記憶體管理高效:區域性變數通常分配在棧上,函式或程式碼塊執行完畢後自動釋放,記憶體管理非常高效。
  3. 程式碼可讀性強:區域性變數使得變數的作用範圍明確,增強了程式碼的可讀性和可維護性。

2.2 記憶體管理

區域性變數通常分配在棧上。當函式或程式碼塊執行完畢後,這些區域性變數會被自動釋放。這種記憶體管理方式使得區域性變數的分配和釋放非常高效。

func calculate() int {
    var result int = 0
    for i := 0; i < 10; i++ {
        result += i
    }
    return result
}

func main() {
    sum := calculate()
    fmt.Println("Sum:", sum) // 輸出: Sum: 45
}

calculate函式中,變數resulti都是區域性變數,它們的記憶體分配在棧上。當calculate函式執行完畢後,這些變數會被自動釋放。

2.3 併發環境中的區域性變數

在Go語言中,併發程式設計是其一大特性。在併發環境中使用區域性變數可以避免資料競爭,因為每個goroutine都有自己獨立的棧空間,區域性變數不會在不同的goroutine之間共享。

package main

import (
    "fmt"
    "sync"
)

func printNumber(wg *sync.WaitGroup, num int) {
    defer wg.Done()
    fmt.Println("Number:", num)
}

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go printNumber(&wg, i)
    }

    wg.Wait()
}

在上述示例中,每個printNumber函式呼叫都會在新的goroutine中執行,num作為區域性變數不會在不同的goroutine之間共享,確保了併發執行的安全性。

3. 全域性作用域

全域性作用域指的是在包級別宣告的變數,它們在同一包內的所有檔案中都可見。全域性變數的使用需要謹慎,因為它們的生命週期貫穿整個程式執行過程,如果管理不當,可能會導致命名衝突、資料競爭等問題。在本章節中,我們將詳細探討全域性作用域的定義、記憶體管理及在併發環境中的使用。

3.1 全域性作用域的定義

全域性變數是在包級別宣告的變數,這些變數在包內的所有檔案中都可見,並且它們的生命週期從程式啟動開始,到程式結束時結束。全域性變數可以在包的任意位置宣告,一般在包級別的開頭宣告。

  1. 包級別宣告

    • 全域性變數通常在包的開頭宣告,使得包內所有檔案都可以訪問這些變數。
    • 示例:
    package main
    
    import "fmt"
    
    var globalVar int = 100 // 全域性變數
    
    func main() {
        fmt.Println("globalVar in main:", globalVar) // 輸出: globalVar in main: 100
    }
    
  2. 跨檔案訪問

    • 全域性變數可以在同一包內的不同檔案中訪問。這對於共享資料或狀態資訊非常有用。
    • 示例:
    // file1.go
    package main
    
    var sharedVar int = 200 // 全域性變數
    
    // file2.go
    package main
    
    import "fmt"
    
    func printSharedVar() {
        fmt.Println("sharedVar in printSharedVar:", sharedVar) // 輸出: sharedVar in printSharedVar: 200
    }
    
    func main() {
        printSharedVar()
    }
    

全域性變數的優點

  1. 跨檔案共享資料:全域性變數可以在包內的所有檔案中共享資料或狀態資訊,方便模組化程式設計。
  2. 永續性:全域性變數的生命週期貫穿程式執行始終,適用於需要持久儲存的資料。

3.2 記憶體管理

全域性變數通常分配在堆上。由於全域性變數的生命週期從程式啟動到程式結束,記憶體管理需要特別注意,確保沒有不必要的記憶體佔用。

package main

import "fmt"

var counter int = 0 // 全域性變數

func increment() {
    counter++
}

func main() {
    for i := 0; i < 10; i++ {
        increment()
    }
    fmt.Println("Final counter value:", counter) // 輸出: Final counter value: 10
}

在上述示例中,變數counter是全域性變數,生命週期貫穿整個程式執行過程。當increment函式被呼叫時,counter的值會遞增。

3.3 併發環境中的全域性變數

在Go語言中,併發程式設計是其一大特性。全域性變數在併發環境中需要特別小心,因為多個goroutine可能會同時訪問和修改全域性變數,從而導致資料競爭和不一致性。

package main

import (
    "fmt"
    "sync"
)

var counter int = 0 // 全域性變數
var mu sync.Mutex   // 互斥鎖

func increment(wg *sync.WaitGroup) {
    defer wg.Done()
    mu.Lock()   // 加鎖
    counter++
    mu.Unlock() // 解鎖
}

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go increment(&wg)
    }

    wg.Wait()
    fmt.Println("Final counter value:", counter) // 輸出: Final counter value: 10
}

在上述示例中,counter是一個全域性變數,為了在併發環境中安全地訪問和修改它,我們使用了互斥鎖(sync.Mutex)來避免資料競爭。

4. 塊作用域

塊作用域(Block Scope)是指在特定程式碼塊(如條件語句、迴圈語句等)內部宣告的變數,其作用範圍僅限於該程式碼塊。塊作用域變數在宣告它們的程式碼塊外部不可見。理解塊作用域對於編寫高效且可維護的程式碼非常重要。在本章節中,我們將詳細探討塊作用域的定義、記憶體管理及在不同程式碼結構中的使用。

1. 塊作用域的定義

塊作用域指的是變數在程式碼塊內部宣告,其作用範圍僅限於該程式碼塊。程式碼塊可以是由大括號 {} 包圍的一段程式碼,如函式、條件語句、迴圈語句等。塊作用域變數的生命週期從程式碼塊開始到程式碼塊結束。

  1. 條件語句中的塊作用域

    • 在條件語句(如 ifelse ifelse)內部宣告的變數,其作用範圍僅限於該條件語句塊。
    • 示例:
    package main
    
    import "fmt"
    
    func main() {
        x := 10
        if x > 5 {
            y := 20
            fmt.Println("y in if block:", y) // 輸出: y in if block: 20
        }
        // fmt.Println("y outside if block:", y) // 編譯錯誤: y 未定義
    }
    
  2. 迴圈語句中的塊作用域

    • 在迴圈語句(如 forrange)內部宣告的變數,其作用範圍僅限於該迴圈語句塊。
    • 示例:
    package main
    
    import "fmt"
    
    func main() {
        for i := 0; i < 3; i++ {
            msg := "Iteration"
            fmt.Println(msg, i) // 輸出: Iteration 0, Iteration 1, Iteration 2
        }
        // fmt.Println(msg) // 編譯錯誤: msg 未定義
    }
    
  3. 巢狀塊作用域

    • 塊作用域可以巢狀,一個程式碼塊內部可以包含多個巢狀的程式碼塊,每個程式碼塊都有自己的區域性變數。
    • 示例:
    package main
    
    import "fmt"
    
    func main() {
        x := 10
        if x > 5 {
            y := 20
            if y > 15 {
                z := 30
                fmt.Println("z in nested if block:", z) // 輸出: z in nested if block: 30
            }
            // fmt.Println("z outside nested if block:", z) // 編譯錯誤: z 未定義
        }
        // fmt.Println("y outside if block:", y) // 編譯錯誤: y 未定義
    }
    

塊作用域的優點

  1. 避免命名衝突:由於塊作用域變數的作用範圍有限,它們不會與其他塊或函式的變數發生命名衝突。
  2. 記憶體管理高效:塊作用域變數通常分配在棧上,程式碼塊執行完畢後自動釋放,記憶體管理非常高效。
  3. 程式碼可讀性強:塊作用域使得變數的作用範圍明確,增強了程式碼的可讀性和可維護性。

2. 記憶體管理

塊作用域變數通常分配在棧上。當程式碼塊執行完畢後,這些變數會被自動釋放。這種記憶體管理方式使得塊作用域變數的分配和釋放非常高效。

package main

import "fmt"

func calculateSum() int {
    sum := 0
    for i := 1; i <= 10; i++ {
        sum += i
    }
    return sum
}

func main() {
    result := calculateSum()
    fmt.Println("Sum:", result) // 輸出: Sum: 55
}

在上述示例中,變數 sumi 都是在 for 迴圈語句塊內部宣告的塊作用域變數,它們的記憶體分配在棧上,for 迴圈執行完畢後,這些變數會被自動釋放。

3. 塊作用域在不同程式碼結構中的使用

塊作用域在條件語句中非常有用,因為它們可以限制變數的作用範圍,使得變數只在條件成立時存在。

package main

import "fmt"

func main() {
    x := 5
    if x < 10 {
        message := "x is less than 10"
        fmt.Println(message) // 輸出: x is less than 10
    } else {
        message := "x is 10 or more"
        fmt.Println(message)
    }
    // fmt.Println(message) // 編譯錯誤: message 未定義
}

在上述示例中,變數 messageifelse 塊中分別宣告,具有各自獨立的作用域。

**迴圈語句中的塊作用域

在迴圈語句中使用塊作用域變數,可以確保每次迭代都有獨立的變數例項,避免變數狀態被意外修改。

package main

import "fmt"

func main() {
    for i := 0; i < 5; i++ {
        count := i * 2
        fmt.Println("Count:", count) // 輸出: Count: 0, 2, 4, 6, 8
    }
    // fmt.Println("Count outside loop:", count) // 編譯錯誤: count 未定義
}

在上述示例中,變數 countfor 迴圈的每次迭代中宣告,並且每次迭代都是一個新的例項。

**巢狀程式碼塊中的塊作用域

使用巢狀程式碼塊可以有效地管理變數的作用範圍,避免變數的命名衝突。

package main

import "fmt"

func main() {
    total := 0
    for i := 1; i <= 3; i++ {
        partial := i * 10
        {
            temp := partial + 5
            fmt.Println("Temp:", temp) // 輸出: Temp: 15, 25, 35
        }
        // fmt.Println("Temp outside nested block:", temp) // 編譯錯誤: temp 未定義
    }
}

在上述示例中,變數 temp 僅在巢狀的程式碼塊內可見,離開該塊後即不可見。

5. 包作用域

包作用域(Package Scope)是指變數在包級別宣告,其作用範圍覆蓋整個包,即同一個包中的所有檔案都可以訪問這些變數。包作用域在Go語言中非常重要,因為它有助於實現模組化程式設計和程式碼的可維護性。在本章節中,我們將詳細探討包作用域的定義、記憶體管理及其在不同程式碼結構中的使用。

5.1 包作用域的定義

包作用域變數是在包級別宣告的,這些變數在同一個包中的所有檔案中都可見。包作用域變數的生命週期從包被載入開始,到程式結束時結束。通常,包作用域變數在包的頂層宣告。

  1. 包級別宣告

    • 包作用域變數通常在包的開頭或檔案的最頂層宣告,使得包內所有檔案都可以訪問這些變數。
    • 示例:
    package main
    
    import "fmt"
    
    var packageVar int = 100 // 包作用域變數
    
    func main() {
        fmt.Println("packageVar in main:", packageVar) // 輸出: packageVar in main: 100
    }
    
  2. 跨檔案訪問

    • 包作用域變數可以在同一個包內的不同檔案中訪問,這對於共享資料或狀態資訊非常有用。
    • 示例:
    // file1.go
    package main
    
    var sharedVar int = 200 // 包作用域變數
    
    // file2.go
    package main
    
    import "fmt"
    
    func printSharedVar() {
        fmt.Println("sharedVar in printSharedVar:", sharedVar) // 輸出: sharedVar in printSharedVar: 200
    }
    
    func main() {
        printSharedVar()
    }
    

包作用域的優點

  1. 跨檔案共享資料:包作用域變數可以在包內的所有檔案中共享資料或狀態資訊,方便模組化程式設計。
  2. 永續性:包作用域變數的生命週期從包載入到程式結束,適用於需要持久儲存的資料。

5.2 記憶體管理

包作用域變數通常分配在堆上。由於包作用域變數的生命週期從程式啟動到程式結束,記憶體管理需要特別注意,確保沒有不必要的記憶體佔用。

package main

import "fmt"

var counter int = 0 // 包作用域變數

func increment() {
    counter++
}

func main() {
    for i := 0; i < 10; i++ {
        increment()
    }
    fmt.Println("Final counter value:", counter) // 輸出: Final counter value: 10
}

在上述示例中,變數counter是包作用域變數,其生命週期貫穿整個程式執行過程。當increment函式被呼叫時,counter的值會遞增。

5.3 包作用域在不同程式碼結構中的使用

模組化程式設計中的包作用域

包作用域在模組化程式設計中非常重要,它可以將相關的功能和資料封裝在一個包中,實現高內聚、低耦合的設計。

// config.go
package config

var AppName string = "MyApp" // 包作用域變數
var Version string = "1.0"

// main.go
package main

import (
    "fmt"
    "config"
)

func main() {
    fmt.Println("App Name:", config.AppName) // 輸出: App Name: MyApp
    fmt.Println("Version:", config.Version)  // 輸出: Version: 1.0
}

在上述示例中,config包中的變數AppNameVersion具有包作用域,可以在main包中訪問,從而實現配置的集中管理。

包作用域與初始化函式

包作用域變數可以與初始化函式(init函式)結合使用,在程式開始時進行必要的初始化操作。

package main

import "fmt"

var configVar string

func init() {
    configVar = "Initialized" // 初始化包作用域變數
}

func main() {
    fmt.Println("configVar:", configVar) // 輸出: configVar: Initialized
}

在上述示例中,init函式在程式啟動時自動執行,對包作用域變數configVar進行初始化。

包作用域與併發程式設計

在併發程式設計中,包作用域變數需要特別小心,因為多個goroutine可能會同時訪問和修改包作用域變數,從而導致資料競爭和不一致性。

package main

import (
    "fmt"
    "sync"
)

var counter int = 0 // 包作用域變數
var mu sync.Mutex   // 互斥鎖

func increment(wg *sync.WaitGroup) {
    defer wg.Done()
    mu.Lock()   // 加鎖
    counter++
    mu.Unlock() // 解鎖
}

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go increment(&wg)
    }

    wg.Wait()
    fmt.Println("Final counter value:", counter) // 輸出: Final counter value: 10
}

在上述示例中,counter是一個包作用域變數,為了在併發環境中安全地訪問和修改它,我們使用了互斥鎖(sync.Mutex)來避免資料競爭。

6. 函式作用域

函式作用域(Function Scope)指的是在函式內部宣告的變數,其作用範圍僅限於該函式。這些變數在函式外部不可見,離開函式後即被銷燬。函式作用域在Go語言中非常重要,因為它可以有效地管理變數的生命週期,避免命名衝突和記憶體洩漏。在本章節中,我們將詳細探討函式作用域的定義、記憶體管理及其在不同程式碼結構中的使用。

6.1 函式作用域的定義

函式作用域是指在函式內部宣告的變數,這些變數只能在該函式內部訪問,函式執行結束後,這些變數就會被銷燬。函式作用域的變數包括函式引數、區域性變數以及在函式內部宣告的任何其他變數。

  1. 函式內部宣告的變數

    • 這些變數只能在宣告它們的函式內部訪問,生命週期從函式呼叫開始,到函式返回結束。
    • 示例:
    package main
    
    import "fmt"
    
    func calculate(a int, b int) int {
        sum := a + b // sum 是函式作用域變數
        return sum
    }
    
    func main() {
        result := calculate(3, 4)
        fmt.Println("Result:", result) // 輸出: Result: 7
    }
    
  2. 函式引數

    • 函式引數也是函式作用域的一部分,它們在函式呼叫時被傳遞,在函式內部使用。
    • 示例:
    package main
    
    import "fmt"
    
    func greet(name string) {
        message := "Hello, " + name // name 是函式引數,具有函式作用域
        fmt.Println(message)
    }
    
    func main() {
        greet("Alice") // 輸出: Hello, Alice
    }
    

函式作用域的優點

  1. 避免命名衝突:由於函式作用域變數的作用範圍僅限於函式內部,它們不會與其他函式的變數發生命名衝突。
  2. 記憶體管理高效:函式作用域變數通常分配在棧上,函式執行完畢後自動釋放,記憶體管理非常高效。
  3. 程式碼可讀性強:函式作用域使得變數的作用範圍明確,增強了程式碼的可讀性和可維護性。

6.2 記憶體管理

函式作用域變數通常分配在棧上。當函式執行完畢後,這些變數會被自動釋放。這種記憶體管理方式使得函式作用域變數的分配和釋放非常高效。

記憶體分配示例

package main

import "fmt"

func factorial(n int) int {
    if n == 0 {
        return 1
    }
    return n * factorial(n-1)
}

func main() {
    result := factorial(5)
    fmt.Println("Factorial:", result) // 輸出: Factorial: 120
}

在上述示例中,n 是函式 factorial 的引數,其記憶體分配在棧上,函式執行完畢後自動釋放。

6.3 函式作用域在不同程式碼結構中的使用

巢狀函式中的函式作用域

Go語言支援在一個函式內部宣告另一個函式,這使得函式作用域可以巢狀使用。

package main

import "fmt"

func outerFunction() {
    outerVar := "I am outside!"
    
    func innerFunction() {
        innerVar := "I am inside!"
        fmt.Println(outerVar) // 輸出: I am outside!
        fmt.Println(innerVar) // 輸出: I am inside!
    }

    innerFunction()
    // fmt.Println(innerVar) // 編譯錯誤: innerVar 未定義
}

func main() {
    outerFunction()
}

在上述示例中,innerFunction 是在 outerFunction 內部宣告的巢狀函式。outerVarouterFunction 的區域性變數,但在 innerFunction 中可見,而 innerVar 僅在 innerFunction 內部可見。

閉包中的函式作用域

閉包是指在其詞法作用域內引用了自由變數的函式。Go語言中的閉包可以捕獲並記住其外層函式中的變數。

package main

import "fmt"

func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
        fmt.Println(pos(i))  // 累加正數
        fmt.Println(neg(-2*i)) // 累加負數
    }
}

在上述示例中,adder 函式返回一個閉包,該閉包捕獲了外層函式的變數 sum,並在多次呼叫中累加 sum 的值。

6.4 函式作用域與併發程式設計

在併發程式設計中,函式作用域變數對於保證資料安全和避免資料競爭非常重要。每個 goroutine 都有自己的函式作用域,因此函式內部的區域性變數在不同的 goroutine 之間不會共享。

package main

import (
    "fmt"
    "sync"
)

func printNumbers(wg *sync.WaitGroup, start int) {
    defer wg.Done()
    for i := start; i < start+5; i++ {
        fmt.Println(i)
    }
}

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 3; i++ {
        wg.Add(1)
        go printNumbers(&wg, i*10)
    }

    wg.Wait()
}

在上述示例中,每個 printNumbers 函式呼叫在不同的 goroutine 中執行,且 istart 變數均具有函式作用域,保證了併發執行的安全性。

如有幫助,請多關注
TeahLead KrisChang,10+年的網際網路和人工智慧從業經驗,10年+技術和業務團隊管理經驗,同濟軟體工程本科,復旦工程管理碩士,阿里雲認證雲服務資深架構師,上億營收AI產品業務負責人。

相關文章