В математике и программировании вариативная функция — это функция неопределенной арности , т. е. функция, которая принимает переменное число аргументов . Поддержка вариативных функций сильно различается в разных языках программирования .
Термин вариативный — неологизм , появившийся в 1936–1937 годах. [1] Термин не использовался широко до 1970-х годов.
Существует множество математических и логических операций, которые естественным образом воспринимаются как вариативные функции. Например, суммирование чисел или конкатенация строк или других последовательностей — это операции, которые можно считать применимыми к любому числу операндов (хотя формально в этих случаях применяется свойство ассоциативности ).
Другая операция, которая была реализована как вариативная функция во многих языках, — это форматирование вывода. Функция Cprintf
и функция Common Lispformat
— два таких примера. Обе принимают один аргумент, который задает форматирование вывода, и любое количество аргументов, которые предоставляют значения для форматирования.
Вариативные функции могут выявлять проблемы безопасности типов в некоторых языках. Например, C printf
, если их использовать неосторожно, может привести к появлению класса уязвимостей безопасности, известных как атаки на форматную строку . Атака возможна, поскольку поддержка языком вариативных функций не является типобезопасной: она позволяет функции пытаться извлечь из стека больше аргументов, чем было там помещено, что приводит к повреждению стека и неожиданному поведению. Вследствие этого Координационный центр CERT считает вариативные функции в C высокой степенью риска безопасности. [2]
В функциональных языках программирования вариативные функции можно считать дополнительными к функции apply , которая принимает функцию и список/последовательность/массив в качестве аргументов и вызывает функцию с аргументами, указанными в этом списке, тем самым передавая функции переменное количество аргументов. [ требуется ссылка ] В функциональном языке Haskell вариативные функции могут быть реализованы путем возврата значения типа class T
; если экземпляры T
представляют собой конечное возвращаемое значение r
и функцию (T t) => x -> t
, это допускает любое количество дополнительных аргументов x
. [ требуется дополнительное объяснение ]
Связанный предмет в исследовании переписывания терминов называется хеджированием или хедж-переменными . [3] В отличие от вариативных переменных, которые являются функциями с аргументами, хеджирования являются последовательностями самих аргументов. Они также могут иметь ограничения (например, «принимать не более 4 аргументов») до такой степени, что они не являются переменной длины (например, «принимать ровно 4 аргумента») — таким образом, называть их вариативными переменными может быть вводящим в заблуждение. Однако они относятся к одному и тому же явлению, и иногда формулировка смешивается, что приводит к таким названиям, как вариативная переменная (синоним хеджирования). Обратите внимание на двойное значение слова « переменная» и разницу между аргументами и переменными в функциональном программировании и переписывании терминов. Например, терм (функция) может иметь три переменные, одна из которых является хеджированием, что позволяет терму принимать три или более аргументов (или два или более, если хеджирование разрешено быть пустым).
Для переносимой реализации вариативных функций в языке Cstdarg.h
используется стандартный заголовочный файл. Старый varargs.h
заголовок был устарел в пользу . В C++ используется stdarg.h
заголовочный файл . [4]cstdarg
#include <stdarg.h> #include <stdio.h> двойное среднее ( int count , ...) { va_list ap ; int 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 )); return 0 ; }
Это вычислит среднее значение произвольного числа аргументов. Обратите внимание, что функция не знает числа аргументов или их типов. Вышеуказанная функция ожидает, что типы будут int
, и что число аргументов передается в первом аргументе (это частое использование, но никоим образом не навязывается языком или компилятором). В некоторых других случаях, например printf , число и типы аргументов определяются из строки формата. В обоих случаях это зависит от программиста, который должен предоставить правильную информацию. (В качестве альтернативы можно использовать контрольное значение, например NULL
или , nullptr
для указания конца списка параметров.) Если передано меньше аргументов, чем считает функция, или типы аргументов неверны, это может привести к чтению ею недопустимых областей памяти и может привести к уязвимостям, таким как атака на строку формата . В зависимости от системы, даже использование NULL
в качестве контрольного может столкнуться с такими проблемами; nullptr
или для их избежания можно использовать выделенный нулевой указатель правильного целевого типа.
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 ) { // Возвращает сумму целых чисел в args, игнорируя a и b. int sum = 0 ; foreach ( int i in args ) sum += i ; return sum ; } static void Main ( string [] args ) { Console . WriteLine ( Foo ( 1 , 2 )); // 0 Console . WriteLine ( Foo ( 1 , 2 , 3 , 10 , 20 )); // 33 int [] manyValues = new int [] { 13 , 14 , 15 }; Console . WriteLine ( Foo ( 1 , 2 , manyValues )); // 42 } }
Базовая вариативная возможность в C++ во многом идентична таковой в C. Единственное отличие заключается в синтаксисе, где запятая перед многоточием может быть опущена. C++ допускает вариативные функции без именованных параметров , но не предоставляет способа доступа к этим аргументам, поскольку va_start
требует имени последнего фиксированного аргумента функции.
#include <iostream> #include <cstdarg> void simple_printf ( const char * fmt ...) // C-style "const char* fmt, ..." также допустим { va_list args ; va_start ( args , 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' ; } ++ fmt ; } va_end ( args ); } int main () { simple_printf ( "dcff" , 3 , 'a' , 1.999 , 42.5 ); }
Вариативные шаблоны (пакет параметров) также можно использовать в 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 ) // Также вариативная функция. 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#, в JavaObject
этот тип доступен как универсальный.
public class Program { // Методы с переменным числом аргументов хранят любые дополнительные аргументы, которые они получают, в массиве. // Следовательно, `printArgs` на самом деле является методом с одним параметром: // массивом переменной длины `String`. private static void printArgs ( String ... strings ) { for ( String string : strings ) { System . out . println ( string ); } } public static void main ( String [] args ) { printArgs ( "hello" ); // сокращение от printArgs(["hello"]) printArgs ( "hello" , "world" ); // сокращение от printArgs(["hello", "world"]) } }
JavaScript не заботится о типах вариативных аргументов.
функция сумма (... числа ) { вернуть числа . уменьшить (( a , b ) => a + b , 0 ); } console.log ( sum ( 1,2,3 ) ) ; // 6 console.log ( sum ( 3,2 ) ) ; // 5 console.log ( sum ( ) ) ; // 0
Также возможно создать вариативную функцию с использованием объекта arguments, хотя ее можно использовать только с функциями, созданными с помощью function
ключевого слова.
функция sum ( ) { return Array.prototype.reduce.call ( arguments , ( a , b ) = > a + b , 0 ) ; } console.log ( sum ( 1,2,3 ) ) ; // 6 console.log ( sum ( 3,2 ) ) ; // 5 console.log ( sum ( ) ) ; // 0
Функции Lua могут передавать varargs другим функциям так же, как и другие значения, используя return
ключевое слово. Таблицы можно передавать в вариативные функции с помощью , в Lua версии 5.2 или выше [9] table.unpack
или Lua 5.1 или ниже [10] unpack
. Varargs можно использовать в качестве таблицы, построив таблицу с vararg в качестве значения.
функция sum (...) --... обозначает локальную сумму с переменным числом аргументов = 0 для _ , v в парах ({...}) do --создание таблицы с переменным числом аргументов аналогично созданию таблицы со стандартными значениями sum = sum + v end return sum endзначения = { 1 , 2 , 3 , 4 } сумма ( 5 , table.unpack ( значения )) -- возвращает 15. table.unpack должен следовать после любых других аргументов, в противном случае не все значения будут переданы в функцию.function add5 (...) return ... + 5 --это неправильное использование varargs, и будет возвращено только первое предоставленное значение endзаписи = {} функция process_entries () локальная обработанная = {} for i , v in pairs ( entry ) do processing [ i ] = v --placeholder код обработки конец return table.unpack ( processing ) -- возвращает все записи таким образом, что их можно использовать как vararg конецprint ( process_entries ()) — функция print принимает все varargs и записывает их в stdout, разделяя символами новой строки
Pascal стандартизирован стандартами ISO 7185 («Стандартный Pascal») и 10206 («Расширенный Pascal»). Ни одна из стандартизированных форм Pascal не поддерживает вариативные процедуры, за исключением некоторых встроенных процедур ( read
/ readLn
и write
/ writeLn
, а также дополнительно в EP readStr
/ writeStr
).
Тем не менее, диалекты Pascal реализуют механизмы, напоминающие вариативные процедуры. Delphi определяет тип данных, который может быть связан с последним формальным параметром . В определении процедуры это , массив вариантных записей . [ 11]
Член вышеупомянутого типа данных позволяет проверять тип данных аргумента и последующую соответствующую обработку. Free Pascal Compiler также поддерживает вариативные процедуры Delphi. [12]array of const
array of const
array of TVarRec
VType
record
Однако эта реализация технически требует одного аргумента, то есть array
. Pascal накладывает ограничение, что массивы должны быть однородными. Это требование обходит использование вариантной записи. 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 ); }echo sum ( 1 , 'a' , 3 ); // TypeError: Аргумент 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 и подразделяется на три группы:
Эти параметры объявляются с помощью одной звездочки ( *
) и выравнивают аргументы, удаляя один или несколько слоев элементов, которые можно итерировать (т. е. итерируемые объекты).
sub foo ( $a , $b , * @args ) { say @args . perl ;}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 ) { say @args . perl ; }бар ( 1 , 2 ); # [] бар ( 1 , 2 , 3 ); # [3] бар ( 1 , 2 , 3 , "привет" ); # [3 "привет"] бар ( 1 , 2 , 3 , [ 4 , 5 ], [ 6 ]); # [3, [4, 5], [6]]
Эти параметры объявляются со +
знаком плюс ( ) и применяют "правило одного аргумента" , которое решает, как обрабатывать аргумент slurpy на основе контекста. Проще говоря, если передан только один аргумент и этот аргумент итерируемый, этот аргумент используется для заполнения массива параметров slurpy. В любом другом случае +@
работает как **@
(т. е. неразвернутый slurpy).
под zaz ( $a , $b , + @args ) { say @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 ) печать аргументов конец foo ( 1 ) # печатает `[1]=> nil`foo ( 1 , 2 ) # печатает `[1, 2]=> nil`
Rust не поддерживает вариативные аргументы в функциях. Вместо этого он использует макросы . [14]
macro_rules! calculate { // Шаблон для одного `eval` ( eval $e : expr ) => {{ { let val : usize = $e ; // Принудительно сделать типы целыми числами println! ( "{} = {}" , stringify! { $e }, val ); } }}; // Рекурсивно разложить несколько `eval`ов ( eval $e : expr , $( eval $es : expr ), + ) => {{ calculate ! { eval $e } calculate ! { $( eval $es ), + } }}; } fn main () { calculate ! { // Смотри, ма! Variadic `calculate!`! eval 1 + 2 , eval 3 + 4 , eval ( 2 * 3 ) + 1 } }
Rust может взаимодействовать с вариативной системой C через c_variadic
переключатель функций. Как и в случае с другими интерфейсами C, система считается unsafe
Rust. [15]
object Program { // Методы с переменным числом аргументов хранят любые дополнительные аргументы, которые они получают, в массиве. // Следовательно, `printArgs` на самом деле является методом с одним параметром: // массивом переменной длины `String`. private def printArgs ( strings : String * ): Unit = { strings . foreach ( println ) } def main ( args : Array [ String ]): Unit = { printArgs ( "hello" ); // сокращение от printArgs(["hello"]) printArgs ( "hello" , "world" ); // сокращение от printArgs(["hello", "world"]) } }
SwiftAny
учитывает тип вариативных аргументов, но доступен и универсальный тип.
func greet ( timeOfTheDay : String , names : String ...) { // здесь names — это [String] print ( "Похоже, у нас есть \( имена . количество ) человек" ) для имени в именах { print ( "Привет \( имя ) , хорошо \( времяДня ) " ) } }приветствовать ( timeOfTheDay : "утро" , имена : "Джозеф" , "Клара" , "Уильям" , "Мария" )// Вывод: // Похоже, нас 4 человека // Привет, Джозеф, доброе утро // Привет, Клара, доброе утро // Привет, Уильям, доброе утро // Привет, Мария, доброе утро
Процедура Tcl или лямбда является вариативной, когда ее последний аргумент — args
: он будет содержать список (возможно, пустой) всех оставшихся аргументов. Этот шаблон распространен во многих других процедурах-подобных методах. [16] [17]
proc greet { timeOfTheDay args } { puts "Похоже, у нас [llength $args] человек" foreach name $args { puts "Привет $name, хорошее $timeOfTheDay" } } приветствуйте "утро" "Джозеф" "Клара" "Уильям" "Мария" # Вывод: # Похоже, нас 4 человека # Привет, Джозеф, доброе утро # Привет, Клара, доброе утро # Привет, Уильям, доброе утро # Привет, Мария, доброе утро