Variadische Funktion

Als variadische Funktion bezeichnet man in Programmiersprachen Funktionen, Prozeduren oder Methoden mit unbestimmter Arität, also solche, deren Parameteranzahl nicht bereits in ihrer Deklaration festgelegt ist.

In einigen Sprachen wie C, C++, Java und Lua wird dies in der Funktionsdeklaration mit Auslassungspunkten angezeigt, der sogenannten Ellipse. An Stelle der Ellipse können beliebig viele Argumente (oder auch keine) übergeben werden. Nützlich sind variadische Funktionen beispielsweise bei der Verknüpfung mehrerer Zeichenketten oder beim Aufsummieren von Zahlenreihen und generell bei Operationen, die prinzipiell auf eine beliebige Anzahl Operanden angewandt werden können.

In Abhängigkeit von den Konventionen der Programmiersprache können die Argumente unterschiedliche Datentypen haben (z. B. in JavaScript) oder müssen denselben Datentyp haben (z. B. in Java).

Umsetzung in verschiedenen Programmiersprachen

C

Der Zugriff auf variadische Parameter erfolgt hier über spezielle Makros.[1] Einige Bibliotheksfunktionen von C sind mit Ellipse deklariert, da es in C keine Möglichkeit der Funktionsüberladung gibt, Beispiele sind die Bibliotheksfunktionen printf() und scanf() zur formatierten Aus- beziehungsweise Eingabe.[2] Ein grundlegendes Problem bei diesem Mechanismus ist, dass der aufgerufenen Funktion keine Information über Anzahl und Typ der übergebenen Argumente zur Verfügung steht. Der Programmierer muss also durch zusätzliche Maßnahmen (wie etwa mit dem format string bei printf()) sicherstellen, dass die Argumente richtig interpretiert werden und nicht mehr Argumente verarbeitet werden, als tatsächlich vorhanden waren. Außerdem muss mindestens ein fester Parameter vor der Ellipse angegeben werden.

Die Funktionsparameter werden, bevor sie an die Funktion übergeben werden, automatisch nach folgendem Schema in größere Typen umgewandelt:

AusgangstypZieltyp
char, signed char, shortint
unsigned char, unsigned shortunsigned int
floatdouble

Alle anderen Typen bleiben erhalten.

Beispielsweise könnte man in C den Mittelwert von beliebig vielen Ganzzahlen über folgende Funktion errechnen lassen:

#include <stdarg.h>

double mittelwert(int num_zahlen, ...)
{
    va_list argumente;
    int i;
    double summe = 0;

    va_start(argumente, num_zahlen); /* Benötigt den letzten bekannten Parameter, um die Speicheradresse der anderen Parameter berechnen zu können */
    for (i = 0; i < num_zahlen; i++) {
        summe += va_arg(argumente, double); /* Inkremetiert argumente zum nächsten Argument */
    }
    va_end(argumente);

    return summe / num_zahlen;
}

C++

In C++ sind Ellipsen im C-Stil zwar weiterhin möglich, aber sie gelten als überholt, da C++ elegantere Möglichkeiten zur Funktionsüberladung und das Konzept der Defaultargumente bietet. Ellipsen gelten zudem als schlechter Stil, da sie keine Typsicherheit bieten.[3] Außerdem lassen sich nicht alle Datentypen als Teil einer Ellipse übergeben.

In C++ gibt es zusätzlich noch die Möglichkeit, variadische Funktionstemplates zu benutzen. Diese sind typsicher, aber aufwändiger zu benutzen, da man die sogenannten „template parameter packs“ rekursiv auflösen muss:

unsigned produkt(unsigned u) {return u;}  // Rekursions-Ende

template<typename... Args>
unsigned produkt(unsigned u1, Args... rest)
{
    return u1 * produkt(rest...);
}

// Aufruf:
unsigned p = produkt(1,2,3,4,5);

