9.6Go之函数之可变参数
可变参数的概念
概念:
可变参数是指:
- 传入的参数类型可变
- 传入的参数数量可变
传入参数数量可变
特点:
- 以切片举例,通常我们指传入参数的值的数量可变
- 在Go语言中提供了一种语法糖(syntactic sugar)
...type
这种语法对语言的功能并没有影响,能够增加程序的可读性,减少程序出错的可能。
- 类型
...type
本质上是一个数组切片,也就是[]type
,所以参数 args 可以用 for 循环来获得每个传入的参数。
示例:
package main
import "fmt"
func getValue(args ...int) {
for _, v := range args {
fmt.Printf("value is:%d\n", v)
}
/*
这里的形参带了...会自动将数据类型转为数组类型使用切片的方式进行接收和打印
*/
}
/*
如果没有...的语法糖那么在实现的时候需要这样写
*/
func getValue2(args []int) {
for _, v := range args {
fmt.Printf("Value is:%d\n", v)
}
}
func main() {
getValue(1,2,3,4,5)
fmt.Println("##########")
//调用的时候传入的参数就必须是数组切片
getValue2([]int{5,4,3,2,1})
}
通常这种情况用于接收相同类型的参数,由于可变参数列表的数量不固定,传入的参数是一个切片,如果需要获得每一个参数的具体值时:
- 对可变参数变量进行遍历
- 将可变参数的内容进行拼接
使用bytes
包下封装好的方法--->主要用到Buffer
、WriteString
、String
方法
示例:
package main
import (
"bytes"
"fmt"
)
func jointString(slist ...string) string {
//定义变量--->这个变量相当于字符串拼接完成后的值--->这是一个字节数组
var b bytes.Buffer //Buffer是一个结构体,创建字节数组类型的变量
//循环获取形参数组里面的内容
for _, v := range slist {
//将遍历出的字符串连续写入字节数组
b.WriteString(v) //将每一个传入参数放到 bytes.Buffer 中
}
return b.String() //传入字节数组的指针,返回拼接后的字符串值
}
func main() {
//连续输入字符将它们拼接成一个字符串
fmt.Println(jointString("Jun", "is", "a", "handsome", "boy!"))
}
/*
要获取可变参数的数量,使用 len() 函数对可变参数变量对应的切求长度,获得可变参数数量
*/
传入参数类型、数量均可变
特点:
- 要使传入参数的类型、数量均可变则需要采用
interface{}
- Go语言标准库中 fmt.Printf() 的函数原型:
func Printf(format string, args ...interface{}) {
// ...
}
/*
使用interface{}是类型安全的
*/
示例:
package main
import "fmt"
func parameters(args ...interface{}) {
for _, v := range args {
switch v.(type) {
case string:
fmt.Printf("Value is:%s\n", v)
case int:
fmt.Printf("Value is:%d\n", v)
case float32:
fmt.Printf("Value is:%f\n", v)
case float64:
fmt.Printf("Value is:%f", v)
default:
fmt.Println("Unknow type!!!")
}
}
/*
首先:
1、interface{}前必须+"...",否则会认为是单个值,不能进行循环
2、switch的内容是v变量,该变量被声明为interface{}当中的每一个值
3、通过.type类型去判断里面的值的类型然后进行相应的处理
4、由进行的匿名循环看出,这些类型被内置定义成了一个类似切片的类型,所以才可以进行匿名循环
*/
/*
Go语言中的interface{}又比Java当中的inteface强大了很多。
Java当中的interface是一个类型,能接收的对象是实现了该接口的类的对象
Go当中的interface则不是
*/
}
func main() {
parameters(1,"JunkingBoy",3.1415)
}
/*
在传递参数的时候可以传递任意类型的参数
*/
由于上诉操作可变参数为 interface{} 类型,可以传入任何类型的值,下面将这些内容拼接成字符串并且打印出来:
package main
import (
"bytes"
"fmt"
)
func printTypeValue(slist ...interface{}) string {
//定义字节数组
var b bytes.Buffer
//定义一个描述类型的字符串
var valueType string
//循环获取interface{}当中的值
for _, s := range slist {
//定义一个写入的值的变量
str := fmt.Sprintf("%v", s) //注意这个方法是Sprintf不是Printf
/*
使用 fmt.Sprintf 配合%v动词,可以将 interface{} 格式的任意值转为字符串
*/
//判断值的类型
switch s.(type) {
case bool:
valueType = "bool"
case int:
valueType = "int"
case string:
valueType = "string"
default:
fmt.Println("Unknow value type!")
}
/*
switch s.(type) 可以对 interface{} 类型进行类型断言,判断变量的实际类型
*/
//分别写入字符串前缀、值、类型
b.WriteString("value is:")
b.WriteString(str + "\t")
b.WriteString("type:")
b.WriteString(valueType)
b.WriteString("\n")
}
return b.String()
}
func main() {
fmt.Println(printTypeValue(100, "JunkingBoy", false))
}
在多个可变参数函数中传递参数--->将可变参数整体传递给下一个可变参数函数
特点:
- 可变参数变量是一个包含所有参数的切片,要将这个含有可变参数的变量传递给下一个可变参数函数,在传递时给可变参数变量后面添加
...
,就可以将切片中的元素进行传递,而不是传递可变参数变量本身。 - 传递可变参数中每一个参数元素本身而不是可变参数这个变量--->非常重要
示例:--->创建两个函数,将第二个函数的形参完整的传递给第一个函数,在第二个函数里面调用第一个函数(有点类似闭包但是却不是)
package main
import "fmt"
/*
第一个函数
*/
func rawPrint(slist ...interface{}) {
//遍历切片
for _, v := range slist {
//打印参数
fmt.Println(v)
}
}
/*
第二个函数,在内部调用第一个函数
*/
func print(slist ...interface{}) {
//将传入的形参元素完整的传递给第一个函数
rawPrint(slist...) //将变量在 print 的可变参数列表中添加...后传递给 rawPrint()。
/*
这个时候通过打印的结果可以看出slist类型不是作为一个整体传入rawPrint当中,而是逐个元素传入
*/
rawPrint("fmt", slist) //此时,slist(类型为 []interface{})将被作为一个整体传入 rawPrint(),rawPrint() 函数中遍历的变量也就是 slist 的切片值。
}
func main() {
//外部调用第二个函数
print(1, "JunkingBoy", 2)
}
可变参数使用...
进行传递与切片间使用 append 连接是同一个特性。