Jump to content

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

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

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

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

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 не заботится о типах переменных аргументов.

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 не заботится о типах переменных аргументов, если аргумент не является типизированным.

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 не заботится о типах переменных аргументов.

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

См. также

[ редактировать ]

Примечания

[ редактировать ]
  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. ^ «Параметры (Делфи)» . Проверено 28 августа 2023 г.
  12. ^ «Free Pascal — Справочное руководство» . Проверено 28 августа 2023 г.
  13. ^ «Руководство по GNU Pascal» . Проверено 28 августа 2023 г.
  14. ^ «Вариадики» . Ржавчина на примере .
  15. ^ «2137-вариативный» . Книга Rust RFC .
  16. ^ "страница руководства по процедуре" . Документация Tcl/Tk .
  17. ^ "аргументы" . Вики Тклера .
[ редактировать ]
Arc.Ask3.Ru: конец переведенного документа.
Arc.Ask3.Ru
Номер скриншота №: 9a755f53befa2cd3e9a145c946782591__1709993760
URL1:https://arc.ask3.ru/arc/aa/9a/91/9a755f53befa2cd3e9a145c946782591.html
Заголовок, (Title) документа по адресу, URL1:
Variadic function - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть. Любые претензии, иски не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, вы не можете использовать данный сайт и информация размещенную на нем (сайте/странице), немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, Денежную единицу (имеющую самостоятельную стоимость) можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)