Введение
Функция с переменным количеством аргументов — это функция, которая принимает ноль, одно или больше значений в качестве одного аргумента. Хотя функции с переменным количеством аргументов встречаются редко, их можно использовать, чтобы сделать код более чистым и удобным для чтения.
Функции с переменным количеством аргументов встречаются чаще, чем кажется. Наиболее распространенная из них — функция Println
из пакета fmt
.
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
. Затем мы передаем значения one
и two
, и в заключение one
, two
и three
.
Запустим программу с помощью следующей команды:
Результат должен выглядеть так:
Output
one
one two
one two three
Первая выводимая строка будет пустой. Это связано с тем, что мы не передали никаких аргументов при первом вызове fmt.Println
. При втором вызове будет выведено значение one
. Затем будут выведены значения one
и two
, а в заключение — one
, two
и three
.
Мы показали, как вызывать функцию с переменным количеством аргументов, а теперь посмотрим, как можно определить собственную функцию с переменным количеством аргументов.
Определение функции с переменным количеством аргументов
Для определения функции с переменным количеством аргументов мы используем символ многоточия (...
) перед аргументом. Создадим программу, которая будет приветствовать людей при отправке их имен в функцию:
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 %sn", n)
}
}
Мы создали функцию sayHello
, которая принимает только один параметр с именем names
. Это параметр с переменным количеством аргументов, поскольку мы поставили многоточие (...
) перед типом данных: ...string
. Это указывает Go, что функция может принимать ноль, один или много аргументов.
Функция sayHello
получает параметр names
в качестве среза
. Поскольку используется тип данных string
, параметр names
можно рассматривать как срез строк ([]string
) в теле функции. Мы можем создать цикл с оператором range
и выполнять итерацию в слайсе строк.
Если мы запустим программу, результат будет выглядеть так:
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 %sn", n)
}
}
Теперь, если при использовании выражения if
не передаются никакие значения, длина names
будет равна 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
}
В этой программе мы передаем запятую (,
) в качестве разделителя для функции join
. В этом случае мы передаем срез значений для объединения. Результат будет выглядеть так:
Output
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
}
Если мы запустим эту программу, результат будет выглядеть как предыдущая программа:
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
}
Теперь при вызове функции join
мы раскрываем срез names
посредством добавления многоточия (...
).
Так программа работает ожидаемым образом:
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
Теперь мы знаем, как передавать в функцию с переменным количеством аргументов ноль, один или несколько аргументов, а также раскрываемый срез.
Заключение
В этой статье мы показали, как можно использовать функции с переменным количеством аргументов, чтобы сделать код более удобочитаемым. Хотя и требуются не всегда, но они могут оказаться для вас полезными:
- Если вы создаете временный срез, только чтобы передать его функции.
- Если количество входных параметров неизвестно или может меняться при вызове.
- Если вы хотите сделать код более удобочитаемым.
Чтобы узнать больше о создании и вызове функций, вы можете прочитать материал Определение и вызов функций в Go.