什么是可变参数函数?

函数通常只接受固定数量的参数。可变参数函数是接受可变数量参数的函数。如果函数定义的最后一个参数是用省略号 … 作为前缀的,则该函数可以接受该参数的任意数量的参数。

函数的最后一个参数只能是可变参数。我们将在本教程的下一节中学习为什么会出现这种情况。


语法


  1. func hello(a int, b ...int) {
  2. }

在上面的函数中,参数 b 是可变的,因为它的前缀是省略号,可以接受任意数量的参数。可以使用语法调用此函数


  1. hello(1, 2) //passing one argument "2" to b
  2. hello(5, 6, 7, 8, 9) //passing arguments "6, 7, 8 and 9" to b

在上面的代码中,第一行我们将 2 传给参数 b 调用 hello 函数,下一行中我们将 6, 7, 8, 9 传给参数 b

也可以将零个参数传递给可变参数函数。

  1. hello(1)

在上面代码中,我们调用 hello 但是没有传递参数给 b

现在我猜你明白了为什么可变的参数要放在最后。

让我们尝试使 hello 函数的第一个参数为可变参数。

语法看起来像

  1. func hello(b ...int, a int) {
  2. }

在上面的函数中,不能将参数传递给参数 a,因为我们传递的任何参数都将分配给第一个参数 b,因为它是可变参数。因此可变参数只能出现在函数定义的最后。以上函数编译失败抛出错误 syntax error: cannot use ... with non-final parameter b


可变函数的示例和工作原理

我们来创建我们自己的可变参数函数。我们将编写一个简单的程序来查找整数列表中是否存在某个整数。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func find(num int, nums ...int) {
  6. fmt.Printf("type of nums is %T\n", nums)
  7. found := false
  8. for i, v := range nums {
  9. if v == num {
  10. fmt.Println(num, "found at index", i, "in", nums)
  11. found = true
  12. }
  13. }
  14. if !found {
  15. fmt.Println(num, "not found in ", nums)
  16. }
  17. fmt.Printf("\n")
  18. }
  19. func main() {
  20. find(89, 89, 90, 95)
  21. find(45, 56, 67, 45, 90, 109)
  22. find(78, 38, 56, 98)
  23. find(87)
  24. }

Run in playground

在上面的程序中,第 7 行 func find(num int, nums ...int) nums 参数接受可变数量的参数。在 find 函数中,nums 的类型是 []int ,即一个整数切片。

可变参数函数的工作方式是将可变数量的参数转换为变量参数类型的切片。例如,在上面程序的 22 行中,find 函数的参数的可变参数是 89, 90, 95。find 函数需要一个可变的 int 参数。因此,这三个参数将由编译器转换为 []int{89, 90, 95} int 类型的切片,然后它将被传递给 find 函数。

第 10 行, for 循环 nums 切片,如果切片中存在 num ,则打印 num 的位置。如果没有,则打印出未找到该数字。

上面程序输出

  1. type of nums is []int
  2. 89 found at index 0 in [89 90 95]
  3. type of nums is []int
  4. 45 found at index 2 in [56 67 45 90 109]
  5. type of nums is []int
  6. 78 not found in [38 56 98]
  7. type of nums is []int
  8. 87 not found in []

在上面的程序第 25 行中,find 函数调用只有一个参数。 我们没有向可变的 ...int 参数传递任何参数。 如上面所述,这是完全合法的,在这种情况下,nums 将是一个长度和容量为 0 的 nil 切片。

切片参数 vs 可变参数

我们现在肯定会有一个问题有困惑。在上一节中,我们了解到函数的可变参数实际上是转换为切片。那么为什么当我们使用切片实现相同的功能时,我们还需要可变函数?我已经使用切片重写了上面的程序

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func find(num int, nums []int) {
  6. fmt.Printf("type of nums is %T\n", nums)
  7. found := false
  8. for i, v := range nums {
  9. if v == num {
  10. fmt.Println(num, "found at index", i, "in", nums)
  11. found = true
  12. }
  13. }
  14. if !found {
  15. fmt.Println(num, "not found in ", nums)
  16. }
  17. fmt.Printf("\n")
  18. }
  19. func main() {
  20. find(89, []int{89, 90, 95})
  21. find(45, []int{56, 67, 45, 90, 109})
  22. find(78, []int{38, 56, 98})
  23. find(87, []int{})
  24. }

Run in playground

