跳转到主要内容

介绍


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

可变参数函数比看起来更常见。 最常见的是 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 时,我们只传入一个参数,值为 1。 然后我们通过一和二,最后是一、二和三。

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

go run print.go


我们将看到以下输出:

Output

one
one two
one two three

 

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

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

定义可变参数函数


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

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 参数视为函数体内的字符串切片 ([]string)。 我们可以使用范围运算符创建一个循环并遍历字符串切片。

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

Output
Hello Sammy
Hello Sammy
Hello Jessica
Hello Drew
Hello Jamie

请注意,我们第一次调用 sayHello 时没有打印任何内容。 这是因为可变参数是一个空的字符串切片。 由于我们正在遍历切片,因此没有什么可以迭代,并且 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 语句,如果没有传递任何值,则名称的长度将为 0,我们将打印出没有人来打招呼:

Output
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
}

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

Output
Sammy,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(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
Sammy,Jessica,Drew,Jamie
Sammy,Jessica
Sammy


虽然 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(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 函数,看看会发生什么:

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,但我们不能将字符串切片作为参数传递。 这是因为编译器需要字符串的离散参数。

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

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
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
}

 

Output
Sammy,Jessica,Drew,Jamie
Sammy,Jessica,Drew,Jamie
Sammy,Jessica
Sammy

 

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

结论


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

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