快捷搜索:

golang2021函数与包(6)Go语言可变参数(变参函数)

 

    在C语言时代大家一般都用过  printf() 函数,从那个时候开始其实已经在感受可变参数的魅力和价值,如同C语言中的 printf() 函数,Go语言标准库中的  fmt.Println() 等函数的实现也依赖于语言的可变参数功能。

     

    本节我们将介绍可变参数的用法。合适地使用可变参数,可以让代码简单易用,尤其是输入输出类函数,比如日志函数等。

    可变参数类型

    可变参数是指函数传入的参数个数是可变的,为了做到这点,首先需要将函数定义为可以接受可变参数的类型:

    func  myfunc(args ...int) {
          for _, arg := range args {
              fmt.Println(arg)
          }
      }

    上面这段代码的意思是,函数  myfunc() 接受不定数量的参数,这些参数的类型全部是 int,所以它可以用如下方式调用:

    myfunc(2, 3, 4)

    myfunc(1, 3, 7, 13)

    形如...type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数,它是一个语法糖(syntactic  sugar),即这种语法对语言的功能并没有影响,但是更方便程序员使用,通常来说,使用语法糖能够增加程序的可读性,从而减少程序出错的可能。

     

    从内部实现机理上来说,类型...type本质上是一个数组切片,也就是[]type,这也是为什么上面的参数 args 可以用  for 循环来获得每个传入的参数。

     

    假如没有...type这样的语法糖,开发者将不得不这么写:

    func  myfunc2(args []int) {
          for _, arg := range args {
              fmt.Println(arg)
          }
      }

    从函数的实现角度来看,这没有任何影响,该怎么写就怎么写,但从调用方来说,情形则完全不同:

    myfunc2([]int{1, 3, 7,  13})

    大家会发现,我们不得不加上[]int{}来构造一个数组切片实例,但是有了...type这个语法糖,我们就不用自己来处理了。

    任意类型的可变参数

    之前的例子中将可变参数类型约束为  int,如果你希望传任意类型,可以指定类型为 interface{},下面是Go语言标准库中 fmt.Printf() 的函数原型:

    func Printf(format  string, args ...interface{}) {

        // ...

    }

    用 interface{}  传递任意类型数据是Go语言的惯例用法,使用 interface{} 仍然是类型安全的,这和 C/C++  不太一样,下面通过示例来了解一下如何分配传入 interface{} 类型的数据。

    package  main
      import "fmt"
      func MyPrintf(args ...interface{}) {
          for _, arg := range args {
              switch arg.(type) {
                  case int:
                      fmt.Println(arg,  "is an int value.")
                  case string:
                      fmt.Println(arg,  "is a string value.")
                  case int64:
                      fmt.Println(arg, "is  an int64 value.")
                  default:
                      fmt.Println(arg,  "is an unknown type.")
              }
          }
      }
      func main() {
          var v1 int = 1
          var v2 int64 = 234
          var v3 string =  "hello"
          var v4 float32 = 1.234
          MyPrintf(v1, v2, v3, v4)
      }

    该程序的输出结果为:

    1 is an int value.

    234 is an int64 value.

    hello is a string  value.

    1.234 is an unknown  type.

    遍历可变参数列表——获取每一个参数的值

    可变参数列表的数量不固定,传入的参数是一个切片,如果需要获得每一个参数的具体值时,可以对可变参数变量进行遍历,参见下面代码:

    package  main

    import  (
          "bytes"
          "fmt"
      )
      // 定义一个函数, 参数数量为0~n, 类型约束为字符串
      func joinStrings(slist ...string) string {

    //  定义一个字节缓冲, 快速地连接字符串
          var b bytes.Buffer
          // 遍历可变参数列表slist, 类型为[]string
          for _, s := range slist {
              // 将遍历出的字符串连续写入字节数组
              b.WriteString(s)
          }

    //  将连接好的字节数组转换为字符串并输出
          return b.String()
      }

    func  main() {
          // 输入3个字符串, 将它们连成一个字符串
          fmt.Println(joinStrings("pig  ", "and", " rat"))
           fmt.Println(joinStrings("hammer", " mom", "  and", " hawk"))
      }

    代码输出如下:

    pig and rat

    hammer mom and hawk

    代码说明如下:

    • 第 8       行,定义了一个可变参数的函数,slist 的类型为 []string,每一个参数的类型都是       string,也就是说,该函数只接受字符串类型作为参数。

    • 第 11       行,bytes.Buffer 在这个例子中的作用类似于 StringBuilder,可以高效地进行字符串连接操作。

    • 第 13 行,遍历       slist 可变参数,s 为每个参数的值,类型为 string。

    • 第 15       行,将每一个传入参数放到 bytes.Buffer 中。

    • 第 19 行,将       bytes.Buffer 中的数据转换为字符串作为函数返回值返回。

    • 第 24 行,输入       3 个字符串,使用 joinStrings() 函数将参数连接为字符串输出。

    • 第 25 行,输入       4 个字符串,连接后输出。

    如果要获取可变参数的数量,可以使用  len() 函数对可变参数变量对应的切片进行求长度操作,以获得可变参数数量。

    获得可变参数类型——获得每一个参数的类型

    当可变参数为 interface{}  类型时,可以传入任何类型的值,此时,如果需要获得变量的类型,可以通过 switch 获得变量的类型,下面的代码演示将一系列不同类型的值传入  printTypeValue() 函数,该函数将分别为不同的参数打印它们的值和类型的详细描述。

     

    打印类型及值:

    package  main

    import  (
          "bytes"
          "fmt"
      )

    func  printTypeValue(slist ...interface{}) string {

    //  字节缓冲作为快速字符串连接
          var b bytes.Buffer

    //  遍历参数
          for _, s := range slist {

    //  将interface{}类型格式化为字符串
              str :=  fmt.Sprintf("%v", s)

    //  类型的字符串描述
              var typeString string

    //  对s进行类型断言
              switch s.(type) {
              case bool:    // 当s为布尔类型时
                  typeString =  "bool"
              case string:    // 当s为字符串类型时
                  typeString =  "string"
              case int:    // 当s为整型类型时
                  typeString =  "int"
              }

    //  写字符串前缀
              b.WriteString("value:  ")

    //  写入值
              b.WriteString(str)

    //  写类型前缀
              b.WriteString(" type:  ")

    //  写类型字符串
              b.WriteString(typeString)

    //  写入换行符
              b.WriteString("\n")

    }
          return b.String()
      }

    func  main() {

    //  将不同类型的变量通过printTypeValue()打印出来
          fmt.Println(printTypeValue(100,  "str", true))
      }

    代码输出如下:

    value: 100 type: int

    value: str type: string

    value: true type: bool

    代码说明如下:

    • 第 8       行,printTypeValue() 输入不同类型的值并输出类型和值描述。

    • 第 11       行,bytes.Buffer 字节缓冲作为快速字符串连接。

    • 第 14 行,遍历       slist 的每一个元素,类型为 interface{}。

    • 第 17 行,使用 fmt.Sprintf 配合%v动词,可以将 interface{} 格式的任意值转为字符串。

    • 第 20       行,声明一个字符串,作为变量的类型名。

    • 第 23       行,switch s.(type) 可以对 interface{} 类型进行类型断言,也就是判断变量的实际类型。

    • 第 24~29 行为       s 变量可能的类型,将每种类型的对应类型字符串赋值到 typeString 中。

    • 第 33~42       行为写输出格式的过程。

    在多个可变参数函数中传递参数

    可变参数变量是一个包含所有参数的切片,如果要将这个含有可变参数的变量传递给下一个可变参数函数,可以在传递时给可变参数变量后面添加...,这样就可以将切片中的元素进行传递,而不是传递可变参数变量本身。

     

    下面的例子模拟 print()  函数及实际调用的 rawPrint() 函数,两个函数都拥有可变参数,需要将参数从 print 传递到 rawPrint 中。

     

    可变参数传递:

    package  main

    import  "fmt"

    //  实际打印的函数
      func rawPrint(rawList ...interface{}) {

    //  遍历可变参数切片
          for _, a := range rawList {

    //  打印参数
              fmt.Println(a)
          }
      }

    //  打印函数封装
      func print(slist ...interface{}) {

    //  将slist可变参数切片完整传递给下一个函数
          rawPrint(slist...)
      }

    func  main() {

    print(1,  2, 3)
      }

    代码输出如下:

    1

    2

    3

    对代码的说明:

    • 第 9~13       行,遍历 rawPrint() 的参数列表 rawList 并打印。

    • 第 20 行,将变量在 print 的可变参数列表中添加...后传递给 rawPrint()。

    • 第 25 行,传入       1、2、3 这 3 个整型值并进行打印。

    如果尝试将第 20 行修改为:

    rawPrint("fmt",  slist)

    再次执行代码,将输出:

    [1 2 3]

    此时,slist(类型为  []interface{})将被作为一个整体传入 rawPrint(),rawPrint() 函数中遍历的变量也就是 slist 的切片值。

     

    可变参数使用...进行传递与切片间使用 append 连接是同一个特性。



