Variadische Funktion - Variadic function

In der Mathematik und in der Computerprogrammierung ist eine variadische Funktion eine Funktion von unbestimmter Stelligkeit , dh eine Funktion , die eine variable Anzahl von Argumenten akzeptiert . Die Unterstützung für variadische Funktionen unterscheidet sich stark zwischen den Programmiersprachen .

Der Begriff variadische ist ein Neologismus , aus dem Jahr 1936-1937. Der Begriff war bis in die 1970er Jahre nicht weit verbreitet.

Überblick

Es gibt viele mathematische und logische Operationen, die auf natürliche Weise als variadische Funktionen erscheinen. Das Summieren von Zahlen oder das Verketten von Strings oder anderen Folgen sind beispielsweise Operationen, die man sich auf eine beliebige Anzahl von Operanden anwenden kann (auch wenn in diesen Fällen formal die assoziative Eigenschaft angewendet wird).

Eine weitere Operation, die in vielen Sprachen als Variadic-Funktion implementiert wurde, ist die Ausgabeformatierung. Die C- Funktion printfund die Common-Lisp- Funktion formatsind zwei solche Beispiele. Beide akzeptieren ein Argument, das die Formatierung der Ausgabe angibt, und eine beliebige Anzahl von Argumenten, die die zu formatierenden Werte bereitstellen.

Variadische Funktionen können aussetzen Typsicherheit Probleme in einigen Sprachen. Zum Beispiel printfkönnen Cs, wenn sie unvorsichtig verwendet werden, zu einer Klasse von Sicherheitslücken führen, die als Format-String-Angriffe bekannt sind . Der Angriff ist möglich, weil die Sprachunterstützung für variadische Funktionen nicht typsicher ist: Sie erlaubt der Funktion, zu versuchen, mehr Argumente vom Stapel zu entfernen , als dort abgelegt wurden, was den Stapel beschädigt und zu unerwartetem Verhalten führt. Als Konsequenz daraus betrachtet das CERT-Koordinationszentrum variadische Funktionen in C als ein Sicherheitsrisiko mit hohem Schweregrad.

In funktionalen Sprachen können variadics als komplementär zur Funktion apply angesehen werden, die eine Funktion und eine Liste/Sequenz/Array als Argumente verwendet und die Funktion mit den in dieser Liste angegebenen Argumenten aufruft, wodurch eine variable Anzahl von Argumenten an die Funktion übergeben wird. In der funktionalen Sprache Haskell können variadische Funktionen implementiert werden, indem ein Wert einer Typklasse zurückgegeben wird T ; Wenn Instanzen von Tein endgültiger Rückgabewert rund eine Funktion sind (T t) => x -> t, ermöglicht dies eine beliebige Anzahl zusätzlicher Argumente x.

Ein verwandtes Thema in der Term-Rewriting- Forschung wird Hedges oder Hedge-Variablen genannt . Im Gegensatz zu Variadics, die Funktionen mit Argumenten sind, sind Hedges selbst Sequenzen von Argumenten. Sie können auch Einschränkungen haben (z. B. „nicht mehr als 4 Argumente annehmen“) bis zu dem Punkt, an dem sie keine variable Länge haben (wie „genau 4 Argumente annehmen“) - daher kann es irreführend sein , sie als Variaden zu bezeichnen. Sie beziehen sich jedoch auf das gleiche Phänomen, und manchmal sind die Formulierungen gemischt, was zu Namen wie variadic variable (synonym für Hedge) führt. Beachten Sie die Doppelbedeutung des Wortes Variable und den Unterschied zwischen Argumenten und Variablen bei der funktionalen Programmierung und dem Umschreiben von Begriffen. Ein Term (eine Funktion) kann beispielsweise drei Variablen haben, eine davon eine Absicherung, sodass der Term drei oder mehr Argumente annehmen kann (oder zwei oder mehr, wenn die Absicherung leer sein darf).

Beispiele

In C

Um variadic-Funktionen portabel in der Programmiersprache C zu implementieren , wird die Standard- stdarg.hHeader-Datei verwendet. Der ältere varargs.hHeader wurde zugunsten von veraltetstdarg.h . In C++ wird die Header-Datei cstdargverwendet.

