Go语言 可变函数

导言

  • 原文链接: Part 12: Variadic Functions
  • If translation is not allowed, please leave me in the comment area and I will delete it as soon as possible.

什么是可变函数?

一般来说,函数只能接收固定数量的参数。而一个可变函数,它可以接受任意数量的参数。
如果函数的最后一个参数使用 ... 前缀进行修饰,那这个函数就可以接收任意长度的参数 — 这个函数也被称为可变函数。

对于一个函数,只有最后一个参数才能是可变的。我们将在下面讨论,为什么是这样的。

句式

func hello(a int, b ...int) {  
}

在上面的函数中,参数 b 是可变的,因为它的前缀是 ...,于是它可以接收任意数量的参数。这个函数可以采用以下句式调用。

hello(1, 2) //passing one argument "2" to b  
hello(5, 6, 7, 8, 9) //passing arguments "6, 7, 8 and 9" to b  

在上面的代码段中:

  • 1 行,我们传递了 1 个参数 2b
  • 2 行,我们传递了 4 个参数 6, 7, 8, 9b

我们也可以传递 0 个参数给 b

hello(1)

在上面的代码中,我们调用 函数hello 并传递 0 个参数给 b,这也是可以的。

现在,我猜你已经理解了: 为什么可变参数只能有一个,而且只能位于最后。

接下来,我们试试把 可变参数b 作为第一个参数。句式如下:

func hello(b ...int, a int) {  
}

在上面的函数中,我们不可能传递参数给 a,因为无论我们传递什么参数,这些参数都会被分配给 b,因为它是可变参数。因此,可变参数必须位于函数定义的最后。 而上面的函数编译时将产生错误:
syntax error: cannot use … with non-final parameter b

通过一些例子理解可变函数的运作模式

接下来,我们来创建一个可变函数。这个函数的功能是 — 从 输入集nums 中找出 特定输入num 是否存在。

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, "found at index", i, "in", nums)
            found = true
        }
    }
    if !found {
        fmt.Println(num, "not found in ", nums)
    }
    fmt.Printf("\n")
}
func main() {  
    find(89, 89, 90, 95)
    find(45, 56, 67, 45, 90, 109)
    find(78, 38, 56, 98)
    find(87)
}

在上面的程序中,第 7 行的 func find(num int, nums ...int),接收一个可变长度的 nums参数。在 find函数 内部,nums 的类型其实是 []int,一个整型切片。

可变函数的运作方式如下:
1. 首先,该函数会把可变参数转换为对应类型的切片。举个例子,在第 22 行,find函数的可变参数是 89, 90, 95find函数 期待一个可变长度的 int参数。因此,这三个参数将会被编译器转换为一个 int类型 的切片 — []int{89, 90, 95}
2. 之后,再把这个切片传递给 find函数。

在第 10 行,for循环 将遍历 nums切片,输出nums中,数值为 num 的索引。如果 num 不存在,那将会输出 the number is not found.

运行上面的程序,输出如下:

type of nums is []int  
89 found at index 0 in [89 90 95]

type of nums is []int  
45 found 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  []  

上面程序的第 25 行,我们只传递了 1 个参数 给 find函数 ,此时 nums参数 接收了 0 个参数。如前所述,这也是合法的。此时,nums 将会是一个 长度为0、容量为0nil切片。

切片参数 vs 可变参数

此时,在你的脑海中可能有一个问题挥之不去。在上一节中,我们发现: 当把可变参数传递给函数时,实际上传递的是一个切片。那为什么我们要使用可变参数呢?直接使用切片作为参数不好吗?
接下来,我使用切片,重写上面的函数。

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, "found at index", i, "in", nums)
            found = true
        }
    }
    if !found {
        fmt.Println(num, "not found in ", nums)
    }
    fmt.Printf("\n")
}
func main() {  
    find(89, []int{89, 90, 95})
    find(45, []int{56, 67, 45, 90, 109})
    find(78, []int{38, 56, 98})
    find(87, []int{})
}

与使用切片作为参数相比,可变参数的优点如下:

  1. 每次函数调用的时候,我们并不需要创建一个切片。你可以发现,在上面的程序中,每次函数调用,我们必须创建一个新切片。这些切片的创建其实是可以避免的 — 使用可变参数。
  2. 在上面程序的第 25 行,为了满足函数签名,我们创建了一个空切片,而对于使用可变参数来说,这完全是没有必要的。当我们使用可变函数时,我们只需使用 find(87) 便可实现函数调用。
  3. 与切片相比,我个人觉得,使用可变参数会使程序可读性提高。

append函数 就是一个可变函数

之前你可能会疑问: 为什么 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, "found at index", i, "in", nums)
            found = true
        }
    }
    if !found {
        fmt.Println(num, "not found in ", nums)
    }
    fmt.Printf("\n")
}
func main() {  
    nums := []int{89, 90, 95}
    find(89, nums)
}

在第 23 行,我们把一个切片传递给了函数,而这个函数期待的是一个可变参数。

这会导致出错。上面的程序将会带来如下的编译错误:

./prog.go:23:10: cannot use nums (type []int) as type int in argument to find

为什么会报错呢?其实,这很简单。

find函数 的签名如下:

func find(num int, nums ...int)  

根据一个可变函数的定义,nums ...int 表示它只能接收任意数量的参数,且参数类型必须为 int

在第 23 行,nums 类型是 []int,却被传递给了 find函数,而该函数期待的是 int类型 的参数。如前所述,这些可变参数将会被转换为 int类型 的切片。而在上面的例子中,nums 已经是 []int类型 的切片,于是,编译器将会尝试执行下面的操作:

find(89, []int{nums})  

这将导致错误,因为 nums 的类型是 []int 而非 int

那么到底,我们是否可以将切片传递给一个可变函数呢?这其实是可以的!

通过 语法糖...,我们可以实现将切片作为参数传递给可变函数。如果想这样做,你只需为切片加一个 后缀...。这样,切片将直接传递给函数,而且不会有新切片产生、

在上面的程序,第 23 行,如果你把 find(89, nums) 替换为 find(89, nums...),此时编译将可以通过,并有如下输出:

type of nums is []int  
89 found at index 0 in [89 90 95]  

这是完整的代码,供你参考。

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, "found at index", i, "in", nums)
            found = true
        }
    }
    if !found {
        fmt.Println(num, "not found in ", nums)
    }
    fmt.Printf("\n")
}
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],那么恭喜你,你已经理解了可变函数和切片。如果你没有说中,这也不是大问题,让我给你解释解释。

在上面程序的第 13 行,通过使用 语法糖...,我们将切片作为可变参数传递给了 change函数。

正如之前所说,使用 ... 可以使 welcome切片 作为参数传递给函数,并且不会有新切片产生。因此,welcome 将会以参数形式传递给 change函数。

change函数 内,切片的第一个元素被修改为 Go。因此程序输出为:

[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)
}

这里我就不公布答案了,留给你做小练习。想一想,上面的程序输出是啥~

以上就是可变函数了~

原作者留言

优质内容来之不易,您可以通过该 链接 为我捐赠。

最后

感谢原作者的优质内容。

这是我的第二次翻译,欢迎指出文中的任何错误。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值