Funktionsdefinitionen in C++
Das Programm "kuckuck.cpp" gibt die ersten drei Strophen eines Liedes aus.
main.cpp
#include <iostream>
#include <ostream>
#include <string> // ""susing namespace ::std::literals;
int main()
{ { ::std::cout << "Auf einem Baum ein Kuckuck, -\n"s;
{ ::std::cout << "Sim sa la dim, bam ba,\n"s;
::std::cout << "Sa la du, sa la dim -\n"s; }
::std::cout << "Auf einem Baum ein Kuckuck sass.\n"s; }
{ ::std::cout << "Da kam ein junger Jaeger, -\n"s;
{ ::std::cout << "Sim sa la dim, bam ba,\n"s;
::std::cout << "Sa la du, sa la dim -\n"s; }
::std::cout << "Da kam ein junger Jaegersmann.\n"s; }
{ ::std::cout << "Der schoss den armen Kuckuck, -\n"s;
{ ::std::cout << "Sim sa la dim, bam ba,\n"s;
::std::cout << "Sa la du, sa la dim -\n"s; }
::std::cout << "Der schoss den armen Kuckuck tot.\n"s; }}Protokoll
Auf einem Baum ein Kuckuck, -
Sim sa la dim, bam ba,
Sa la du, sa la dim -
Auf einem Baum ein Kuckuck sass.
Da kam ein junger Jaeger, -
Sim sa la dim, bam ba,
Sa la du, sa la dim -
Da kam ein junger Jaegersmann.
Der schoss den armen Kuckuck, -
Sim sa la dim, bam ba,
Sa la du, sa la dim -
Der schoss den armen Kuckuck tot.
In C++ kann ein Name an eine Verbundanweisung gebunden werden. Dadurch wird eine Funktion definiert.
Im Programm "kuckuck3.cpp" wird eine Funktion zur Ausgabe des Kehrreimes definiert und dreimal aufgerufen. Die Ausgabe des Programms "kuckuck3.cpp" ist der Ausgabe des Programms "kuckuck.cpp" gleich, denn bei jedem Aufruf der Funktion "kehrreim" wird wieder der Block ausgeführt, der den Kehrreim ausgibt.
kuckuck3.cpp
#include <iostream>
#include <ostream>
#include <string> // ""susing namespace ::std::literals;
void kehrreim()
{ ::std::cout << "Sim sa la dim, bam ba,\n"s;
::std::cout << "Sa la du, sa la dim -\n"s; }int main()
{ { ::std::cout << "Auf einem Baum ein Kuckuck, -\n"s;
::kehrreim();
::std::cout << "Auf einem Baum ein Kuckuck sass.\n"s; }
{ ::std::cout << "Da kam ein junger Jaeger, -\n"s;
::kehrreim();
::std::cout << "Da kam ein junger Jaegersmann.\n"s; }
{ ::std::cout << "Der schoss den armen Kuckuck, -\n"s;
::kehrreim();
::std::cout << "Der schoss den armen Kuckuck tot.\n"s; }}::std::cout
Auf einem Baum ein Kuckuck, -
Sim sa la dim, bam ba,
Sa la du, sa la dim -
Auf einem Baum ein Kuckuck sass.
Da kam ein junger Jaeger, -
Sim sa la dim, bam ba,
Sa la du, sa la dim -
Da kam ein junger Jaegersmann.
Der schoss den armen Kuckuck, -
Sim sa la dim, bam ba,
Sa la du, sa la dim -
Der schoss den armen Kuckuck tot.
Der Quelltext enthält zwei Funktionsdefinitionen, die mit dem Quelltext "void kehrreim()" bzw. dem Quelltext "int main()" beginnen und die Funktion "kehrreim" sowie die Funktion "main" definieren.
Die erste Funktionsdefinition definiert die Funktion "kehrreim", indem der Name "kehrreim" an den Block "{ ::std::cout << "Sim sa la dim, bam ba,\n"; ::std::cout << "Sa la du, sa la dim -\n"; }" gebunden wird. Dadurch kann dieser Name dann in der Funktion "main", auch wiederholt, aufgerufen werden, um die Ausführung dieses Blocks zu bewirken.
Das Schlüsselwort "void" legt fest, daß die definierte Funktion keine Wertfunktion ist: Der Aufruf der Funktion ergibt also keinen Wert. Es handelt sich dann um eine reine Wirkfunktion, deren Sinn eine bestimmte Wirkung ist. Im Falle der Funktion "kehrreim" ist diese Wirkung die Ausgabe des Refrains. (Dies ist wie es zuvor schon in Zusammenhang mit der Dokumentation von Funktionen der Standardbibliothek erklärt wurde.)
Die „Ausführung“ eines Programms beginnt in C++ (nach der Ausführung bestimmter Initialisierungen, die in anderen Lektionen behandelt werden) immer bei der Funktion "main", die in der hier angegebenen Form (oder einer Variante, die in einer anderen Lektion behandelt wird) definiert werden muß. Die Funktion "main" (engl.: main, Haupt-) ist damit sozusagen die „Hauptfunktion“ eines Programms, oder der vorgegebene „Einstiegspunkt“.
Die Namen der definierten Funktionen befinden im globalen Namensraum, es sind globale Namen, der Aufruf erfolgt also mit »::« vor ihrem Namen, was aber entfallen kann, wenn es in dem Block mit dem Aufruf keine Blockvariable mit demselben Namen gibt.
Der sprechende Name »kehrreim« vermittelt dem Leser, daß es sich bei dem ausgegebenen Text um einen Kehrreim handelt und erleichtert dadurch das Verständnis des Programms. Das Verständnis des Programms wird auch dadurch erleichtert, daß die Struktur des Liedes sich in der Programmstruktur wiederspiegelt (der Kehrreim, der auch im Lied eine eigene Rolle hat, hat im Programm eine eigene Funktion).
Syntax
Eine Funktionsdefinition besteht, etwas vereinfacht, aus optionalen Deklarationsspezifizieren 〈decl-specifier-seq 〉, einem Deklarator 〈declarator 〉 und einem Funktionsrumpf 〈function-body 〉.
- 〈function-definition 〉 ::=
- [〈decl-specifier-seq 〉] 〈declarator 〉 〈function-body 〉.
Ohne zu sehr in die Details zu gehen, kann man sagen, daß das Schlüsselwort "void" ein Deklarationsspezifizierer sind.
Auf die Deklarationsspezifizierer 〈decl-specifier-seq 〉 folgt dann der Deklarator, der in den hier gegebenen Beispielen durch den Deklarator "kehrreim()" bzw. den Deklarator "main()" gegeben ist.
Der Funktionsrumpf ist eine Verbundanweisung.
- 〈function-body 〉 ::=
- 〈compound-statement 〉.
Etwas einfacher kann man sich den Aufbau einer Funktionsdefinition zerlegt denken in einen Kopf und einen Rumpf oder Funktionsverbundanweisung auf. Der Kopf legt dabei vorwiegend die Schnittstelle der Funktion fest, also ihre nach außen direkt sichtbaren Eigenschaften, und der Rumpf enthält dann die Implementation der Funktion, er stimmt mit dem (äußersten) Block der Funktionsdefinition überein.
kehrreim [Funktionsdefinition]
void kehrreim() // Kopf, Schnittstelle
{ ::std::cout <<
"Sim sa la dim, bam ba,\n"s; // Rumpf, Funktions-Verbundanweisung, Implementation
::std::cout << //
"Sa la du, sa la dim -\n"s; } //::std::cout
Sim sa la dim, bam ba,
Sa la du, sa la dim -
Der Kopf entspricht in etwa der Kurzbeschreibung einer Funktion in der schon behandelten Dokumentation.
In einer Übersetzungseinheit (einem Quelltext) können verschiedene Funktionsdefinitionen hintereinander stehen.
Funktionsdefinitionen dürfen aber nicht verschachtelt sein, es ist also nicht erlaubt, innerhalb einer Funktionsdefinition eine weitere Funktionsdefinition zu schreiben.
Eine Funktionsdefinition muß zunächst vor anderen Funktionsdefinitionen stehen, in denen sie aufgerufen wird. (In anderen Lektionen wird gezeigt, wie davon abgewichen werden kann.)
main.cpp
#include <iostream>
#include <ostream>
#include <string> // ""susing namespace ::std::literals;
int main()
{ { ::std::cout << "Auf einem Baum ein Kuckuck, -\n"s; ::kehrreim();
::std::cout << "Auf einem Baum ein Kuckuck sass.\n"s; }
{ ::std::cout << "Da kam ein junger Jaeger, -\n"s; ::kehrreim();
::std::cout << "Da kam ein junger Jaegersmann.\n"s; }
{ ::std::cout << "Der schoss den armen Kuckuck, -\n"s; ::kehrreim();
::std::cout << "Der schoss den armen Kuckuck tot.\n"s; }}void kehrreim()
{ ::std::cout << "Sim sa la dim, bam ba,\n"s;
::std::cout << "Sa la du, sa la dim -\n"s; }Protokoll
main.cpp: In function 'int main()':
main.cpp:6:58: error: '::kehrreim' has not been declared
{ { ::std::cout << "Auf einem Baum ein Kuckuck, -\n"s; ::kehrreim();
^
main.cpp:8:58: error: '::kehrreim' has not been declared
{ ::std::cout << "Da kam ein junger Jaeger, -\n"s; ::kehrreim();
^
main.cpp:10:58: error: '::kehrreim' has not been declared
{ ::std::cout << "Der schoss den armen Kuckuck, -\n"s; ::kehrreim();
^
In C++ ist es üblich, Funktionsnamen ganz aus kleinen Buchstaben zu formen. Gelegentlich werden auch Grundstriche verwendet. Die Verwendung von Großbuchstaben in Funktionsnamen ist allerdings in bestimmten Anwendungsbereichen von C++ inzwischen auch üblich.
Es sollten bis auf weiteres nicht mehrere Funktionen mit demselben Namen definiert werden.
Die Bedeutung der Kopfzeile
Zur Entschlüsselung der Bedeutung des Kopfes einer Funktionsdefinition können die Kenntnisse über das Lesen der Dokumentation einer Funktion verwendet werden.
- Kopf einer Funktionsdefinition
void kehrreim()
- Dokumentation von »::std::abort« (gekürzt und überarbeitet)
#include <cstdlib>
void abort();- causes abnormal program termination
So bedeutet das »void« in »void kehrreim()«, daß die Funktion »kehrreim« keinen Wert liefert, und die Leerheit der runden Klammern besagt, daß die Funktion »kehrreim« ohne Argumente aufzurufen ist. Die Bedeutung des Kopfes ist also so, wie wir das schon bei der Verwendung vordefinierter Funktionen kennengelernt haben.
Der globale Namensraum »::«
Durch Voranstellung des unären Bereichsoperators »::« können Namen aus dem globalen Namensraum von lokalen Namen unterschieden werden. So kann »::terminate« nach der Einführung einer gleichnamigen lokalen Variablen weiterhin aufgerufen werden.
main.cpp
#include <iostream>
#include <ostream>
#include <string> /* ""s */using namespace ::std::literals; /* ""s */
void example(){ ::std::cout << "example\n"s; }
int main()
{ ::example();
example();
int example;
::example(); }transcript
example
example
example
Etwas vereinfacht gesagt, ist ein globaler Name ein Name, der außerhalb eines Blocks definiert ist, während ein lokaler Name innerhalb eines Blocks definiert wurde. Eine Funktionsdefinition gilt dabei als eine Definition des Funktionsnamens.
Der Namensraum »::std«
Durch den Namensraum »::std« können Funktionen der Standardbibliothek von gleichnamigen Funktionen unterschieden werden.
main.cpp
#include <iostream>
#include <ostream>
#include <exception> /* ::std::terminate() */
#include <string> // ""susing namespace ::std::literals;
void terminate(){ ::std::cout << "terminate\n"s; }
int main()
{ ::terminate();
terminate();
::std::terminate(); }::std::cout
terminate
terminate
terminate called without an active exception
Funktionen als eine Art von Abkürzung
main.cpp
#include <iostream>
#include <ostream>
#include <string> // ""susing namespace ::std::literals;
void LKW(){ ::std::cout << "Lastkraftwagen"s; }
int main()
{ ::std::cout << "Als Fernfahrer wird jemand bezeichnet, der einen "s; LKW();
::std::cout << " im Fernverkehr lenkt.\n"s; }::std::cout
Als Fernfahrer wird jemand bezeichnet, der einen Lastkraftwagen im Fernverkehr lenkt.
Übungsfragen
- ? Was ist die Ausgabe des folgenden Programms?
#include <iostream>
#include <ostream>
#include <string> // ""susing namespace ::std::literals;
void alpha(){ ::std::cout << "alpha\n"s; }
int main() { ::std::cout << "gamma\n"s; }
- Übungsfrage Was gibt das obige Programm aus?
- Übungsfrage Ist die Funktion »alpha()« eine Wertfunktion?
- Übungsfrage Ist die Funktion »alpha()« eine Wirkfunktion?
- Wiederholungen
#include <iostream>
#include <ostream>
#include <string> // ""susing namespace ::std::literals;
void gamma(){ ::std::cout << "Hallo!\n"s; }
void alpha(){ gamma(); gamma(); }
int main() { alpha(); gamma(); }
- Übungsaufgabe Was gibt das obige Programm aus?
- ? Was ist die Ausgabe des folgenden Programms? *
#include <iostream>
#include <ostream>
#include <string> // ""susing namespace ::std::literals;
void arg(){ ::std::cout << "0"s; }
int main() { ::std::cout << "("s; arg(); ::std::cout << ")\n"s; }
- ? Was ist die Ausgabe des folgenden Programms? *
#include <iostream>
#include <ostream>
#include <string> // ""susing namespace ::std::literals;
void f() { ::std::cout << ".\n"s; f(); }
int main() { f(); }
Übungsaufgaben
- / Delta-Zeile ausgeben
- Schreiben Sie mit Hilfe des Ausdrucks »::std::cout << "delta\n"s« (dessen Auswertung die Zeile »delta« ausgibt) eine Definition einer Funktion namens »printdelta«, welche die Zeile »delta« ausgibt.
Zusatzaufgaben ⃗
- / Funktionen definieren (Refaktor) ⃗
- Schreiben Sie das folgende Programm um, so daß sich wiederholende Anweisungen oder Anweisungsgruppen von einer Funktion ausgeführt werden, die dann wiederholt aufgerufen wird. Das Umschreiben soll die Ausgabe des Programmes nicht verändern (Refaktor).
- Für nur einmal vorkommende Anweisungen soll aber keine Funktion definiert werden, es soll also nur eine weitere Funktionsdefinition hinzugefügt werden.
Gemuese.cpp
#include <iostream>
#include <ostream>
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::cout << "Tomaten\n"s;
::std::cout << "Rotkohl Gruenkohl\n"s;
::std::cout << "Gurken\n"s;
::std::cout << "Tomaten\n"s;
::std::cout << "Rotkohl Gruenkohl\n"s;
::std::cout << "Spinat\n"s;
::std::cout << "Tomaten\n"s;
::std::cout << "Rotkohl Gruenkohl\n"s;
::std::cout << "Kohlrabi\n"s; }::std::cout
Tomaten
Rotkohl Gruenkohl
Gurken
Tomaten
Rotkohl Gruenkohl
Spinat
Tomaten
Rotkohl Gruenkohl
Kohlrabi- Hinweis Falls diese Aufgabe schwerfällt, kann das Programm "Kuckuck3.cpp" als Vorlage für die Lösung verwendet werden, indem darin schrittweise die Texte durch die für die Lösung dieser Aufgabe passende Texte ersetzt werden.
Ergänzungsaufgaben
- / Funktion umbenennen (Refaktor)
- Ändern Sie den Namen der Funktion »gamma« in dem folgenden Programm in den neuen Namen »delta«, ohne daß sich dadurch das Verhalten des Programms ändert.
- (Daß sich das Verhalten nicht ändern soll, bedeutet, daß das Programm nach der Überarbeitung noch dasselbe machen soll wie vorher.)
#include <iostream>
#include <ostream>
#include <string> // ""susing namespace ::std::literals;
void gamma(){ ::std::cout << "Hallo!\n"s; }
void alpha(){ gamma(); gamma(); }
int main() { alpha(); gamma(); }
- / Funktionen umordnen (Refaktor)
- Ändern Sie die Reihenfolge der Funktion »alpha« und der Funktion »gamma« in dem folgenden Programm, ohne daß sich dadurch das Verhalten des Programms ändert.
#include <iostream>
#include <ostream>
#include <string> // ""susing namespace ::std::literals;
void gamma(){ ::std::cout << "Gaby\n"s; }
void alpha(){ ::std::cout << "Anton\n"s; }
int main() { alpha(); gamma(); }