Like Share Discussion Bookmark Smile

J.J. Huang   2025-03-03   Getting Started Golang 04.函式   瀏覽次數:次   DMCA.com Protection Status

Go | 進階函式參數與回傳值

💬 簡介

在 Go 語言中,函式是組織程式碼的重要單位,而參數與回傳值則是函式的核心組成部分。
本篇文章將深入探討 Go 語言中 進階函式參數與回傳值 的應用,包括:

  • 同類型參數的定義與應用
  • 可變參數(...)的使用
  • 空白標識符(_)的應用
  • 改變外部變數的方式(值傳遞與指標傳遞)
  • 命名返回值與多重回傳值

這些概念對於提升 Go 語言的函式設計能力至關重要,讓我們一同來探索吧!

圖片來源:Gophers


🔎 進階函式參數

1️⃣ 同類型參數的簡化定義

當函式的多個參數為相同型別時,可以省略部分型別標註,僅在最後一個參數標註型別。

  • 範例:簡化參數定義
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package main

    import "fmt"

    func add(a, b int) int {
    return a + b
    }

    func main() {
    fmt.Println(add(10, 20)) // 輸出 30
    }

    📝 ab 具有相同的 int 型別,因此僅需標註一次 int

2️⃣ 可變參數(Variadic Parameter)

Go 語言允許函式接受 不定數量的參數,透過 … 語法來定義可變參數(Variadic Parameter)。

  • 範例:可變參數函式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package main

    import "fmt"

    // 定義可變參數函式
    func sum(numbers ...int) int {
    total := 0
    for _, num := range numbers {
    total += num
    }
    return total
    }

    func main() {
    fmt.Println(sum(1, 2, 3, 4, 5)) // 輸出 15
    }

    📝 numbers ...int 允許傳入任意數量的 int,並以 切片(Slice) 形式接收。

  • 可變參數的特殊應用
    如果已有 切片(Slice),可使用 展開運算符(...) 傳遞 切片(Slice) 內容:
    1
    2
    nums := []int{10, 20, 30}
    fmt.Println(sum(nums...)) // 展開 slice 傳入

3️⃣ 空白標識符 _ 的應用

在 Go 語言中,空白標識符 _ 可以用來忽略不需要的參數或回傳值。

  • 範例:忽略函式回傳值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package main

    import "fmt"

    func getValues() (int, string, bool) {
    return 42, "hello", true
    }

    func main() {
    number, _, flag := getValues() // 忽略字串回傳值
    fmt.Println(number, flag) // 輸出 42 true
    }

    📝 _ 代表忽略的值,避免變數未使用的警告。


🎯 進階回傳值處理

1️⃣ 改變外部變數的方式:值傳遞與指標傳遞

在 Go 中,函式的參數預設為「值傳遞」,但可以使用「指標傳遞」來改變外部變數的值。

  • 範例:值傳遞

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package main

    import "fmt"

    func incrementByValue(num int) {
    num++
    }

    func main() {
    x := 10
    incrementByValue(x)
    fmt.Println(x) // 輸出 10,值未改變
    }

    📝 num 為值傳遞,修改 num 不影響 x

  • 範例:指標傳遞

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package main

    import "fmt"

    func incrementByPointer(num *int) {
    *num++
    }

    func main() {
    x := 10
    incrementByPointer(&x) // 傳遞變數 `x` 的位址
    fmt.Println(x) // 輸出 11,值已改變
    }

    📝 使用 *num 來修改記憶體位址上的值,使 x 變更。

🚨 當函式傳遞較大記憶體佔用的變數時,使用指標能顯著提高效能。然而,指標傳遞可能導致不確定的行為,因此要小心那些會改變外部變數的函式,並在必要時加上註解,讓其他人明白函式內部的變化。

2️⃣ 命名返回值與多重回傳值

Go 支援多重回傳值,並可使用「命名返回值」來簡化回傳變數的定義。

  • 範例:命名返回值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package main

    import "fmt"

    func divide(a, b float64) (result float64, err error) {
    if b == 0 {
    err = fmt.Errorf("除數不能為 0")
    return
    }
    result = a / b
    return
    }

    func main() {
    res, err := divide(10, 2)
    if err != nil {
    fmt.Println("錯誤:", err)
    } else {
    fmt.Println("結果:", res) // 輸出 5
    }
    }

    📝 resulterr 在函式內部已經預先定義,回傳時可以直接 return


⚠️ 注意事項

🔥 指標傳遞的注意事項

  • 指標傳遞雖然可以改變外部變數的值,但也可能引發 意想不到的副作用,需要小心:

    • 指向無效記憶體(nil pointer dereference):
      若指標未初始化或指向 nil,則會導致程式崩潰(panic)。
      1
      2
      var ptr *int
      *ptr = 10 // 會觸發 panic,因為 ptr 為 nil
    • 修改共享變數時需考慮同步問題(尤其在多執行緒情境):
      若多個 goroutine 同時修改同一個指標變數,可能會導致 競爭條件(Race Condition)
      1
      2
      3
      var counter int
      go func() { counter++ }() // 競爭條件
      go func() { counter++ }() // 競爭條件

      👉 建議: 若變數需要在多執行緒間共享,應使用 sync.Mutexsync/atomic 來確保同步。

📌 可變參數的影響

  • 可變參數會產生新的 slice,可能影響效能
    1
    2
    3
    func example(args ...int) {
    fmt.Println(len(args)) // args 其實是 `[]int`
    }

    📝 每次傳遞可變參數時,Go 會建立一個 新的 slice,若頻繁使用,可能會有額外的 記憶體分配開銷。

  • 當傳入 slice 時,需注意 slice 的可變性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    func modify(s ...int) {
    s[0] = 100
    }

    func main() {
    nums := []int{1, 2, 3}
    modify(nums...)
    fmt.Println(nums) // 可能輸出 [100 2 3]
    }

    👉 注意: s ...int 其實是 slice,因此函式內部修改 s[0] 會影響原始 nums

○ 空白標識符 _ 的誤用

  • 避免忽略錯誤(Error)
    1
    2
    value, _ := strconv.Atoi("abc")  // 忽略錯誤
    fmt.Println(value) // 預設為 0,但我們不知道是否發生錯誤

    👉 建議: 除非確定錯誤不影響邏輯,否則應該妥善處理錯誤回傳值。

  • 可能導致變數未使用的錯誤
    1
    2
    3
    func main() {
    _, name := getValues() // 變數 `name` 未使用
    }

    👉 解法: 若 name 沒有用到,可以直接忽略所有回傳值 _ = getValues(),或使用 fmt.Println(name) 避免編譯錯誤。


🎯總結

  • 參數 可以簡化定義,或使用 可變參數(…) 接收多個值。
  • 空白標識符 _ 可用於忽略回傳值,避免變數未使用的警告。
  • 指標傳遞 可用於修改外部變數,而 值傳遞 則不會影響原變數。
  • 多重回傳值 讓函式能夠返回多個結果,並可使用 命名返回值 簡化回傳邏輯。
    這些技巧能幫助我們更靈活地運用 Go 的函式機制,讓程式更具可讀性與擴展性!

最後建議回顧一下 Go | 菜鳥教學 目錄,了解其章節內容。


註:以上參考了
Go