Tutorial

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

GoDevelopment

Введение

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

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

Creative Commons License