可变函数

720 阅读4分钟

这是『就要学习 Go 语言』系列的第 16 篇分享文章

上篇文章里,讲解了关于函数的概念和用法,函数接收的参数数目都是确定的。而可变参数函数的参数数目是不确定的,这一节,就来讲讲可变参数函数

可变参数函数

可变参数函数,接收可变数量的参数的函数。如果一个函数的最后一个参数的表示形如:...Type,则该参数(形参)可以接受不同数目的参数(实参)。

func f(elems ...Type)

需要注意的一点,只允许最后一个参数是可变参数。 Go 中的一些内置函数都是可变参数函数,例如:append() 函数:

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

在上面的函数声明中,elems 就是可变参数,可以接收任意数目的实参。

s := []int{1,2,3}
s = append(s,4,5,6,7,8)

如何工作

你是不是也很想知道,为什么可变参数函数能够接收任意数目的参数呢? 刚开始学的时候,我也很疑惑。现在一步步给你揭开它的面纱。

func change(str1 string, s ...string) {
	fmt.Println(str1)
	fmt.Printf("%T\n",s)     // %T 输出变量类型
	fmt.Println(s)
}

func main() {
	blog := "seekload.net"
	change(blog,"Hello","World","Go")
}

输出

seekload.net
[]string
[Hello World Go]

从输出结果看出,change() 函数中,参数 s 是 []string 类型的切片。
可变参数函数的工作原理就是把可变参数转化成切片。 调用 change() 函数时可变参数是 Hello、World、Go,这三个参数被编译器转化成切片 []string{"Hello","World","Go"},然后被传入 change() 函数。
另外,调用 change() 函数时候,可以不传可变参数,在 Go 语言里也是合法的,这种情况下,s是一个长度和容量都是 0 的 nil 切片。

其他用法

将切片传递给可变参数函数
func change(str1 string, s ...string) {
	fmt.Println(str1)
	fmt.Printf("%T\n",s)
	fmt.Println(s)
}

func main() {
	slice := []string{"Hello","World","Go"}
	blog := "seekload.net"
	change(blog,slice)
}

上述代码中,将切片 slice 传给可变参数函数 change(),结果编译出错:cannot use slice (type []string) as type string in argument to change 。
原因很简单,由可变参数函数的定义可知,s ...string 意味它可以接受 string 类型的可变参数。代码第 10 行,slice 作为可变参数传入 change() 函数。前面我们知道,可变参数会被转换为 string 类型切片然后再传入 change() 函数中。但 slice 是一个 string 类型的切片,编译器试图通过下面这种方式在 slice 基础上再创建一个切片:

change("seekload.net", []string{s})

之所以会失败,因为 slice 是 []string 类型,而不是 string 类型。
庆幸的是,Go 提供了将切片传入可变参数函数的语法糖:直接在切片后加上...后缀。这样,切片将直接传入函数,不会再创建新的切片。
修改上面的代码:

change(blog,slice...)

输出

seekload.net
[]string
[Hello World Go]

前面提到一点,通过 Go 提供的语法糖将可变参数函数传入切片,不会创建新的切片。如果在函数中改变切片的值会发生什么呢?

func change(s ...string) {
	s[0] = "seekload.net"
	fmt.Printf("%T\n",s)
	fmt.Println(s)
}

func main() {
	slice := []string{"Hello","World","Go"}
	change(slice...)
	fmt.Println(slice)
}

输出

[]string
[seekload.net World Go]
[seekload.net World Go]

从结果可以看出,main() 函数的切片已经改变了。
为什么会有这样的输出呢?代码第 9 行,使用了语法糖...并且将切片作为可变参数传入 change() 函数。如上面讨论的,如果使用了...,切片本身会作为参数直接传入,不会再创建一个新的切片。所以在 change() 函数中,第一个元素被替换成 seekload.net,会影响到 main() 函数的切片。

另外,我们也可以通过将数组转化成切片传递给可变参数函数。

func change(s ...string) {
	s[0] = "seekload.net"
	fmt.Printf("%T\n",s)
	fmt.Println(s)
}

func main() {
	arr := [3]string{"Hello","World","Go"}
	change(arr[:]...)
	fmt.Println(arr)
}

输出结果跟上面的例子是一样的。

其他

给大家列几种比较常见的写法:

func change(s ...string) {
	fmt.Printf("%T\n",s)
	fmt.Println(s)
}
func main() {
	slice1 := []string{"Hello","World","Go"}
	slice2 := []string{"Aa","Bb"}
	// 1、
	change(append(slice1,"Again")...)
	// 2、
	change(append(slice1,slice2...)...)
}

输出:

[]string
[Hello World Go Again]
[]string
[Hello World Go Aa Bb]

希望这篇文章对你有用!

参考:
1、非懂不可的Slice(一)-- 就要学习Go语言
2、Go 语言文档:...语法糖append() 函数


(全文完)

原创文章,若需转载请注明出处!
欢迎扫码关注公众号「Golang来啦」或者移步 seekload.net ,查看更多精彩文章。

公众号「Golang来啦」给你准备了一份神秘学习大礼包,后台回复【电子书】领取!

公众号二维码