stringtranslate.com

Вариативная функция

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

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

  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#

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

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

Однако эта реализация технически требует одного аргумента, то есть array. Pascal накладывает ограничение, что массивы должны быть однородными. Это требование обходит использование вариантной записи. 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 ); }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, система считается unsafeRust. [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 человека # Привет, Джозеф, доброе утро # Привет, Клара, доброе утро # Привет, Уильям, доброе утро # Привет, Мария, доброе утро

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

Примечания

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

Ссылки

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

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