如何在Go中使用Variadic函数

可变函数是一个函数,它接受零个,一个或多个值作为单个参数。虽然可变函数不是常见的情况,但它们可用于使代码更清晰,更易读。

介绍

可变函数是一个函数,它接受零个,一个或多个值作为单个参数。 虽然可变函数不是常见的情况,但它们可用于使代码更清晰,更易读。

变量函数比它们看起来更常见。 最常见的是fmt包中的Println函数。

func Println(a ...interface{}) (n int, err error)

具有以一组省略号( ... )开头的参数的函数被认为是可变函数。 省略号表示提供的参数可以是零个,一个或多个值。 对于fmt.Println包,它声明参数a是可变参数。

让我们创建一个使用fmt.Println函数的程序,并传入零个,一个或多个值:

print.go
package main

import "fmt"

func main() {
    fmt.Println()
    fmt.Println("one")
    fmt.Println("one", "two")
    fmt.Println("one", "two", "three")
}

我们第一次调用fmt.Println ,我们不传递任何参数。 我们第二次调用fmt.Println我们只传入一个参数,值为one 然后我们传递onetwo ,最后onetwothree

让我们使用以下命令运行程序:

go run print.go

我们将看到以下输出:


one
one two
one two three

输出的第一行是空白的。 这是因为我们在第一次fmt.Println时没有传递任何参数。 第二次打印出one值。 然后是one two ,最后是onetwothree

现在我们已经了解了如何调用可变参数函数,让我们看一下如何定义自己的可变参数函数。

定义Variadic函数

我们可以通过在参数前面使用省略号( ... )来定义可变参数函数。 让我们创建一个程序,当他们的名字被发送到函数时迎接他们:

hello.go
package main

import "fmt"

func main() {
    sayHello()
    sayHello("Sammy")
    sayHello("Sammy", "Jessica", "Drew", "Jamie")
}

func sayHello(names ...string) {
    for _, n := range names {
        fmt.Printf("Hello %s\n", n)
    }
}

我们创建了一个sayHello函数,它只接受一个名为names参数。 该参数是可变参数,因为我们在数据类型之前放置了省略号( ... ): ...string 这告诉Go该函数可以接受零个,一个或多个参数。

sayHello函数接收names参数作为slice 由于数据类型是一个string ,因此names参数可以像处理函数体内的[]string字符串( []string )一样对待。 我们可以使用range运算符创建一个循环,并遍历字符串切片。

如果我们运行该程序,我们将得到以下输出:

Hello Sammy
Hello Sammy
Hello Jessica
Hello Drew
Hello Jamie

请注意,我们第一次打电话给sayHello时没有打印。 这是因为可变参数是一个空的string slice 由于我们循环切片,因此无需迭代,并且永远不会调用fmt.Printf

让我们修改程序以检测没有发送值:

hello.go
package main

import "fmt"

func main() {
    sayHello()
    sayHello("Sammy")
    sayHello("Sammy", "Jessica", "Drew", "Jamie")
}

func sayHello(names ...string) {
    if len(names) == 0 {
        fmt.Println("nobody to greet")
        return
    }
    for _, n := range names {
        fmt.Printf("Hello %s\n", n)
    }
}

现在,通过使用if语句 ,如果没有传递值, names的长度将为0 ,我们将打印出nobody to greet

nobody to greet
Hello Sammy
Hello Sammy
Hello Jessica
Hello Drew
Hello Jamie

使用可变参数可以使您的代码更具可读性。 让我们创建一个将单词与指定分隔符连接在一起的函数。 我们将首先创建没有可变参数函数的程序,以显示它将如何读取:

join.go
package main

import "fmt"

func main() {
    var line string

    line = join(",", []string{"Sammy", "Jessica", "Drew", "Jamie"})
    fmt.Println(line)

    line = join(",", []string{"Sammy", "Jessica"})
    fmt.Println(line)

    line = join(",", []string{"Sammy"})
    fmt.Println(line)
}

func join(del string, values []string) string {
    var line string
    for i, v := range values {
        line = line + v
        if i != len(values)-1 {
            line = line + del
        }
    }
    return line
}

在这个程序中,我们将逗号( , )作为分隔符传递给join函数。 然后我们传递一片值来加入。 这是输出:

Sammy,Jessica,Drew,Jamie
Sammy,Jessica
Sammy

因为函数将一片字符串作为values参数,所以当我们调用join函数时,我们必须将所有单词包装在一个切片中。 这可能使代码难以阅读。

现在,让我们编写相同的函数,但我们将使用可变函数:

join.go
package main

import "fmt"

