Deklarationen in C++
Alle bisher vorgestellten Definitionen sind auch Deklarationen. Eine Deklaration legt den Typ eines Bezeichners fest. Eine Definition legt darüber hinaus auch fest, an welche Entität (also an welches Objekt oder an welche Funktion) ein Bezeichner gebunden ist. Es gibt aber auch Deklarationen, die keine Definitionen sind. Diese geben nur den Typ eines Bezeichners an, ohne dem Bezeichner eine Entität zuzuordnen. Bei einer Funktionsdeklaration sagt man auch, solch eine Deklaration lege den Prototyp einer Funktion fest, um zu sagen, daß der Leser nun ihren Typ kenne. Zusammen mit der Angabe der nötigen Kopfdirektive bildet eine Deklaration auch die Synopsis in Dokumentation.
Mindestens der Typ eines Bezeichners muß bei Verwendung des Bezeichners bekannt sein, damit der Leser des Programms (z.B. der Übersetzer) prüfen kann, ob die Verwendung hinsichtlich des Typs korrekt ist. Soll ein Bezeichner erst nach der Verwendung im Quelltext definiert werden, dann muß er also vor der Verwendung zumindest deklariert werden.
In dem folgenden Beispielprogramm wird die Funktion "hallo" zunächst ohne Definition deklariert. Dabei wird nur der Typ festgelegt. Die Funktion kann dann schon verwendet werden, weil der Übersetzer ihren Typ an der Stelle des Aufrufs kennt. Nach der ersten Verwendung folgt weiter unten im Quelltext die Definition (Implementation). Der Übersetzer liest den Quelltext nur einmal von vorne bis hinten durch. Erst der Verbinder wird dann die Adresse der Funktion »hallo« an den Stellen des Aufrufs dieser Funktion einsetzen, das ermöglicht dann den Aufruf. Der Verbinder kann solch Ersetzungen auch dann erzeugen, wenn eine Definition im Programmtext nach der Verwendung erfolgt.
Es ist erlaubt, einen Bezeichner mehrfach zu deklarieren, wenn alle Deklarationen übereinstimmen. Eine Definition eines Bezeichners darf jedoch in der Regel nur einmal innerhalb eines Namensraumes und Gültigkeitsbereichs erfolgen (abgesehen von bestimmten Ausnahmen, wie Überladungen).
main.cpp
#include <iostream>
#include <ostream>
#include <string> /* ::std::string */using namespace ::std::literals;
void hallo(); // Deklaration von "hallo" (ohne Definition)
int main(){
hallo // Verwendung von "hallo"
(); }void hallo(); // identische Redeklaration ist erlaubt
void hallo() // Definition und erneute Deklaration von "hallo"
{ ::std::cout << "Hallo!\n"s; } //::std::cout
Hallo!
Ohne die Deklaration kennte der C++ -Leser die Funktion beim Aufruf nicht gut genug, um die Richtigkeit ihrer Verwendung zu prüfen.
main.txt
#include <iostream>
#include <ostream>
#include <string> /* ::std::string */using namespace ::std::literals;
int main(){ hallo(); }
void hallo(){ ::std::cout << "Hallo!\n"s; }
- Konsole
"deklaration.txt", line 3: error: identifier "hallo" is undefined
int main(){ hallo(); }
^1 error detected in the compilation of "deklaration.txt".
Wenn eine Funktion vor ihrer ersten Verwendung definiert wird, ist eine zusätzliche Deklaration nicht nötig.
main.cpp
#include <iostream>
#include <ostream>
#include <string> /* ::std::string */using namespace ::std::literals;
void hallo() // Definition von "hallo"
{ ::std::cout << "Hallo!\n"s; } //int main(){
hallo // Verwendung von "hallo"
(); }::std::cout
Hallo!
Natürlich könnte der Eindruck entstehen, daß Deklarationen, die nicht gleichzeitig Definitionen sind, nicht so dringend benötigt werden, weil Funktionen ja vor ihrer Verwendung definiert werden können. Das ist aber nicht immer möglich. Deklarationen, die nicht gleichzeitig Definitionen sind, spielen eine wichtige Rolle. Beispielsweise kann ein C++ -Programm Funktionen der Standardbibliothek benutzen, die es nicht selber definiert hat. Das geht aber nur dann, wenn diese vorher deklariert wurden. Diese Deklaration wird von den #include-Direktiven, wie beispielsweise »#include <iostream>« bewirkt, ohne daß der Quelltext der Definition von Standardfunktionen zugänglich sein muß.
Eine vorherige Deklaration einer Funktion erlaubt es auch, zwei Funktionen zu schreiben, die sich gegenseitig aufrufen.
- Deklaration von Standardfunktionen *
- The only reliable way to declare an object or function signature from the Standard C library is by including the header that declares it, notwithstanding the latitude granted in 7.1.4 of the C Standard.
- Fußnote 179 zu 17.6.2.3p2 in C++ 2015
Kopf und Typ
Nicht alle Bestandteile des Kopfes einer Funktionsdefinition (des Teils vor der ersten geschweiften Klammer) legen den Typ der Funktion fest. Namen von Parametern und die const-Eigenschaft eines Parameters sind nur Implementationsfestlegungen, welche nicht Bestandteil des Typ des Funktions sind. (Sie sind kein Bestandteil der Schnittstelle der Funktion.) Deswegen ist es in dem folgenden Programmbeispiel erlaubt, daß in einigen Deklaration von »next« das »const« fehlt oder der Parameter einen anderen Namen hat als in der Definition von »next(int)«.
main.cpp
#include <iostream>
#include <ostream>int next( int );
int next( int const );
int next( int const number );
int main(){ std::cout << next( 3 ) << '\n'; }
int next( int const value ){ return value + 1; }
::std::cout
4
Lokale Deklarationen
Innerhalb eines Blocks ist die Definition einer Funktion nicht möglich. Eine Funktion kann jedoch innerhalb eines Blocks deklariert werden. Der Typ der so deklarierten Funktion ist dann nur innerhalb dieses Blocks bekannt. Daher spricht man auch von einer (zu diesem Block) lokalen Deklaration.
main.cpp
#include <iostream>
#include <ostream>
#include <string> /* ::std::string */using namespace ::std::literals;
int main(){
void hallo(); // lokale Deklaration von "hallo"
hallo // Verwendung von "hallo"
(); }void hallo() // Definition von "hallo"
{ ::std::cout << "Hallo!\n"s; } //::std::cout
Hallo!
Leere Klammern oder Schlüsselwort "void"?
Sowohl in einer Definition als auch in einer Deklaration können die runden Klammern leer sein oder das Schlüsselwort »void« enthalten, das ändert nichts an der Bedeutung. In dem folgenden Programmbeispiel »voidpar.cpp« gilt die zweite reine Deklaration daher als identische Redeklaration. Auch in der Definition kann das Schlüsselwort »void« in die runden Klammern geschrieben werden.
main.cpp
#include <iostream>
#include <ostream>
#include <string> /* ::std::string */using namespace ::std::literals;
void hallo(); // Deklaration von "hallo" (ohne Definition)
void hallo( void ); // identische Redeklaration ist erlaubt
void hallo( void ) // Definition und erneute Deklaration von "hallo"
{ ::std::cout << "Hallo!\n"s; } //int main(){ hallo(); }
::std::cout
Hallo!
Es wird empfohlen, das Schlüsselwort »void« nie in den runden Klammern zu verwenden, sondern diese gegebenenfalls immer leer zu lassen, da dies bei gleicher Bedeutung kürzer ist.
- »void« als Parameterliste *
The parameter list (void) is equivalent to the empty parameter list.
»ISO/IEC 14882:1998(E)«
»ISO/IEC 14882:2003(E)«, 8.3.5 p2.
Fehlwerte für Parameter in Deklarationen
In eine Deklaration kann auch ein Fehlwert für eine Parameter geschrieben werden, der dann verwendet wird, wenn das entsprechende Argument fehlt.
main.cpp
#include <iostream>
#include <ostream>
#include <string> /* ::std::string */using namespace ::std::literals;
int sum( int, int = 0 );
int main()
{ std::cout << sum( 3, 4 ) << '\n' << sum( 3 ) << '\n'; }int sum( int const left, int const right )
{ return left + right; }::std::cout
7
3
Es gibt einige Feinheiten zu Fehlwerten zu beachten, wenn mehrfache Deklarationen verwendet werden, auf die hier nicht eingegangen werden soll.
Übungsfragen
? Deklaration und Definition
Welche der folgenden Aussagen sind wahr?
- Das folgende Programm enthält eine Definition der Funktion »main()«.
- Das folgende Programm enthält eine Deklaration der Funktion »main()«.
main.cpp
int main(){}
Übungsaufgaben
/ Funktionen umordnen (Refaktor)
Ändern Sie die Reihenfolge der Definitionen der Funktionen in dem folgenden Programm von „bottom-up “ (Aufgerufenes vor Aufrufendem) nach „top-down “ (Aufgerufenes nach Aufrufendem), ohne daß sich dadurch das Verhalten des Programms ändert. Dabei können zusätzliche Deklarationen zum Programm hinzugefügt werden.
main.cpp
#include <iostream>
#include <ostream>
#include <string> /* ::std::string */using namespace ::std::literals;
void beta(){ ::std::cout << "Hallo!\n"s; }
void alpha(){ beta(); beta(); }
int main() { alpha(); beta(); }