Argumente, Parameter

Die Sprachen C und C++ erlauben dem Programmierer, Eingabewerte für eine Funktion festzulegen. Bei einem Funktionsaufruf übergibt man einer Funktion somit Argumente, welche sodann innerhalb der Funktion als Parameter verfügbar sind.

Details

Ein Argument bezeichnet einen Übergabewert, welcher bei einem Funktionsaufruf angegeben wird. Ein Parameter hingegen bezeichnet eine Variable, welche innerhalb der Funktion das übergebene Argument als Eingabewert speichert. Beim einem Aufruf einer Funktion spricht man somit von Argumenten, innerhalb der aufgerufenen Funktion jedoch von Parametern. Da beide Begriffe beinahe dasselbe bezeichnen, werden sie im allgemeinen Sprachgebrauch mehr oder weniger vermischt. Auf ManderC wird versucht, diese Begriffe konsistent zu benutzen.

Im Allgemeinen ist der Umgang mit Argumenten und Parametern problemlos: Eine Funktion deklariert anhand der Parameterliste, wieviele und was für welche Parameter sie speichern kann. Beim Funktionsaufruf wird dementsprechend eine zur Funktions-Signatur passende Liste von Argumenten angegeben, welche der Programmierer an die Funktion übergeben möchte. Man unterscheidet bei C sprachlich zwischen zwei Methoden, wie Argumente übergeben werden können. In C++ kam eine dritte Methode hinzu.

Übergabe per Wert

In C gilt grundsätzlich, dass eine Funktion eine abgeschlossene und vom restlichen Code unabhängige Einheit bildet, womit die innerhalb der Funktion definierten Parameter die übergebenen Argumente nicht verändern. Bei der Übergabe per Wert wird somit der Wert des Argumentes in den Parameter der Funktion kopiert. Der Wert des ursprünglichen Argumentes bleibt unberührt, der Parameter wird am Ende der Funktion verworfen.



5
4
3
2
1




Liftoff!
#include <stdio.h>

void countdown(int count){
  while(count){
    printf("%d\n", count);
    count--;
  }
}

int main(){
  countdown(5);
  printf("Liftoff!\n");
  return 0;
}

Die Übergabe per Wert ist speziell geeignet für die eingebauten Basistypen. Diese Methode der Übergabe ist die einzige Methode, welche auch mit rvalues funktioniert. Vorsicht ist geboten in C++, wenn ganze Objekte per Wert übergeben werden: Der Compiler ruft hierbei zu Beginn der Funktion automatisch einen Copy-Constructor und am Ende der Funktion automatisch den Destructor auf, was bei häufigen Funktionsaufrufen je nach Klasse sehr kostspielig sein kann.

Mittels der Übergabe per Wert sind beispielsweise rekursive Programme ohne Probleme zu programmieren. Der Computer reserviert bei jedem Funktionsaufruf für jeden Parameter einen neuen Speicherplatz auf dem Stack. Bei Funktionsende wird der Stack wieder abgebaut und die originalen Argumente bleiben bei der Übergabe per Wert unberührt.

Übergabe per Pointer

Nicht selten möchte ein Programmierer, dass eine Funktion gewisse Argumente dennoch verändert. Gründe hierfür gibts vielerlei und haben häufig mit dem Programmierstil eines jeden einzelnen Programmierers zu tun. Ein wichtiger Grund ist jedoch, dass häufig Funktionen benötigt werden, welche mehr wie einen Wert zurückgeben, doch C und C++ erlauben einer Funktion zwar viele Eingabewerte, jedoch nur einen Rückgabewert (return-value). Um somit übergebene Argumente zu verändern, behilft man sich der Übergabe mittels Pointer.

Bei der Übergabe per Pointer wird nicht der eigentliche Wert, sondern der Pointer des Wertes übergeben. Damit steht der Funktion eine Adresse zur Verfügung, wo Änderungen sowohl innerhalb als auch ausserhalb der Funktion wirksam werden. In der Parameterliste der Funktion wird somit ein Pointer auf den gewünschten Typ deklariert. Um einen Pointer auf einen Wert beim Funktionsaufruf zu übergeben, wandelt man das gewünschte Argument mithilfe des Adress-Operators & in einen Pointer um. Die Übergabe per Pointer funktioniert somit nur mit lvalues welche sich im Speicher befinden.











12
#include <stdio.h>
#include <string.h>

void countchars(const char* string, int* count){
  *count = strlen(string);
}

int main(){
  int length;
  countchars("Hello World!", &length);
  printf("%d\n", length);
  return 0;
}

Um auf den gewünschten Wert innerhalb der Funktion zuzugreifen, muss der Pointer mittels des Dereferenz-Operators *, des Pointer-Zugriffs-Operators -> oder des Array-Element-Operators [] angesprochen werden. Eine Änderung des dereferenzierten Wertes innerhalb der aufgerufenen Funktion bewirkt gleichzeitig auch eine Änderung des Wertes im Bereich des Funktionsaufrufes. Dies ist nebst globalen Variablen unter C die einzige Möglichkeit, auf eine Variable ausserhalb einer Funktion zuzugreifen.