[veraltet] In C++17 werden sogenannte „fold expressions“ eingeführt[4], mit denen sich variadische Funktionen wie im obigen Beispiel stark vereinfachen lassen:

template<typename... Args>
unsigned produkt(Args... factors)
{
    return (1u * ... * factors);
}

Des Weiteren lassen sich Ellipsen oft durch eine std::initializer_list ersetzen, wobei die Syntax beim Aufruf leicht abweicht, da die Argumente in geschweifte Klammern gefasst werden müssen:

unsigned produkt(std::initializer_list<unsigned> factors)
{
    return std::accumulate(factors.begin(), factors.end(), 1u, std::multiplies<unsigned>);
}

// Aufruf:
unsigned p = produkt({1,2,3,4,5});

C#

In C# wird in der Deklaration das Schlüsselwort params verwendet. Auch der Typ wird mit angegeben. Einer solchen Methode kann eine durch Kommas getrennte Liste übergeben werden (diese Liste kann auch leer sein). Nach dem Schlüsselwort params sind keine weiteren Parameter mehr zulässig. Zudem darf es nur einmal in der Methodendeklaration vorkommen.[5]

public class Summe
{
    public static int sum(params int[] list)
    {
        int result = 0;

        for (int i = 0; i < list.Length; i++)
        {
            result += list[i];
        }

        return result;
    }

    static void Main()
    {
        int a = sum();             // Ergebnis: a = 0
        int b = sum(1);            // Ergebnis: b = 1
        int c = sum(1, 2, 3, 4);   // Ergebnis: c = 10
    }
}

Go

Die Sprache Go erlaubt die Deklaration variadischer Funktionen über den Anhang einer Ellipse ... an den Namen des letzten Parameters.[6] Es können dann beliebig viele Parameter des angegebenen Types übergeben werden, diese sind dann über ein Slice in der aufgerufenen Funktion verfügbar. Ferner ist es möglich, durch Anhängen einer Ellipse an den letzten Parameter im Aufruf einer variadischen Funktion, direkt ein Slice an Stelle einzelner Parameter zu übergeben. Es folgt ein Beispiel analog zu obigem C#-Beispiel.

// Deklariere eine Funktion Sum, die mit beliebig viele Parameter vom Typ int aufgerufen
// wird und die Summe ihrer Parameter zurückgibt.
func Sum(x ...int) (n int) {
    for i := range x { // Iteriere über alle Indizes von x
        n += x[i]
    }

    return
}

// Beispielfunktion zum Aufruf der Funktion Sum()
func ExampleSum() {
    Sum() // leere Summe. Gibt 0 zurück
    Sum(1, 2, 3) // Gibt 6 zurück. x hat den Wert []int{1, 2, 3}
    v := []int{23, 42, 1337}
    Sum(v...) // Wie Sum(23, 42, 1337)
}

Java

Auch in Java verwendet man in der Deklaration die Ellipse. Hier spricht man von Methoden mit variabler Argumentanzahl, oder kurz Varargs.[7] Im Gegensatz zu C wird aber der Typ mit angegeben. Im Hintergrund wird die Parameterliste in ein Array übersetzt, so dass auch innerhalb des Funktionsrumpfes der Parameter wie ein Array behandelt werden muss.[8]

JavaScript

In JavaScript werden die beliebig vielen Funktionsargumente dem arguments-Objekt übergeben, das ein Array-artiges Objekt mit den zusätzlichen Eigenschaften arguments.length und arguments.callee ist. Mit dem arguments-Objekt können sogar die Funktionsparameter überschrieben werden, wie das nachfolgende Beispiel zeigt:

function meineFunk(x) {
    arguments[0] = 5; // die Variable x wird hier überschrieben!
    ...               // x ist nun 5.
}

Seit ECMAScript 2015 ist es möglich Funktionsargumente mittels rest-Parameter in Form einer Ellipse ... zu sammeln.

function sum(...items) {
    return items.reduce((s, i) => s + i, 0);
}

