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包下封装好的方法--->主要用到BufferWriteStringString方法

示例:

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 连接是同一个特性。