sunwenfei
  • 关于我
  • Golang
    • Goglang基础教程【译】
      • 介绍
        • 安装
        • Hello World
      • 变量、基本类型以及常量
        • 变量
        • 基本类型
        • 常量
      • 函数和包
        • 函数
        • 包
      • 条件、循环流程控制语句
        • if else条件语句
        • switch语句
        • 循环语句
      • 数组、切片、变参函数
        • 数组(Array)
        • 切片(Slice)
        • 变参函数
      • 其他数据类型
        • 映射(Map)
        • 字符串
      • 指针、结构体和方法
        • 指针
        • 结构体
        • 方法
      • 面向对象编程
        • 结构体 vs 类
        • 组合 vs 继承
        • 接口
        • 多态
      • 并发
        • 并发介绍
        • 协程(goroutine)
        • 管道(channel)
        • 带缓存的管道(buffered channel)
        • 协程池
        • 管道选择器(select)
        • 互斥锁(Mutex)
      • Defer
      • 一等公民函数
      • 反射
      • 错误
        • 错误处理
        • 自定义错误类型
        • panic和recover
      • 文件读写
        • 读文件
        • 写文件
    • Golang面向对象编程
    • Golang函数式编程
    • Golang并发编程
    • Golang web服务编程
    • Golang数据结构与算法
  • Shell编程
    • Find命令
  • JavaScript
    • browser
    • Node.JS
    • Deno
  • TypeScript
  • HTTP
    • 【译】通过信鸽理解HTTPS交互原理
  • React
    • React16
      • Hooks
        • 使用React Hooks拉取数据
  • 移动端开发
    • 原生
    • Flutter
    • ReactNative
    • 小程序
  • 前端测试
Powered by GitBook
On this page

Was this helpful?

  1. Golang
  2. Goglang基础教程【译】
  3. 数组、切片、变参函数

变参函数

变参函数

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

变参函数的工作原理其实很简单,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)
}
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 world playground]
[Go world]

怎么样,你想明白了吗?

Previous切片(Slice)Next其他数据类型

Last updated 6 years ago

Was this helpful?

该程序中,func find(num int, nums ...int)的参数nums即为可变参数。在函数find内部,我们打印出了nums参数的类型,打印结果为[]int,可以看出在函数内部,可变参数的类型是切片,这里nums类型是整型切片。

这里我们直接将多个参数手动人工包装好然后传给可变参数。执行报错:

该程序直接结果如下:

Go Playground在线运行
Go Playground在线运行
Go Playground在线运行
Go Playground在线运行