Getrennte Übersetzung in C++
Teile einer Übersetzungseinheit, die gemeinsam eine bestimmte Aufgabe erledigen, können von dieser in eine weitere Übersetzungseinheit abgetrennt werden. Das ist so üblich und verbessert die Übersicht, wenn jede Übersetzungseinheit einen klar definierten Zuständigkeitsbereich hat. Es erlaubt außerdem, eine Übersetzungseinheit in verschiedenen Projekten (z.B. Programmen) zu verwenden und so einmal investierte Arbeit mehrfach zu nutzen.
Dieses Vorgehen soll zunächst an einem einfachen Beispiel gezeigt werden.
monolith.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
void hallo(){ ::std::cout << "Hallo, "; }
int main(){ ::hallo(); ::std::cout << "Peter!\n"; }::std::cout
Hallo, Peter!
Es kann der Eindruck entstehen, daß die Funktion zur Ausgabe von "Hallo, " eine eigene Übersetzungseinheit verdient, weil sie in mehreren Projekten verwendet werden könnte. (In der Praxis wäre sie dafür meist zu klein, doch bleibt das Beispiel so zunächst überschaubar. In Anwendungsprojekten sind die Übersetzungseinheiten oft größer.)
Eine mögliche Zerlegung—noch nicht ganz perfekt—besteht aus der Übersetzungseinheit "hallo.cpp" und der Übersetzungseinheit "main.cpp".
hallo.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
void hallo(){ ::std::cout << "Hallo, "; }Konsole
Compile succeeded.
main.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
int main(){ ::hallo(); ::std::cout << "Peter!\n"; }Konsole
"main.c", line 3: error: the global scope has no "hallo"
int main(){ ::hallo(); ::std::cout << "Peter!\n"; }
^
1 error detected in the compilation of "main.c".
Die Übersetzungseinheit "main.cpp" kann nicht übersetzt werden, weil der Bezeichner "hallo" im globalen Namensbereich nicht bekannt ist. Das Problem kann durch eine Deklaration des Bezeichners behoben werden. Durch die Deklaration wird dem Übersetzer zugesichert, daß ein Bezeichner "hallo" für eine Funktion (ohne Ergebnis und Parameter) im globalen Namensbereich definiert wird. Auch wenn der Übersetzer die Definition nicht kennt, ist ihm dann der Typ bekannt (nach dem Motto “trust the programmer ”) und das genügt.
main.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
void hallo();
int main(){ ::hallo(); ::std::cout << "Peter!\n"; }Konsole
Compile succeeded.
Das Programm läuft nun noch nicht, aber beide Übersetzungseinheiten konnten zunächst in Zwischendateien (die Objektdateien ) übersetzt werden.
Die erzeugten Zwischendateien müssen nun zu einem lauffähigen Programm verbunden werden, wobei der Bezug auf die Funktion "::hallo" aufgelöst werden muß (Beim Aufruf der Funktion "::hallo" muß die Definition aus einer anderen Objektdatei aufgerufen werden.). Zum Auflösen des Bezugs muß ein Verbinder aufgerufen werden. Wie genau dieser Verbinder bedient wird, hängt vom verwendeten Betriebssystem und Entwicklungssystem ab, es ist nicht durch die Programmiersprache C++ festgelegt. Da es hierfür viele verschiedene Möglichkeiten gibt, muß hierfür die Anleitung des verwendeten Entwicklungssystems herangezogen werden. Das Vorgehen zur Aktivierung dieser Verbindung kann daher nicht in dieser Lektion beschrieben werden. Nach dem Verbinden ist dann ein lauffähiges Programm entstanden, das die Zeile "Hallo, Peter!" ausgibt.
::std::cout
Hallo, Peter!
Es könnte nun noch eine andere Übersetzungseinheit "main1.cpp" mit einer Funktion "main" definiert werden, die mit der Übersetzungseinheit "hallo.cpp" verbunden werden kann, um ein weiteres ausführbares Programm zu erzeugen.
main1.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
void hallo(); // Eine weitere Kopie der Deklaration
int main(){ ::hallo(); ::std::cout << "Susanne!\n"; }Konsole
Compile succeeded.
::std::cout
Hallo, Susanne!
Das vorgestellte Verfahren hat aber noch Mängel: Wenn die Übersetzungseinheit "hallo.cpp" tatsächlich in mehreren Projekten verwendet werden sollte, dann muß die Deklaration der Funktion "hallo" in jeder Klienten-Übersetzungseinheit stehen. Es entstünden also viele Kopien. Bei den häufigen größeren Übersetzungseinheiten mit mehreren Funktionen müßten sogar mehrere Funktionsdeklarationen in jeden Klienten (Benutzer) kopiert werden. Außerdem kann der Übersetzer nicht prüfen, ob die Deklaration der Funktion "hallo" mit der Definition verträglich ist.
Um das unnötige Kopieren von Deklarationen zu verhindern, wird diese nun traditionell einmal in eine Datei geschrieben, die dann mit einer Vorverarbeiter-Direktive am „Kopf“ (Anfang) aller Klienten eingefügt wird und daher auch als „Kopfdatei“ (header ) bezeichnet wird. Diese Datei enthält die Deklarationen der Bezeichner einer Übersetzungseinheit, die in anderen Übersetzungseinheiten verwendet werden können, und weiteres Material, das der Übersetzer sehen muß, damit eine Einheit verwendet werden kann. Solch eine Kopfdatei hat oft die Dateinamenserweiterung ".h" oder die Dateinamenserweiterung ".hpp" und heißt sonst wie die zugehörige Übersetzungseinheit (ohne die Erweiterung ".cpp"). In dem hier verwendeten Beispiel enthält die Kopfdatei "hallo.h" die Deklaration der Funktion "hallo".
hallo.h
#ifndef hallo_h_INCLUDED_20030115
#define hallo_h_INCLUDED_20030115
void hallo();
#endif
Alle Zeilen in einer Kopfdatei werden Bestandteil jeder Datei, in die sie eingefügt wird, also ihres Klienten. Daher sollte der Autor einer Kopfdatei nur das unbedingt Nötige in sie aufnehmen.
Allgemein gesagt, müssen alle Informationen, die der Compiler bei der Übersetzung sehen muß, in die Kopfdatei geschrieben werden, alles andere gehört in die Implementationsdatei. Einem Anfänger fehlt aber oft noch die nötig Vorstellung von den Vorgängen bei der Herstellung, um dieses Kriterium immer richtig anzuwenden, daher können folgende Regelungen beachtet werden: Deklarationen und Klassenspezifizierer kommen in die Kopfdatei, Definitionen in die Implementationsdatei. Makrodefinitionen kommen dann in die Kopfdatei, wenn sie von Klienten gesehen werden sollen. Einzureihende Funktionsdefinitionen gehören auch in die Kopfdatei, using -Deklarationen oder using -Direktiven sollten in Kopfdateien nicht verwendet werden, da sie sonst in den Klienten wirksam werden, was fast immer unerwünscht ist.
Nun kann jeder Klient der Dienste (wie beispielsweise der Funktionen) der Übersetzungseinheit "hallo.cpp" alle benötigten Deklarationen mit einer Einfügedirektive (Kopfdirektive) einfügen (inkludieren).
main2.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
#include "hallo.h"
int main(){ ::hallo(); ::std::cout << "Peter!\n"; }Konsole
Compile succeeded.
::std::cout
Hallo, Peter!
Man kann sich vorstellen, daß die Standardkopfdirektiven, wie "#include <iostream>" auch auf das Einfügen solcher Kopfdateien zurückgehen. Bei den Standardkopfdirektiven werden die Namen jedoch in spitze Klammern "<>" statt in Anführungszeichen geschrieben und es wurde inzwischen festgelegt, daß sie nicht mehr unbedingt Dateien im wörtlichen Sinne bereitstellen, sondern es reicht, wenn der Übersetzer sie erkennt und daraufhin die entsprechenden Deklarationen so bereitstellt, als ob eine entsprechende Kopfdatei eingefügt worden sei. Zum besseren Verständnis kann man sich dabei aber vorstellen, daß diese Standarddirektiven Kopfdateien mit Deklarationen für Standardbezeichner, wie den Bezeichner "::std::cout", einfügen, so daß diese dann verwendet werden können, auch wenn sie in einer Übersetzungseinheit nicht definiert werden.
Eine Übersetzungseinheit, wie die Übersetzungseinheit "hallo.cpp", zusammen mit ihrer Kopfdatei, wie die Kopfdatei "hallo.h" kann man insgesamt als ein Modul ansehen, das bestimmte Dienste anbietet. Perfekt wird solch ein Modul dann mit einer Dokumentationsdatei, die auch noch erklärt, wie diese Dienste benutzt werden können. Zur Herstellung solch einer Dokumentationsdatei können Dokumentationskommentare in der Kopfdatei verwendet werden. Man kann dann auch schön erkennen, wie die Kopfdatei eine Schnittstelle beschreibt (Sicht von außen) und die cpp -Datei die Implementation (Sicht von innen), weswegen sie auch Implementationsdatei genannt wird.
In die Implementationsdatei des Moduls wird die Kopfdatei auch noch einmal eingefügt, weil es dem Übersetzer dann auffallen würde, wenn die Deklarationen mit dem Typ der definierten Entitäten nicht verträglich wären und eine entsprechende Fehlermeldung erzeugt werden würde. Außerdem benötigt die Implementationsdatei oft selber auch bestimmten Informationen aus der Kopfdatei.
hallo3.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
#include "hallo3.h"
void hallo3(){ ::std::cout << "Hallo, "; }hallo3.h
#ifndef hallo3_h_INCLUDED_20030115
#define hallo3_h_INCLUDED_20030115
/** @file hallo3.h
@brief Unterstuetzung bei der Ausgabe von "Hallo, ". */
/// Ausgabe des Textes "Hallo, "
void hallo3();
#endifhallo3.txt [Dokumentation]
hallo3.h Dateireferenz
Unterstuetzung bei der Ausgabe von "Hallo, ".
Funktionen
void hallo3()
Ausgabe des Textes "Hallo, ".hallo3_ec.cpp
/** @file hallo3_ec.cpp
@brief Testklient des Moduls hallo3. */
#include <iostream> // ::std::cout
#include <ostream> // <<
#include "hallo3.h"
int main(){ ::hallo3(); ::std::cout << "Peter!\n"; }Konsole
Compile succeeded.
::std::cout
Hallo, Peter!
Man darf aber nicht glauben, die Programmiersprache C++ habe einen bestimmten Begriff von einem Modul. Ein Modul ist lediglich eine bestimmte etablierte Art der Verwendung von C++, es wird aber nicht speziell durch C++ unterstützt, sondern vom Programmierer aus verschiedenen C++ -Sprachelementen zusammengebastelt. Es handelt sich als um eine bestimmte tradierte Art der Verwendung von C++ aber nicht um einen Begriff der Sprache selber. In diesem Sinne gilt „Es gibt in C++ keine Module.“ Es ist lediglich eine Entscheidung des Programmierers und dieses Textes eine Implementationsdatei (wie die Datei "hallo3.cpp") und eine Kopfdatei (wie die Datei "hallo3.h") mit der dazugehörigen Dokumentationsdatei (wie der Datei "hallo3.txt") zusammen gedanklich als ein Modul "hallo3" aufzufassen.
Die Dokumentationsdatei und die Schnittstellendatei eines Moduls müssen so geschrieben werden, daß diesen beiden Dateien alle Informationen zur Benutzung des Moduls entnommen werden können, ohne daß es nötig ist, auf die Implementationsdatei zurückzugreifen. Im Allgemeinen sollten alle zur Verwendung eines Moduls nötigen Informationen der Dokumentation entnommen werden können.
Wenn ein Bezeichner von einem Modul definiert wird und mit dieser Definition anderen Übersetzungseinheiten zur Verfügung gestellt wird, sagt man auch, der Bezeichner werde exportiert oder er habe externe Verbindung (external linkage ). Soll eine Funktionsdefinition von anderen Übersetzungseinheiten aus nicht erreichbar sein, so kann sie als Schlüsselwortes "static" gekennzeichnet werden. Man sagt dann auch, diese Definition sei von anderen Übersetzungseinheiten aus nicht sichtbar und der definierte Bezeichner habe interne Verbindung (internal linkage ). Bezeichner mit interner Verbindung werden ihrer Sichtbarkeit entsprechend auch nicht in der Kopfdatei deklariert. Ein Beispiel dafür findet sich im Programm "hallo4.cpp". Jedoch sollte diese Verwendung des Schlüsselwortes "static" vermieden werden, weil es für denselben Zweck bessere Mittel gibt.
- Getrennte Kompilierung
- Schreiben Sie ein Modul "englisch", das die Funktion "vielleicht" enthält. Diese Funktion soll das Englische Wort für „vielleicht“ (das Wort "perhaps") ausgeben. Schreiben Sie einen Testklienten als weitere Übersetzungseinheit und benutzen sie darin das Modul "englisch", um das englische Wort für „vielleicht“ auszugeben. Im Testklienten muß danach auch noch ein Zeilenende ausgegeben werden, bevor das Programm endet.
Getrennte Übersetzung von Klassen
Viele C++ -Programme machen umfangreichen Gebrauch von Klassen. Dabei werden zusammengehörige Klassen innerhalb einer Übersetzungseinheit definiert.
Die in einer anderen Lektion definierten Klassen zur Ausgabe von Liedern können jeweils in eine eigene Datei geschrieben werden. Dadurch wird kann das unübersichtliche große Programm in übersichtliche kleine Übersetzungseinheiten aufgeteilt werden. Beide Liedklassen könnten gemeinsam in einem Lieder-Modul definiert werden. In dieser Lektion werden jedoch beide Lieder in jeweils eine eigene Datei geschrieben. (Diese Dateien könnten dann übersichtlich in einem Lieder-Verzeichnis zusammengefaßt werden.)
Der Klassenspezifizierer wird in die Kopfdatei des Moduls geschrieben. Die Definition der Elemente der Klasse erfolgt in der Implementationsdatei. Wie zuvor bereits erwähnt, sind die drei erzeugten Objektdateien miteinander zu verbinden, um ein lauffähiges Programm zu erzeugen.
Jede Implementationsdatei braucht nur ihre eigene Kopfdatei einzufügen. Der Testklient benötigt beide Kopfdateien, da er beide Klassen benutzen will.
Die Kopfdateien enthalten selber den Bezeichner "::std::cout" oder den Operator "<<" nicht, deren Verwendung die Direktive "#include <iostream>" bzw. die Direktive "#include <ostream>" nötig machen würde. Daher treten diese Direktiven in den Kopfdateien auch nicht auf. Das ist so auch besser, da der Klient sonst ebenfalls diese Direktiven enthalten würde, was aber nicht immer erwünscht ist.
kuckuck.h
#ifndef kuckuck_h_INCLUDED_20030115
#define kuckuck_h_INCLUDED_20030115
/** @file kuckuck.h
@brief Das Lied "Auf einem Baum ein Kuckuck sass" */
/// Das Lied "Auf einem Baum ein Kuckuck sass"
struct kuckuck
{ /// Das Lied ausgeben
static void ausgeben();
/// Refrain des Liedes ausgeben
static void refrain(); };
#endifkuckuck.cpp
#include <iostream>
#include <ostream>
#include "kuckuck.h"
void ::kuckuck::refrain()
{ ::std::cout << "Sim sa la dim, bam ba,\n";
::std::cout << "Sa la du, sa la dim -\n"; }
void ::kuckuck::ausgeben()
{ { ::std::cout << "Auf einem Baum ein Kuckuck, -\n"; refrain();
::std::cout << "Auf einem Baum ein Kuckuck sass.\n"; }
{ ::std::cout << "Da kam ein junger Jaeger, -\n"; refrain();
::std::cout << "Da kam ein junger Jaegersmann.\n"; }
{ ::std::cout << "Der schoss den armen Kuckuck, -\n"; refrain();
::std::cout << "Der schoss den armen Kuckuck tot.\n"; }}vogel.h
#ifndef vogel_h_INCLUDED_20030115
#define vogel_h_INCLUDED_20030115
/** @file vogel.h
@brief Das Lied "Vogelhochzeit" */
/// Das Lied "Ein Vogel wollte Hochzeit machen"
struct vogel
{ /// Das Lied ausgeben
static void ausgeben();
/// Refrain des Liedes ausgeben
static void refrain(); };
#endifvogel.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
#include "vogel.h"
void ::vogel::refrain()
{ ::std::cout << "Fi-di-ra-la-la, fi-di-ra-la-la, fi-di-ra-la-la-la-la!\n"; }
void ::vogel::ausgeben()
{ { ::std::cout << "Ein Vogel wollte Hochzeit machen, in dem gruenen Walde.\n";
refrain(); }
{ ::std::cout << "Die Drossel war der Braeutigam, die Amsel war die Braute.\n";
refrain(); }
{ ::std::cout << "Die Lerche, die Lerche, die fuehrt die Braut zur Kerche.\n";
refrain(); }}lieder_ec.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
#include "kuckuck.h"
#include "vogel.h"
/// Beispielklient
int main()
{ ::kuckuck::ausgeben(); ::vogel::ausgeben(); }::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.
Ein Vogel wollte Hochzeit machen, in dem gruenen Walde.
Fi-di-ra-la-la, fi-di-ra-la-la, fi-di-ra-la-la-la-la!
Die Drossel war der Braeutigam, die Amsel war die Braute.
Fi-di-ra-la-la, fi-di-ra-la-la, fi-di-ra-la-la-la-la!
Die Lerche, die Lerche, die fuehrt die Braut zur Kerche.
Fi-di-ra-la-la, fi-di-ra-la-la, fi-di-ra-la-la-la-la!
using -Deklaration und using -Direktive *
Eine using -Deklaration (beginnend mit dem Schlüsselwort "using" ohne direkt folgendes Schlüsselwort "namespace") oder using -Direktive (beginnend mit dem Quelltext "using namespace") mit Namensraum-Gültigkeitsbereich sollte in einer Kopfdatei nicht enthalten sein, da sie dann auch für alle Klienten gelten würde. Daher sollten die Bezeichner in Kopfdateien immer mit vorangestelltem Namensraum geschrieben werden oder der Gültigkeitsbereich der verwendeten using -Deklarationen oder using -Direktiven muß so eingeschränkt werden, daß diese nicht mehr für Klienten gelten, beispielsweise indem diese in einem Block verwendet werden.
Makrodefinitionen *
Makros muß der Übersetzer (genauer: der Vorverarbeiter) samt ihrer Definition kennen, damit er sie richtig verwenden kann. Deswegen müssen sie in der Kopfdatei eines Moduls definiert werden, wenn sie im Klienten verwendet werden sollen. Dadurch können Implementationsdetails sichtbar werden, die nicht sichtbar werden sollten. (Ein Klient könnte sich an diese binden, was ihre Änderbarkeit verringert.) Daher sollten Makros möglichst vermieden werden. Das folgende Beispiel zeigt wie ein Modul ein Makro exportiert.
hallo4.cpp
// keine Implementationsdatei noetig
hallo4.h
#ifndef hallo4_h_INCLUDED_20030115
#define hallo4_h_INCLUDED_20030115
/// Ausgabe des Textes "Hallo, " (Makroversion)
#define HALLO4 { ::std::cout << "Hallo, "; }
#endifhallo4.txt [Dokumentation]
hallo4.h Dateireferenz
Makrodefinitionen
#define HALLO4 { ::std::cout << "Hallo, "; }
Ausgabe des Textes "Hallo, " (Makroversion).main4.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
#include "hallo4.h"
int main()
{ HALLO4 ::std::cout << "Michael!\n"; }Konsole
Compile succeeded.
::std::cout
Hallo, Michael!
Definitionen einzureihender Funktionen außerhalb einer Klasse *
Einzureihende Funktionen (inline -Funktionen) muß der Übersetzer samt ihrer Definition kennen, damit er sie richtig verwenden kann. Deswegen müssen sie in der Kopfdatei zu einer Übersetzungseinheit nicht nur deklariert, sondern sogar vollständig definiert werden. Das weicht leider von der schönen Grundregel ab, die empfiehlt, daß die Kopfdatei nur die Schnittstelle eines Moduls enthält und die Implementationsdatei die Definitionen und ist daher auch ein Nachteil einzureihender Funktionen. Dadurch, daß der Klient aus der Kopfdatei Interna der Definition erfahren könnte, kann er sich an diese Binden und es dadurch erschweren, diese bei Bedarf zu ändern.
Das folgende Beispiel zeigt, wie ein Modul eine einzureihende Funktion exportiert.
hallo5.cpp
// keine Implementationsdatei noetig
hallo5.h
#ifndef hallo5_h_INCLUDED_20030115
#define hallo5_h_INCLUDED_20030115
/** @file hallo5.h
@brief Unterstuetzung bei der Ausgabe von "Hallo, ". */
/// Ausgabe des Textes "Hallo, " (einzureihende Funktion)
inline void hallo5(){ ::std::cout << "Hallo, "; }
#endifhallo5.txt [Dokumentation]
hallo5.h Dateireferenz
Funktionen
void hallo5() [inline]
Ausgabe des Textes "Hallo, " (einzureihende Funktion).main5.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
#include "hallo5.h"
int main(){ ::hallo5(); ::std::cout << "Susanne!\n"; }Konsole
Compile succeeded.
::std::cout
Hallo, Susanne!
Definitionen einzureihender Funktionen innerhalb einer Klasse *
Einzureihende Funktionen (inline -Funktionen) muß der Übersetzer samt ihrer Definition kennen, damit er sie richtig verwenden kann. Deswegen müssen sie in der Kopfdatei zu einer Übersetzungseinheit nicht nur deklariert, sondern sogar vollständig definiert werden. Das weicht leider von der schönen Grundregel ab, die empfiehlt, daß die Kopfdatei nur die Schnittstelle eines Moduls enthält und die Implementationsdatei die Definitionen und ist daher auch ein Nachteil einzureihender Funktionen. Das folgende Beispiel zeigt, wie ein Modul eine einzureihende Funktion exportiert.
Um eine einzureihende Funktion als Mitglied einer Klasse zu definieren, gibt es zwei Möglichkeiten.
- Die einzureihende Funktion wird innerhalb des Klassenspezifizierers definiert. In diesem Fall ist keine Verwendung des Schlüsselworts "inline" nötig: Die Funktion gilt bereits aufgrund des Ortes ihrer Definition als einzureihend.
- Die einzureihende Funktion wird innerhalb des Klassenspezifizierers nur deklariert und außerhalb des Klassenspezifizierers definiert. Dann muß das Schlüsselwort "inline" der Definition als Deklarationsspezifizierer vorangestellt werden. Die Definition muß aber in der Kopfdatei enthalten sein.
Die letztgenannte Schreibweise ist vorzuziehen, da der Klassenspezifizierer übersichtlicher ist, wenn er nur reine Deklarationen enthält.
Einzureihende Funktionen einer Klasse vergrößern die Kopplung zwischen der Klasse und ihren Klienten und mindern die Kapselung. Deswegen sollten sie nur verwendet werden, wenn diesem Nachteil ein entsprechender Vorteil durch ihre Verwendung entgegensteht.
Im Normalfall sollten Definitionen in der Implementationsdatei stehen—der Klassenspezifizierer sollte nur reine Deklarationen enthalten. Einzureihende Funktionen, also auch innerhalb des Klassenspezifizierers definierte Funktionen, sollten die Ausnahme sein.
hallo6.cpp
// keine Implementationsdatei noetig
hallo6.h
#ifndef hallo6_h_INCLUDED_20030115
#define hallo6_h_INCLUDED_20030115
/** @file hallo6.h
@brief Zwei Funktionen zur Ausgabe von "Hallo, " */
/// Funktionen zur Ausgabe von "Hallo, "
struct hallo6
{ /// Ausgabe des Textes "Hallo, " (einzureihende Funktion)
static void hallo6a(){ ::std::cout << "Hallo, "; }
/// Ausgabe des Textes "Hallo, " (einzureihende Funktion)
static void hallo6b(); };
inline void hallo6::hallo6b(){ ::std::cout << "Hallo, "; }
#endifhallo6.txt [Dokumentation]
hallo6.h Dateireferenz
Funktionen
void hallo6a() [inline]
Ausgabe des Textes "Hallo, " (einzureihende Funktion).
void hallo6b() [inline]
Ausgabe des Textes "Hallo, " (einzureihende Funktion).main6.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
#include "hallo6.h"
int main()
{ ::hallo6::hallo6a(); ::std::cout << "Michael!\n";
::hallo6::hallo6b(); ::std::cout << "Katharina!\n"; }Konsole
Compile succeeded.
::std::cout
Hallo, Michael!
Hallo, Katharina!
Definitionen mit interner Bindung *
Eine Funktionsdefinition kann mit dem Deklarationsspezifizierer "static" gekennzeichnet werden. Der definierte Bezeichner hat dann interne Bindung. Das bedeutet, daß er nur innerhalb der die Definition enthaltenden Übersetzungseinheit sichtbar ist. Aus einer anderen Übersetzungseinheit ist dieser Bezeichner also nicht mit der definierten Bedeutung sichtbar. (Er könnte in anderen Übersetzungseinheiten allerdings ebenfalls definiert werden.)
In C++ wird für die Definition von Bezeichnern mit Dateigültigkeitsbereich, die nur innerhalb einer Übersetzungseinheit verwendet werden sollen, allerdings ein anderes Vorgehen unter Verwendung eines anonymen Namensraums empfohlen, das aber nicht in dieser Lektion behandelt wird. Deswegen soll diese Verwendung des Schlüsselworts "static" vermieden werden. Sie wird hier nur der Vollständigkeit halber vorgestellt, aber nicht empfohlen.
hallo7.h
#ifndef hallo7_h_INCLUDED_20030115
#define hallo7_h_INCLUDED_20030115
/** @file hallo7.h
@brief Eine Funktion zur Ausgabe von "Hallo, " */
/// Ausgabe des Textes "Hallo, ".
void hallo4(); // Deklaration einer Funktion mit externe Verbindung
#endifhallo7.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
#include "hallo7.h"
static void hallo4_impl() // interne Verbindung
{ ::std::cout << "Hallo, "; }
void hallo4() // externe Verbindung
{ ::hallo4_impl(); }hallo7.txt [Dokumentation]
hallo7.h Dateireferenz
Funktionen
void hallo7()
Ausgabe des Textes "Hallo, ".main7.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
#include "hallo7.h"
int main()
{ // hallo4_impl(); // nicht moeglich
::hallo4(); ::std::cout << "Peter!\n"; }::std::cout
Hallo, Peter!
Funktionsschablonen
Wenn eine Schablonenfunktion von anderen Übersetzungseinheiten aus verwendet werden soll, dann darf sie nicht in einem namenlosen Namensraum definiert werden und muß mit dem Schlüsselwort "export" gekennzeichnet werden (nicht zu verwechseln mit dem ähnlich klingenden Schlüsselwort "extern").
maximum1.h
template<typename T>
extern T maximum( T const a, T const b );maximum1.cpp
export template<typename T>
T ::maximum( T const a, T const b ){ return a > b ? a : b; };maximum1_ec.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
#include "maximum1.h" // ::maximum
int main()
{ ::std::cout << ::maximum<int>( 10, 10 )<< '\n';
::std::cout << ::maximum( 10, 10 )<< '\n'; }
Es gibt allerdings nur wenige C++ -Implementation. Viele sogenannte „C++ -Compiler“ unterstützen in Wirklichkeit nur einen Teil der Sprache C++, zu dem das Schlüsselwort "export" nicht gehört. Bei solchen Produkten muß die Schablone dann ersatzweise in der Kopfdatei des sie exportierenden Moduls definiert werden.
maximum2.h
template<typename T>
T maximum( T const a, T const b ){ return a > b ? a : b; };maximum2_ec.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
#include "maximum2.h" // ::maximum
int main()
{ ::std::cout << ::maximum<int>( 10, 10 )<< '\n';
::std::cout << ::maximum( 10, 10 )<< '\n'; }