Menu

Go 可变参数函数:...T 参数与 s... 展开

Go 的可变参数函数可以在末尾接收任意数量、同一类型的参数。学习 ...T 语法、如何用 s... 传入切片、共享切片的陷阱,以及 fmt.Println 如何使用 ...any。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

...T 参数

在最后一个参数的类型前加上 ...。这样调用方就可以传入任意数量的该类型参数,一个都不传也可以。在函数内部,这个参数是一个切片。

程序输出 0、5 和 10。没有参数时,nums 是一个长度为 0 的 nil 切片,所以循环根本不会执行。

普通参数可以放在前面,只有最后一个参数可以是可变参数:

func logf(level string, format string, args ...any)

用 s... 传入切片

如果你手上已经有一个切片,不能直接把它传进去:sum(nums) 会报错 cannot use nums (variable of type []int) as int value in argument to sum。在参数后面加上 ... 把它展开:

append 也是可变参数函数,所以拼接两个切片的写法是 append(a, b...)。

可变参数的位置上只能放这一个展开的切片。sum(1, nums...) 会报错 too many arguments in call to sum。先用 append 构造出合并后的切片。

切片是共享的,不是复制的

用单个参数调用时,Go 会为这些参数新建一个切片。用 s... 展开一个已有的切片时,函数收到的就是同一个切片。修改它的元素会改变调用方的数据。

输出:

[7 8 9]
[0 8 9]

会修改参数的可变参数函数应该在文档里写明这一点,或者先用 slices.Clone 复制一份。大多数可变参数函数只读取参数,所以这个问题很少出现,但在有些语言里剩余参数总是一个新数组,这一点和它们确实不同。

...any 与 fmt.Println 的原理

fmt.Println 的声明是 func Println(a ...any) (n int, err error)。any(从 Go 1.18 起是 interface{} 的别名)可以接收任何类型,所以你可以在一次调用里传入字符串、数字和结构体。

关键在于转发:logf 把 args... 传给 Printf。如果漏掉这三个点,Printf 只会收到一个参数,也就是一个 []any,警告那一行会打印成 [WARN] [3 8] of %!d(MISSING) workers idle。只有当格式参数被原样传递时,比如 fmt.Printf(format, args),go vet 才会报告 missing ... in args forwarded to printf-like function。这里的格式字符串是用 + 拼出来的,所以 vet 不会提示,这三个点得靠你自己记住。

[]string 不能展开传给 ...any。元素类型必须完全一致,所以要先转换:

names := []string{"a", "b"}
args := make([]any, len(names))
for i, n := range names {
	args[i] = n
}
fmt.Println(args...)

要求至少传一个参数

可变参数可以接收零个参数。如果函数至少需要一个参数,就把第一个参数写成普通参数:

这样 maxOf() 就会变成编译错误,而不需要在运行时检查。内置的 max 也是这样设计的:它至少需要一个参数。

用可变参数模拟可选参数

Go 没有参数默认值。有时人们会用可变参数来模拟一个可选参数:

func connect(addr string, timeout ...time.Duration)

这种写法能用,但可读性差:调用方可以传入三个超时时间,而函数签名并没有说明哪个生效。如果有不止一个选项,更好的做法是用一个配置结构体,或者使用函数式选项模式(...Option,每个 Option 都是一个设置某个字段的函数)。这种模式也是可变参数,但每个参数的含义都很清楚。

规则速查

规则示例
只有最后一个参数可以是可变参数func f(a string, b ...int)
在函数内部,参数是一个切片b 的类型是 []int
不传参数时得到 nil 切片f("x") 使 b == nil
用 ... 展开切片f("x", nums...)
展开时共享底层数组写入 b[i] 会改变 nums
展开不能和额外的值混用f("x", 1, nums...) 无法通过编译

常见问题

Go 中的可变参数函数是什么?

最后一个参数写成 ...T 形式的函数,例如 func sum(nums ...int) int。调用时可以传入零个或多个 T 类型的参数,在函数内部,这个参数是一个 []T 切片。

在 Go 中如何把切片传给可变参数函数?

在切片后面加上 ...:sum(nums...)。切片会原样传入,不会复制,所以函数看到的是同一个底层数组。展开和单个参数不能混用:sum(1, nums...) 无法通过编译。

Go 有展开运算符吗?

只有可变参数调用的最后一个参数可以展开:f(s...)。Go 没有通用的展开语法,不能用它构造数组或结构体,也不能用切片调用非可变参数函数。append(a, b...) 就是把同一条规则用在内置的 append 上。

一个 Go 函数可以有两个可变参数吗?

不可以。只有最后一个参数能是可变参数,而且每个函数只能有一个。其他列表请作为普通的切片参数传入。

Coddy 编程语言插图

和 Coddy 一起学编程

开始