[注:本文部分图片来自互联网!未经授权,不得转载!每天跟着我们读更多的书]


互推传媒文章转载自第三方或本站原创生产,如需转载,请联系版权方授权,如有内容如侵犯了你的权益,请联系我们进行删除!

如若转载,请注明出处:http://www.hfwlcm.com/info/73670.html

正常情况下,在Java中入参是不建议用做返回值的。除了造成代码不易理解、语义不清等问题外,可能还埋下了陷阱等你入坑。问题背景比如有这么一段代码:@Named public class AService {

  • 函数是C语言一种非常重要的语法机制,一方面是任务分解、模块化的需要;另一方面也是代码重用的需要。C语言要求函数不能嵌套定义,但可以嵌套调用。一方面,主调函数与被调函数

    函数是C语言一种非常重要的语法机制,一方面是任务分解、模块化的需要;另一方面也是代码重用的需要。C语言要求函数不能嵌套定义,但可以嵌套调用。一方面,主调函数与被调函数

  • 本文最初发布于 rubikscode.com 网站,经原作者授权由 InfoQ 中文站翻译并分享。围绕模型优化这一主题发展出来的许多子分支之间的差异之大往往令人难以置信。其中的一个子分支叫做超参

    本文最初发布于 rubikscode.com 网站,经原作者授权由 InfoQ 中文站翻译并分享。围绕模型优化这一主题发展出来的许多子分支之间的差异之大往往令人难以置信。其中的一个子分支叫做超参