Programmieren in C++: Einführung


Kapitel 5: Funktionen


Inhaltsverzeichnis

Dieses Buch ist unter einer Creative Commons-Lizenz lizensiert.


5.1 Definition

Funktionen erstellen

Eine Funktion ist eine Ansammlung von Anweisungen, der ein Name gegeben wird und die jederzeit im Programm über diesen Namen aufgerufen und ausgeführt werden kann. Während mit den bisher kennengelernten Variablen, Operatoren und Kontrollstrukturen bereits recht leistungsfähige Programme möglich sind, können mit Funktionen Anweisungen getrennt in Pakete gelegt und Programme übersichtlicher gestaltet werden.

Um Funktionen in Programmen nutzen zu können, müssen sie erstmal definiert werden. Die Definition ist immer der erste Schritt, um eine Funktion nutzen zu können.

In all Ihren Beispielprogrammen haben Sie bereits immer eine Funktion definiert gehabt. Betrachten Sie hierfür folgendes Beispiel.

int main() 
{ 
} 

Sie sehen eine Funktionsdefinition, wie sie in jedem C++-Programm vorkommt. Die Funktion main() ist die Funktion, mit der ein Programm startet.

Eine Funktionsdefinition beginnt immer mit dem Datentyp des Rückgabewertes. Der Rückgabewert ist das Ergebnis, das die Funktion liefert. Eine Funktion muss nicht unbedingt ein Ergebnis liefern. Sie muss dann mit dem Schlüsselwort void definiert werden. void bedeutet einfach, dass die Funktion kein Ergebnis liefert. Die Funktion main() muss mit dem Datentyp int für den Rückgabewert definiert werden - dies schreibt der C++-Standard vor.

Hinter dem Datentyp des Rückgabewertes wird der Funktionsname angegeben. Wie bei Variablen auch können beliebige Funktionsnamen vergeben werden, die - ebenfalls wie bei Variablen - jedoch darauf hindeuten sollten, welchen Sinn und Zweck die Funktion hat. Kryptische und sinnlose Funktionsnamen erschweren nur die Übersicht im Programm.

Hinter dem Funktionsnamen werden zwei runde Klammern geschrieben, zwischen denen eine Parameterliste angegeben werden kann. Im obigen Beispiel sind die runden Klammern hinter main() leer - die Funktion main() erwartet demnach keine Parameter.

Parameter sind das Gegenstück zum Rückgabewert: So wie jede Funktion ein Ergebnis zurückgeben kann, kann eine Funktion Parameter als Eingabe akzeptieren. Innerhalb der Funktion wird dann mit den Parametern gearbeitet, und am Ende kann ein Ergebnis zurückgeliefert werden.

Mit den runden Klammern und der optionalen Parameterliste ist der Funktionskopf abgeschlossen. Dahinter folgt der Anweisungsblock, der wie gewohnt zwischen zwei geschweiften Klammern steht. Im Anweisungsblock können nun Variablen, Operatoren und Kontrollstrukturen untergebracht werden, um die Funktion zu implementieren. Was auf keinen Fall innerhalb des Anweisungsblocks stehen darf ist eine Definition einer anderen Funktion.

Während Datentyp des Rückgabewerts, Funktionsname und optionale Parameterliste als Funktionskopf bezeichnet werden, kann der Anweisungsblock der Funktion Funktionsrumpf genannt werden.

Um einen Wert oder eine Variable als Ergebnis einer Funktion zurückzugeben, muss auf das Schlüsselwort return zugegriffen werden. Betrachten Sie folgendes Beispiel.

int main() 
{ 
  return 0; 
} 

Der Rückgabewert wird hinter return angegeben. Im obigen Beispiel wird also die Zahl 0 von der Funktion main() als Ergebnis zurückgegeben. Dies ist insofern kein Problem als dass die Funktion main() ja so definiert ist, dass sie einen Rückgabewert besitzt, der vom Typ int ist. Die Zahl 0 stimmt mit dem Datentyp int überein, so dass das Beispiel einwandfrei kompiliert.

