В математике и в компьютерном программировании , VARIADIC функция является функцией неопределенной арности , то есть один , который принимает переменное число аргументов . Поддержка вариативных функций сильно различается между языками программирования .
Термин вариадический - это неологизм , восходящий к 1936–1937 гг. [1] Этот термин не использовался широко до 1970-х годов.
Обзор
Есть много математических и логических операций, которые естественно воспринимаются как функции с переменными числами. Например, суммирование чисел или конкатенация строк или других последовательностей - это операции, которые можно рассматривать как применимые к любому количеству операндов (даже если формально в этих случаях применяется свойство ассоциативности ).
Еще одна операция, реализованная в виде вариативной функции на многих языках, - это форматирование вывода. С функцией printf
и Common Lisp функциями format
являются двумя такими примерами. Оба принимают один аргумент, определяющий форматирование вывода, и любое количество аргументов, которые предоставляют значения для форматирования.
Функции с переменным числом аргументов могут выявить проблемы безопасности типов на некоторых языках. Например, C printf
, если его использовать неосторожно, может привести к появлению класса дыр в безопасности, известного как атаки на строку формата . Атака возможна, потому что языковая поддержка вариативных функций не является типобезопасной: она позволяет функции пытаться вытолкнуть больше аргументов из стека, чем было помещено туда, что повреждает стек и приводит к неожиданному поведению. Как следствие этого, Координационный центр CERT считает вариативные функции на языке C высоким риском безопасности. [2]
В функциональных языках вариативность можно рассматривать как дополнение к функции apply , которая принимает функцию и список / последовательность / массив в качестве аргументов и вызывает функцию с аргументами, указанными в этом списке, тем самым передавая функции переменное количество аргументов. [ необходима цитата ] В функциональном языке Haskell вариативные функции могут быть реализованы путем возврата значения класса типа T
; если экземпляры T
являются конечным возвращаемым значением r
и функцией (T t) => x -> t
, это позволяет использовать любое количество дополнительных аргументов x
. [ требуется дальнейшее объяснение ]
Связанная с этим тема в исследовании переписывания терминов называется хеджированием или переменными хеджирования . [3] В отличие от вариативных функций, которые представляют собой функции с аргументами, хеджирование - это сами последовательности аргументов. Они также могут иметь ограничения (например, «принимать не более 4 аргументов») до такой степени, что они не имеют переменной длины (например, «принимать ровно 4 аргумента») - таким образом, называть их вариативными числами может вводить в заблуждение. Однако они относятся к одному и тому же явлению, и иногда формулировка смешанная, что приводит к таким именам, как переменная с переменным числом аргументов (синоним хеджирования). Обратите внимание на двойное значение слова « переменная» и разницу между аргументами и переменными в функциональном программировании и переписывании терминов. Например, термин (функция) может иметь три переменных, одна из которых является хеджированием, что позволяет термину принимать три или более аргумента (или два или более, если хеджирование может быть пустым).
Примеры
В C
Для переносимой реализации вариативных функций на языке программирования C используется стандартный stdarg.h
файл заголовка. Старый varargs.h
заголовок устарел и заменен на stdarg.h
. В C ++ используется файл заголовка cstdarg
. [4]
#include #include двойной средний ( ИНТ счетчик , ...) { va_list ар ; int j ; двойная сумма = 0 ; va_start ( ap , count ); / * Требуется последний фиксированный параметр (для получения адреса) * / 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
объект и ссылку на последний параметр функции (тот, который стоит перед многоточием; макрос использует его, чтобы ориентироваться). Он инициализирует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 #
C # описывает вариативные функции с помощью params
ключевого слова. Для аргументов должен быть указан тип, хотя object[]
его можно использовать как универсальный.
используя Систему ;class Program { статический int Foo ( int a , int b , params int [] args ) { // Возвращаем сумму целых чисел в args, игнорируя a и b. int sum = 0 ; foreach ( int i в args ) sum + = i ; сумма возврата ; } static void Main ( string [] args ) { Консоль . WriteLine ( Foo ( 1 , 2 )); // 0 Консоль . WriteLine ( Foo ( 1 , 2 , 3 , 10 , 20 )); // 33 } }
В C ++
Базовые возможности вариативности в C ++ во многом идентичны таковым в C. Единственная разница заключается в синтаксисе, где запятую перед многоточием можно опустить.
#include #include void simple_printf ( const char * fmt ...) ;int main () { simple_printf ( "dcff" , 3 , 'а' , 1.999 , 42.5 ); }void simple_printf ( const char * fmt ...) // C-стиль «const char * fmt, ...» также допустим { va_list args ; va_start ( аргументы , fmt ); в то время как ( * fmt ! = '\ 0' ) { если ( * 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' ; } иначе, если ( * fmt == 'f' ) { double d = va_arg ( args , double ); std :: cout << d << '\ n' ; } ++ fmt ; } va_end ( аргументы ); }
Шаблоны с переменными параметрами (пакет параметров) также могут использоваться в C ++ со встроенными в язык выражениями свертки .
#include 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 ++ над VARIADIC функцией C-типом из - за более низкий риск неправильного использования. [5]
In Go
Функции с переменным числом аргументов в Go можно вызывать с любым количеством конечных аргументов. [6] fmt.Println
- обычная вариативная функция; он использует пустой интерфейс как универсальный тип.
основной пакетимпорт "FMT"// Эта функция с переменным числом аргументов принимает произвольное количество целых чисел в качестве аргументов. func sum ( nums ... int ) { fmt . Print ( "Сумма" , nums ) // Также вариативная функция. total : = 0 для _ , 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
В Java
Как и в C #, Object
тип в Java доступен как универсальный.
Открытый класс Program { // Методы Variadic хранят любые дополнительные аргументы, которые они получают, в массиве. // Следовательно, printArgs на самом деле является методом с одним параметром: // массив переменной длины из String`s. private static void printArgs ( String ... strings ) { for ( String string : strings ) { System . из . println ( строка ); } } public static void main ( String [] args ) { printArgs ( "привет" ); // сокращение от printArgs (["привет"]) printArgs ( "привет" , "мир" ); // сокращение от printArgs (["привет", "мир"]) } }
В JavaScript
JavaScript не заботится о типах вариативных аргументов.
function sum (... числа ) { возвращать числа . уменьшить (( a , b ) => a + b , 0 ); }консоль . журнал ( сумма ( 1 , 2 , 3 )); // 6 консоль . журнал ( сумма ( 3 , 2 )); // 5 консоль . журнал ( сумма ()); // 0
В Паскале
Паскаль имеет четыре встроенных процедуры, которые определены как вариативные, которые из-за этого особого условия являются внутренними для компилятора. Эти чтения , ReadLn , запись и Writeln процедура. Однако существуют альтернативные спецификации, позволяющие использовать аргументы по умолчанию для процедур или функций, которые заставляют их работать вариативно, а также полиморфизм, который позволяет процедуре или функции иметь разные параметры.
Все процедуры чтения [ln] и записи [ln] имеют одинаковый формат:
- чтение [ln] [([файл,] переменная [, переменная ...])];
- write [ln] [([файл] [, значение [, значение ...])];
где
- Файл является необязательным переменным файлом, который , если не указан, по умолчанию входа для чтения и ReadLn , или по умолчанию к выходу для записи и WriteLn ;
- переменная - это скаляр, такой как char (символ), целое число или вещественное число (или для некоторых компиляторов, определенные типы записей или типы массивов, такие как строки), и
- значение - это переменная или константа.
Пример:
вар f : текст ; ch : char ; n , a , I , B : целое число ; S : строка ;начинать Write ( 'Введите имя файла для записи результатов:' ) ; readln ( s ) ; присваивать ( f , S ) ; переписать ( е ) ; Напишите ( 'Как тебя зовут?' ) ; readln ( Ввод , S ) ; Write ( 'Hello,' , S , '! Введите количество вычислений, которое вы хотите выполнить:' ) ; Writeln ( вывод ) ; Напишите ( '?' ) ; readln ( N ) ; Напишите ( 'Для каждой формулы ' , n , 'введите' ) ; write ( 'два целых числа, разделенных одним или несколькими пробелами' ) ; Writeln ; для i : = от 1 до N делать начинать Write ( 'Введите номер пары' , i , '?' ) ; читать ( а , б ) ; READLN ; WRITELN ( Out , 'A [' , a , '] + B [' , B , '] =' , A + B ) ; конец ; закрыть ( OUT ) ;конец .
В приведенном выше примере, что касается компилятора, строки 9 и 13 идентичны, потому что, если ввод - это файловая переменная, считываемая оператором read или readln, файловая переменная может быть опущена. Кроме того, компилятор считает строки 15 и 20 идентичными, потому что, если записываемая файловая переменная выводится, ее можно опустить, что означает (в строке 20), поскольку в процедуру не передаются аргументы, в скобках перечислены аргументы. можно не указывать. Строка 26 показывает, что оператор Writeln может иметь любое количество аргументов, и они могут быть строкой в кавычках, переменной или даже результатом формулы.
Object Pascal поддерживает полиморфные процедуры и функции, в которых разные процедуры или функции могут иметь одно и то же имя, но различаются предоставленными им аргументами.
Паскаль также поддерживает аргументы по умолчанию , где значению аргумента, если оно не указано, присваивается значение по умолчанию.
Для первого примера полиморфизма рассмотрим следующее:
функция add ( a1 , a2 : integer ) : Integer ; начало добавить : = a1 + a2 конец ; функция add ( r1 , r2 : real ) : real ; начало добавить : = a1 + a2 конец ; функция add ( a1 : целое ; r2 : вещественное ) : вещественное ; начало добавить : = реальный ( a1 ) + a2 конец ; функция add ( r1 : real , a2 : integer ) : real ; начало добавить : = а1 + реальный ( а2 ) конец ;
В приведенном выше примере, если добавить как вызываемое с двумя целочисленными значениями, будет вызвана функция, объявленная в строке 1; если один из аргументов является целым числом, а один - действительным, вызывается функция в строке 3 или 4 в зависимости от того, какой из аргументов является целым. Если оба действительны, вызывается функция в строке 2.
В качестве параметров по умолчанию примите во внимание следующее:
const Три = 3 ;вар K : целое число ; функция add ( i1 : integer = 0 ; i2 : целое число = 0 ; i3 : целое число = 0 ; i4 : целое число = 0 ; i5 : целое число = 0 ; i6 : целое число = 0 ; i7 : целое число = 0 ; i8 : integer = 0 ) : integer ;начинать сложить : = i1 + i2 + i3 + I4 + I5 + i6 + I7 + I8 ;конец ;начинать K : = добавить ; {K равно 0} K : = добавить ( K , 1 ) ; {K равно 1} K : = добавить ( 1 , 2 ) ; {K равно 3} K : = добавить ( 1 , 2 , Три ) ; {K равно 6 и т. Д.}конец .
В строке 6 (и строках ниже) параметр = 0 сообщает компилятору: «Если аргумент не указан, предполагается, что аргумент равен нулю». В строке 19 не было задано никаких аргументов, поэтому функция возвращает 0. В строке 20 для любого аргумента может быть указано число или переменная и, как показано в строке 22, константа.
В PHP
PHP не заботится о типах вариативных аргументов, если аргумент не введен.
функция sum ( ... $ nums ) : int { return array_sum ( $ nums ); } сумма эхо ( 1 , 2 , 3 ); // 6
И набрал вариативные аргументы:
функция sum ( int ... $ nums ) : int { return array_sum ( $ nums ); }эхо- сумма ( 1 , 'a' , 3 ); // TypeError: аргумент 2, переданный в sum (), должен иметь тип int (начиная с PHP 7.3)
В Python
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)
.
В Раку
В Раке , тип параметров , которые создают VARIADIC функции известны как заглатывание параметры массива , и они подразделяются на три группы:
- Сплющенный хлюпающий
- Эти параметры объявляются с помощью одной звездочки (
*
), и они сглаживают аргументы, растворяя один или несколько слоев элементов, которые можно повторять (например, Iterables ).sub foo ( $ a , $ b , * @args ) { скажем @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]
- Непроверенный прихлебывающий
- Эти параметры объявляются с двумя звездочками (), и они не сглаживают какие-либо итерируемые аргументы в списке, но сохраняют аргументы более или менее как есть:
sub bar ( $ a , $ b , ** @args ) { скажем, @args . perl ;}бар ( 1 , 2 ); # [] бар ( 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 ;}заз ( 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
Ruby не заботится о типах вариативных аргументов.
def foo ( * args ) print args endfoo ( 1 ) # выводит `[1] => nil`foo ( 1 , 2 ) # выводит `[1, 2] => nil`
В ржавчине
Rust не поддерживает вариативные аргументы в функциях. Вместо этого он использует макросы . [7]
macro_rules! вычислить { // Шаблон для одиночного `eval` ( eval $ e : expr ) => {{ { пусть val : usize = $ e ; // Заставить типы быть целыми числами println! ( "{} = {}" , строка! { $ e }, val ); } }}; // Рекурсивно разбираем несколько `eval` ( eval $ e : expr , $ ( eval $ es : expr ), + ) => {{ рассчитать ! { eval $ e } рассчитать ! { $ ( eval $ es ), + } }};}fn main () { рассчитать ! { // Смотри, мама! Вариативное `вычислить!`! оценка 1 + 2 , оценка 3 + 4 , eval ( 2 * 3 ) + 1 }}
Rust может взаимодействовать с вариативной системой языка Си через c_variadic
переключатель функций. Как и другие интерфейсы C, система считается unsafe
Rust. [8]
В Swift
Swift заботится о типах вариативных аргументов, но доступен универсальный Any
тип.
func greet ( timeOfTheDay : String , names : String ...) { // здесь имена [String] print ( "Похоже, у нас \ ( names . count ) человек" ) для имени в именах { print ( "Hello \ ( name ) , good \ ( timeOfTheDay ) " ) } }Приветствуйте ( timeOfTheDay : "утро" , имена : "Иосиф" , "Клара" , "Уильям" , "Мария" )// Вывод: // Похоже, у нас 4 человека // Привет Джозеф, доброе утро // Привет Клара, доброе утро // Привет Уильям, доброе утро // Привет Мария, доброе утро
Смотрите также
- Varargs на языке программирования Java
- Variadic macro (язык программирования C)
- Вариативный шаблон
Рекомендации
- ^ Генри С. Леонард и HN Гудман, Исчисление людей . Резюме выступления на втором собрании Ассоциации символической логики, состоявшемся в Кембридже 28–30 декабря 1936 г. [1] , Journal of Symbolic Logic 2 (1) 1937, 63.
- ^ Клеменс, Бен (2014). 21 век C: C Советы новой школы . O'Reilly Media, Inc. стр. 224. ISBN 1491904445.
- ^ CLP (H): Программирование логики ограничений для хеджирования
- ^ « (stdarg.h) - Справочник по C ++» . www.cplusplus.com .
- ^ «DCL50-CPP. Не определяйте вариативную функцию в стиле C» .
- ^ https://gobyexample.com/variadic-functions
- ^ «Вариадика» . Ржавчина на примере .
- ^ «2137-вариативный» . Книга RFC Rust .
Внешние ссылки
- Вариадическая функция . Задача Rosetta Code, показывающая реализацию вариативных функций на более чем пятидесяти языках программирования.
- Функции с переменными аргументами - Учебное пособие по функциям с переменными аргументами для C ++
- Руководство по GNU libc