Ein wichtiges Sprachelement von C+ + kam bisher noch überhaupt nicht vor: die Funktion. Die Möglichkeit, Funktionen zu bilden, ist ein herausragendes Merkmal einer Programmiersprache. Ganz allgemein versteht man unter einer Funktion einen in sich geschlossenen Programmteil, der eine bestimmte Aufgabe erfüllt.
Der wichtigste Vorteil einer Funktion ist die WIEDERVERWENDBARKEIT von Programmcode.
Betrachten wir folgendes Beispiel:
int add( int x, int y) { int z = x+y; return z; }
An diesem Codeausschnitt erkennen Sie alle Bestandteile einer Funktion:
Zu all diesen Teilen ist natürlich noch Einiges zu sagen. Vorher noch ein typografischer Hinweis: Es hat sich eingebürgert, in Büchern zwei Klammern hinter Bezeichner zu setzen, die sich auf eine Funktion beziehen, zum Beispiel in Sätzen wie: Mit add() erreichen Sie die Addition zweier Ganzzahlen. Das sagt noch nichts über Art und Umfang der Argumentliste aus, sondern soll Sie lediglich daran erinnern, dass es dabei um eine Funktion und nicht um eine Variable geht. Ich will mich auch in diesem Buch daran halten.
In C+ + muss jede Funktion einen Typ für den Wert angeben, den sie zurückliefert. Manchmal ist es aber auch gar nicht nötig oder sinnvoll, dass eine Funktion überhaupt einen Rückgabewert hat. In diesem Fall geben Sie als Typ void an.
Was macht man nun mit einem solchen Wert? Der Programmteil, der die Funktion aufruft, kann diese an allen Stellen einsetzen, wo er sonst eine Variable oder Konstante angeben würde (in obiger Form allerdings nur dort, wo lediglich der Wert benötigt wird), also etwa:
int main() { int a = 5; int b = 12; int c = add(a,b); cout << "a = " << a << ", c = " << c << ", a+c = " << add(a,c) << endl; return 0; }
Dieses Programm hat dann die Ausgabe: a = 5, c = 17, a+c = 22
Übrigens: Selbst wenn eine Funktion einen Wert zurückgibt, müssen Sie ihn nicht beachten. Sie dürfen auch schreiben:
int a = 5; int b = 12; add(a,b);
auch wenn das hier keinen Sinn machen würde. Bei Funktionen mit Rückgabetyp void ist das hingegen die übliche Form des Aufrufs. Allgemein kommt es aber häufiger vor, dass Rückgabewerte ignoriert werden. Beispielsweise geben viele Funktionen Statusinformationen darüber zurück, wie gut (oder schlecht) sie ihre Aufgabe erfüllen konnten. Viele Anwender solcher Funktionen interessieren sich nicht für den Status und übergehen ihn. Das kann manchmal aber auch gefährlich werden, wenn etwa aufgetretene Fehler aus diesem Grund zunächst unentdeckt bleiben.
Wie alle anderen Bezeichner in C + + dürfen Sie auch Funktionsnamen nur aus Buchstaben, Ziffern sowie dem Unterstrich _ bilden. Außerdem ist es nicht erlaubt, Funktionsnamen zu verwenden, die Schlüsselwörten gleichen (etwa for).
Eine Funktion kann immer nur auf den Daten arbeiten, die ihr lokal vorliegen. Außer global (das heißt außerhalb aller Funktionen) definierten Variablen sind das nur die Parameter, die das Hauptprogramm an die Funktion übergibt. Von diesen Parametern (auch Argumente genannt) können Sie keinen, einen oder mehrere angeben, die Sie dann durch Kommas trennen.
Wenn Sie eine Funktion ohne Argumente schreiben wollen, lassen Sie den Bereich zwischen den beiden runden Klammern einfach leer – denn die Klammern müssen Sie stets schreiben! – oder Sie setzen ein Argument vom Typ void ein.
Ansonsten geben Sie für jeden Parameter seinen Datentyp und einen Namen an, unter dem er in der Funktion bekannt sein soll. Dieser Name kann vollkommen anders sein, als der im Hauptprogramm beim Aufruf verwendete. Auch in obigem Beispiel heißen die Summanden in der Funktion x und y, im Hauptprogramm aber a und b.
Hier stehen die Anweisungen, die bei einem Aufruf der Funktion ausgeführt werden. Man kann darüber streiten, wie lang Funktionen sein sollten. Es gibt Experten, die fordern, dass eine Funktion aus nicht mehr als 50 Zeilen bestehen dürfe, sonst werde sie unleserlich. Es gibt jedoch in der Praxis immer wieder Fälle, in denen längere Funktionen sinnvoll sind. Bei der objektorientierten Programmierung werden Sie allerdings ohnehin wesentlich mehr Funktionen (beziehungsweise Methoden) verwenden, die im Durchschnitt wesentlich kürzer sind als bei der prozeduralen Programmierung.
Innerhalb des Funktionskörpers können Sie die Funktionsparameter wie normale Variablen verwenden; zusätzlich können Sie natürlich auch noch lokale Variablen definieren. Außerdem ist es selbstverständlich erlaubt, aus einer Funktion wieder andere Funktionen aufzurufen. (Sie dürfen sogar die Funktion selbst wieder aufrufen; man spricht dann von einer rekursiven Funktion – aber das ist ein eigenes Thema.)
Die Anweisungen im Funktionskörper werden so lange abgearbeitet, bis das Programm auf das Ende der Funktion oder eine return-Anweisung trifft. Diese erfüllt einen doppelten Zweck:
Sie legen fest, welchen Wert die Funktion an das Hauptprogramm zurückliefern soll. Das kann eine Variable sein oder ein Ausdruck, eine Konstante oder der Rückgabewert einer anderen Funktion (wobei Letzteres als schlechter Stil gilt). Hat Ihre Funktion den Rückgabetyp void, geben Sie an dieser Stelle überhaupt nichts an. Der Typ des angegebenen Werts muss jedoch in jedem Fall mit dem deklarierten Rückgabetyp übereinstimmen. Sie beenden die Funktion und kehren zum Hauptprogramm zurück. Jede return-Anweisung – sei sie nun am Ende oder irgendwo inmitten des Funktionskörpers – markiert das Ende der Abarbeitung der Funktion und den Rücksprung an die Stelle, von der aus die Funktion aufgerufen wurde. Sie können also die Funktion schon beenden, bevor alle Anweisungen ausgeführt sind, zum Beispiel wenn eine bestimmte Bedingung erfüllt ist. Ist der Rückgabetyp void, muss am Ende der Funktion keine return-Anweisung stehen (auch nicht das Schlüsselwort return), wie in folgendem Beispiel:
void ausgabe( int z) { cout << ``Das Ergebnis ist: `` << z << endl; }
Wenn Sie allerdings bei Funktionen mit irgendeinem anderen Rückgabetyp die return-Anweisung vergessen, meldet der Compiler einen Fehler.
Bevor Sie eine Funktion verwenden können, müssen Sie dem Compiler zunächst mitteilen, dass es eine Funktion dieses Namens gibt, wie viele und welche Parameter sie hat und welchen Typ sie zurückliefert. Dies geschieht mit einem so genannten Prototyp der Funktion. Der Prototyp sieht genauso aus wie die Funktion selbst bis auf den Funktionskörper; dieser fehlt und wird durch ein einfaches Semikolon ; ersetzt. (Es ist sogar erlaubt, die Namen der Argumente wegzulassen und nur ihre Typen anzugeben. Analog zu den Klassen (siehe Seite [*]) ist also der Prototyp die Deklaration und die Funktion mit Körper die Definition.
Der Aufruf einer Funktion erfolgt durch Nennung des Funktionsnamens. An den Funktionsnamen schließt sich immer ein Klammerpaar an, das gegebenenfalls auch Parameter enthalten kann. Dieses Klammerpaar ist zwingend erforderlich. Die Parameter des Aufrufs müssen zu den Parametern der Funktion passen. Besitzt die Funktion einen Rückgabewert, kann der Funktionsaufruf als Ausdruck verwendet werden. Er kann also beispielsweise auf der rechten Seite einer Zuweisung stehen.
C+ + kennt mehrere Varianten, wie einer Funktion die Argumente übergeben werden können: call-by-value, call-by-reference und call-by-pointer.
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> #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; getch(); 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
Sollen die von einer Funktion vorgenommen Änderungen auch für das Hauptprogramm sichtbar sein, müssen in C sogenannte Zeiger verwendet werden. C+ + stellt ebenfalls Zeiger zur Verfügung. C+ + gibt Ihnen aber auch die Möglichkeit, diese Zeiger mittels Referenzen zu umgehen, was im alten C nicht möglich war. 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 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.
#include <iostream> #include <conio.h> using namespace std; //Prototyp, Deklaration int sumR(int &x,int &y); //Parameterübergabe per Referenz (Reference) int main(int argc, char** argv) { cout << "Hello World" << endl; cout << "Parameteruebergabe per Referenz" << endl; a=5,b=6,erg=0; erg=sumR(a, b); cout << "a: " << a <<endl; cout << "b: " << b <<endl; getch(); return 0; } //Funktionendefinition - Parameterübergabe per Referenz int sumR(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 |
|---|---|---|
| x,a | 0x0001 | 5 → 6 |
| y,b | 0x0005 | 6 → 7 |
| … | … | … |
| … | … | … |
| … | … | … |
| … | … | … |
Die Ausgabe des Programms ist:
Hello World Parameteruebergabe per Referenz x: 6 y: 7 a: 6 b: 7
Wenn Sie einen Zeiger als Parameter an eine Funktion übergeben, können Sie den Wert an der übergebenen Adresse ändern.
#include <iostream> #include <conio.h> using namespace std; //Prototyp, Deklaration int sumZ(int *x,int *y); //Parameterübergabe per Zeiger (Pointer) int main(int argc, char** argv) { cout << "Hello World" << endl; cout << "Parameteruebergabe per Zeiger" << endl; a=5,b=6,erg=0; erg=sumZ(&a, &b); cout << "a: " << a <<endl; cout << "b: " << b <<endl; getch(); return 0; } //Funktionendefinition - Parameterübergabe per Zeiger int sumZ(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 → 6 |
| b | 0x0005 | 6 → 7 |
| … | … | … |
| … | … | … |
| *x | 0x00a1 | 0x0001 |
| *y | 0x00a5 | 0x0005 |
Die Ausgabe des Programms ist:
Hello World Parameteruebergabe per Zeiger x: 6 y: 7 a: 6 b: 7