Go 零基础教程

Go 变长参数函数

在 Go 语言中,变长参数函数(Variadic functions) 提供了一种极其灵活的方式,让函数能够接收数量不定的参数。当你事先不知道一个函数需要处理多少个输入值时,这个特性就显得尤为重要。

理解如何定义和使用变长参数函数,是你编写更具适应性、更高复用性代码的关键。

本章将建立在上一章函数基础之上,带你全面探索 Go 语言中变长参数函数的语法、使用技巧以及实战应用。

1. 理解变长参数函数

变长参数函数是指其最后一个参数可以接收零个或多个值的函数。在 Go 语言中,实现这一点非常简单:只需在函数签名最后一个参数的类型前面,加上省略号 ... 即可。

1.1 语法与声明

声明变长参数函数的基础语法如下:

func myFunc(arg1 type1, arg2 type2, variadicArg ...type3) returnType {
    // 函数体逻辑
}

在这个语法结构中:

  • arg1arg2 是普通的固定参数。
  • variadicArg 是变长参数,它可以接收零个或多个 type3 类型的值。
  • 核心机制: 在函数体内部,这个变长参数 variadicArg 会被 Go 语言自动当成一个切片 (Slice) []type3 来处理。

1.2 示例:数字求和

让我们来写一个简单的变长参数函数,它可以对任意数量的整数进行求和:

package main

import "fmt"

// sum 函数计算不定数量整数的总和。
func sum(numbers ...int) int {
    total := 0
    // 在函数内部,numbers 被当作 []int 切片来遍历
    for _, num := range numbers {
        total += num
    }
    return total
}

func main() {
    fmt.Println(sum())           // 输出: 0 (传入 0 个参数)
    fmt.Println(sum(1, 2, 3))    // 输出: 6 (传入 3 个参数)
    fmt.Println(sum(4, 5, 6, 7)) // 输出: 22 (传入 4 个参数)
}

在这个例子中:

  • sum 函数接收了一个变长参数 numbers ...int
  • 在函数内部,numbers 就是一个整数切片 []int
  • 函数通过 for...range 循环遍历这个切片,并将所有数字累加。

2. 将切片传递给变长参数函数

如果你手里已经有了一个现成的切片,你想把它直接传给变长参数函数该怎么办?你可以再次使用 ... 操作符将切片**“展开” (Unfurl)** 成一个个独立的参数。

package main

import "fmt"

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

func main() {
    nums := []int{10, 20, 30}
    
    // 使用 nums... 将切片展开并传递给变长参数函数
    fmt.Println(sum(nums...)) // 输出: 60
}

在这个例子中:

  • 我们把切片 nums 传递给 sum 函数时,写成了 nums...
  • 后缀 ... 操作符会把切片“打散”,变成 10, 20, 30 这三个独立的参数送入函数。

3. 实战案例演示

为了展示变长参数函数的强大通用性,我们来看看更多实际开发中的例子。

3.1 格式化字符串

变长参数函数经常被用来格式化字符串,Go 标准库里的 fmt.Printf 就是最经典的例子。

package main

import "fmt"

// format 函数将一个格式化模板和任意数量的参数结合起来。
func format(formatString string, args ...interface{}) string {
    return fmt.Sprintf(formatString, args...)
}

func main() {
    name := "Alice"
    age := 30
    message := format("姓名: %s, 年龄: %d", name, age)
    
    fmt.Println(message) // 输出: 姓名: Alice, 年龄: 30
}

在这个例子中:

  • format 函数接收一个普通的字符串参数 formatString,以及一个变长参数 args ...interface{}
  • interface{}(空接口)代表任意数据类型。这意味着你可以传数字、字符串、布尔值等任何东西进来。
  • 函数内部调用了 fmt.Sprintf,并用 args... 将接收到的参数原封不动地展开传了进去。

3.2 寻找最大值

另一个极其常见的用例是找出一堆数字里的最大值。

package main

import "fmt"

// max 找出不定数量整数中的最大值。
func max(numbers ...int) int {
    if len(numbers) == 0 {
        return 0 // 如果没有传入任何参数,返回 0
    }
    
    maxValue := numbers[0]
    for _, num := range numbers {
        if num > maxValue {
            maxValue = num
        }
    }
    return maxValue
}

func main() {
    fmt.Println(max(1, 5, 2, 8, 3)) // 输出: 8
    fmt.Println(max())              // 输出: 0
}

在这个例子中,max 函数遍历接收到的所有数字,并不断更新 maxValue。如果没有传入任何参数(切片长度为 0),它会安全地返回 0。

3.3 拼接字符串

你还可以用它来将不定数量的字符串拼接在一起。

package main

import (
    "fmt"
    "strings"
)

// concatenate 使用指定的分隔符,将不定数量的字符串拼接起来。
func concatenate(separator string, stringsToJoin ...string) string {
	return strings.Join(stringsToJoin, separator)
}

func main() {
	result := concatenate("-", "apple", "banana", "cherry")
	fmt.Println(result) // 输出: apple-banana-cherry
	
	result = concatenate(",", "one", "two")
	fmt.Println(result) // 输出: one,two
	
	result = concatenate(" ", "hello", "world")
	fmt.Println(result) // 输出: hello world
}

这里 concatenate 函数接收一个分隔符 separator,以及一个变长的字符串参数 stringsToJoin,然后利用强大的标准库函数 strings.Join 一次性完成拼接。