变参函数

变参函数

所谓的变参函数其实很简单,是指一个函数能接收的参数数量不是固定的,是可变的。比如某个函数如果既可以接收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...)
}

Go Playground在线运行

防坑指南

现在看一下下面这个例子:

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