Funktionen, die nicht mit void definiert sind, sondern einen Rückgabewert liefern, müssen auch mit return einen Wert zurückgeben. Wenn Sie return nicht angeben, meckert der Compiler. Entweder fügen Sie dann ein return ein oder aber Sie ändern die Definition der Funktion dahingehend, dass Sie den Datentyp des Rückgabewerts auf void setzen.

Eine Ausnahme gibt es: Der C++-Standard definiert die Funktion main() derart, dass diese Funktion - aber auch nur diese - keinen Wert mit return zurückgeben muss. Wenn Sie für main() kein return angeben, gibt diese Funktion automatisch den Wert 0 zurück. Aus diesem Grund wird in allen Beispielprogrammen in diesem Buch für main() kein Wert mit return zurückgegeben.

Die praktische Bedeutung der Rückgabewerte von main() ist beschränkt. Nachdem mit dieser Funktion ein C++-Programm startet, wird auch nach Ablauf der Funktion das Programm beendet. Der Rückgabewert von main() kann daher nicht direkt im Programm verwendet werden, sondern wird vom Betriebssystem übernommen. Über Betriebssystemfunktionen kann der Rückgabewert eines Programms abgefragt werden. Es hat sich eingebürgert, dass der Rückgabewert 0 ein fehlerfreies Programmende bedeutet und ein anderer Wert als 0 ein Programmabruch aufgrund eines Fehlers.

Während der Rückgabewert für main() keine so bedeutende Rolle spielt, kann er für Funktionen, die innerhalb des Programms aufgerufen werden, umso bedeutender sein - vor allem, wenn der Funktion Parameter übergeben werden und sie mit den übergebenen Parametern ein Ergebnis errechnet.

int add(int a, int b) 
{ 
  int r = a + b; 
  return r; 
} 

Sie sehen oben die Definition einer Funktion, die einen Rückgabewert vom Typ int liefert und zwei Parameter vom Typ int erwartet. Parameter werden zwischen den runden Klammern im Funktionskopf angegeben. Werden mehrere Parameter definiert, müssen diese mit Komma getrennt werden.

Die Definition von Parametern besteht lediglich aus der Definition von Variablen: Zuerst wird der Datentyp angegeben, dann der Variablenname. Tatsächlich sind Parameter technisch nichts anderes als Variablen. Innerhalb der Funktion werden sie ebenfalls wie Variablen verwendet und so wie oben beispielsweise mit dem Plus-Operator addiert.

Die Funktion add() ist demnach so definiert, dass sie zwei Parameter als Eingabe erwartet, die übergebenen Werte addiert und die Summe als Ergebnis zurückgibt.


5.2 Aufruf

Funktionen verwenden

Ist eine Funktion definiert, kann sie aufgerufen werden. Im folgenden Beispiel wird auf die Funktion add() zurückgegriffen.

#include <iostream> 

int add(int a, int b) 
{ 
  int r = a + b; 
  return r; 
} 

int main() 
{ 
  int x = 10, y = 5, result; 

  result = add(x, y); 
  std::cout << x << " + " << y << " = " << result << std::endl; 
} 

Der Funktionsaufruf von add() erfolgt in der Funktion main(): Es wird der Funktionsname angegeben, gefolgt von den runden Klammern, in denen zwei Variablen als Parameter übergeben werden. Die Funktion add() ist so definiert, dass sie zwei Parameter erwartet. Deswegen müssen auch beim Aufruf zwei Variablen in Klammern angegeben werden. So wie Parameter in der Parameterliste durch Komma getrennt werden, werden auch mehrere Variablen beim Aufruf einer Funktion durch Komma getrennt.

Der Rückgabewert von add() wird per Zuweisungsoperator = in der Variablen result gespeichert. Wichtig ist, dass die Variable result den gleichen Typ besitzt wie der Rückgabewert der Funktion. Dies ist im Code-Beispiel der Fall: Sowohl result als auch der Rückgabewert von add() sind vom Datentyp int.

Im obigen Beispielprogramm geschieht also folgendes: Die zwei Variablen x und y werden der Funktion add() als Parameter übergeben. Der Wert der ersten Variable wird in den ersten Parameter der Funktion kopiert, der Wert der zweiten Variable in den zweiten Parameter der Funktion. Konkrekt bedeutet dies, dass die Zahl 10 der Variablen x in die Variable a der Funktion add() kopiert wird, und der Wert 5 der Variablen y in die Variable b der Funktion add().

