Использование функций с переменным количеством аргументов в Go

Введение

Функция с переменным количеством аргументов — это функция, которая принимает ноль, одно или больше значений в качестве одного аргумента. Хотя функции с переменным количеством аргументов встречаются редко, их можно использовать, чтобы сделать код более чистым и удобным для чтения.

Функции с переменным количеством аргументов встречаются чаще, чем кажется. Наиболее распространенная из них — функция 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.

Запустим программу с помощью следующей команды:

  • go run print.go

Результат должен выглядеть так:

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 и выполнять итерацию в слайсе строк.

Если мы запустим программу, результат будет выглядеть так:

OutputHello 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, и мы не будем приветствовать никого:

Outputnobody 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. В этом случае мы передаем срез значений для объединения. Результат будет выглядеть так:

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

Если мы запустим эту программу, результат будет выглядеть как предыдущая программа:

OutputSammy,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 посредством добавления многоточия (...).

Так программа работает ожидаемым образом:

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

Теперь мы знаем, как передавать в функцию с переменным количеством аргументов ноль, один или несколько аргументов, а также раскрываемый срез.

Заключение

В этой статье мы показали, как можно использовать функции с переменным количеством аргументов, чтобы сделать код более удобочитаемым. Хотя и требуются не всегда, но они могут оказаться для вас полезными:

  • Если вы создаете временный срез, только чтобы передать его функции.
  • Если количество входных параметров неизвестно или может меняться при вызове.
  • Если вы хотите сделать код более удобочитаемым.

Чтобы узнать больше о создании и вызове функций, вы можете прочитать материал Определение и вызов функций в Go.