Функция с переменным количеством аргументов — это функция, которая принимает ноль, одно или больше значений в качестве одного аргумента. Хотя функции с переменным количеством аргументов встречаются редко, их можно использовать, чтобы сделать код более чистым и удобным для чтения.
Функции с переменным количеством аргументов встречаются чаще, чем кажется. Наиболее распространенная из них — функция Println
из пакета fmt
.
func Println(a ...interface{}) (n int, err error)
Функция с параметром, которому предшествует набор многоточий (...
), считается функцией с переменным количеством аргументов. Многоточие означает, что предоставляемый параметр может иметь ноль, одно или несколько значений. Для пакета fmt.Println
это указывает, что параметр a
является параметром с переменным количеством аргументов.
Создадим программу, которая использует функцию fmt.Println
и передает ноль, одно или несколько значений:
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
.
Запустим программу с помощью следующей команды:
- go run print.go
Результат должен выглядеть так:
Output
one
one two
one two three
Первая выводимая строка будет пустой. Это связано с тем, что мы не передали никаких аргументов при первом вызове fmt.Println
. При втором вызове будет выведено значение one
. Затем будут выведены значения one
и two
, а в заключение — one
, two
и three
.
Мы показали, как вызывать функцию с переменным количеством аргументов, а теперь посмотрим, как можно определить собственную функцию с переменным количеством аргументов.
Для определения функции с переменным количеством аргументов мы используем символ многоточия (...
) перед аргументом. Создадим программу, которая будет приветствовать людей при отправке их имен в функцию:
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
, параметр names
можно рассматривать как срез строк ([]string
) в теле функции. Мы можем создать цикл с оператором range
и выполнять итерацию в слайсе строк.
Если мы запустим программу, результат будет выглядеть так:
OutputHello Sammy
Hello Sammy
Hello Jessica
Hello Drew
Hello Jamie
Обратите внимание, что при первом вызове sayHello
ничего не выводится. Это связано с тем, что значением параметра с переменным количеством аргументов были пустой срез
или пустая строка
. Поскольку мы выполняем цикл в срезе, объектов для итерации нет, и функция fmt.Printf
не вызывается.
Изменим программу так, чтобы она определяла, что никакие значения в нее не отправляются:
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
, и мы не будем приветствовать никого
:
Outputnobody to greet
Hello Sammy
Hello Sammy
Hello Jessica
Hello Drew
Hello Jamie
Использование параметра с переменным количеством аргументов делает код удобнее для чтения. Создадим функцию, объединяющую слова с заданным разделителем. Вначале мы создадим эту программу без функции с переменным количеством аргументов, чтобы показать, как будет проводиться чтение:
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
. Это усложняет чтение кода.
Теперь напишем ту же функцию, но как функцию с переменным количеством аргументов:
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
выполняют одно и то же с программной точки зрения, версия функции с переменным количеством аргументов намного проще читается при вызове.
В функции может быть только один параметр с переменным количеством аргументов, и это должен быть последний определяемый в функции параметр. Определение параметров в функции с переменным количеством аргументов в любом другом порядке вызовет ошибку при компиляции:
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
из последнего раздела и посмотрим, что получится:
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
, мы не можем передать срез строк в качестве аргумента. Это связано с тем, что компилятор ожидает получить дискретные аргументы строк.
Чтобы обойти эту сложность, мы можем раскрыть срез, добавив в него суффикс многоточия (...
) и превратив его в дискретные аргументы, которые будут передаваться в функцию с переменным количеством аргументов:
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
Важно отметить, что мы можем передать ноль, один или несколько аргументов в дополнение к срезу, который мы раскрываем. Вот так будет выглядеть код, передающий все варианты, которые мы видели до этого:
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.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Sign up for Infrastructure as a Newsletter.
Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.