以下是使用可变参数相比较切片的优点。

  1. 在每个函数调用期间无需创建切片。如果你看一下上面的程序,我们在每个函数调用期间都在第 22, 23, 24 和 25 行中创建了新的切片。当使用可变参数函数时,可以避免这种额外的切片创建。

  2. 上面程序第 25 行中, 我们正在创建一个空切片,只是为了满足 find 函数的参数定义。在可变函数的情况下完全不需要这样做。当使用可变参数函数时,该行可以直接写为 find(87)

  3. 我个人觉得具有可变参数函数比使用切片的函数更具可读性:)


Append是一种可变函数

你有没有想过向切片添加元素的 append 函数是如何接受任意数量的参数。这是因为它就是一个可变参数的函数。

  1. func append(slice []Type, elems ...Type) []Type

以上是 append 函数的定义。在这个定义中,elems 是一个可变参数。因此 append可以接受任意数量的参数。

将切片传递给可变参数的函数

让我们将一个切片传递给一个可变参数的函数,并从下面的示例中找出发生了什么。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func find(num int, nums ...int) {
  6. fmt.Printf("type of nums is %T\n", nums)
  7. found := false
  8. for i, v := range nums {
  9. if v == num {
  10. fmt.Println(num, "found at index", i, "in", nums)
  11. found = true
  12. }
  13. }
  14. if !found {
  15. fmt.Println(num, "not found in ", nums)
  16. }
  17. fmt.Printf("\n")
  18. }
  19. func main() {
  20. nums := []int{89, 90, 95}
  21. find(89, nums)
  22. }

Run in playground

在 23 行中,我们将一个切片传递给有可变数量参数的函数。

这是不允许的。上面程序编译器会编译错误而失败 main.go:23: 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 作为可变参数传递给 find 函数。正如我们已经讨论过的,这些可变参数将被转换为 int 类型的切片,因为 find 函数接收可变int参数。在这个例子中,nums 已经是一个 int 切片,并且尝试使用 nums 创建一个新的 []int 切片。编译器会试图这样做

  1. find(89, []int{nums})

因为 nums[]int 而不是 int,所以会失败。

那么有没有办法将切片传递给可变参数函数?答案是肯定的。

这是一个语法糖,可用于将切片传递给可变参数函数。你必须在切片后面使用 ... 。这样做的话,切片将直接传递给函数,而不会创建新切片。

在上面的程序中,如果你把第 23 行的 find(89, nums) 替换成 find(89, nums...),程序将编译并输出


  1. type of nums is []int
  2. 89 found at index 0 in [89 90 95]

下面是完整的程序

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func find(num int, nums ...int) {
  6. fmt.Printf("type of nums is %T\n", nums)
  7. found := false
  8. for i, v := range nums {
  9. if v == num {
  10. fmt.Println(num, "found at index", i, "in", nums)
  11. found = true
  12. }
  13. }
  14. if !found {
  15. fmt.Println(num, "not found in ", nums)
  16. }
  17. fmt.Printf("\n")
  18. }
  19. func main() {
  20. nums := []int{89, 90, 95}
  21. find(89, nums...)
  22. }

Run in playground

疑难杂症

当你在可变参数函数中修改切片时,请确保你知道自己在做什么。

让我们看一个简单的例子。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func change(s ...string) {
  6. s[0] = "Go"
  7. }
  8. func main() {
  9. welcome := []string{"hello", "world"}
  10. change(welcome...)
  11. fmt.Println(welcome)
  12. }

Run in playground

你认为上面程序输出是什么?如果你认为它是 [Go world]。恭喜!你已经理解了可变函数和切片。如果你弄错了,没什么大不了的,让我解释一下我们如何得到这个输出。

上面的程序第 13 行中,我们使用语法糖 ... 并将切片作为变量参数传递给 change 函数。

正如我们已经讨论的那样,如果使用了...welcome 切片本身将作为参数传递,而不会创建新的切片。因此 welcome 将作为参数传递给 change 函数。

在 change 函数内部,切片的第一个元素更改为 Go。因此该程序输出


  1. [Go world]

这是另一个理解可变函数的程序。


  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func change(s ...string) {
  6. s[0] = "Go"
  7. s = append(s, "playground")
  8. fmt.Println(s)
  9. }
  10. func main() {
  11. welcome := []string{"hello", "world"}
  12. change(welcome...)
  13. fmt.Println(welcome)
  14. }

Run in playground

我会把它作为练习。让你弄清楚上面的程序是如何工作的:)。

原文链接

https://golangbot.com/variadic-functions/