stringtranslate.com

Вариадная функция

В математике и компьютерном программировании вариатическая функция — это функция неопределенной арности , т. е. такая, которая принимает переменное число аргументов . Поддержка вариативных функций сильно различается в разных языках программирования .

Термин «вариативный»неологизм , возникший в 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в примере), которой будут управлять макросы.

  1. va_startпринимает два аргумента: va_listобъект и ссылку на последний параметр функции (тот, который стоит перед многоточием; макрос использует его для определения направления). В C23 второй аргумент больше не потребуется, и функциям с вариационным числом аргументов больше не потребуется именованный параметр перед многоточием. [примечание 1] [6] Он инициализирует va_listобъект для использования va_argили va_copy. Компилятор обычно выдает предупреждение, если ссылка неверна (например, ссылка на параметр, отличный от последнего, или ссылка на совершенно другой объект), но не препятствует нормальному завершению компиляции.
  2. va_argпринимает два аргумента: va_listобъект (ранее инициализированный) и дескриптор типа. Он расширяется до следующего аргумента переменной и имеет указанный тип. Последовательные вызовы va_argпозволяют по очереди обрабатывать каждый из аргументов переменной. Неопределенное поведение возникает, если тип неверен или отсутствует следующий аргумент переменной.
  3. va_endпринимает один аргумент, va_listобъект. Он служит для очистки. Если бы кто-то захотел, например, просмотреть аргументы переменной более одного раза, программист повторно инициализировал бы ваш va_listобъект, вызвав его va_end, а затем снова.va_start
  4. 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

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 constarray of TVarRecVTyperecord

Однако эта реализация технически требует одного аргумента — файла array. Паскаль накладывает ограничение: массивы должны быть однородными. Это требование можно обойти, используя вариантную запись. GNU Pascal определяет реальную спецификацию формальных параметров с переменным числом аргументов с использованием многоточия ( ...), но по состоянию на 2022 год переносимый механизм для такого использования не был определен. [13]

И GNU Pascal, и FreePascal позволяют внешне объявленным функциям использовать переменную спецификацию формальных параметров с использованием многоточия ( ...).

В PHP

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

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, система считается unsafeRust. [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 человека # Привет, Джозеф, доброе утро # Привет, Клара, доброе утро # Привет, Уильям, доброе утро # Привет, Мария, доброе утро

Смотрите также

Примечания

  1. ^ Сделать именованный параметр необязательным было необходимо, поскольку после удаления определений функций в стиле K&R в C23 не было возможности указать функцию, принимающую неопределенное количество аргументов. Поскольку C++ уже использовал этот синтаксис для той же цели, это изменение также было способом повысить совместимость между языками. [5]

Рекомендации

  1. ^ Генри С. Леонард и Х. Н. Гудман, Исчисление отдельных лиц . Аннотация доклада, сделанного на Втором собрании Ассоциации символической логики, состоявшемся в Кембридже, Массачусетс, 28–30 декабря 1936 г., [1], Journal of Символическая логика 2 (1) 1937, 63.
  2. ^ Клеменс, Бен (2014). 21 век C: Советы C от новой школы . О'Рейли Медиа, Инк. с. 224. ИСБН 978-1491904442.
  3. ^ CLP (H): Программирование логики ограничений для живых изгородей
  4. ^ "<cstdarg> (stdarg.h) — Справочник по C++" . www.cplusplus.com .
  5. ^ «C23 завершен: вот что находится в меню §N2975 — смягчение требований к спискам переменных параметров» . 31 июля 2022 г.
  6. ^ Позолота, Алекс; Менейд, Жан Хейд (15 апреля 2022 г.). «WG14-N2975: Ослабьте требования к спискам переменных параметров, версия 3» (PDF) .
  7. ^ «DCL50-CPP. Не определяйте вариативную функцию в стиле C» .
  8. ^ «Пример: вариативные функции» .
  9. ^ «Справочное руководство по Lua 5.2» . www.lua.org . Проверено 5 февраля 2023 г.
  10. ^ «Справочное руководство по Lua 5.1» . www.lua.org . Проверено 5 февраля 2023 г.
  11. ^ «Параметры (Delphi)» . Проверено 28 августа 2023 г.
  12. ^ «Free Pascal — Справочное руководство» . Проверено 28 августа 2023 г.
  13. ^ «Руководство по GNU Pascal» . Проверено 28 августа 2023 г.
  14. ^ "Вариадики". Ржавчина на примере .
  15. ^ "2137-вариативный". Книга Rust RFC .
  16. ^ "Страница руководства по процедуре" . Документация Tcl/Tk .
  17. ^ "аргументы". Вики Тклера .

Внешние ссылки