// Tutorial //

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

Published on January 24, 2020
Default avatar
By Gopher Guides
Developer and author at DigitalOcean.
Русский
Использование функций с переменным количеством аргументов в 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.

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

  1. 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 %s\n", 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 %s\n", 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.


Want to learn more? Join the DigitalOcean Community!

Join our DigitalOcean community of over a million developers for free! Get help and share knowledge in our Questions & Answers section, find tutorials and tools that will help you grow as a developer and scale your project or business, and subscribe to topics of interest.

Sign up
About the authors
Default avatar
Developer and author at DigitalOcean.

Still looking for an answer?

Was this helpful?
Leave a comment

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!