В математике и в компьютерном программировании функция с переменными числами - это функция с неопределенной арностью, т. е. та, которая принимает переменное количество аргументов. Поддержка вариативных функций сильно различается между языками программирования.
Термин вариативность - это неологизм, восходящий к 1936-1937 гг. Этот термин не использовался широко до 1970-х годов.
Существует множество математических и логических операций, которые естественно воспринимаются как функции с переменными числами. Например, суммирование чисел или конкатенация строк или других последовательностей - это операции, которые можно рассматривать как применимые к любому количеству операндов (даже если формально в этих случаях ассоциативное свойство прилагается).
Другая операция, реализованная как функция с переменными числами во многих языках, - это форматирование вывода. Два таких примера - функция C printf
и Common Lisp функция format
.. Оба принимают один аргумент, определяющий форматирование вывода, и любое количество аргументов, которые предоставляют значения для форматирования.
Функции с переменным числом переменных могут вызывать проблемы безопасности типов на некоторых языках. Например, C printf
, если его использовать неосторожно, может привести к классу дыр в безопасности, известному как атаки на строку формата . Атака возможна, потому что языковая поддержка вариативных функций не является типобезопасной: она позволяет функции пытаться извлечь больше аргументов из стека , чем было помещено туда, что повреждает стек и приводит к неожиданному поведению. Вследствие этого Координационный центр CERT считает, что вариативные функции на языке C представляют серьезную угрозу безопасности.
В функциональных языках вариативность может рассматриваться как дополнение к функция, которая принимает функцию и список / последовательность / массив в качестве аргументов и вызывает функцию с аргументами, указанными в этом списке, тем самым передавая функции переменное количество аргументов. В функциональном языке Haskell вариативные функции могут быть реализованы путем возврата значения типа class T
; если экземпляры T
являются окончательным возвращаемым значением r
и функцией (T t) =>x ->t
, это позволяет использовать любое количество дополнительных аргументов x
.
Связанная тема в исследовании переписывания терминов называется хеджирование или переменные хеджирования . В отличие от вариативных функций, которые представляют собой функции с аргументами, хеджирование - это сами последовательности аргументов. Они также могут иметь ограничения (например, «принимать не более 4 аргументов») до такой степени, что они не имеют переменной длины (например, «принимать ровно 4 аргумента») - таким образом, называть их вариативными числами может вводить в заблуждение. Однако они относятся к одному и тому же явлению, и иногда формулировка смешанная, что приводит к таким именам, как переменная с переменным числом аргументов (синоним хеджирования). Обратите внимание на двойное значение слова переменная и разницу между аргументами и переменными в функциональном программировании и переписывании терминов. Например, терм (функция) может иметь три переменных, одна из которых - хедж, что позволяет термину принимать три или более аргумента (или два или более, если хеджирование может быть пустым).
Для переносимой реализации вариативных функций на языке программирования C стандартный заголовок stdarg.h
файл используется. Старый заголовок varargs.h
был устаревшим в пользу stdarg.h
. В C ++ используется файл заголовка cstdarg
.
#include#include двойное среднее (int count,...) {va_list ap; int j; двойная сумма = 0; va_start (ap, count); / * Требуется последний фиксированный параметр (для получения адреса) * / for (j = 0; j < count; j++) { sum += va_arg(ap, int); /* Increments ap to the next argument. */ } va_end(ap); return sum / count; } int main(int argc, char const *argv) { printf("%f\n", average(3, 1, 2, 3)); return 0; }
Это вычислит среднее значение произвольного количества аргументов. Обратите внимание, что функция не знает количество аргументов или их Типы. Вышеупомянутая функция ожидает, что типы будут int
, и что количество аргументов передается в первом аргументе (это частое использование, но ни в коем случае не обеспечивается языком или компилятором). в некоторых других случаях, например printf, количество и типы аргументов вычисляются из строки формата. В обоих случаях это зависит от правильности информации программистом. Если передано меньше аргументов, чем функция считает, что или типы аргументов неверны, это может привести к чтению в недопустимые области памяти и может привести к уязвимостям, таким как атака строки формата .
stdarg.h
объявляет тип, va_list
и определяет четыре макроса: va_start
, va_arg
, va_copy
и v a_end
. Каждый вызов va_start
и va_copy
должен сопровождаться соответствующим вызовом va_end
. При работе с переменными аргументами функция обычно объявляет переменную типа va_list
(ap
в примере), которой будут управлять макросы.
va_start
принимает два аргумента: объект va_list
и ссылку на последний параметр функции (тот, который стоит перед многоточием; макрос использует его, чтобы ориентироваться). Он инициализирует объект va_list
для использования va_arg
или va_copy
. Обычно компилятор выдает предупреждение, если ссылка неверна (например, ссылка на параметр, отличный от последнего, или ссылка на совершенно другой объект), но не препятствует нормальному завершению компиляции.va_arg
принимает два аргумента: объект va_list
(ранее инициализированный) и дескриптор типа. Он расширяется до следующего аргумента переменной и имеет указанный тип. Последовательные вызовы va_arg
позволяют по очереди обрабатывать каждый из переменных аргументов. Неопределенное поведение возникает, если тип неверен или отсутствует следующий аргумент переменной.va_end
принимает один аргумент, объект va_list
. Он служит для уборки. Если вы хотите, например, просканировать аргументы переменных более одного раза, вы должны повторно инициализировать свой объект va_list
, вызвав va_end
, а затем снова va_start
на it.va_copy
принимает два аргумента, оба из которых являются объектами va_list
. Он клонирует второй (который должен быть инициализирован) в первый. Возвращаясь к примеру «сканировать переменные аргументы более одного раза», этого можно добиться, вызвав va_start
в первом va_list
, а затем используя va_copy
для клонирования во второй va_list
. После сканирования аргументов переменных в первый раз с помощью va_arg
и первого va_list
(избавившись от него с помощью va_end
), вы можете сканировать аргументы переменных во второй раз с помощью va_arg
и второй va_list
. Не забудьте va_end
клон va_list
.C # описывает функции с переменным числом аргументов с помощью ключевого слова params
. Для аргументов должен быть указан тип, хотя объект
может использоваться как универсальный.
с использованием системы; class Program {static int Foo (int a, int b, params int args) {// Возвращает сумму целых чисел в args, игнорируя a и b. int sum = 0; foreach (int i в args) sum + = i; сумма возврата; } static void Main (строковые аргументы) {Console.WriteLine (Foo (1, 2)); // 0 Console.WriteLine (Foo (1, 2, 3, 10, 20)); // 33}}
#include#include void simple_printf (const char * fmt...); int main () {simple_printf ("dcff", 3, 'а', 1.999, 42.5); } void simple_printf (const char * fmt...) // "const char * fmt,..." в стиле C также допустимо {va_list args; va_start (аргументы, fmt); в то время как (* fmt! = '\ 0') {если (* fmt == 'd') {int я = va_arg (args, int); std :: cout << i << '\n'; } else if (*fmt == 'c') { // note automatic conversion to integral type int c = va_arg(args, int); std::cout << static_cast (c) << '\n'; } else if (*fmt == 'f') { double d = va_arg(args, double); std::cout << d << '\n'; } ++fmt; } va_end(args); }
функции с переменным числом аргументов можно вызывать с любым числом завершающих аргументов. fmt.Println
- это обычная функция с переменным числом аргументов; он использует пустой интерфейс как универсальный тип.
основной импорт пакета "fmt" // Эта вариативная функция принимает произвольное количество целых чисел в качестве аргументов. func sum (nums... int) {fmt.Print ("The sum of", nums) // Также вариативная функция. total: = 0 for _, num: = range nums {total + = num} fmt.Println ("is", total) // Также вариативная функция. } func main () {// Функции с переменным числом аргументов могут быть вызваны обычным способом с отдельными // аргументами. sum (1, 2) // «Сумма [1 2] равна 3» sum (1, 2, 3) // «Сумма [1 2 3] равна 6» // Если у вас уже есть несколько аргументов в срез, примените их к // функции с переменным числом аргументов, используя функцию func (slice...) следующим образом. nums: = int {1, 2, 3, 4} sum (nums...) // «Сумма [1 2 3 4] равна 10»}
Вывод:
Сумма [1 2 ] равно 3 Сумма [1 2 3] равна 6. Сумма [1 2 3 4] составляет 10
Как и в C #, объект Object
type доступен как универсальный.
программа открытого класса {private static void printArgs (String... strings) {for (String string: strings) {System.out.println (string); }} public static void main (String args) {// компилятор помещает аргумент (ы), переданный в printArgs, внутрь массива // что означает, что printArgs - это просто метод, который принимает единственный аргумент, который представляет собой массив строк переменной длины printArgs (" Здравствуйте"); // сокращение от printArgs (["hello"]) printArgs ("hello", "world"); // сокращение от printArgs (["hello", "world"])}}
JavaScript не заботится о типах вариативных аргументов.
функция sum (... числа) {return numbers.reduce ((a, b) =>a + b); } sum (1, 2, 3) // 6 sum (3, 2) // 5
PHP не заботится о типах вариативных аргументов.
функция sum (... $ nums) {return array_sum ($ nums); } эхо-сумма (1, 2, 3); // 6
Python не заботится о типах вариативных аргументов.
def foo (a, b, * args): print (args) # args - это кортеж (неизменяемая последовательность). foo (1, 2) # () foo (1, 2, 3) # (3,) foo (1, 2, 3, "привет") # (3, "привет")
Аргументы ключевого слова могут быть сохранены в словаре, например def bar (* args, ** kwargs)
.
В Raku тип параметров, создающих вариативные функции, известен как параметры массива slurpy, и они подразделяются на три группы:
*
), и они сглаживают аргументы, растворяя один или несколько слоев элементов, которые можно повторять (например, Iterables ).sub foo ($ a, $ b, * @ args) {say @ args.perl;} foo (1, 2) # foo (1, 2, 3) # [3] foo (1, 2, 3, "привет") # [3 " hello "] foo (1, 2, 3, [4, 5], [6]); # [3, 4, 5, 6]
subbar ($ a, $ b, ** @ args) {say @ args.perl; } bar (1, 2); # bar (1, 2, 3); # [3] bar (1, 2, 3, "привет"); # [3 "привет"] bar (1, 2, 3, [4, 5], [6]); # [3, [4, 5], [6]]
+
) знак, и они применяют «правило одного аргумента », которое решает, как обрабатывать аргумент slurpy на основе контекста. Проще говоря, если передан только один аргумент и этот аргумент является повторяемым, этот аргумент используется для заполнения массива параметров slurpy. В любом другом случае + @
работает как ** @
(т. Е. Не сглаженное болтовня). sub zaz ($ a, $ b, + @ args) {скажем @ args.perl; } zaz (1, 2); # zaz (1, 2, 3); # [3] zaz (1, 2, 3, "привет"); # [3 "привет"] zaz (1, 2, [4, 5]); # [4, 5], единственный аргумент заполняет массив zaz (1, 2, 3, [4, 5]); # [3, [4, 5]], ведет себя как ** @ zaz (1, 2, 3, [4, 5], [6]); # [3, [4, 5], [6]], поведение как ** @
Ruby не заботится о типах аргументов с переменным числом аргументов.
def foo (* args) print args end foo (1) # выводит `[1] =>nil` foo (1, 2) # выводит` [1, 2] =>nil`
Swift заботится о типах вариативных аргументов, но доступен универсальный тип Any
.
func greet (timeOfTheDay: String, names: String...) {// здесь имена [String] print ("Похоже, у нас есть \ (names.count) people") вместо имени в именах {print ( "Hello \ (name), good \ (timeOfTheDay)")}} greet (timeOfTheDay: "morning", names: "Joseph", "Clara", "William", "Maria") // Вывод: // Похож на у нас 4 человека // Привет Джозеф, доброе утро // Привет, Клара, доброе утро // Привет, Уильям, доброе утро // Привет, Мария, доброе утро