sum();        // 0
sum(1);       // 1
sum(1, 2, 3); // 6
sum(1, 2, 5); // 8

Lua

In Lua kann in der Parameterdeklaration einer Funktion als letztes (auch einziges) Argument die Ellipse ... angegeben werden. Diese kann dann an andere Funktionen weitergereicht werden, wodurch sie sich nach Länge (Anzahl) und einzelnen Elementen analysieren lässt, namentlich mittels select().

Perl

Da in Perl 5 die Argumente ohne Rücksicht auf ihren Typ im Spezialarray @_ landen, sind alle Subroutinen immer variadisch, solange es kein Prototyp explizit verhindert. Die mit 5.20 eingeführten, optionalen Signaturen prüfen die Arität. Damit sind nur solche Routinen variadisch, deren letzter Parameter ein Array oder Hash (verlangt gerade Anzahl von Werten) ist.

In Perl 6 kann man als letzten Parameter ein slurpy Array angeben, welches mit dem Präfix * gekennzeichnet ist und alle zusätzlichen Argumente „aufsaugt“.

PHP

PHP unterstützt erst ab Version 4 variadische Funktionen. Bis Version 5.5 wird kein besonderes Kennzeichen in der Funktionsdeklaration verwendet; die Argumente können mit Hilfe spezieller Funktionen ausgewertet werden.[9] Ab Version 5.6 stehen variadische Funktionen als natives Sprachfeature über die Ellipse zur Verfügung.[10] In Versionen vor 5.6 kann eine variadische Funktion bspw. wie folgt definiert werden:

function sum ( /** beliebig viele Argumente **/ ) {
  $result = 0;
  foreach ( func_get_args() as $i ) {
    $result += $i; // hier wird davon ausgegangen, dass alle Parameter vom Typ Integer sind
  }
  return $result;
}

// Beispielhafter Aufruf der Funktion sum()
echo sum(); // gibt 0 aus
echo sum( 1, 2 ); // gibt 3 aus
echo sum( 4, 4, 5, 7 ); // gibt 20 aus

Ab PHP 5.6.0 kann dieselbe Funktion wie folgt umgesetzt werden:

function sum ( ...$values ) {
  $result = 0;
  foreach ( $values as $i ) {
    $result += $i; // hier wird davon ausgegangen, dass alle Parameter vom Typ Integer sind
  }
  return $result;
}

Die Ausgabe beim Aufruf von sum() ist dieselbe wie beim Beispiel für Versionen vor 5.6.0. Der Vorteil bei der Umsetzung von variadischen Funktionen ab Version 5.6.0 ist, dass hierbei auch das sogenannte Type Hinting eingesetzt werden kann, wodurch innerhalb der Funktion eine Überprüfung auf einen korrekt übergebenen Objekttyp entfallen kann. So besteht in den oben genannten Beispielen das Problem, dass ein Nutzer der Funktion sum() dieser auch einen String, anstelle eines Integers übergeben könnte, was ggf. zu einer falschen Ausgabe oder einer Exception führen könnte (wenn die Funktion Objekte eines bestimmten Typs erwartet). Mit variadischen Funktionen ab PHP 5.6.0 kann die obere Funktion bspw. wie folgt mit Type Hinting umgesetzt werden:

class IntObject {
  private $value;

  public function __construct( $value ) {
    $this->value = $value;
  }

  public function getValue() {
    return $this->value;
  }
}

function sum ( IntObject ...$values ) {
  $result = 0;
  foreach ( $values as $i ) {
    $result += $i->getValue(); // Ein Zugriff auf die Funktion getValue() ist ohne weitere Überprüfung möglich, da PHP bereits überprüft hat, dass alle Werte in $values vom Typ IntObject sind
  }
  return $result;
}

