变参函数
变参函数
所谓的变参函数其实很简单,是指一个函数能接收的参数数量不是固定的,是可变的。比如某个函数如果既可以接收2个参数,也可以接收3个参数,那么该函数就是一个变参函数。
语法
如果一个函数的最后一个参数类型为...T
,那么该参数即表示可变参数,该可变参数位置可以接收任意多数量的参数。注意,可变参数只能是函数参数列表的最后一个参数。
示例
我们前面使用过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, "fount at index", i, "in", nums)
found = true
}
}
if !found {
fmt.Println(num, "not found in ", nums)
}
fmt.Println()
}
func main() {
find(89, 89, 90, 95)
find(45, 56, 67, 45, 90, 109)
find(78, 38, 56, 98)
find(87)
}
Go Playground在线运行 该程序中,func find(num int, nums ...int)
的参数nums
即为可变参数。在函数find
内部,我们打印出了nums
参数的类型,打印结果为[]int
,可以看出在函数内部,可变参数的类型是切片,这里nums
类型是整型切片。
变参函数的工作原理其实很简单,Golang自动将可变参数接收到的多个参数包装成为一个切片。例如find(89, 89, 90, 95)
中,可变参数列表为89, 90, 95
,由于find
函数声明的可变参数类型为int
,因此Golang将89, 90, 95
包装成为一个整型切片[]int{89, 90, 95}
,然后再将这个封装好的整型切片传给函数find
进行调用。
find
函数中使用for
循环来遍历可变参数接收到的整型切片nums
,并判断该切片内是否包含参数num
。该程序执行结果如下:
type of nums is []int
89 fount at index 0 in [89 90 95]
type of nums is []int
45 fount 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 []
注意我们最后那次调用find
函数时仅传了一个参数,并没有给可变参数传任何参数。这也是合法的,只不过这时可变参数值为nil
,长度和容量均为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, "fount at index", i, "in", nums)
found = true
}
}
if !found {
fmt.Println(num, "not found in ", nums)
}
fmt.Println()
}
func main() {
nums := []int{89, 90, 95}
find(89, nums)
}
Go Playground在线运行 这里我们直接将多个参数手动人工包装好然后传给可变参数。执行报错:
cannot use nums (type []int) as type int in argument to find
为什么会报错呢?其实前面已经说过,Golang会帮我们做这个切片打包。即使我们手动已经将多个参数包装好,Golang也不认,会尝试将受到的我们传进来的单个参数包装进一个整型切片:
find(89, []int{nums})
这时由于nums
本身不是int
类型,因此执行会报错。
那么如果我们已经有一个包装好的切片类型了,难道就没办法直接传给可变参数用了么?Golang也考虑到了这一点,因此提供了语法糖,我们只需要在已经包装好的切片后面带上...
即可,这时Golang会将该切片直接传入可变参数。
前面程序如果我们将find(89, nums)
改为find(89, nums...)
即可成功调用:
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, "fount at index", i, "in", nums)
found = true
}
}
if !found {
fmt.Println(num, "not found in ", nums)
}
fmt.Println()
}
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]
如果你猜对了,那么恭喜你,你已经了解了可变参数和切片。前面我们已经提到过了,我们传给可变参数的本身已经是包装好的切片了,那么Golang会直接将该切片作为可变参数传进去。因此这里是直接将welcome
参数传给可变参数,又由于切片本身存储的是底层数组的引用,因此在函数change
内部通过引用改变了函数外的welcome
引用的底层数组元素,因此才打印输出[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)
}
Go Playground在线运行 该程序直接结果如下:
[Go world playground]
[Go world]
怎么样,你想明白了吗?
Last updated
Was this helpful?