Innerhalb der Funktion add() wird mit den übergebenen Werten gearbeitet: Es wird die Summe gebildet, in der Variablen r gespeichert und dann als Ergebnis der Funktion zurückgegeben. In der Funktion main() wird daraufhin der Rückgabewert von add() in der dort definierten Variablen result gespeichert. Im letzten Schritt werden die Variablen x, y und result an die Standardausgabe weitergereicht und auf den Bildschirm ausgegeben.

Wenn eine Funktion einen Rückgabewert liefert, kann sie überall dort aufgerufen werden, wo ein Wert von dem Datentyp erwartet wird, den die Funktion zurückgibt. Das heißt, es ist nicht unbedingt notwendig, erst den Rückgabewert einer Funktion in einer Variablen zwischenzuspeichern und dann diese Variable weiterzugeben. Betrachten Sie hierzu folgendes Beispiel.

#include <iostream> 

int add(int a, int b) 
{ 
  int r = a + b; 
  return r; 
} 

int main() 
{ 
  int x = 10, y = 5; 

  std::cout << x << " + " << y << " = " << add(x, y) << std::endl; 
} 

In der Funktion main() wird nun keine Variable result mehr definiert. Der Aufruf von add() findet jetzt an der Stelle statt, an der vorher die Variable result den von add() gespeicherten Rückgabewert an die Standardausgabe weitergereicht hat. Das Beispiel funktioniert so wie oben einwandfrei: Der Rückgabewert von add(), ein Wert vom Datentyp int, wird direkt an die Standardausgabe weitergereicht. Nachdem der Rückgabewert nicht anderweitig im Programm benötigt wird, kann also die unnötige Variablendefinition von result entfallen.

So wie der Rückgabewert einer Variablen direkt weitergegeben werden kann, kann beispielsweise auch das Ergebnis einer Addition direkt weitergereicht werden, ohne vorher in einer Variablen gespeichert zu werden.

#include <iostream> 

int add(int a, int b) 
{ 
  return a + b; 
} 

int main() 
{ 
  int x = 10, y = 5; 

  std::cout << x << " + " << y << " = " << add(x, y) << std::endl; 
} 

Werfen Sie einen Blick auf die Definition von add(). Die Addition der Parameter a und b findet nun direkt hinter dem Schlüsselwort return statt: a und b werden addiert, und die Summe wird direkt ohne unnötige Speicherung in einer anderen Variablen als Rückgabewert mit return weitergereicht.

Das Programm um noch einen Schritt vereinfacht sieht wie folgt aus.

#include <iostream> 

int add(int a, int b) 
{ 
  return a + b; 
} 

int main() 
{ 
  std::cout << 10 << " + " << 5 << " = " << add(10, 5) << std::endl; 
} 

Anstatt die Zahlen 10 und 5 in Variablen zu speichern werden sie nun direkt an den Stellen angegeben, an denen sie verwendet werden sollen. In diesem Fall werden also auch der Funktion add() Werte direkt übergeben. Auch diese werden wie gewohnt in die Variablen a und b der Funktion add() kopiert.


5.3 Referenzvariablen

Verweise auf andere Variablen

In den bisherigen Beispielprogrammen in diesem Kapitel wurden Werte, die als Parameter an Funktionen weitergegeben wurden, kopiert. Sehen Sie sich folgendes Beispielprogramm an, das dies verdeutlicht.

#include <iostream> 

void set(int a) 
{ 
  a = 10; 
} 

int main() 
{ 
  int x = 5; 

  set(x); 
  std::cout << x << std::endl; 
} 

In diesem Beispiel wird eine Funktion set() definiert, die kein Ergebnis zurückliefert und einen Parameter erwartet. Die Funktion ist so definiert, dass die Variable a aus der Parameterliste auf den Wert 10 gesetzt wird.

