- 原文地址:Part 12: Variadic Functions
- 原文作者:Naveen R
- 译者:咔叽咔叽 转载请注明出处。
什么是变参函数
变参函数是一个可以接受参数数量可变的函数。
语法
如果函数的最后一个参数用...T表示,那么该函数最后一个参数可以接受任意数量的T类型的参数。
注意,只允许函数的最后一个参数为可变参数。
示例
append函数可以增加任意数量的参数给切片。变参函数就是用到了切片的这个原理。
func append(slice []Type, elems ...Type) []Type
以上是append函数的定义。在这个定义中,elems是一个可变参数。通过这个语法,append可以接受可变数量的参数。
来创建一个变参函数。我们将编写一个查找输入列表中是否存在指定整数的简单程序。
package main
import (
"fmt"
)
func find(num int, nums ...int) {
fmt.Printf("type of nums is %T\n", nums)
found := false
for i, v := range nums {
if v == num {
fmt.Println(num, "found at index", i, "in", nums)
found = true
}
}
if !found {
fmt.Println(num, "not found in ", nums)
}
fmt.Printf("\n")
}
func main() {
find(89, 89, 90, 95)
find(45, 56, 67, 45, 90, 109)
find(78, 38, 56, 98)
find(87)
}
在上面的程序中,func find(num int, nums ...int)接收可变数量的参数nums。在函数find中,nums的类型等价于[]int,即整数切片。
变参函数的原理是将传递的可变参数转换为一个新切片。例如,在上面程序的第 22 中,find函数的可变参数是 89, 90, 95。find函数需要一个可变的int参数。因此,这三个参数将由编译器转换为 int 型的[] int {89,90,95}切片,然后它将被传递给find函数。
上述程序将打印,
type of nums is []int
89 found at index 0 in [89 90 95]
type of nums is []int
45 found at index 2 in [56 67 45 90 109]
type of nums is []int
78 not found in [38 56 98]
type of nums is []int
87 not found in []
上面程序的第 25 行,find函数调用只有一个参数。我们没有向可变参数传递任何值。这是完全合法的,在这种情况下,nums是一个长度和容量都为 0 的切片。
使用切片作为变参函数的参数
我们将一个切片传递给一个变参函数,并看看下面的例子会发生什么。
package main
import (
"fmt"
)
func find(num int, nums ...int) {
fmt.Printf("type of nums is %T\n", nums)
found := false
for i, v := range nums {
if v == num {
fmt.Println(num, "found at index", i, "in", nums)
found = true
}
}
if !found {
fmt.Println(num, "not found in ", nums)
}
fmt.Printf("\n")
}
func main() {
nums := []int{89, 90, 95}
find(89, nums)
}
在第 23 行,我们将一个切片传递给一个变参函数。
上面的程序将会出现编译错误main.go:23: cannot use nums (type []int) as type int in argument to find
为什么这个例子不能运行呢?因为find函数的签名如下所示,
func find(num int, nums ...int)
根据变参函数的定义,nums ...int意味着它将接受 int 类型的可变数量的参数。
在第 23 行中,nums作为可变参数传递给find函数。正如我们已经讨论过的,这些可变参数将被转换为 int 类型的切片,因为 find 需要可变的 int 参数。在这个例子中,nums已经是一个 int 切片了,并且还尝试使用 nums 去创建切片,即编译器尝试执行
find(89, []int{nums})
因为 nums 是[] int而不是 int,所以会失败。
那么有没有办法将切片传递给可变参数函数?当然可以。
有一个语法糖可用于将切片传递给变参函数。就是在切片后使用...。如果这样做,切片将直接传递给函数,而不会创建新切片。
上述代码中,我们用find(89, nums...)替换find(89, nums),程序将运行并打印,
type of nums is []int
89 found at index 0 in [89 90 95]
这是完整的程序供参考。
package main
import (
"fmt"
)
func find(num int, nums ...int) {
fmt.Printf("type of nums is %T\n", nums)
found := false
for i, v := range nums {
if v == num {
fmt.Println(num, "found at index", i, "in", nums)
found = true
}
}
if !found {
fmt.Println(num, "not found in ", nums)
}
fmt.Printf("\n")
}
func main() {
nums := []int{89, 90, 95}
find(89, nums...)
}
注意事项
当你在变参函数中修改切片时,请确保知道自己在做什么。
来看个例子。
package main
import (
"fmt"
)
func change(s ...string) {
s[0] = "Go"
}
func main() {
welcome := []string{"hello", "world"}
change(welcome...)
fmt.Println(welcome)
}
你觉得上面的代码会输出什么?如果你觉得是 [Go world],那么恭喜你!你已经理解了变参函数和切片。如果你弄错了,没什么大不了的,让我解释一下我们为什么是这个输出。
上面程序的第 13 行,我们使用语法糖...并将切片作为可变参数传递给change函数。
正如我们已经讨论的那样,如果使用了...,welcome切片本身将作为参数传递,而不会创建新的切片。因此welcome将作为参数传递给change函数。
在change函数内部,切片的第一个元素更改为 Go。因此该程序输出
[Go world]
这是另一个了解变参函数的程序。
package main
import (
"fmt"
)
func change(s ...string) {
s[0] = "Go"
s = append(s, "playground")
fmt.Println(s)
}
func main() {
welcome := []string{"hello", "world"}
change(welcome...)
fmt.Println(welcome)
}
这个作为练习让你弄清楚上面的程序是如何工作的:)。