C++-Programmierung/ Weitere Grundelemente/ Headerdateien
Sie haben bereits mit 2 Headerdateien Bekanntschaft gemacht: iostream
und string
. Beide sind so genannte Standardheader, das heißt, sie sind in der Standardbibliothek jedes Compilers enthalten.
Was ist eine Headerdatei?
[Bearbeiten]Headerdateien sind gewöhnliche C++-Dateien, die im Normalfall Funktionsdeklarationen und Ähnliches enthalten. Sicher werden Sie sich erinnern: Deklarationen machen dem Compiler bekannt, wie etwas benutzt wird. Wenn Sie also eine Headerdatei einbinden und darin eine Funktionsdeklaration steht, dann weiß der Compiler wie die Funktion aufgerufen wird. Der Compiler weiß zu diesem Zeitpunkt nicht, was die Funktion tut, aber das ist auch nicht nötig um sie aufrufen zu können.
Das Einbinden einer Headerdatei erfolgt über die Präprozessordirektive #include
. Der Code in der Datei die mit include
referenziert wird, wird vom Präprozessor einfach an der Stelle eingefügt, an der das include
stand.
In der nebenstehenden Darstellung wird die Headerdatei mal2.hpp
von den Quelldateien main.cpp
und mal2.cpp
eingebunden. Um dieses Beispiel zu übersetzen, müssen die Dateien alle im gleichen Verzeichnis liegen. Sie übergeben die Quelldateien an einen Compiler und rufen anschließend den Linker auf, um die entstandenen Objektdateien zu einem Programm zu binden.
Falls Sie mit der GCC arbeiten, beachten Sie bitte, dass g++ sowohl Compiler als auch Linker ist. Wenn Sie nur die beiden Quelldateien ohne weitere Parameter angeben, wird nach dem Kompilieren automatisch gelinkt. Der zweite Aufruf entfällt somit.
Einige Informationen zu Compilern und Linkern finden Sie übrigens im Kapitel Compiler.
Namenskonventionen
[Bearbeiten]Übliche Dateiendungen für C++-Quelltexte sind „.cpp“ oder „.cc“. Headerdateien haben oft die Endungen „.hpp“, „.hh“ und „.h“. Letztere ist allerdings auch die gebräuchlichste Dateiendung für C-Header, weshalb zugunsten besserer Differenzierung empfohlen wird, diese nicht zu benutzen.
Die Standardheader von C++ haben überhaupt keine Dateiendung, wie Sie vielleicht schon anhand der beiden Headerdateien iostream
und string
erraten haben. In der Anfangszeit von C++ endeten Sie noch auf „.h“, inzwischen ist diese Notation aber nicht mehr gültig, obwohl sie immer noch von vielen Compilern unterstützt wird. Der Unterschied zwischen „iostream“ und „iostream.h“ besteht darin, dass in ersterer Headerdatei alle Deklarationen im Standardnamespace std
vorgenommen werden.
Prinzipiell kommt es nicht darauf an, welche Dateiendungen Sie Ihren Dateien geben, dennoch ist es sinnvoll, eine übliche Endung zu verwenden, wenn auch nur, um einer möglichen Verwechslungsgefahr vorzubeugen. Wenn Sie sich einmal für eine Dateiendung entschieden haben, ist es vorteilhaft, darin eine gewisse Konstanz zu bewahren, nicht nur vielleicht aus Gemütlichkeit und Zeiteinsparnis, sondern auch im Sinne des schnellen Wiederfindens. Einige Beispiele für Headerdateiendungen finden Sie, wenn Sie sich einfach mal einige weitverbreite C++-Bibliotheken ansehen. Boost verwendet „.hpp“, wxWidgets nutzt „.h“ und Qt orientiert sich an der Standardbibliothek, hat also gar keine Dateiendung.
Schutz vor Mehrfacheinbindung
[Bearbeiten]Da Headerdateien oft andere Headerdateien einbinden, kann es leicht passieren, dass eine Headerdatei mehrfach eingebunden wird. Da viele Header nicht nur Deklarationen, sondern auch Definitionen enthalten, führt dies zu Compiler-Fehlermeldungen, da innerhalb einer Übersetzungseinheit ein Name stets nur genau einmal definiert werden darf (mehrfache Deklarationen, die keine Definitionen sind, sind jedoch erlaubt). Um dies zu vermeiden, wird der Präprozessor verwendet. Am Anfang der Headerdatei wird ein Präprozessor-ifndef ausgeführt, das prüft, ob ein Symbol nicht definiert wurde, ist dies der Fall, wird das Symbol definiert. Am Ende der Headerdatei wird die Abfrage mit einem Präprozessor-endif wieder beendet.
Das Symbol wird üblicherweise aus dem Dateinamen des Headers abgeleitet. In diesem Fall wurde der Punkt durch einen Unterstrich ersetzt und ein weiterer Unterstrich vor und nach dem Dateinamen eingefügt. Wenn der Präprozessor nun die Anweisung bekommt, diese Datei in eine andere einzubinden, wird er das anstandslos tun. Findet er eine zweite Anweisung, die Datei einzubinden, wird er alles von #ifndef
bis #endif
überspringen, da das Symbol _mal2_hpp_
ja nun bereits definiert wurde.
Sie können sich eine beliebige Methode zum Generieren von Symbolen ausdenken. Da es schnell passieren kann, dass Sie eine Bibliothek verwenden, in der Headerdateien enthalten sind, welche Sie auch in ihrem eigenen Projekt bereits verwenden, ist es jedoch empfehlenswert an dieser Stelle nicht mit Zeichen zu geizen. Eine mögliche Variante ist beispielsweise neben dem Dateinamen auch noch den Projektnamen zu verwenden und eventuell auch noch eine feste Zeichenfolge, um das Symbol unmissverständlich als Mehrfacheinbindungsschutz zu kennzeichnen. Das könnte dann etwa so aussehen:
_Projektname_Dateiname_Dateiendung_INCLUDED_
Mehr Informationen über Präprozessor-Anweisungen finden Sie im Kapitel Vorarbeiter des Compilers.
Inline-Funktionen
[Bearbeiten]Im Kapitel „Prozeduren und Funktionen“ haben Sie bereits erfahren was eine Inline-Funktion ist. Das Schlüsselwort inline
empfiehlt dem Compiler, beim Aufruf einer Funktion den Funktionsaufruf direkt durch den Funktionsrumpf zu ersetzen, wodurch bei kurzen Funktionen die Ausführungsgeschwindigkeit gesteigert werden kann. Es bewirkt aber noch mehr. Normalerweise darf eine Definition immer nur einmal gemacht werden. Da für das Inlinen einer Funktion aber die Definition bekannt sein muss, gibt es für Inline-Funktionen eine Ausnahme: Sie dürfen beliebig oft definiert werden, solange alle Definitionen identisch sind. Deshalb dürfen (und sollten) Inline-Funktionen in den Headerdateien definiert werden, ohne dass sich der Linker später über eine mehrfache Definition in verschiedenen Objektdateien beschweren wird.
Werden Klassenmethoden bereits bei der Deklaration definiert (also mit einem Körper versehen), so werden sie automatisch als inline
verwendet, auch ohne daß der Bezeichner angegeben wird. Dies verbessert die Ausführung von einfachen Datenzugriffen erheblich. Alternativ zur Definition bei der Deklaration können die Methoden auch explizit als "inline" deklariert und an einem anderen Ort (z.B. weiter unten in der Deklarationsdatei) ausgeführt werden. Das kann die Übersichtlichkeit verbessern.
Inline-Funktionen dürfen auch in cpp-Dateien deklariert und definiert werden. Allerdings können sie dann auch nur innerhalb der Objektdatei, die aus der cpp-Datei erzeugt wird, ge-inlinet werden und das ist in aller Regel nicht beabsichtigt.