> 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]
```

怎么样，你想明白了吗？


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://sunwenfei.gitbook.io/sunwenfei/golang/golang-ji-chu-jiao-cheng/shu-zu-qie-pian-bian-can-han-shu/bian-can-han-shu.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