Aufgrund der Tatsache, dass PHP's Type Hinting erst ab Version 7.0 skalaren Typen wie Integer oder String unterstützt, wurde für dieses Beispiel die simple Klasse IntObject definiert, welche einen Wert speichern kann (der bspw. auch eine Zeichenkette sein kann). Für die schematische Darstellung genügt dieses Beispiel, auch wenn es trotz allem keine Typensicherheit umsetzt.

Ruby

In Ruby werden variable Argumentanzahlen durch einen Stern vor dem Parameternamen gekennzeichnet.[11] Auch hier wird dieser Parameter als ein Array behandelt, in dem alle Argumente gesammelt werden, die der vorgegebenen Anzahl an Argumenten folgen.

Python

Python bietet dieselbe Funktionalität wie Ruby. Darüber hinaus kann man überzählige Argumente benennen und deren Namen zusammen mit dem Wert in einer Tabelle („Wörterbuch“ im Python-Jargon) speichern, wenn Parameternamen zwei Sterne vorangestellt sind.[12]

Swift

In Swift wird eine variable Parameteranzahl mit drei Punkten hinter der Deklaration des Parameters gekennzeichnet. Innerhalb der Funktion stehen alle übergebenen Werte als Array zur Verfügung:[13]

func summiere(zahlen: Int...) -> Int {

    var sum = 0
    for zahl in zahlen {
        sum += zahl
    }

    return sum
}

Tool Command Language

In Tcl wird eine variable Parameteranzahl mit dem Wort args als letztes Element der Parameterliste gekennzeichnet. Innerhalb der Funktion stehen alle übergebenen Werte als Liste mit diesem Namen zur Verfügung:[14]

proc summiere {args} {
    return [expr [join $args +]]
}
puts [summiere 4 10 8]  ;# gibt 22 aus (ganzzahlig)
puts [summiere 4.5 1 6] ;# gibt 11.5 aus (Fließkommazahl)

Haskell

Haskell erlaubt es nicht direkt, variable Argumentanzahlen zu verwenden, allerdings kann man dies über trickreiche Verwendung von Typklassen nahezu beliebig nachbilden. (So sind mit einer entsprechenden Implementation z. B. auch typsichere variadische Funktionen mit verschiedenen Argumenttypen möglich).[15][16]

Siehe auch

Einzelnachweise

  1. Variable Argumentlisten in C. Abgerufen am 9. September 2010.
  2. Definition von printf. Abgerufen am 9. September 2010 (englisch).
  3. Ellipses (and why to avoid them). Abgerufen am 9. September 2010 (englisch).
  4. http://en.cppreference.com/w/cpp/language/fold
  5. Params in C#. Abgerufen am 23. August 2013 (englisch).
  6. Spezifikation der Sprache Go. Abgerufen am 26. März 2014 (englisch).
  7. Varargs in Java. Archiviert vom Original am 15. April 2018; abgerufen am 10. Juni 2020.
  8. Varargs in Java im Sprachüberblick auf oracle.com. Abgerufen am 1. September 2011 (englisch).
  9. Variadische Funktionen in PHP. Abgerufen am 9. September 2010.
  10. PHP Dokumentation: Funktionsparameter. Abgerufen am 8. September 2015.
  11. David Thomas, Andrew Hunt: Programmieren mit Ruby. Addison-Wesley, München 2002, ISBN 3-8273-1965-X, S. 281 (eingeschränkte Vorschau in der Google-Buchsuche).
  12. Abschnitt Function Definition in der Python-Schnellübersicht. Archiviert vom Original am 26. Januar 2014; abgerufen am 9. September 2010 (englisch).
  13. Variadic Parameters: flexible Funktionen in Swift. Abgerufen am 13. September 2016.
  14. proc manual page. Abgerufen am 6. Oktober 2019 (englisch).
  15. Implementierung in Haskell mit detaillierter Beschreibung. Abgerufen am 12. August 2010 (englisch).
  16. Beschreibung des Vorgehen bei der Haskell-Variante von printf zur Erzeugung einer polyvariadischen Funktion. Abgerufen am 12. August 2010 (englisch).