一般来说,函数只能接收固定数量的参数。而一个可变函数,它可以接受任意数量的参数。
如果函数的最后一个参数使用 ...
前缀进行修饰,那这个函数就可以接收任意长度的参数 — 这个函数也被称为可变函数。
对于一个函数,只有最后一个参数才能是可变的。我们将在下面讨论,为什么是这样的。
func hello(a int, b ...int) {
}
在上面的函数中,参数 b
是可变的,因为它的前缀是 ...
,于是它可以接收任意数量的参数。这个函数可以采用以下句式调用。
hello(1, 2) //passing one argument "2" to b
hello(5, 6, 7, 8, 9) //passing arguments "6, 7, 8 and 9" to b
在上面的代码段中:
1
行,我们传递了 1
个参数 2
给 b
。2
行,我们传递了 4
个参数 6, 7, 8, 9
给 b
。我们也可以传递 0
个参数给 b
。
hello(1)
在上面的代码中,我们调用 函数hello
并传递 0
个参数给 b
,这也是可以的。
现在,我猜你已经理解了: 为什么可变参数只能有一个,而且只能位于最后。
接下来,我们试试把 可变参数b
作为第一个参数。句式如下:
func hello(b ...int, a int) {
}
在上面的函数中,我们不可能传递参数给 a
,因为无论我们传递什么参数,这些参数都会被分配给 b
,因为它是可变参数。因此,可变参数必须位于函数定义的最后。 而上面的函数编译时将产生错误:
syntax error: cannot use … with non-final parameter b
接下来,我们来创建一个可变函数。这个函数的功能是 — 从 输入集nums
中找出 特定输入num
是否存在。
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)
}
在上面的程序中,第 7
行的 func find(num int, nums ...int)
,接收一个可变长度的 nums
参数。在 find
函数 内部,nums
的类型其实是 []int
,一个整型切片。
可变函数的运作方式如下:
1. 首先,该函数会把可变参数转换为对应类型的切片。举个例子,在第 22
行,find
函数的可变参数是 89, 90, 95
。find
函数 期待一个可变长度的 int
参数。因此,这三个参数将会被编译器转换为一个 int
类型 的切片 — []int{89, 90, 95}
。
2. 之后,再把这个切片传递给 find
函数。
在第 10
行,for
循环 将遍历 nums
切片,输出nums
中,数值为 num
的索引。如果 num
不存在,那将会输出 the number is not found.
。
运行上面的程序,输出如下:
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
行,我们只传递了 1
个参数 给 find
函数 ,此时 nums
参数 接收了 0
个参数。如前所述,这也是合法的。此时,nums
将会是一个 长度为0
、容量为0
的 nil
切片。
此时,在你的脑海中可能有一个问题挥之不去。在上一节中,我们发现: 当把可变参数传递给函数时,实际上传递的是一个切片。那为什么我们要使用可变参数呢?直接使用切片作为参数不好吗?
接下来,我使用切片,重写上面的函数。
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, []int{89, 90, 95})
find(45, []int{56, 67, 45, 90, 109})
find(78, []int{38, 56, 98})
find(87, []int{})
}
与使用切片作为参数相比,可变参数的优点如下:
25
行,为了满足函数签名,我们创建了一个空切片,而对于使用可变参数来说,这完全是没有必要的。当我们使用可变函数时,我们只需使用 find(87)
便可实现函数调用。append
函数 就是一个可变函数之前你可能会疑问: 为什么 append
函数 可以为切片追加任意数量的元素呢?这其实是因为,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() {
nums := []int{89, 90, 95}
find(89, nums)
}
在第 23
行,我们把一个切片传递给了函数,而这个函数期待的是一个可变参数。
这会导致出错。上面的程序将会带来如下的编译错误:
./prog.go:23:10: cannot use nums (type []int) as type int in argument to find
为什么会报错呢?其实,这很简单。
find
函数 的签名如下:
func find(num int, nums ...int)
根据一个可变函数的定义,nums ...int
表示它只能接收任意数量的参数,且参数类型必须为 int
。
在第 23
行,nums
类型是 []int
,却被传递给了 find
函数,而该函数期待的是 int
类型 的参数。如前所述,这些可变参数将会被转换为 int
类型 的切片。而在上面的例子中,nums
已经是 []int
类型 的切片,于是,编译器将会尝试执行下面的操作:
find(89, []int{nums})
这将导致错误,因为 nums
的类型是 []int
而非 int
。
那么到底,我们是否可以将切片传递给一个可变函数呢?这其实是可以的!
通过 语法糖...
,我们可以实现将切片作为参数传递给可变函数。如果想这样做,你只需为切片加一个 后缀...
。这样,切片将直接传递给函数,而且不会有新切片产生、
在上面的程序,第 23
行,如果你把 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)
}
这里我就不公布答案了,留给你做小练习。想一想,上面的程序输出是啥~
以上就是可变函数了~
优质内容来之不易,您可以通过该 链接 为我捐赠。
感谢原作者的优质内容。
这是我的第二次翻译,欢迎指出文中的任何错误。