func main() {
    var line string

    line = join(",", "Sammy", "Jessica", "Drew", "Jamie")
    fmt.Println(line)

    line = join(",", "Sammy", "Jessica")
    fmt.Println(line)

    line = join(",", "Sammy")
    fmt.Println(line)
}

func join(del string, values ...string) string {
    var line string
    for i, v := range values {
        line = line + v
        if i != len(values)-1 {
            line = line + del
        }
    }
    return line
}

如果我们运行程序,我们可以看到我们获得与前一个程序相同的输出:

Sammy,Jessica,Drew,Jamie
Sammy,Jessica
Sammy

虽然两个版本的join函数以编程方式执行完全相同的操作,但函数的可变参数版本在调用时更容易阅读。

Variadic Argument Order

函数中只能有一个可变参数,它必须是函数中定义的最后一个参数。 以最后一个参数以外的任何顺序在可变参数函数中定义参数将导致编译错误:

join.go
package main

import "fmt"

func main() {
    var line string

    line = join(",", "Sammy", "Jessica", "Drew", "Jamie")
    fmt.Println(line)

    line = join(",", "Sammy", "Jessica")
    fmt.Println(line)

    line = join(",", "Sammy")
    fmt.Println(line)
}

func join(values ...string, del string) string {
    var line string
    for i, v := range values {
        line = line + v
        if i != len(values)-1 {
            line = line + del
        }
    }
    return line
}

这次我们将values参数放在join函数中。 这将导致以下编译错误:

./join_error.go:18:11: syntax error: cannot use ... with non-final parameter values

定义任何可变参数函数时,只有最后一个参数可以是可变参数。

爆炸的争论

到目前为止,我们已经看到我们可以将零个,一个或多个值传递给可变参数函数。 但是,有时我们会有一些值,我们希望将它们发送到可变参数函数。

让我们看看上一节中的join函数,看看会发生什么:

join.go
package main

import "fmt"

func main() {
    var line string

    names := []string{"Sammy", "Jessica", "Drew", "Jamie"}

    line = join(",", names)
    fmt.Println(line)
}

func join(del string, values ...string) string {
    var line string
    for i, v := range values {
        line = line + v
        if i != len(values)-1 {
            line = line + del
        }
    }
    return line
}

如果我们运行此程序,我们将收到编译错误:

./join-error.go:10:14: cannot use names (type []string) as type string in argument to join

即使可变参数函数将values ...string的参数转换为字符串[]string切片,我们也不能将一片字符串作为参数传递。 这是因为编译器需要字符串的离散参数。

为了解决这个问题,我们可以通过使用一组省略号( ... )对其进行Postfix并将其转换为将传递给可变函数的离散参数来爆炸切片:

join.go
package main

import "fmt"

func main() {
    var line string

    names := []string{"Sammy", "Jessica", "Drew", "Jamie"}

    line = join(",", names...)
    fmt.Println(line)
}

func join(del string, values ...string) string {
    var line string
    for i, v := range values {
        line = line + v
        if i != len(values)-1 {
            line = line + del
        }
    }
    return line
}

这次,当我们调用join函数时,我们通过附加省略号( ... )来展开names切片。

这允许程序现在按预期运行:

Sammy,Jessica,Drew,Jamie

值得注意的是,我们仍然可以传递零个,一个或多个参数,以及我们爆炸的切片。 这是通过我们目前所见的所有变体的代码:

join.go
package main

import "fmt"

func main() {
    var line string

    line = join(",", []string{"Sammy", "Jessica", "Drew", "Jamie"}...)
    fmt.Println(line)

    line = join(",", "Sammy", "Jessica", "Drew", "Jamie")
    fmt.Println(line)

    line = join(",", "Sammy", "Jessica")
    fmt.Println(line)

    line = join(",", "Sammy")
    fmt.Println(line)

}

func join(del string, values ...string) string {
    var line string
    for i, v := range values {
        line = line + v
        if i != len(values)-1 {
            line = line + del
        }
    }
    return line
}
Sammy,Jessica,Drew,Jamie
Sammy,Jessica,Drew,Jamie
Sammy,Jessica
Sammy

我们现在知道如何将零个,一个或多个参数以及我们爆炸的切片传递给可变参数函数。

结论

在本教程中,我们已经了解了可变函数如何使代码更清晰。 虽然您不总是需要使用它们,但您可能会发现它们很有用:

  • 如果您发现要创建临时切片只是为了传递给函数。
  • 当输入参数的数量未知或在调用时会有所不同。
  • 使代码更具可读性。

要了解有关创建和调用函数的更多信息,可以阅读如何在Go中定义和调用函数