В математике и компьютерном программировании вариатическая функция — это функция неопределенной арности , т. е. такая, которая принимает переменное число аргументов . Поддержка вариативных функций сильно различается в разных языках программирования .
Термин «вариативный» — неологизм , возникший в 1936–1937 годах. [1] Этот термин широко не использовался до 1970-х годов.
Существует множество математических и логических операций, которые естественным образом воспринимаются как вариативные функции. Например, суммирование чисел или объединение строк или других последовательностей — это операции, которые можно рассматривать как применимые к любому числу операндов (хотя формально в этих случаях применяется свойство ассоциативности ).
Другая операция, которая реализована как вариативная функция во многих языках, — это форматирование вывода. Двумя такими примерами являются функция C иprintf
функция Common Lispformat
. Оба принимают один аргумент, определяющий форматирование вывода, и любое количество аргументов, предоставляющих форматируемые значения.
В некоторых языках вариативные функции могут вызывать проблемы с безопасностью типов . Например, буква C printf
при неосторожном использовании может привести к возникновению класса дыр в безопасности, известных как атаки на форматную строку . Атака возможна, поскольку языковая поддержка функций с переменным числом аргументов не является типобезопасной: она позволяет функции пытаться извлечь из стека больше аргументов, чем было там помещено, что повреждает стек и приводит к неожиданному поведению. В результате Координационный центр CERT считает вариативные функции в C серьезной угрозой безопасности. [2]
В языках функционального программирования вариатики можно считать дополнительными к функции apply , которая принимает функцию и список/последовательность/массив в качестве аргументов и вызывает функцию с аргументами, указанными в этом списке, таким образом передавая переменное количество аргументов в функцию. функция. [ нужна цитация ] В функциональном языке Haskell вариативные функции могут быть реализованы путем возврата значения класса типа T
; если экземпляры T
являются окончательным возвращаемым значением r
и функцией (T t) => x -> t
, это позволяет использовать любое количество дополнительных аргументов x
. [ нужны дальнейшие объяснения ]
Связанный с этим вопрос в исследованиях по переписыванию терминов называется хеджированием или хедж-переменными . [3] В отличие от вариадик, которые представляют собой функции с аргументами, хеджи сами по себе представляют собой последовательности аргументов. Они также могут иметь ограничения (например, «принимать не более 4 аргументов») до такой степени, что они не имеют переменной длины (например, «принимать ровно 4 аргумента») — поэтому называть их вариативными аргументами может ввести в заблуждение. Однако они имеют в виду одно и то же явление, а иногда формулировки смешиваются, что приводит к таким названиям, как вариативная переменная (синоним хеджирования). Обратите внимание на двойное значение слова «переменная» и разницу между аргументами и переменными в функциональном программировании и переписывании терминов. Например, терм (функция) может иметь три переменные, одна из которых является хеджем, что позволяет терму принимать три или более аргументов (или два или более, если хедж может быть пустым).
Для портируемой реализации вариативных функций на языке Cstdarg.h
используется стандартный заголовочный файл. Старый varargs.h
заголовок устарел в пользу stdarg.h
. cstdarg
В C++ используется заголовочный файл . [4]
#include <stdarg.h> #include <stdio.h> двойное среднее ( int count , ...) { va_list ap ; интервал j ; двойная сумма = 0 ; va_start ( ap , count ); /* До C23: требуется последний фиксированный параметр (чтобы получить адрес) */ for ( j = 0 ; j < count ; j ++ ) { sum += va_arg ( ap , int ); /* Увеличивает ap до следующего аргумента. */ } va_end ( ap ); вернуть сумму / счет ; } int main ( int argc , char const * argv []) { printf ( "%f \n " , среднее ( 3 , 1 , 2 , 3 )); вернуть 0 ; }
Это вычислит среднее значение произвольного количества аргументов. Обратите внимание, что функция не знает количества аргументов или их типов. Вышеупомянутая функция ожидает, что типы будут int
, и что количество аргументов будет передано в первом аргументе (это частое использование, но ни в коем случае не обязательное для языка или компилятора). В некоторых других случаях, например printf , количество и типы аргументов определяются из строки формата. В обоих случаях это зависит от того, предоставит ли программист правильную информацию. (В качестве альтернативы для указания числа можно использовать контрольное значение , например NULL
.) Если передается меньше аргументов, чем считает функция, или типы аргументов неверны, это может привести к чтению в недопустимые области памяти и может привести к уязвимости, такие как атака на форматную строку .
stdarg.h
объявляет тип va_list
и определяет четыре макроса: va_start
, va_arg
, va_copy
и va_end
. Каждому вызову va_start
и va_copy
должен соответствовать соответствующий вызов va_end
. При работе с переменными аргументами функция обычно объявляет переменную типа va_list
( ap
в примере), которой будут управлять макросы.
va_start
принимает два аргумента: va_list
объект и ссылку на последний параметр функции (тот, который стоит перед многоточием; макрос использует его для определения направления). В C23 второй аргумент больше не потребуется, и функциям с вариационным числом аргументов больше не потребуется именованный параметр перед многоточием. [примечание 1] [6] Он инициализирует va_list
объект для использования va_arg
или va_copy
. Компилятор обычно выдает предупреждение, если ссылка неверна (например, ссылка на параметр, отличный от последнего, или ссылка на совершенно другой объект), но не препятствует нормальному завершению компиляции.va_arg
принимает два аргумента: va_list
объект (ранее инициализированный) и дескриптор типа. Он расширяется до следующего аргумента переменной и имеет указанный тип. Последовательные вызовы va_arg
позволяют по очереди обрабатывать каждый из аргументов переменной. Неопределенное поведение возникает, если тип неверен или отсутствует следующий аргумент переменной.va_end
принимает один аргумент, va_list
объект. Он служит для очистки. Если бы кто-то захотел, например, просмотреть аргументы переменной более одного раза, программист повторно инициализировал бы ваш va_list
объект, вызвав его va_end
, а затем снова.va_start
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
ключевого слова. Для аргументов должен быть указан тип, хотя его object[]
можно использовать и как универсальный. На вызывающем сайте вы можете либо перечислить аргументы по одному, либо передать уже существующий массив с нужным типом элемента. Использование вариационной формы является синтаксическим сахаром для последнего.
используя систему ; class Program { static int Foo ( int a , int b , params int [] args ) { // Возвращаем сумму целых чисел в аргументах, игнорируя a и b. интервал сумма = 0 ; foreach ( int i in args ) sum += i ; сумма возврата ; } static void Main ( string [] args ) { Console . WriteLine ( Foo ( 1 , 2 )); // 0 Консоль . WriteLine ( Foo ( 1 , 2 , 3 , 10 , 20 )); // 33 int [] ManyValues = new int [] { 13 , 14 , 15 }; Консоль . WriteLine ( Foo ( 1 , 2 , ManyValues )); // 42 } }
Базовые возможности вариативного ввода в C++ во многом идентичны таковым в C. Единственная разница заключается в синтаксисе, где запятая перед многоточием может быть опущена. C++ допускает вариативные функции без именованных параметров, но не предоставляет возможности доступа к этим аргументам, поскольку va_start
требует имени последнего фиксированного аргумента функции.
#include <iostream> #include <cstdarg> void simple_printf ( const char * fmt ...) // Также допустимо выражение «const char* fmt, ...» в стиле C { va_list args ; va_start ( аргументы , fmt ); while ( * fmt != '\0' ) { if ( * fmt == 'd' ) { int i = va_arg ( args , int ); std :: cout << i << '\n' ; } else if ( * fmt == 'c' ) { // обратите внимание на автоматическое преобразование в целочисленный тип int c = va_arg ( args , int ); std :: cout << static_cast <char> ( c ) << ' \n ' ; } else if ( * fmt == 'f' ) { double d = va_arg ( args , double ); std :: cout << d << '\n' ; } ++ ФМТ ; } va_end ( аргументы ); } int main () { simple_printf ( "dcff" , 3 , 'a' , 1.999 , 42.5 ); }
Шаблоны Variadic (пакет параметров) также можно использовать в C++ со встроенными в язык выражениями свертки .
#include <iostream> template < typename ... Ts > void foo_print ( Ts ... args ) { (( std :: cout << args << ' ' ), ...); } int main () { std :: cout << std :: boolalpha ; foo_print ( 1 , 3.14f ); // 1 3.14 foo_print ( "Foo" , 'b' , true , nullptr ); // Foo b true nullptr }
Стандарты кодирования CERT для C++ настоятельно отдают предпочтение использованию вариативных шаблонов (пакетов параметров) в C++ вместо вариативной функции в стиле C из-за меньшего риска неправильного использования. [7]
Вариадные функции в Go можно вызывать с любым количеством конечных аргументов. [8] fmt.Println
— обычная вариатическая функция; он использует пустой интерфейс как универсальный тип.
пакет основной импортировать "ФМТ" // Эта вариативная функция принимает в качестве аргументов произвольное количество целых чисел. func sum ( nums ... int ) { fmt . Print ( "Сумма " , nums ) // Также вариативная функция. итого := 0 для _ , число := диапазон чисел { итого += число } 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
тип в Java доступен как универсальный.
public class Program { // Методы с переменным числом аргументов сохраняют любые дополнительные аргументы, которые они получают, в массиве. // Следовательно, `printArgs` на самом деле является методом с одним параметром: // массивом переменной длины `String`. Private static void printArgs ( String ... strings ) { for ( String string : strings ) { System . вне . println ( строка ); } } public static void main ( String [] args ) { printArgs ( «привет» ); // сокращение от printArgs(["привет"]) printArgs ( "привет" , "мир" ); // сокращение от printArgs(["привет", "мир"]) } }
JavaScript не заботится о типах переменных аргументов.
функция sum (... числа ) { возвращает числа . уменьшить (( a , b ) => a + b , 0 ); } консоль . журнал ( сумма ( 1 , 2 , 3 )); // 6 консоль . журнал ( сумма ( 3 , 2 )); // 5 консоль . журнал ( сумма ()); // 0
Также возможно создать вариативную функцию, используя объект аргументов, хотя ее можно использовать только с функциями, созданными с помощью function
ключевого слова.
функция сумма () { возвращение массива . опытный образец . уменьшать . вызов ( аргументы , ( a , b ) => a + b , 0 ); } консоль . журнал ( сумма ( 1 , 2 , 3 )); // 6 консоль . журнал ( сумма ( 3 , 2 )); // 5 консоль . журнал ( сумма ()); // 0
Функции Lua могут передавать переменные аргументы другим функциям так же, как и другие значения, используя return
ключевое слово. Таблицы можно передавать в функции с переменным числом вариантов, используя Lua версии 5.2 или выше [9] table.unpack
или Lua 5.1 или ниже [10] unpack
. Переменные аргументы можно использовать в качестве таблицы, создав таблицу с переменным аргументом в качестве значения.
function sum (...) --... обозначает переменные значения local sum = 0 для _ , v в парах ({...}) do --создание таблицы с переменными аргументами аналогично созданию таблицы со стандартными значениями sum = сумма + v конец вернуть сумму конецзначения = { 1 , 2 , 3 , 4 } sum ( 5 , table.unpack ( values )) -- возвращает 15. table.unpack должен идти после любых других аргументов, иначе не все значения будут переданы в функцию.функция add5 (...) return ... + 5 -- это неправильное использование переменных аргументов, и она вернет только первое предоставленное значение endзаписи = {} функция Process_entries () локальная обработка = {} для i , v в парах ( записях ) do обработано [ i ] = v --заполнитель кода обработки end return table.unpack ( processed ) --возвращает все записи определенным образом который можно использовать как конец varargprint ( process_entries ()) — функция печати принимает все переменные аргументы и записывает их в стандартный вывод, разделяя их символами новой строки.
Паскаль стандартизирован стандартами ISO 7185 («Стандартный Паскаль») и 10206 («Расширенный Паскаль»). Ни одна стандартизированная форма Паскаля не поддерживает вариативные подпрограммы, за исключением некоторых встроенных подпрограмм ( read
/ readLn
и write
/ writeLn
и дополнительно в EP readStr
/ writeStr
).
Тем не менее, диалекты Паскаля реализуют механизмы, напоминающие вариативные процедуры.Delphi определяет тип данных, который может быть связан с последним формальным параметром . В определении подпрограммы это массив вариантов записей . [11]
Член вышеупомянутого типа данных позволяет проверить тип данных аргумента и последующую соответствующую обработку. Компилятор Free Pascal также поддерживает вариативные процедуры Delphi. [12]array of const
array of const
array of TVarRec
VType
record
Однако эта реализация технически требует одного аргумента — файла array
. Паскаль накладывает ограничение: массивы должны быть однородными. Это требование можно обойти, используя вариантную запись. GNU Pascal определяет реальную спецификацию формальных параметров с переменным числом аргументов с использованием многоточия ( ...
), но по состоянию на 2022 год переносимый механизм для такого использования не был определен. [13]
И GNU Pascal, и FreePascal позволяют внешне объявленным функциям использовать переменную спецификацию формальных параметров с использованием многоточия ( ...
).
PHP не заботится о типах переменных аргументов, если аргумент не является типизированным.
функция sum ( ... $nums ) : int { return array_sum ( $nums ); }эхо- сумма ( 1 , 2 , 3 ); // 6
И набрал вариативные аргументы:
функция sum ( int ... $nums ) : int { return array_sum ( $nums ); }эхо- сумма ( 1 , 'a' , 3 ); // Ошибка типа: аргумент 2, передаваемый в sum(), должен иметь тип int (начиная с PHP 7.3)
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 ) { скажем @args . перл ;}foo ( 1 , 2 ) # [] foo ( 1 , 2 , 3 ) # [3] foo ( 1 , 2 , 3 , "привет" ) # [3 "привет"] foo ( 1 , 2 , 3 , [ 4 ) , 5 ], [ 6 ]); # [3, 4, 5, 6]
Эти параметры объявляются с двумя звездочками ( **
), и они не сглаживают повторяющиеся аргументы в списке, а сохраняют аргументы более или менее такими, какие они есть:
суббар ( $a , $b , ** @args ) { скажем @args . перл ; }бар ( 1 , 2 ); # [] бар ( 1 , 2 , 3 ); # [3] бар ( 1 , 2 , 3 , «привет» ); # [3 "привет"] полоса ( 1 , 2 , 3 , [ 4 , 5 ], [ 6 ]); # [3, [4, 5], [6]]
Эти параметры объявляются со +
знаком плюс ( ), и к ним применяется «правило одного аргумента» , которое решает, как обрабатывать аргумент «slurpy» в зависимости от контекста. Проще говоря, если передается только один аргумент и этот аргумент является итеративным, этот аргумент используется для заполнения массива параметров slurpy. В любом другом случае +@
работает как **@
(т.е. нерасплющенная жижа).
sub zaz ( $a , $b , + @args ) { скажем @args . перл ;}заз ( 1 , 2 ); # [] заз ( 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 ) вывести конец аргументов foo ( 1 ) # печатает `[1]=> nil`foo ( 1 , 2 ) # печатает `[1, 2]=> nil`
Rust не поддерживает переменные аргументы в функциях. Вместо этого он использует макросы . [14]
макро_правила! вычислить { // Шаблон для одного `eval` ( eval $e : expr ) => {{ { let val : usize = $e ; // Заставляем типы быть целыми числами println! ( "{} = {}" , stringify! { $e }, val ); } }}; // Рекурсивно разложить несколько `eval` ( eval $e : expr , $( eval $es : expr ), + ) => {{ Calculation ! { eval $e } вычислить ! { $( оценка $es ), + } }}; } fn main () { вычислить ! { // Смотри, ма! Вариатический `вычислить!`! оценка 1 + 2 , оценка 3 + 4 , оценка ( 2 * 3 ) + 1 } }
Rust может взаимодействовать с вариативной системой C через c_variadic
переключатель функций. Как и другие интерфейсы C, система считается unsafe
Rust. [15]
object Program { // Методы с переменным числом аргументов сохраняют любые полученные ими дополнительные аргументы в массиве. // Следовательно, `printArgs` на самом деле является методом с одним параметром: // массивом переменной длины `String`. Private Def printArgs ( strings : String * ): Unit = { strings . Еогеасп ( println ) } def main ( args : Array [ String ]): Unit = { printArgs ( "привет" ); // сокращение от printArgs(["привет"]) printArgs ( "привет" , "мир" ); // сокращение от printArgs(["привет", "мир"]) } }
SwiftAny
заботится о типе переменных аргументов, но доступен и универсальный тип.
func Greeting ( timeOfTheDay : String , Names : String ...) { // здесь имена — [String] print ( "Похоже, у нас есть \ ( names.count ) люди " ) для имени в именах { print ( "Привет \( имя ) , хорошо \( timeOfTheDay ) " ) } }приветствие ( timeOfTheDay : «утро» , имена : «Джозеф» , «Клара» , «Уильям» , «Мария» )// Вывод: // Похоже, нас 4 человека // Привет, Джозеф, доброе утро // Привет, Клара, доброе утро // Привет, Уильям, доброе утро // Привет, Мария, доброе утро
Процедура Tcl или лямбда является переменной, если ее последний аргумент равен args
: она будет содержать список (возможно, пустой) всех оставшихся аргументов. Этот шаблон распространен во многих других методах, подобных процедурам. [16] [17]
proc Greeting { timeOfTheDay args } { puts "Похоже, у нас есть люди [llength $args]" foreach name $args { puts "Привет, $name, доброго $timeOfTheDay" } } поприветствовать «утро» «Джозеф» «Клара» «Уильям» «Мария» # Вывод: # Похоже, нас 4 человека # Привет, Джозеф, доброе утро # Привет, Клара, доброе утро # Привет, Уильям, доброе утро # Привет, Мария, доброе утро