Variadic Functions(可变参数函数)
什么是可变参数函数?
函数通常只接受固定数量的参数。可变参数函数是接受可变数量参数的函数。如果函数定义的最后一个参数以省略号*…*为前缀,则该函数可以接受该参数的任意数量的参数。
只有函数的最后一个参数可以是可变参数。我们将在本教程的下一部分了解为什么会出现这种情况。
句法
1 | func hello(a int, b ...int) { |
在上面的函数中,参数b
是可变参数,因为它以省略号为前缀,并且可以接受任意数量的参数。可以使用语法调用此函数。
1 | hello(1, 2) //passing one argument "2" to b |
在上面的代码中,在第 1 行,我们用一个2
给参数b
来调用参数hello
函数,并在下一行传递四个参数6, 7, 8, 9
给b
。
也可以将零参数传递给可变参数函数。
1 | hello(1) |
在上面的代码中,我们调用hello
函数,并用零参数修饰b
。这很好。
到现在为止,我想您应该已经理解了为什么可变参数应该只放在最后。
让我们尝试将hello
函数的第一个参数设为可变参数。
语法看起来像
1 | func hello(b ...int, a int) { |
在上面的函数中,不可能将参数传递给参数a
,因为我们传递的任何参数都将被分配给第一个参数b
,因为它是可变参数。因此,可变参数只能出现在函数定义的最后。上述函数将无法编译并出现错误syntax error: cannot use ... with non-final parameter b
示例和理解可变参数函数的工作原理
让我们创建自己的可变参数函数。我们将编写一个简单的程序来查找整数输入列表中是否存在整数。
1 | package main |
在上面的程序中,func find(num int, nums ...int)
在第 7 行,参数接受可变数量的nums
参数。在函数find内部,nums
的类型是 []int
即整数切片。
可变参数函数的工作方式是将可变数量的参数转换为可变参数类型的切片。例如,在上面的程序第 22 行中,find
函数的可变参数为 89、90、95。find 函数需要一个可变int
参数。因此,编译器会将这三个参数转换为 int 类型的切片[]int{89, 90, 95}
,然后将其传递给find
函数。
在第 10 行中,for
循环遍历nums
切片并打印num
它是否存在于切片中的位置。如果没有,它会打印出该num
未找到。
上述程序输出:
1 | type of nums is []int |
在上面程序的第25行,find函数的调用只有一个参数。我们没有将任何参数传递给可变nums ...int
参数。如前所述,这是完全合法的,在这种情况下,nums
它将是一个nil
并且长度和容量为 0 的切片。
切片参数与可变参数
我们现在肯定有一个问题萦绕在您的脑海中。在上一节中,我们了解到函数的可变参数实际上是转换为切片的。那么当我们可以使用切片实现相同的功能时,为什么还需要可变参数函数呢?我已经使用下面的切片重写了上面的程序。
1 | package main |
以下是使用可变参数而不是切片的优点。
- 无需在每次函数调用期间创建切片。如果你看过上面的程序,会发现我们在每个函数调用期间创建了新的切片。如22、23、24 和 25行。使用可变参数函数时可以避免这种额外的切片创建。
- 在上面程序的第 25 行,我们创建了一个空切片来满足
find
函数的签名。在可变参数函数的情况下,这完全不需要。这一行只需要在使用可变参数函数时调用find(87)
就可以。 - 我个人觉得带有可变参数的程序比带有切片的程序更具可读性:)
Append 是一个可变参数函数
你有没有想过标准库中用于将值附加到切片的append函数如何接受任意数量的参数。这是因为它是一个可变参数函数。
1 | func append(slice []Type, elems ...Type) []Type |
以上是append
函数的定义。在这个定义中elems
是一个可变参数。因此 append 可以接受可变数量的参数。
将切片传递给可变参数函数
让我们将切片传递给可变参数函数,并从下面的示例中找出发生了什么。
1 | package main |
在第23行中,我们将一个切片传递给一个需要可变数量参数的函数。
这行不通。上述程序将因编译错误而失败./prog.go:23:10: cannot use nums (type []int) as type int in argument to find
为什么这不起作用?嗯,这很简单。该find
函数的签名如下所示,
1 | func find(num int, nums ...int) |
根据可变参数函数的定义,nums ...int
意味着它将接受可变数量的类型参数int
。
在行号。上面程序的第 23 行,nums
即 []int
切片被传递给find
————声明中期望可变int
参数的函数。正如我们已经讨论过的,这些可变参数将被转换为int
类型切片,因为find
需要可变 int 参数。在这种情况下,nums
已经是一个[]int
切片并且编译器尝试创建一个新[]int
的,即编译器尝试做
1 | find(89, []int{nums}) |
这将失败,因为nums
是一个[]int
而不是一个 int
。
那么有没有办法将切片传递给可变参数函数?答案是肯定的。
有一种语法糖可用于将切片传递给可变参数函数。您必须使用省略号为切片添加后缀...
,如果这样做,切片将直接传递给函数,而无需创建新切片。
在上面的程序中,如果你在第 23 行将find(89, nums)
替换为 find(89, nums...)
,程序将编译并打印以下输出。
1 | type of nums is []int |
这是完整的程序供你参考。
1 | package main |
Gotcha
当您在可变参数函数中修改切片时,请确保您知道自己在做什么。
让我们看一个简单的例子。
1 | package main |
你认为上述程序的输出是什么?如果你认为是[Go world]
恭喜!您已经了解可变参数函数和切片。如果你弄错了,没什么大不了的,让我解释一下我们是如何得到这个输出的。
在行号上面程序的第 13 行中,我们使用语法糖...
并将切片作为可变参数传递给change
函数。
正如我们已经讨论过的,如果使用...
,welcome
切片本身将作为参数传递,而不会创建新切片。因此welcome
将作为参数传递给函数change
。
在 change 函数中,切片的第一个元素更改为Go
。因此这个程序输出
1 | [Go world] |
这是另一个理解可变参数函数的程序。
1 | package main |
我会把它留作练习,让你弄清楚上述程序是如何工作的:)。