介绍
可变函数是一个函数,它接受零个,一个或多个值作为单个参数。 虽然可变函数不是常见的情况,但它们可用于使代码更清晰,更易读。
变量函数比它们看起来更常见。 最常见的是fmt
包中的Println
函数。
func Println(a ...interface{}) (n int, err error)
具有以一组省略号( ...
)开头的参数的函数被认为是可变函数。 省略号表示提供的参数可以是零个,一个或多个值。 对于fmt.Println
包,它声明参数a
是可变参数。
让我们创建一个使用fmt.Println
函数的程序,并传入零个,一个或多个值:
package mainimport "fmt"func main() { fmt.Println() fmt.Println("one") fmt.Println("one", "two") fmt.Println("one", "two", "three")}
我们第一次调用fmt.Println
,我们不传递任何参数。 我们第二次调用fmt.Println
我们只传入一个参数,值为one
。 然后我们传递one
和two
,最后one
, two
和three
。
让我们使用以下命令运行程序:
go run print.go
我们将看到以下输出:
oneone twoone two three
输出的第一行是空白的。 这是因为我们在第一次fmt.Println
时没有传递任何参数。 第二次打印出one
值。 然后是one
two
,最后是one
, two
和three
。
现在我们已经了解了如何调用可变参数函数,让我们看一下如何定义自己的可变参数函数。
定义Variadic函数
我们可以通过在参数前面使用省略号( ...
)来定义可变参数函数。 让我们创建一个程序,当他们的名字被发送到函数时迎接他们:
package mainimport "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 SammyHello SammyHello JessicaHello DrewHello Jamie
请注意,我们第一次打电话给sayHello
时没有打印。 这是因为可变参数是一个空的string
slice
。 由于我们循环切片,因此无需迭代,并且永远不会调用fmt.Printf
。
让我们修改程序以检测没有发送值:
package mainimport "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 greetHello SammyHello SammyHello JessicaHello DrewHello Jamie
使用可变参数可以使您的代码更具可读性。 让我们创建一个将单词与指定分隔符连接在一起的函数。 我们将首先创建没有可变参数函数的程序,以显示它将如何读取:
package mainimport "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,JamieSammy,JessicaSammy
因为函数将一片字符串作为values
参数,所以当我们调用join
函数时,我们必须将所有单词包装在一个切片中。 这可能使代码难以阅读。
现在,让我们编写相同的函数,但我们将使用可变函数:
package mainimport "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,JamieSammy,JessicaSammy
虽然两个版本的join
函数以编程方式执行完全相同的操作,但函数的可变参数版本在调用时更容易阅读。
Variadic Argument Order
函数中只能有一个可变参数,它必须是函数中定义的最后一个参数。 以最后一个参数以外的任何顺序在可变参数函数中定义参数将导致编译错误:
package mainimport "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
函数,看看会发生什么:
package mainimport "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并将其转换为将传递给可变函数的离散参数来爆炸切片:
package mainimport "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
值得注意的是,我们仍然可以传递零个,一个或多个参数,以及我们爆炸的切片。 这是通过我们目前所见的所有变体的代码:
package mainimport "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,JamieSammy,Jessica,Drew,JamieSammy,JessicaSammy
我们现在知道如何将零个,一个或多个参数以及我们爆炸的切片传递给可变参数函数。
结论
在本教程中,我们已经了解了可变函数如何使代码更清晰。 虽然您不总是需要使用它们,但您可能会发现它们很有用:
- 如果您发现要创建临时切片只是为了传递给函数。
- 当输入参数的数量未知或在调用时会有所不同。
- 使代码更具可读性。
要了解有关创建和调用函数的更多信息,可以阅读如何在Go中定义和调用函数 。