One place for hosting & domains

      Использование функций с переменным количеством аргументов в 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.

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

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

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

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

      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 %sn", 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.



      Source link