#include <stdarg.h>
#include <stdio.h>

double average(int count, ...) {
    va_list ap;
    int j;
    double sum = 0;

    va_start(ap, count); /* 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;
}

Dadurch wird der Durchschnitt einer beliebigen Anzahl von Argumenten berechnet. Beachten Sie, dass die Funktion weder die Anzahl der Argumente noch deren Typen kennt. Die obige Funktion erwartet, dass die Typen sein werden intund dass die Anzahl der Argumente im ersten Argument übergeben wird (dies ist eine häufige Verwendung, wird jedoch von der Sprache oder dem Compiler auf keinen Fall erzwungen). In einigen anderen Fällen, zum Beispiel printf , werden die Anzahl und die Typen von Argumenten aus einer Formatzeichenfolge ermittelt. In beiden Fällen hängt dies davon ab, dass der Programmierer die richtigen Informationen liefert. (Alternativ kann ein Sentinel-Wert wie NULLverwendet werden, um die Zahl anzugeben.) Wenn weniger Argumente übergeben werden, als die Funktion glaubt, oder die Argumenttypen falsch sind, kann dies dazu führen, dass sie in ungültige Speicherbereiche einliest und zu Schwachstellen wie der Formatstring-Angriff .

stdarg.hdeklariert einen Typ, va_list, und definiert vier Makros: va_start, va_arg, va_copy, und va_end. Jeder Aufruf von va_startund va_copymuss mit einem entsprechenden Aufruf von übereinstimmen va_end. Beim Arbeiten mit Variablenargumenten deklariert eine Funktion normalerweise eine Variable vom Typ va_list( apim Beispiel), die von den Makros manipuliert wird.

  1. va_startnimmt zwei Argumente an, ein va_listObjekt und eine Referenz auf den letzten Parameter der Funktion (der vor den Auslassungspunkten; das Makro verwendet dies, um sich zu orientieren). Es initialisiert das va_listObjekt zur Verwendung durch va_argoder va_copy. Der Compiler gibt normalerweise eine Warnung aus, wenn die Referenz falsch ist (zB eine Referenz auf einen anderen Parameter als den letzten oder eine Referenz auf ein völlig anderes Objekt), verhindert jedoch nicht, dass die Kompilierung normal abgeschlossen wird.
  2. va_argnimmt zwei Argumente an, ein va_listObjekt (zuvor initialisiert) und einen Typdeskriptor. Es wird bis zum nächsten Variablenargument erweitert und hat den angegebenen Typ. Aufeinanderfolgende Aufrufe von va_argerlauben die Verarbeitung jedes der Variablenargumente der Reihe nach. Unspezifiziertes Verhalten tritt auf, wenn der Typ falsch ist oder kein nächstes Variablenargument vorhanden ist.
  3. va_endnimmt ein Argument, ein va_listObjekt. Es dient zum Aufräumen. Wenn man zum Beispiel die Variablenargumente mehr als einmal scannen wollte, würde der Programmierer Ihr va_listObjekt durch Aufrufen va_endund dann va_starterneut initialisieren .
  4. va_copynimmt zwei Argumente an, beide va_listObjekte. Es klont das zweite (das initialisiert sein muss) in das erste. Zurück zum Beispiel "die Variablenargumente mehr als einmal scannen" könnte dies erreicht werden, indem man va_starteine erste aufruft va_listund sie dann mit va_copyin eine zweite klont va_list. Nachdem der Programmierer die Variablenargumente ein erstes Mal mit va_argund das erste Mal va_listgescannt hat (mit entsorgt va_end), könnte der Programmierer die Variablenargumente ein zweites Mal mit va_argund das zweite Mal scannen va_list. Vergessen Sie nicht va_enddie geklonten va_list.

In C#

C# beschreibt variadische Funktionen mit dem paramsSchlüsselwort. Für die Argumente muss ein Typ angegeben werden, object[]der jedoch als Catch-All verwendet werden kann.

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
    }
}

In C++

Die grundlegende Variadic-Funktion in C++ ist weitgehend identisch mit der in C. Der einzige Unterschied besteht in der Syntax, bei der das Komma vor den Auslassungspunkten weggelassen werden kann.

#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); 
}

Variadische Vorlagen (Parameter Pack) kann auch in C ++ verwendet werden , mit der Sprache Einbau- fach Ausdrücke .

#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
}

Die CERT-Coding-Standards für C++ bevorzugen die Verwendung von variadic-Vorlagen (Parameterpaket) in C++ gegenüber der variadic-Funktion im C-Stil aufgrund eines geringeren Missbrauchsrisikos.

In Go

Variadische Funktionen in Go können mit beliebig vielen nachgestellten Argumenten aufgerufen werden. fmt.Printlnist eine gemeinsame Variadic-Funktion; es verwendet eine leere Schnittstelle als Catch-All-Typ.

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"
}

Ausgabe:

The sum of [1 2] is 3 
The sum of [1 2 3] is 6 
The sum of [1 2 3 4] is 10

Auf Java

Wie bei C# steht der ObjectTyp in Java als Catch-All zur Verfügung.

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"])
    }
}

In JavaScript

JavaScript kümmert sich nicht um Typen von variadischen Argumenten.

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

In Pascal

Pascal hat vier eingebaute Prozeduren, die als variadisch definiert sind und aufgrund dieser besonderen Bedingung dem Compiler intrinsisch sind. Dies sind die Prozeduren read , readln , write und writeln . Allerdings gibt es alternative Spezifikationen für das Erlauben Standard Argumente an Prozeduren oder Funktionen , die sie machen variadically arbeiten, sowie Polymorphie , die eine Prozedur oder Funktion ermöglicht verschiedene Parameter haben.

Die Prozeduren read[ln] und write[ln] haben alle das gleiche Format:

read[ln] [( [Datei ,] Variable [, Variable ...] )] ;
write[ln] [( [Datei][, Wert [, Wert ...] )] ;

wo

  • file ist eine optionale Dateivariable , die, wenn sie weggelassen wird, für read und readln standardmäßig die Eingabe oder für write und writeln standardmäßig die Ausgabe verwendet ;
  • Variable ist ein Skalar wie char (Zeichen), Integer oder Real (oder bei einigen Compilern bestimmte Datensatztypen oder Arraytypen wie Strings) und
  • value ist eine Variable oder eine Konstante.

Beispiel:

var 
   f: text;
   ch: char;
   n,a,I,B: Integer;
   S: String;

begin
    Write('Enter name of file to write results: ');
    readln(s);    
    assign(f,S);
    rewrite(f);
    Write('What is your name? ');
    readln(Input,S);        
    Write('Hello, ',S,'! Enter the number of calculations you want to do:');
    writeln(output);
    Write('? ');
    readln(N);
    Write('For each of the ',n,' formulas, enter ');
    write('two integers separated by one or more spaces');
    writeln;
    for i := 1 to N do
    begin   
       Write('Enter pair #',i,'? ');
       read(a,b);
       READLN;
       WRITELN(Out,'A [',a,'] + B [',B,'] =',A+B);
    end;
    close(OUT);
end.

Im obigen Beispiel sind die Zeilen 9 und 13 hinsichtlich des Compilers identisch, denn wenn input die Dateivariable ist, in die durch eine read- oder readln-Anweisung eingelesen wird, kann die Dateivariable weggelassen werden. Außerdem betrachtet der Compiler die Zeilen 15 und 20 als identisch, denn wenn die zu schreibende Dateivariable ausgegeben wird, kann sie weggelassen werden, was bedeutet, dass (in Zeile 20) keine Argumente an die Prozedur übergeben werden die Klammern, die die Argumente auflisten kann ausgelassen werden. Zeile 26 zeigt, dass die writeln-Anweisung eine beliebige Anzahl von Argumenten haben kann, und sie können ein String in Anführungszeichen, eine Variable oder sogar ein Formelergebnis sein.

Object Pascal unterstützt polymorphe Prozeduren und Funktionen, bei denen verschiedene Prozeduren oder Funktionen denselben Namen haben können, sich aber durch die ihnen gelieferten Argumente unterscheiden.

Pascal unterstützt auch Standardargumente , bei denen dem Wert eines Arguments, falls nicht angegeben, ein Standardwert zugewiesen wird.

Betrachten Sie für das erste Beispiel, den Polymorphismus, Folgendes:

function add(a1,a2:integer):Integer; begin add := a1+a2 end;                                
function add(r1,r2:real):real;  begin add := a1+a2 end;                                 
function add(a1:integer;r2:real):real;  begin add := real(a1)+a2 end;                                
function add(r1:real,a2:integer):real;  begin add := a1+real(a2) end;

Wenn im obigen Beispiel add as mit zwei ganzzahligen Werten aufgerufen wird, wird die in Zeile 1 deklarierte Funktion aufgerufen; Wenn eines der Argumente eine ganze Zahl und eines ist reell, wird entweder die Funktion in Zeile 3 oder 4 aufgerufen, je nachdem, welches ganzzahlig ist. Sind beide reell, wird die Funktion in Zeile 2 aufgerufen.

Berücksichtigen Sie bei Standardparametern Folgendes:

const
   Three = 3;
var 
   K: Integer; 

function add(i1: integer = 0; 
             i2: integer = 0;
             i3: integer = 0; 
             i4: integer = 0; 
             i5: integer = 0; 
             i6: integer = 0;  
             i7: integer = 0;  
             i8: integer = 0): integer;
begin
   add := i1+i2+i3+I4+I5+i6+I7+I8;
end;

begin
   K := add; { K is 0}
   K := add(K,1); { K is 1}
   K := add(1,2); { K is 3}
   K := add(1,2,Three); { K is 6, etc.}
end.

In Zeile 6 (und den Zeilen darunter) sagt der Parameter = 0 dem Compiler: "Wenn kein Argument angegeben wird, gehe davon aus, dass das Argument null ist." In Zeile 19 wurden keine Argumente angegeben, daher gibt die Funktion 0 zurück. In Zeile 20 kann für jedes Argument entweder eine Zahl oder eine Variable und, wie in Zeile 22 gezeigt, eine Konstante angegeben werden.

In PHP

PHP kümmert sich nicht um Typen von variadischen Argumenten, es sei denn, das Argument ist typisiert.

function sum(...$nums): int
{
    return array_sum($nums);
}

echo sum(1, 2, 3); // 6

Und typisierte variadische Argumente:

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)

In Python

Python kümmert sich nicht um Typen von variadischen Argumenten.

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")

Schlüsselwortargumente können in einem Wörterbuch gespeichert werden, zB def bar(*args, **kwargs).

In Raku

In Raku werden die Arten von Parametern, die variadische Funktionen erstellen, als slurpy- Array-Parameter bezeichnet und in drei Gruppen eingeteilt:

Abgeflachte slurpy
Diese Parameter werden mit einem einzelnen Sternchen ( *) deklariert und sie glätten Argumente, indem sie eine oder mehrere Ebenen von Elementen auflösen, über die iteriert werden kann (dh 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]
Ungeflacht schludrig
Diese Parameter werden mit zwei Sternchen () deklariert und reduzieren keine iterierbaren Argumente innerhalb der Liste, sondern behalten die Argumente mehr oder weniger unverändert bei:
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]]
Kontextuelle schludrig
Diese Parameter werden mit einem Pluszeichen ( +) deklariert und wenden die " Einzelargumentregel " an , die entscheidet, wie das slurpy-Argument basierend auf dem Kontext behandelt wird. Einfach ausgedrückt, wenn nur ein einziges Argument übergeben wird und dieses Argument iterierbar ist, wird dieses Argument verwendet, um das slurpy Parameter-Array zu füllen. In jedem anderen Fall +@funktioniert wie **@(dh nicht abgeflacht 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 argurment 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 **@

In Ruby

Ruby kümmert sich nicht um Typen von variadischen Argumenten.

def foo(*args)
  print args
end

foo(1)
# prints `[1]=> nil`

foo(1, 2)
# prints `[1, 2]=> nil`

In Rust

Rust unterstützt keine variadischen Argumente in Funktionen. Stattdessen werden Makros verwendet .

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 kann über einen c_variadicFunktionsschalter mit dem Variadic-System von C interagieren . Wie bei anderen C-Schnittstellen gilt das System als unsafeRust.

Schnell

Swift kümmert sich um den Typ der Variadic-Argumente, aber der Catch-All- AnyTyp ist verfügbar.

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

Siehe auch

Verweise

Externe Links