URL: https://www.progressiverobot.com/how-to-use-variadic-functions-in-go-ru/

Введение

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

Функции с переменным количеством аргументов встречаются чаще, чем кажется. Наиболее распространенная из них — функция Println из пакета fmt.

				
					
func Println(a ...interface{}) (n int, err error)

				
			

Функция с параметром, которому предшествует набор многоточий (...), считается функцией с переменным количеством аргументов. Многоточие означает, что предоставляемый параметр может иметь ноль, одно или несколько значений. Для пакета fmt.Println это указывает, что параметр a является параметром с переменным количеством аргументов.

Создадим программу, которая использует функцию fmt.Println и передает ноль, одно или несколько значений:

				
					
[label 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

				
			

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

				
					
[secondary_label Output]



one

one two

one two three

				
			

Первая выводимая строка будет пустой. Это связано с тем, что мы не передали никаких аргументов при первом вызове fmt.Println. При втором вызове будет выведено значение one. Затем будут выведены значения one и two, а в заключение — one, two и three.

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

Определение функции с переменным количеством аргументов

использование функций с переменным количеством аргументов в go illustration for: Определение функции с переменным количеством аргументов

Для определения функции с переменным количеством аргументов мы используем символ многоточия (...) перед аргументом. Создадим программу, которая будет приветствовать людей при отправке их имен в функцию:

				
					
[label 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, параметр names можно рассматривать как срез строк ([]string) в теле функции. Мы можем создать цикл с оператором range и выполнять итерацию в слайсе строк.

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

				
					
[secondary_label Output]

Hello Sammy

Hello Sammy

Hello Jessica

Hello Drew

Hello Jamie

				
			

Обратите внимание, что при первом вызове sayHello ничего не выводится. Это связано с тем, что значением параметра с переменным количеством аргументов были пустой срез или пустая строка. Поскольку мы выполняем цикл в срезе, объектов для итерации нет, и функция fmt.Printf не вызывается.

Изменим программу так, чтобы она определяла, что никакие значения в нее не отправляются:

				
					
[label 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 не передаются никакие значения, длина names будет равна 0, и мы не будем приветствовать никого:

				
					
[secondary_label Output]

nobody to greet

Hello Sammy

Hello Sammy

Hello Jessica

Hello Drew

Hello Jamie

				
			

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

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

				
					
[secondary_label Output]

Sammy,Jessica,Drew,Jamie

Sammy,Jessica

Sammy

				
			

Поскольку функция принимает срез строки в качестве параметра values, нам нужно было заключать все слова в срез при вызове функции join. Это усложняет чтение кода.

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

				
					
[label 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

}

				
			

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

				
					
[secondary_label Output]

Sammy,Jessica,Drew,Jamie

Sammy,Jessica

Sammy

				
			

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

Порядок при переменном количестве аргументов

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

				
					
[label 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. В результате возникнет следующая ошибка компиляции:

				
					
[secondary_label Output]

./join_error.go:18:11: syntax error: cannot use ... with non-final parameter values

				
			

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

Раскрывающиеся аргументы

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

Возьмем функцию join из последнего раздела и посмотрим, что получится:

				
					
[label 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

}

				
			

Если мы запустим эту программу, то получим ошибку компиляции:

				
					
[secondary_label Output]

./join-error.go:10:14: cannot use names (type []string) as type string in argument to join

				
			

Хотя функция с переменным количеством аргументов конвертирует параметр values ...string в срез строк []string, мы не можем передать срез строк в качестве аргумента. Это связано с тем, что компилятор ожидает получить дискретные аргументы строк.

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

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

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

				
					
[secondary_label Output]

Sammy,Jessica,Drew,Jamie

				
			

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

				
					
[label 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

}

				
			
				
					
[secondary_label Output]

Sammy,Jessica,Drew,Jamie

Sammy,Jessica,Drew,Jamie

Sammy,Jessica

Sammy

				
			

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

Заключение

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

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

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