如何在Go中使用可变参数函数

来自菜鸟教程
跳转至:导航、​搜索

介绍

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

可变参数函数比看起来更常见。 最常见的是 fmt 包中的 Println 函数。

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

函数 的参数前面带有一组省略号 (...) 被视为可变参数函数。 省略号表示提供的参数可以是零、一个或多个值。 对于 fmt.Println 包,说明参数 a 是可变参数。

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

打印.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

我们将看到以下输出:

Output
one
one two
one two three

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

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

定义可变参数函数

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

你好.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)。 我们可以使用 range 运算符创建一个循环并遍历字符串切片。

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

OutputHello Sammy
Hello Sammy
Hello Jessica
Hello Drew
Hello Jamie

请注意,我们第一次调用 sayHello 时没有打印任何内容。 这是因为可变参数是 string 的空 slice。 由于我们正在遍历切片,因此没有任何内容可以迭代,并且永远不会调用 fmt.Printf

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

你好.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

Outputnobody to greet
Hello Sammy
Hello Sammy
Hello Jessica
Hello Drew
Hello Jamie

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

加入.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 函数。 然后我们传递一个值来加入。 这是输出:

OutputSammy,Jessica,Drew,Jamie
Sammy,Jessica
Sammy

因为该函数将字符串切片作为 values 参数,所以当我们调用 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
}

如果我们运行该程序,我们可以看到我们得到了与之前的程序相同的输出:

OutputSammy,Jessica,Drew,Jamie
Sammy,Jessica
Sammy

虽然 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函数的前面。 这将导致以下编译错误:

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

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

爆炸性的论点

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

让我们看看上一节中的 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
}

如果我们运行这个程序,我们会收到一个编译错误:

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

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

为了解决这个问题,我们可以 explode 一个切片,通过在切片后面加上一组省略号 (...) 并将其转换为离散参数,然后传递给可变参数函数:

加入.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 切片。

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

OutputSammy,Jessica,Drew,Jamie

重要的是要注意,我们仍然可以传递零个、一个或多个参数,以及我们分解的切片。 以下是我们迄今为止看到的所有变体的代码:

加入.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
}
OutputSammy,Jessica,Drew,Jamie
Sammy,Jessica,Drew,Jamie
Sammy,Jessica
Sammy

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

结论

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

  • 如果你发现你正在创建一个临时切片只是为了传递给一个函数。
  • 当输入参数的数量未知或调用时会发生变化。
  • 使您的代码更具可读性。

想了解更多关于创建和调用函数的知识,可以阅读如何在Go中定义和调用函数