首页 / Go 语言入门教程 / 变参函数

Go 语言入门教程

变参函数

本教程共 80 篇 · 第 23 篇 · 更新于 2026-07-27 · 约 6 分钟阅读

GoGo 入门教程变参函数可变参数切片展开fmt.PrintfGo 函数

23. 变参函数

本节目标:掌握 Go 变参函数的语法,学会传递切片给变参函数,理解变参函数的实际应用。

什么是变参函数

变参函数(Variadic function)是参数个数可变的函数。你在调用时可以传 0 个、1 个或多个值。

最典型的例子就是你一直在用的 fmt.Println

fmt.Println("a")
fmt.Println("a", "b")
fmt.Println("a", "b", "c", 1, 2, 3, true)

它接收任意数量、任意类型的参数。

声明变参函数

在参数类型前加 ... 就变成了变参:

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

nums ...int 表示 nums 是一个 []int 类型的切片。在函数内部,nums 就是个普通切片。

调用

func main() {
    fmt.Println(sum(1, 2, 3))       // 6
    fmt.Println(sum(10, 20))        // 30
    fmt.Println(sum())              // 0
    fmt.Println(sum(1, 2, 3, 4, 5)) // 15
}

你可以传任意数量的参数,甚至不传。

变参的本质

变参函数的参数在函数内部是一个切片

func printArgs(args ...string) {
    fmt.Printf("类型:%T\n", args)   // 类型:[]string
    fmt.Printf("长度:%d\n", len(args))
    for i, v := range args {
        fmt.Printf("args[%d] = %s\n", i, v)
    }
}

调用 printArgs("Go", "Python", "Rust") 输出:

类型:[]string
长度:3
args[0] = Go
args[1] = Python
args[2] = Rust

...string[]string 在函数内部完全等价,区别只在调用方式。

传递切片

如果你已经有一个切片,想传给变参函数,在切片后面加 ...

func main() {
    nums := []int{1, 2, 3, 4, 5}
    fmt.Println(sum(nums...))  // 15
}

nums... 表示把切片展开成一个个独立的参数。

Note

sum(nums...)sum(nums[0], nums[1], nums[2], nums[3], nums[4]) 是等价的,只是写法更简洁。

变参与普通参数混用

变参可以和普通参数一起用,但变参必须放在最后:

func printf(format string, args ...any) {
    fmt.Printf(format, args...)
}

func main() {
    printf("名字:%s,年龄:%d\n", "Go", 18)
}

format 是普通参数,args 是变参。调用时先传普通参数,剩下的都归变参。

错误示例

// 报错:变参不在最后
func bad(args ...int, name string) {}

变参必须是参数列表的最后一个。

实际示例

求最大值

func max(nums ...int) int {
    if len(nums) == 0 {
        return 0
    }
    m := nums[0]
    for _, n := range nums[1:] {
        if n > m {
            m = n
        }
    }
    return m
}

func main() {
    fmt.Println(max(3, 1, 4, 1, 5, 9, 2, 6))  // 9
}
Tip

Go 1.21 引入了内置函数 maxmin,现在你不需要自己写了。直接 max(3, 1, 4, 1, 5) 就行。

日志函数

func log(message string, args ...any) {
    timestamp := time.Now().Format("2006-01-02 15:04:05")
    fmt.Printf("[%s] %s\n", timestamp, fmt.Sprintf(message, args...))
}

func main() {
    log("用户 %s 登录", "admin")
    log("查询到 %d 条记录", 42)
    log("服务器启动")
}

拼接字符串

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
}

func main() {
    fmt.Println(join(", ", "Go", "Python", "Rust"))  // Go, Python, Rust
}
Note

实际开发中用 strings.Join 更高效,这里只是演示变参函数。

变参函数的注意事项

不传参数时是 nil 切片

func test(args ...int) {
    fmt.Println(args)        // []
    fmt.Println(args == nil) // true
}

func main() {
    test()  // 不传参数
}

不传参数时,argsnil,但 len(args) 是 0,range 也不会报错。

修改变参切片不影响原切片

func modify(nums ...int) {
    for i := range nums {
        nums[i] *= 2
    }
}

func main() {
    nums := []int{1, 2, 3}
    modify(nums...)
    fmt.Println(nums)  // [1 2 3],没有被修改
}

变参函数内部收到的是切片的副本,修改元素不影响原切片(除非元素是指针类型)。

但如果用 append 扩容了底层数组,原切片更不受影响:

func appendItem(nums ...int) []int {
    return append(nums, 999)
}

func main() {
    nums := []int{1, 2, 3}
    result := appendItem(nums...)
    fmt.Println(nums)    // [1 2 3]
    fmt.Println(result)  // [1 2 3 999]
}

传递切片时不会复制

func test(nums ...int) {
    fmt.Printf("内部地址:%p\n", nums)
}

func main() {
    nums := []int{1, 2, 3}
    fmt.Printf("外部地址:%p\n", nums)
    test(nums...)
}

虽然参数是值传递,但切片底层包含指针,所以 nums... 传递时不会复制底层数组,只是复制了切片头(指针、长度、容量)。

any 类型变参

fmt.Printf 的签名是 func Printf(format string, a ...any),用 any 接收任意类型的参数。

any 是 Go 1.18 引入的 interface{} 的别名:

type any = interface{}

...any 可以让函数接收任意类型、任意数量的参数:

func printAll(args ...any) {
    for _, v := range args {
        fmt.Printf("%v (%T)\n", v, v)
    }
}

func main() {
    printAll("hello", 42, 3.14, true)
}
// 输出:
// hello (string)
// 42 (int)
// 3.14 (float64)
// true (bool)

常见问题

传切片忘了加 …

nums := []int{1, 2, 3}
sum(nums)  // 报错:cannot use nums as int

变参函数期望的是一个个 int,你传了一个 []int,类型不匹配。需要用 nums... 展开。

变参传给变参

func outer(args ...int) {
    inner(args...)  // 正确,用 ... 展开
}

func inner(args ...int) {
    // ...
}

要把变参传递给另一个变参函数,加 ... 展开就行。

变参和 nil

func test(args ...int) {
    // ...
}

test(nil)  // 报错:cannot use nil as int

不能直接传 nil。如果需要传空,不传参数就行:test()

学完这节你能做什么

  • ...T 声明变参函数
  • slice... 传递切片给变参函数
  • 变参和普通参数混用
  • 理解变参在函数内部是切片
  • ...any 接收任意类型参数

下一节讲匿名函数和闭包。