In der Funktion main() wird eine Variable x definiert und mit dem Wert 5 initialisiert. Als nächstes erfolgt ein Funktionsaufruf von set(), dem als Parameter die Variable x übergeben wird. In der letzten Zeile des Programms wird x an die Standardausgabe weitergereicht und auf den Bildschirm ausgegeben.

Welchen Wert gibt das Programm aus? Obiges Beispielprogramm zeigt am Bildschirm die Zahl 5 an. Innerhalb der Funktion set() wird nämlich nicht die Variable x selbst auf den Wert 10 gesetzt, sondern nur die Variable a. In diese wird zwar beim Funktionsaufruf der Wert von x kopiert, es handelt sich aber tatsächlich nur um eine Kopie und um mehr nicht. Die Variable a hat mit der Variable x nichts zu tun.

Es ist in C++ möglich, Funktionen so zu definieren, dass Parameter nicht kopiert werden, sondern auf die Originalvariable verweisen. Dies erfolgt mit Referenzvariablen. Um die Funktion set() so zu definieren, dass der Wert von x geändert wird, muss lediglich der Referenzoperator & hinzugefügt werden.

#include <iostream> 

void set(int &a) 
{ 
  a = 10; 
} 

int main() 
{ 
  int x = 5; 

  set(x); 
  std::cout << x << std::endl; 
} 

Wenn Sie den Code mit dem des vorherigen Beispielprogramms vergleichen: Der einzige Unterschied ist, dass vor dem Parameter a in der Funktionsdefinition von set() nun der Referenzoperator & angegeben ist.

Der Referenzoperator & bewirkt folgendes: Beim Aufruf der Funktion set() wird nicht der Wert von x in die Variable a kopiert, sondern a erhält eine Referenz auf x. Das bedeutet, dass der Variablenname a in der Funktion set() letztendlich nur ein anderer Name für die Variable x ist, es sich dabei jedoch tatsächlich um ein und dieselbe Variable handelt. Deswegen wird in diesem Beispiel nun tatsächlich die Variable x innerhalb von set() auf den Wert 10 gesetzt, und auch 10 vom Programm auf den Bildschirm ausgegeben.

Referenzvariablen können übrigens nicht nur in Funktionen verwendet werden.

#include <iostream> 

int main() 
{ 
  int x = 5; 
  int &y = x; 

  ++y; 
  std::cout << x << std::endl; 
} 

Obiges Programm definiert eine Variable x und eine Referenzvariable y. Die Variable x wird auf den Wert 5 gesetzt, die Referenzvariable auf die Variable x. Dies bedeutet, dass auch über y auf die Variable x zugegriffen werden kann - es handelt sich schlichtweg um einen anderen Namen für x. Daher wird mit dem Inkrementoperator ++ genaugenommen die Variable x um 1 erhöht, wie dann auch die Ausgabe von x auf den Bildschirm beweist: Dort wird die Zahl 6 angezeigt.

Referenzvariablen werden in der Praxis nur innerhalb von Funktionsdefinitionen verwendet. Es macht normalerweise keinen Sinn, innerhalb einer Funktion eine Variable auch über andere Namen anzusprechen. Außerdem - und das ist eine große Beschränkung - muss einer Referenzvariablen direkt bei der Definition eine Variable zugewiesen werden, auf die referenziert werden soll. Somit verweist eine Referenzvariable während ihrer Lebensdauer immer auf die gleiche Variable.

Mit Referenzvariablen lassen sich Mechanismen aus C, die dort nur über Zeiger möglich sind, einfach in C++ einsetzen. Aufgrund der Beschränkung, dass Referenzvariablen immer auf die gleiche Variable verweisen müssen, muss auch in C++ in bestimmten Situationen auf Zeiger zugegriffen werden. Zeiger haben den Vorteil, dass sie jederzeit mit dem Zugriffsoperator = neu gesetzt werden können und daher während ihrer Lebensdauer auf unterschiedliche Variablen zeigen können.


5.4 Gültigkeitsbereiche

Beschränkter Lebensraum

Variablen, die innerhalb einer Funktion definiert werden, heißen lokale Variablen. Auf lokale Variablen kann nur innerhalb der Funktion zugegriffen werden, die die Variablen definiert. Andere Funktionen können auf diese Variablen nicht zugreifen.

