Вариадная функция
В математике и компьютерном программировании вариативная функция — это функция неопределенной арности , т. е. такая, которая принимает переменное число аргументов . Поддержка вариативных функций сильно различается в разных языках программирования .
Термин «вариативный» — неологизм , возникший в 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 стандарт stdarg.h
используется заголовочный файл. Чем старше varargs.h
заголовок устарел в пользу stdarg.h
. В C++ заголовочный файл cstdarg
используется. [4]
#include <stdarg.h>
#include <stdio.h>
double average(int count, ...) {
va_list ap;
int j;
double sum = 0;
va_start(ap, count); /* Before C23: Requires the last fixed parameter (to get the address) */
for (j = 0; j < count; j++) {
sum += va_arg(ap, int); /* Increments ap to the next argument. */
}
va_end(ap);
return sum / count;
}
int main(int argc, char const *argv[]) {
printf("%f\n", average(3, 1, 2, 3));
return 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
принимает два аргумента, ava_list
объект и ссылку на последний параметр функции (тот, который стоит перед многоточием; макрос использует его для определения направления). В C23 второй аргумент больше не потребуется, а функциям с вариационным числом аргументов больше не потребуется именованный параметр перед многоточием. [примечание 1] [6] Он инициализируетva_list
объект для использованияva_arg
илиva_copy
. Компилятор обычно выдает предупреждение, если ссылка неверна (например, ссылка на параметр, отличный от последнего, или ссылка на совершенно другой объект), но не препятствует нормальному завершению компиляции.va_arg
принимает два аргумента, ava_list
объект (ранее инициализированный) и дескриптор типа. Он расширяется до следующего аргумента переменной и имеет указанный тип. Последовательные вызовыva_arg
позволяют обрабатывать каждый из аргументов переменной по очереди. Неопределенное поведение возникает, если тип неверен или отсутствует следующий аргумент переменной.va_end
принимает один аргумент, ava_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[]
можно использовать как универсальное средство. На вызывающем сайте вы можете либо перечислить аргументы по одному, либо передать уже существующий массив с нужным типом элемента. Использование вариационной формы является синтаксическим сахаром для последнего.
using System;
class Program
{
static int Foo(int a, int b, params int[] args)
{
// Return the sum of the integers in args, ignoring a and 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, ..." is also valid
{
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') {
// note automatic conversion to integral type
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);
}
Шаблоны 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
– обычная вариатическая функция; он использует пустой интерфейс как универсальный тип.
package main
import "fmt"
// This variadic function takes an arbitrary number of ints as arguments.
func sum(nums ...int) {
fmt.Print("The sum of ", nums) // Also a variadic function.
total := 0
for _, num := range nums {
total += num
}
fmt.Println(" is", total) // Also a variadic function.
}
func main() {
// Variadic functions can be called in the usual way with individual
// arguments.
sum(1, 2) // "The sum of [1 2] is 3"
sum(1, 2, 3) // "The sum of [1 2 3] is 6"
// If you already have multiple args in a slice, apply them to a variadic
// function using func(slice...) like this.
nums := []int{1, 2, 3, 4}
sum(nums...) // "The sum of [1 2 3 4] is 10"
}
Выход:
The sum of [1 2] is 3 The sum of [1 2 3] is 6 The sum of [1 2 3 4] is 10
На Яве
[ редактировать ]Как и в случае с C#, Object
type в Java доступен как универсальный.
public class Program {
// Variadic methods store any additional arguments they receive in an array.
// Consequentially, `printArgs` is actually a method with one parameter: a
// variable-length array of `String`s.
private static void printArgs(String... strings) {
for (String string : strings) {
System.out.println(string);
}
}
public static void main(String[] args) {
printArgs("hello"); // short for printArgs(["hello"])
printArgs("hello", "world"); // short for printArgs(["hello", "world"])
}
}
В JavaScript
[ редактировать ]JavaScript не заботится о типах переменных аргументов.
function sum(...numbers) {
return numbers.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(3, 2)); // 5
console.log(sum()); // 0
Также возможно создать вариативную функцию, используя объект аргументов, хотя ее можно использовать только с функциями, созданными с помощью function
ключевое слово.
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 могут передавать переменные аргументы другим функциям так же, как и другие значения, используя return
ключевое слово. таблицы можно передавать в функции с переменным числом аргументов, используя в Lua версии 5.2 или выше [9] table.unpack
, или Lua 5.1 или ниже [10] unpack
. Переменные аргументы можно использовать в качестве таблицы, создав таблицу с переменным аргументом в качестве значения.
function sum(...) --... designates varargs
local sum=0
for _,v in pairs({...}) do --creating a table with a varargs is the same as creating one with standard values
sum=sum+v
end
return sum
end
values={1,2,3,4}
sum(5,table.unpack(values)) --returns 15. table.unpack should go after any other arguments, otherwise not all values will be passed into the function.
function add5(...)
return ...+5 --this is incorrect usage of varargs, and will only return the first value provided
end
entries={}
function process_entries()
local processed={}
for i,v in pairs(entries) do
processed[i]=v --placeholder processing code
end
return table.unpack(processed) --returns all entries in a way that can be used as a vararg
end
print(process_entries()) --the print function takes all varargs and writes them to stdout separated by newlines
В Паскале
[ редактировать ]Паскаль стандартизирован стандартами ISO 7185 («Стандартный Паскаль») и 10206 («Расширенный Паскаль»).
Ни одна стандартизированная форма Паскаля не поддерживает вариативные подпрограммы, за исключением некоторых встроенных подпрограмм ( read
/ readLn
и write
/ writeLn
и дополнительно в EP readStr
/ writeStr
).
Тем не менее, диалекты Паскаля реализуют механизмы, напоминающие вариативные процедуры.
Delphi определяет array of const
тип данных, который может быть связан с последним формальным параметром .
В рамках рутинного определения array of const
это array of TVarRec
, массив вариантов записей . [11]
VType
член вышеупомянутого record
Тип данных позволяет проверить тип данных аргумента и последующую соответствующую обработку.
Компилятор Free Pascal также поддерживает вариативные процедуры Delphi. [12]
Однако эта реализация технически требует одного аргумента, то есть array
.
Паскаль накладывает ограничение: массивы должны быть однородными.
Это требование можно обойти, используя вариантную запись.
GNU Pascal определяет реальную спецификацию формальных параметров с переменным числом аргументов с помощью многоточия ( ...
), но по состоянию на 2022 год переносимый механизм для его использования не определен. [13]
И GNU Pascal, и FreePascal позволяют объявленным извне функциям использовать переменную спецификацию формальных параметров с использованием многоточия ( ...
).
В PHP
[ редактировать ]PHP не заботится о типах переменных аргументов, если аргумент не является типизированным.
function sum(...$nums): int
{
return array_sum($nums);
}
echo sum(1, 2, 3); // 6
И набрал вариативные аргументы:
function sum(int ...$nums): int
{
return array_sum($nums);
}
echo sum(1, 'a', 3); // TypeError: Argument 2 passed to sum() must be of the type int (since PHP 7.3)
На Python
[ редактировать ]Python не заботится о типах переменных аргументов.
def foo(a, b, *args):
print(args) # args is a tuple (immutable sequence).
foo(1, 2) # ()
foo(1, 2, 3) # (3,)
foo(1, 2, 3, "hello") # (3, "hello")
Аргументы ключевого слова могут храниться в словаре, например def bar(*args, **kwargs)
.
и Рак
[ редактировать ]В Raku тип параметров, создающих вариативные функции, известен как параметры массива slurpy и подразделяется на три группы:
Сплющенная каша
[ редактировать ]Эти параметры объявляются одной звездочкой ( *
) и они сглаживают аргументы, растворяя один или несколько слоев элементов, которые можно перебирать (т. е. Iterables ).
sub foo($a, $b, *@args) {
say @args.perl;
}
foo(1, 2) # []
foo(1, 2, 3) # [3]
foo(1, 2, 3, "hello") # [3 "hello"]
foo(1, 2, 3, [4, 5], [6]); # [3, 4, 5, 6]
Нерасплющенная каша
[ редактировать ]Эти параметры объявлены двумя звездочками ( **
), и они не сглаживают какие-либо итерируемые аргументы в списке, а сохраняют аргументы более или менее такими, какие они есть:
sub bar($a, $b, **@args) {
say @args.perl;
}
bar(1, 2); # []
bar(1, 2, 3); # [3]
bar(1, 2, 3, "hello"); # [3 "hello"]
bar(1, 2, 3, [4, 5], [6]); # [3, [4, 5], [6]]
Контекстуальная неряшливость
[ редактировать ]Эти параметры объявляются с плюсом ( +
) подписывают, и они применяют « правило одного аргумента » , которое решает, как обрабатывать невнятный аргумент в зависимости от контекста. Проще говоря, если передается только один аргумент и этот аргумент является итеративным, этот аргумент используется для заполнения массива параметров slurpy. В любом другом случае +@
работает как **@
(т. е. несплюснутая каша).
sub zaz($a, $b, +@args) {
say @args.perl;
}
zaz(1, 2); # []
zaz(1, 2, 3); # [3]
zaz(1, 2, 3, "hello"); # [3 "hello"]
zaz(1, 2, [4, 5]); # [4, 5], single argument fills up array
zaz(1, 2, 3, [4, 5]); # [3, [4, 5]], behaving as **@
zaz(1, 2, 3, [4, 5], [6]); # [3, [4, 5], [6]], behaving as **@
В Рубине
[ редактировать ]Ruby не заботится о типах переменных аргументов.
def foo(*args)
print args
end
foo(1)
# prints `[1]=> nil`
foo(1, 2)
# prints `[1, 2]=> nil`
В ржавчине
[ редактировать ]Rust не поддерживает переменные аргументы в функциях. Вместо этого он использует макросы . [14]
macro_rules! calculate {
// The pattern for a single `eval`
(eval $e:expr) => {{
{
let val: usize = $e; // Force types to be integers
println!("{} = {}", stringify!{$e}, val);
}
}};
// Decompose multiple `eval`s recursively
(eval $e:expr, $(eval $es:expr),+) => {{
calculate! { eval $e }
calculate! { $(eval $es),+ }
}};
}
fn main() {
calculate! { // Look ma! Variadic `calculate!`!
eval 1 + 2,
eval 3 + 4,
eval (2 * 3) + 1
}
}
Rust может взаимодействовать с вариативной системой C через c_variadic
переключатель функций. Как и другие интерфейсы C, система считается unsafe
к Русту. [15]
В масштабе
[ редактировать ]object Program {
// Variadic methods store any additional arguments they receive in an array.
// Consequentially, `printArgs` is actually a method with one parameter: a
// variable-length array of `String`s.
private def printArgs(strings: String*): Unit = {
strings.foreach(println)
}
def main(args: Array[String]): Unit = {
printArgs("hello"); // short for printArgs(["hello"])
printArgs("hello", "world"); // short for printArgs(["hello", "world"])
}
}
В Свифте
[ редактировать ]Swift заботится о типе вариативных аргументов, но универсальные аргументы Any
тип доступен.
func greet(timeOfTheDay: String, names: String...) {
// here, names is [String]
print("Looks like we have \(names.count) people")
for name in names {
print("Hello \(name), good \(timeOfTheDay)")
}
}
greet(timeOfTheDay: "morning", names: "Joseph", "Clara", "William", "Maria")
// Output:
// Looks like we have 4 people
// Hello Joseph, good morning
// Hello Clara, good morning
// Hello William, good morning
// Hello Maria, good morning
В ТКЛ
[ редактировать ]Процедура Tcl или лямбда является вариативной, если ее последний аргумент равен args
: будет содержать список (возможно, пустой) всех оставшихся аргументов. Этот шаблон распространен во многих других методах, подобных процедурам. [16] [17]
proc greet {timeOfTheDay args} {
puts "Looks like we have [llength $args] people"
foreach name $args {
puts "Hello $name, good $timeOfTheDay"
}
}
greet "morning" "Joseph" "Clara" "William" "Maria"
# Output:
# Looks like we have 4 people
# Hello Joseph, good morning
# Hello Clara, good morning
# Hello William, good morning
# Hello Maria, good morning
См. также
[ редактировать ]- Варарги в языке программирования Java
- Вариатический макрос (язык программирования C)
- Вариатический шаблон
Примечания
[ редактировать ]- ^ Сделать именованный параметр необязательным было необходимо, поскольку после удаления определений функций в стиле K&R в C23 не было возможности указать функцию, принимающую неопределенное количество аргументов. Поскольку C++ уже использовал этот синтаксис для той же цели, это изменение также было способом повысить совместимость между языками. [5]
Ссылки
[ редактировать ]- ^ Генри С. Леонард и Х. Н. Гудман, Исчисление отдельных лиц . Аннотация доклада, сделанного на Втором собрании Ассоциации символической логики, состоявшемся в Кембридже, Массачусетс, 28–30 декабря 1936 г., [1] , Journal of Символическая логика 2 (1) 1937, 63.
- ^ Клеменс, Бен (2014). 21 век C: Советы C от новой школы . О'Рейли Медиа, Инк. с. 224. ИСБН 978-1491904442 .
- ^ CLP (H): Программирование логики ограничений для живых изгородей
- ^ «<cstdarg> (stdarg.h) — Справочник по C++» . www.cplusplus.com .
- ^ «C23 завершен: вот что находится в меню §N2975 — смягчение требований к спискам переменных параметров» . 31 июля 2022 г.
- ^ Позолота, Алекс; Менейд, Жан Хейд (15 апреля 2022 г.). «WG14-N2975: Ослабьте требования к спискам переменных параметров, версия 3» (PDF) .
- ^ «DCL50-CPP. Не определяйте вариативную функцию в стиле C» .
- ^ «Пример: вариативные функции» .
- ^ «Справочное руководство по Lua 5.2» . www.lua.org . Проверено 5 февраля 2023 г.
- ^ «Справочное руководство по Lua 5.1» . www.lua.org . Проверено 5 февраля 2023 г.
- ^ «Параметры (Делфи)» . Проверено 28 августа 2023 г.
- ^ «Free Pascal — Справочное руководство» . Проверено 28 августа 2023 г.
- ^ «Руководство по GNU Pascal» . Проверено 28 августа 2023 г.
- ^ «Вариадики» . Ржавчина на примере .
- ^ «2137-вариативный» . Книга Rust RFC .
- ^ "страница руководства по процедуре" . Документация Tcl/Tk .
- ^ "аргументы" . Вики Тклера .
Внешние ссылки
[ редактировать ]- Вариадическая функция . Задача Rosetta Code , показывающая реализацию вариативных функций на более чем 120 языках программирования.
- Функции с переменным аргументом — Учебное пособие по функциям с переменным аргументом для C++
- Руководство по GNU libc