Man beachte, dass bei genauerer Betrachtung es sich bei der Übergabe per Pointer um nichts anderes als eine Übergabe per Wert handelt: Der übergebene Wert ist ein Pointer, welcher in den Parameter der Funktion kopiert wird und welcher am Ende der Funktion wieder verworfen wird. Erst durch die Dereferenzierung des Pointers wird auf den eigentlichen Wert zugegriffen. Diese Dereferenzierung bewirkt stets eine Änderung auch ausserhalb der Funktion. Nicht immer jedoch ist es gewünscht, dass eine Funktion Werte mittels eines Pointer-Argumentes verändern kann. Will man verhindern, dass ein Argument, welches einen Pointer-Typ besitzt, von einer Funktion verändert werden kann, so bedient man sich dem const-Keyword. Weiterführende Informationen findet man auch bei der const-safe-Programmierung.

Arrays werden üblicherweise als Pointer übergeben. Dabei entspricht der übergebene Pointer einem Pointer auf den ersten Eintrag des Arrays. Mittels des Array-Element-Operators [] kann sodann innerhalb der Funktion das Array angesprochen und verändert werden, was stets auch ausserhalb der Funktion Wirksamkeit erlangt. Arrays können bei der Funktionsdeklaration anstelle mittels einer Pointer-Deklaration * auch mittels einer Array-Deklaration [] geschrieben werden, es wird jedoch vom Compiler nicht geprüft, ob die Anzahl Array-Einträge stimmen, da das Array stets in einen Pointer umgewandelt wird. Man beachte jedoch, dass die Übergabe per Referenz (siehe weiter unten) sich in diesem Punkt anders verhält.

Die Übergabe per Pointer eignet sich unter C++ insbesondere für Objekte, welche viele Member-Variablen aufweisen, welche bei einer Übergabe per Wert stets kopiert und später wieder dealloziiert werden müssten, was sehr kostspielig sein kann. Durch die Übergabe per Pointer wird ausser der Adresse des Objektes nichts weiter kopiert, was bei vielen Funktionsaufrufen sich positiv bemerkbar macht.

Übergabe per Referenz

Diese Methode ist erst in C++ verfügbar. Sie wird in anderen Programmiersprachen auch mit Variablen-Übergabe bezeichnet, da im Prinzip die Variable selbst, sprich die Adresszuordnung, die auf den gewünschten Wert zeigt, übergeben wird. Ähnlich wie bei der Übergabe per Pointer bewirkt somit eine Änderung innerhalb der Funktion auch eine Änderung ausserhalb. Der Unterschied zur Übergabe per Pointer ist, dass der Parameter innerhalb der Funktion nicht als Pointer, sondern direkt als Wert verfügbar ist.

Um eine Variable per Referenz zu übergeben, muss der Parameter in der Parameterliste der Funktion mittels dem Referenz-Typ & deklariert werden. Die Übergabe eines Argumentes beim Funktionsaufruf kann wie bei der Übergabe per Wert direkt angegeben werden.











12
#include <stdio.h>
#include <string.h>

void countchars(const char* string, int& count){
  count = strlen(string);
}

int main(){
  int length;
  countchars("Hello World!", length);
  printf("%d\n", length);
  return 0;
}

Innerhalb der Funktion steht der Parameter als normale Variable zur Verfügung und muss nicht erst mittels Dereferenzierung angesprochen werden. Dies ist ein grosser Vorteil, wenn man mit Objekten arbeitet, welche Operatorenüberladungen besitzen. Bei einer Übergabe per Pointer müsste man die jeweiligen Objekte stets erst dereferenzieren, was den Code unleserlich macht.

Die Übergabe per Referenz ist jedoch nicht unproblematisch. Insbesondere bei durchgängiger const-safe-Programmierung werden häufig explizit ausprogrammierte Accessoren und Mutatoren sowohl für const- als auch für non-const-Zugriffe notwendig, welche bei einer Übergabe per Pointer umgangen werden können. Durch das Fehlen der Zwischenstufe über den Pointer können Compiler zudem beim Linken durcheinanderkommen (beispielsweise versagt an einigen Stellen die zero-link-Technologie). Während des Debuggings können Referenz-Parameter oftmals nicht korrekt dargestellt werden, da der Debugger theoretisch jede Änderung auf jeder Ebene des Callstacks mitführen müsste.

Arrays können ebenfalls per Referenz übergeben werden. Wenn der Referenz-Parameter mittels einer Array-Deklaration [] geschrieben wird, muss die Referenz-Deklaration mit runden Klammern () umschlossen werden, da ansonsten der Parameter vom Compiler als ein Array auf Referenzen aufgefasst wird, was nicht erlaubt ist. Bei einer solchen Deklaration wird eine Konsistenzprüfung durch den Compiler durchgeführt, wobei auf die Anzahl Elemente des Arrays geachtet wird. Wird ein Array mit einer unterschiedlichen Anzahl Elemente übergeben, wie dass der Parameter erwartet, so meldet der Compiler einen Fehler.




















a: 0
e: 1
i: 0
o: 2
u: 0
#include <stdio.h>
#include <string.h>

void countvowels(const char* string, int (&vowels)[5]){
  for(int v=0; v<5; v++){vowels[v] = 0;}
  for(int i=0; i<strlen(string); i++){
    switch(string[i]){
    case 'a': vowels[0]++; break;
    case 'e': vowels[1]++; break;
    case 'i': vowels[2]++; break;
    case 'o': vowels[3]++; break;
    case 'u': vowels[4]++; break;
    }
  }
}

int main(){
  int vowels[5];
  countvowels("Hello World!", vowels);
  printf("a: %d\n", vowels[0]);
  printf("e: %d\n", vowels[1]);
  printf("i: %d\n", vowels[2]);
  printf("o: %d\n", vowels[3]);
  printf("u: %d\n", vowels[4]);
  return 0;
}