本文 AI 產出,尚未審核

Golang – 函數與方法

變長參數(Variadic Functions)


簡介

在日常開發中,我們常會遇到「參數個數不固定」的需求,例如日誌記錄、字串拼接或是統計計算。Go 語言提供了 變長參數(variadic) 的語法,讓函式可以接受任意數量的同類型參數,而不必事先定義多個重載版本。

變長參數不僅讓程式碼更簡潔,也提升了 API 的彈性與可讀性。對於剛踏入 Go 世界的初學者,掌握這項特性是邁向「寫得好、用得好」的關鍵一步;對於已有開發經驗的中級工程師,則能在設計通用函式庫時發揮更大的威力。


核心概念

1. 基本語法

在函式宣告的參數列表最後加上 ...,即可將該參數標記為變長。例如:

func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}
  • nums 會被編譯器自動轉換成 []int(切片)。
  • 變長參數 只能放在最後一個,且每個變長函式只能有 一個 變長參數。

2. 呼叫方式

fmt.Println(sum(1, 2, 3, 4))   // 直接列出參數
fmt.Println(sum())            // 空參數,返回 0

arr := []int{5, 6, 7}
fmt.Println(sum(arr...))      // 使用已有切片,必須加上 ...
  • 若已有切片想傳入變長參數,必須在切片名稱後加上 ...,告訴編譯器「把切片展開」成多個參數。

3. 變長參數與普通參數混用

func greet(prefix string, names ...string) {
    for _, n := range names {
        fmt.Printf("%s %s\n", prefix, n)
    }
}
  • prefix 為普通參數,必須先提供;之後才是任意數量的 names

4. 多個變長參數(不允許)

Go 不允許 同時宣告兩個變長參數,例如 func foo(a ...int, b ...string) 會編譯錯誤。若需要同時接受多種型別的多參數,可考慮使用 interface{} 或自訂結構切片。

5. 變長參數的底層是切片

在函式內部,變長參數實際上是一個 只讀的切片。因此可以直接使用切片的所有操作(len, cap, append 等),但要注意 不要在外部直接修改傳入的切片,除非你已經明確了解其共享的行為。


程式碼範例

以下示範 5 個實用範例,從簡單累加到較進階的日誌與錯誤收集。

範例 1:簡易加總

func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

// 使用
fmt.Println("Sum:", sum(1, 2, 3, 4, 5)) // Output: Sum: 15

說明nums 內部是 []int,迭代累加後回傳結果。


範例 2:字串拼接(類似 Python 的 join

func join(sep string, parts ...string) string {
    if len(parts) == 0 {
        return ""
    }
    result := parts[0]
    for _, p := range parts[1:] {
        result += sep + p
    }
    return result
}

// 使用
fmt.Println(join("-", "2023", "12", "19")) // Output: 2023-12-19

說明:第一個參數是分隔符,後面的 parts 為任意字串。若未提供任何字串,直接回傳空字串。


範例 3:日誌函式(支援任意參數)

func Log(level string, v ...interface{}) {
    // 取得時間戳記
    ts := time.Now().Format("2006-01-02 15:04:05")
    // 使用 fmt.Sprint 把任意型別組合成單一字串
    msg := fmt.Sprint(v...)
    fmt.Printf("%s [%s] %s\n", ts, level, msg)
}

// 使用
Log("INFO", "伺服器啟動於 port", 8080)
Log("ERROR", "無法連線至資料庫:", err)

說明...interface{} 讓 Log 可以接受任何型別的參數,類似 fmt.Println 的彈性。


範例 4:統計資料(平均值、最大值)

func stats(nums ...float64) (avg, max float64) {
    if len(nums) == 0 {
        return 0, 0
    }
    sum := 0.0
    max = nums[0]
    for _, n := range nums {
        sum += n
        if n > max {
            max = n
        }
    }
    avg = sum / float64(len(nums))
    return
}

// 使用
average, maximum := stats(3.5, 7.2, 4.8, 9.1)
fmt.Printf("Avg=%.2f Max=%.2f\n", average, maximum)
// Output: Avg=6.15 Max=9.10

說明:返回多個值時,直接使用命名返回值 avg, max,讓呼叫端更易讀。


範例 5:將切片展開傳入變長函式

func multiply(factor int, nums ...int) []int {
    result := make([]int, len(nums))
    for i, n := range nums {
        result[i] = n * factor
    }
    return result
}

// 已有切片
values := []int{2, 4, 6}
scaled := multiply(3, values...) // 必須加 ...
fmt.Println(scaled) // Output: [6 12 18]

說明values... 把切片展開成多個參數,等同於 multiply(3, 2, 4, 6)


常見陷阱與最佳實踐

陷阱 說明 解決方案
忘記在切片後加 ... 直接傳入切片會被視為單一參數,編譯錯誤。 必須使用 slice... 形式。
變長參數不是「可變長度」的切片 變長參數在函式內部是 只讀 的切片,若在函式內 append,不會影響呼叫端的原始切片。 若需要回傳新切片,直接 return 結果;若要修改原始切片,請傳入 []T 而非變長參數。
混用多個變長參數 Go 語法限制只能有一個變長參數。 使用 interface{} 或自訂結構體封裝多種型別。
過度使用 ...interface{} 會失去型別安全,編譯器無法檢查錯誤。 只在需要高度彈性的 API(如日誌、測試框架)中使用,其他情況盡量使用具體型別。
效能考量 每次呼叫變長函式都會產生一個切片,若在熱點迴圈中大量呼叫,可能產生 GC 壓力。 可先建立切片,再使用 slice... 呼叫;或在函式內部使用 append 前先 make 足夠容量的切片。

最佳實踐

  1. 保持單一型別:盡量讓變長參數的型別具體化(如 ...int...string),避免使用 ...interface{} 除非必要。
  2. 先檢查長度:在函式內部若對參數長度有特殊需求(如至少一個參數),務必先 if len(params) == 0 { … } 防止 panic。
  3. 文件化:在函式註解中說明變長參數的預期用途與限制,讓使用者快速了解。
  4. 避免在變長參數上直接 append:若需要動態擴充,建議在函式外部先建立切片,再傳入。

實際應用場景

  1. 日誌與除錯

    • Log(level string, v ...interface{}) 能一次接受多個資訊,讓開發者不必自行拼字串。
  2. 資料統計與分析

    • 統計函式(平均值、標準差、最大最小)常需要接受不定筆數的數值。
  3. 字串處理

    • 拼接 URL、產生 CSV 行、組合錯誤訊息等,都可以透過 join(sep string, parts ...string) 簡化。
  4. 測試框架

    • assert.Equal(t, expected, actual, msg ...string) 允許測試者自行添加說明訊息。
  5. API 客戶端

    • 建構 HTTP 請求時,AddHeaders(headers ...Header) 可一次加入多個自訂標頭。

總結

變長參數是 Go 語言中一個 簡潔且功能強大 的特性,讓函式能彈性接受任意數量的同型別參數。掌握其語法、呼叫方式以及底層切片的行為,能讓你在撰寫日誌、字串處理、統計計算等常見需求時,寫出更乾淨、可讀性更高的程式碼。

同時,了解常見的陷阱(如忘記 ...、過度使用 interface{})以及遵守最佳實踐,能避免效能與可維護性問題。將變長參數納入你的工具箱,未來在設計公共函式庫或 API 時,就能更自信地提供彈性且安全的介面。

祝你在 Golang 的旅程中,玩得開心、寫得順手! 🚀