> For the complete documentation index, see [llms.txt](https://sunwenfei.gitbook.io/sunwenfei/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sunwenfei.gitbook.io/sunwenfei/golang/golang-ji-chu-jiao-cheng/shu-zu-qie-pian-bian-can-han-shu/bian-can-han-shu.md).

# 变参函数

#### 变参函数

所谓的**变参函数**其实很简单，是指一个函数能接收的参数数量不是固定的，是可变的。比如某个函数如果既可以接收2个参数，也可以接收3个参数，那么该函数就是一个变参函数。

#### 语法

如果一个函数的最后一个参数类型为`...T`，那么该参数即表示可变参数，该可变参数位置可以接收任意多数量的参数。注意，可变参数只能是函数参数列表的最后一个参数。

#### 示例

我们前面使用过`append`函数来向切片添加新元素(可以一次添加多个)，`append`之所以支持传入任意多个参数就是因为该函数是一个变参函数。

```go
func append(slice []Type, elems ...Type) []Type
```

上面是变参函数`append`的定义。这里`elems`即为一个可变参数，因此`append`才支持传入任意多个数目的待添加元素。

现在我们来自己实现一个变参函数。我们写一个程序来判断某个整数是否在一个整数列表内。

```go
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在线运行](https://play.golang.org/p/_bQBTJy4p0W) 该程序中，`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。

#### 使用切片传参

现在我们直接给可变参数传一个目标类型的切片看看什么效果：

```go
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在线运行](https://play.golang.org/p/ji5iPpA0Pmu) 这里我们直接将多个参数手动人工包装好然后传给可变参数。执行报错：

```
cannot use nums (type []int) as type int in argument to find
```

为什么会报错呢？其实前面已经说过，Golang会帮我们做这个切片打包。即使我们手动已经将多个参数包装好，Golang也不认，会尝试将受到的我们传进来的单个参数包装进一个整型切片：

```go
find(89, []int{nums})
```

这时由于`nums`本身不是`int`类型，因此执行会报错。

那么如果我们已经有一个包装好的切片类型了，难道就没办法直接传给可变参数用了么？Golang也考虑到了这一点，因此提供了语法糖，我们只需要在已经包装好的切片后面带上`...`即可，这时**Golang会将该切片直接传入可变参数**。

前面程序如果我们将`find(89, nums)`改为`find(89, nums...)`即可成功调用：

```go
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在线运行](https://play.golang.org/p/2tJNjPdMDiW)

#### 防坑指南

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

```go
package main

import "fmt"

func change(s ...string) {
    s[0] = "Go"
}

func main() {
    welcome := []string{"hello", "world"}
    change(welcome...)
    fmt.Println(welcome)
}
```

&#x20;输出如下：

```
[Go world]
```

如果你猜对了，那么恭喜你，你已经了解了可变参数和切片。前面我们已经提到过了，我们传给可变参数的本身已经是包装好的切片了，那么Golang会直接将该切片作为可变参数传进去。因此这里是直接将`welcome`参数传给可变参数，又由于切片本身存储的是底层数组的引用，因此在函数`change`内部通过引用改变了函数外的`welcome`引用的底层数组元素，因此才打印输出`[Go world]`。

再来一个例子理解下变参函数：

```go
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在线运行](https://play.golang.org/p/vAoHAfQN1fs) 该程序直接结果如下：

```
[Go world playground]
[Go world]
```

怎么样，你想明白了吗？