void a() 
{ 
  int x; 
} 

void b() 
{ 
  int x; 
} 

Nachdem lokale Variablen nur innerhalb einer Funktion sichtbar sind, können Sie denselben Variablennamen in unterschiedlichen Funktionen verwenden. Im obigen Beispiel wird eine Variable x in der Funktion a() und in der Funktion b() definiert. Nachdem es sich hierbei um lokale Variablen handelt und die Variablen nur in den jeweiligen Funktionen gelten, würde obiger Code einwandfrei kompilieren. Würden beide Variablendefinitionen in derselben Funktion stehen, würde dies zu einem Fehler führen, weil Variablennamen in einem Gültigkeitsbereich nur ein einziges Mal vergeben werden dürfen.

In C++ gibt es beispielsweise im Gegensatz zu Java nicht nur lokale, sondern auch globale Variablen. Globale Variablen werden außerhalb einer Funktion definiert.

int x; 

int main() 
{ 
  x = 10; 
} 

Im obigen Code-Beispiel wird eine Variable x als globale Variable definiert - die Definition steht außerhalb jeder Funktion. Während lokale Variablen nur einen begrenzten Gültigkeitsbereich besitzen, der sich nur über eine Funktion erstreckt, existieren globale Variablen im gesamten Programm. Das heißt, jede Funktion kann auf die globale Variable zugreifen, und zwar jederzeit. Globale Variablen existieren vom Programmstart an bis zum Ende, während lokale Variablen immer nur während der Ausführung der Funktion existieren.

Lokale und globale Variablen lassen sich leicht unterscheiden: Lokale Variablen werden immer innerhalb geschweifter Klammern definiert. Ob diese Klammern einer Funktionsdefinition angehören oder nicht spielt hierbei keine Rolle. Sie können geschweifte Klammern beliebig setzen, um Gültigkeitsbereiche für Variablen festzulegen.

int main() 
{ 
  { 
    int y = 10; 
  } 
  { 
    int y = 20; 
  } 
} 

Im obigen Beispiel werden innerhalb der Funktion main() zwei Variablen mit dem Namen y definiert. Dies ist nur deswegen möglich, weil die Variablen in unterschiedlichen Gültigkeitsbereichen definiert sind. Diese Gültigkeitsbereiche sind durch willkürliche Verwendung der geschweiften Klammern festgelegt worden, die wie Sie sehen nicht unbedingt einen Anweisungsblock einer Funktion oder einer Kontrollstruktur festlegen müssen.


5.5 Aufgaben

Übung macht den Meister

Sie können die Lösungen zu allen Aufgaben in diesem Buch als ZIP-Datei erwerben.

  1. Entwickeln Sie eine C++-Anwendung, die den Anwender zur Eingabe von zwei Zahlen auffordert. Das Programm soll daraufhin die Potenz der beiden Zahlen errechnen. Die Berechnung soll hierbei in einer eigenen Funktion stattfinden, die die beiden eingegebenen Zahlen vom Anwender als Parameter erwartet und das Ergebnis der Berechnung als Wert zurückgibt. Das Ergebnis der Berechnung soll dann auf den Bildschirm ausgeben werden.

  2. Entwickeln Sie eine C++-Anwendung, die den Anwender zur Eingabe einer Zahl auffordert. Das Programm soll daraufhin die Reihenfolge der Ziffern in der Zahl umdrehen - beispielsweise soll 1234 in 4321 umgewandelt werden. Das Umdrehen der Reihenfolge der Ziffern soll in einer eigenen Funktion erfolgen, die keinen Rückgabewert besitzt und einen Parameter erwartet. Die Ausgabe der umgedrehten Zahl auf den Bildschirm soll in der Funktion main() erfolgen.

  3. Für Experten: Schreiben Sie Ihre Lösung zur Aufgabe 2 so um, dass die Umkehrung der Ziffernreihenfolge durch Rekursion stattfindet. Rekursion bedeutet, dass Anweisungen nicht innerhalb einer Schleife wiederholt ausgeführt werden, sondern dass eine Funktion sich wiederholt selbst aufruft.