Unter einer Funktion (function, in anderen Programmiersprachen auch Prozedur oder Subroutine genannt) versteht man ein Unterprogramm, das eine bestimmte Aufgabe erfüllt.
Funktionen sind unter anderem sinnvoll, um sich oft wiederholende Befehle zu kapseln, so dass diese nicht jedes Mal neu geschrieben werden müssen. Zudem verbessert es die Übersichtlichkeit.
Die spezielle Funktion main() ist uns schon mehrfach begegnet. In C+ + lassen sich Funktionen nach folgenden Kriterien unterscheiden:
Dem Funktionsbegriff der Mathematik entsprechen diejenigen C+ + -Funktionen, die sowohl Parameter haben als auch einen Wert zurückgeben. Dieser Wert kann im Programm weiter genutzt werden, um ihn z.B. einer Variablen zuzuweisen.
int sum = add(3,5); // Aufruf einer Funktion
Damit diese Anweisung fehlerfrei kompiliert wird, muss vorher die Funktion add() deklariert worden sein. Bei einer Funktion bedeutet Deklaration die Angabe des Funktionsprototyps. Das heißt, der Typ von Parametern und Rückgabewert muss angegeben werden. Das folgende Beispiel deklariert bspw. eine Funktion, die einen Parameter vom Typ int besitzt und als Rückgabewert einen int-Wert zurückgibt.
int add (int a, int b); // Funktionsprototyp == Deklaration
Soll eine Funktion keinen Wert zurückliefern, lautet der Rückgabetyp formal void.
Nach dem Compilieren ist das Linken der entstanden Objektdateien zu einem ausführbaren Programm
nötig. Der Linker benötigt die Definition der aufzurufenden Funktion. Eine Funktionsdefinition
umfasst auch die Implementation der Funktion, d.h. den Code, der beim Aufruf der Funktion
ausgeführt werden soll. In unserem Fall wäre das:
int add(int a, int b); // Funktionsdeklaration int main(){ int sum = add(3, 6); // Funktionsaufruf // a hat jetzt den Wert 9 return 0; } int add(int a, int b){ // Funktionsdefinition return a+b; }
Der Compiler muss die Deklaration kennen, um eventuelle Typ-Unverträglichkeiten abzufangen. Würden Sie die obige Funktion z.B. als int a = add(2.5, 3); aufrufen, käme die Warnung, dass add() ein ganzzahliges Argument erwartet und keine Fließkommazahl. Eine Definition ist für den Compiler auch immer eine Deklaration, das heißt Sie müssen nicht explizit eine Deklaration einfügen um eine Funktion aufzurufen, die zuvor definiert wurde.
Die Trennung von Deklaration und Definition kann zu übersichtlicher Code-Strukturierung bei größeren Projekten genutzt werden. Insbesondere ist es sinnvoll, Deklarationen und Definitionen in verschiedene Dateien zu schreiben. Oft will man, wenn man fremden oder alten Code benutzt, nicht die Details der Implementierung einer Funktion sehen, sondern nur das Format der Parameter o.ä. und kann so in der Deklarationsdatei (header file, üblicherweise mit der Endung .hpp, z.T. auch .h oder .hh) nachsehen, ohne durch die Funktionsrümpfe abgelenkt zu werden. (Bei proprietärem Fremdcode bekommt man die Implementation in der Regel gar nicht zu Gesicht!)
Bei einer Deklaration ist es nicht nötig, die Parameternamen mit anzugeben, denn diese sind für den Aufruf der Funktion nicht relevant. Es ist allerdings üblich die Namen dennoch mit anzugeben um zu verdeutlichen was der Parameter darstellt. Der Compiler ignoriert die Namen in diesem Fall einfach, weshalb es auch möglich ist den Parametern in der Deklaration und der Definition unterschiedliche Namen zu geben. Allerdings wird davon aus Gründen der Übersichtlichkeit abgeraten.
Mit der Anweisung return gibt die Funktion einen Wert zurück, in unserem Beispiel x * x, wobei die Variable x als Parameter bezeichnet wird. Als Argument bezeichnet man eine Variable oder einen Wert, mit denen eine Funktion aufgerufen wird. Bei Funktionen mit dem Rückgabetyp void schreiben Sie einfach return; oder lassen die return-Anweisung ganz weg.
Nach einem return wird die Funktion sofort verlassen, d.h. alle nachfolgenden Anweisungen des Funktionsrumpfs werden ignoriert. Erwartet eine Funktion mehrere Argumente, so werden die Parameter durch Kommas getrennt. Eine mit
int g(int x, double y); </cpp> deklarierte Funktion könnte z.B. so aufgerufen werden: <code cpp> int a = g(123, -4.44);
Für eine leere Parameterliste schreiben Sie hinter dem Funktionsnamen einfach ().
int h(){ // Deklaration von h() // Quellcode ... } int a = h(); // Aufruf von h()
In C+ + gibt es mehrere Varianten, wie einer Funktion die Argumente übergeben werden können:
Bei call-by-value (Wertübergabe) wird der Wert des Arguments in einen Speicherbereich kopiert, auf den die Funktion mittels Parametername zugreifen kann. Ein Werteparameter verhält sich wie eine lokale Variable, die „automatisch“ mit dem richtigen Wert initialisiert wird. Der Kopiervorgang kann bei Klassen (Thema eines späteren Kapitels) einen erheblichen Zeit- und Speicheraufwand bedeuten!
#include <iostream> void f1(int x) { x = 3 * x; std::cout << x << std::endl; } int main() { int a = 7; f1(a); // Ausgabe: 21 f1(5); // Ausgabe: 15 std::cout << x; // Fehler! x ist hier nicht definiert std::cout << a; // a hat immer noch den Wert 7 return 0; }
#include <iostream> #include <conio.h> using namespace std; //Prototyp, Deklaration int sumW(int x,int y); //Parameterübergabe per Wert (Value) int main(int argc, char** argv) { cout << "Hello World" << endl; int a=5,b=6,erg=0; cout << "Parameteruebergabe per Wert" << endl; erg=sumW(a, b); cout << "a: " << a <<endl; cout << "b: " << b <<endl; return 0; } //Funktionendefinition - Parameterübergabe per Wert int sumW(int x,int y){ x+=1; //x=x+1; x++; ++x; y+=1; cout << "x: " << x <<endl; cout << "y: " << y <<endl; return x+y; }
| Variable | Adresse | Wert |
|---|---|---|
| a | 0x0001 | 5 |
| b | 0x0005 | 6 |
| … | . | . |
| … | . | . |
| x | 0x00a1 | 5 → 6 |
| y | 0x00a5 | 6 → 7 |
Hello World Parameteruebergabe per Wert x: 6 y: 7 a: 5 b: 6
Die Sprache C kennt nur call-by-value. Sollen die von einer Funktion vorgenommen Änderungen auch für das Hauptprogramm sichtbar sein, müssen sogenannte Zeiger verwendet werden.
C+ + hingegen stellt Prozeduren und Funktionen ebenfalls Zeiger (pointer) zur Verfügung. C+ + gibt Ihnen aber auch die Möglichkeit, diese Zeiger mittels Referenzen zu umgehen. Beide sind jedoch noch Thema eines späteren Kapitels.
Im Gegensatz zu call-by-value wird bei call-by-reference die Speicheradresse des Arguments übergeben, also der Wert nicht kopiert. Änderungen der (Referenz-)Variable betreffen zwangsläufig auch die übergebene Variable vom Hauptprogramm selbst und bleiben nach dem Funktionsaufruf erhalten.
Um call-by-reference anzuzeigen, wird der Operator & verwendet, wie Sie gleich im Beispiel sehen werden. Wird keine Änderung des Inhalts gewünscht, sollten Sie den Referenzparameter als const deklarieren um so den Speicherbereich vor Änderungen zu schützen.
Fehler, die sich aus der ungewollten Änderung des Inhaltes einer übergebenen Referenz ergeben, sind in der Regel schwer zu finden.
Die im folgenden Beispiel definierte Funktion swap() vertauscht ihre beiden Argumente. Weil diese als Referenzen übergeben werden, überträgt sich das auf die Variablen mit denen die Funktion aufgerufen wurde:
#include <iostream> void swap(int &a, int &b) { int tmp = a; // "temporärer" Variable den Wert von Variable a zuweisen a = b; // a mit dem Wert von Variable b überschreiben b = tmp; // b den Wert der "temporären" Variable zuweisen (Anfangswert von Variable a) } int main() { int x = 5, y = 10; swap(x, y); std::cout << "x=" << x << " y=" << y << std::endl; return 0; } **Ausgabe** x=10 y=5