Die Routinen und die Variable heißen Member des Namespaces.
Sie sind nun nicht mehr einfach über ihren Namen anzusprechen,
sondern müssen mit dem Namen des Namensraums qualifiziert werden
(mit dem Scope-Operator ::).
- library1::initialize();
library2::initialize();
Es ist aber manchmal anstrengend (und macht das Programm eher
unübersichtlich), wenn zu viele Namen qualifiziert angegeben werden
müssen. Dafür gibt es die using-Deklarationen und
-Direktiven:
- using library1::id_string; // using-Deklaration
using namespace library1; // using-Direktive
Nach der Deklaration kann man das intitialize aus library1
im aktuellen Gültigkeitsbereich ohne Scope verwenden:
- class myclass
{
using library1::initialize();
// ...
};
Nur im Code der Klasse myclass kann so ohne Scope die erste Routine
benutzt werden.
Nach der using-Direktive von oben sind alle Bezeichner aus dem
Namespace library1 im aktuellen Gültigkeitsbereich direkt
verwendbar.
Man sollte es vermeiden, using-Direktiven global einzusetzen, da der
entsprechende Namespace sonst völlig im globalen Namensraum untergeht und
es doch wieder leicht zu Namenskonflikten kommen könnte. Innerhalb einer
Funktion oder Klasse sind Direktiven dagegen manchmal sinnvoller als
eine lange Liste von Deklarationen.
Ein Namespace kann Namen aus einem anderen einbinden. Das sollte man
natürlich nur bei eng zusammengehörigen Einheiten tun:
- namespace ns1
{
int func1();
int func2();
}
namespace ns2
{
int func3();
using ns1::func1();
}
Danach ist ns1::func1 auch als ns2::func1
ansprechbar.
Durch einen neuen Namespace, der mehrere andere -- vollständig oder
teilweise -- einbindet, kann man diese
also zu einem einzigen verschmelzen -- vollständig oder nur so weit,
wie es für eine neue Einheit Sinn macht.
Man kann auch zusätzliche Namen für einen bereits bestehenden
Namespace vergeben, beispielsweise zur Abkürzung:
- namespace my_favourite_library_version_1_1_2_May_1997
{
// ...
}
namespace mylib=my_favourite_library_version_1_1_2_May_1997;
Der kurze Name mylib sollte natürlich nur relativ lokal sein.
So kann man auch leicht auf neuere Versionen einer Bibliothek übergehen,
ohne an allen Stellen mit entsprechender Qualifizierung den neuen
Namen angeben zu müssen.
Funktionen (nicht Variablen) müssen nicht in der Namespace-Definition
selbst definiert, sondern nur deklariert werden (d.h. ihre
Signatur muß angegeben werden). Bei der Definition an einer späteren
Stelle müssen die Bezeichner dann qualifiziert werden:
- namespace myspace
{
double square(double);
}
double myspace::square(double d) { return d*d; }
Man kann so allerdings einem bereits definierten Namespace keine neuen
Member hinzufügen -- das funktioniert aber mit einer weiteren
Namespace-Definition:
- namespace myspace
{
int square(int i) { return i*i; }
double average(double *,int);
}
Danach hat der Namespace alle drei Funktionen als Member.
Mehrere einzelne Namespace-Definitionen (jeweils bevor der entsprechende
Code folgt) können manchmal das Programm übersichtlicher machen. Wenn
der Namespace aber dazu dient, eine vollständige Schnittstelle zu
einer Bibliothek zur Verfügung zu stellen, sollte man lieber alle
Definitionen sammeln.
Klassen und Blöcke ({}) bilden eigene Namespaces. Entsprechend
gilt der Zugriff auf Klassen-Member mit der Scope-Syntax genauso auch für
Klassen (das werden wir natürlich später ausführlich
kennenlernen). Blöcke sind unbenannte Namespaces -- auf ihre Member kann
nie von außen zugegriffen werden.
Es ist denkbar, verschiedene Namespace-Definitionen für die
gleiche Routinen-Sammlung anzugeben -- beispielsweise eine vereinfachte Version
für die Benutzer der Bibliothek und eine vollständige für die
Implementation:
- namespace playit // Benutzer-Version, z.B. in playit.h
{
void start();
void stop();
}
namespace playit // Implementations-Version, z.B. in playit_i.h
{
void start();
void stop();
void help_func1();
void help_func2();
// ... (much much more)
}
Hier muß man natürlich aufpassen, daß die Signaturen der beiden
verschiedenen Definitionen konsistent sind. Die zweite Header-Datei sollte
also die erste einbinden. Der Compiler überprüft die Konsistenz, so
weit er kann (unpassende Rückgabe-Typen). Irrt man sich aber versehentlich
bei einem Argument-Typ (void start(int)), so wird die entsprechende
Funktion als überladen angesehen (siehe späteres Kapitel)!
Manchmal möchte man mit Namespaces nicht dem Benutzer eine bestimmte
Schnittstelle zur Verfügung stellen, sondern einfach einige Namen vor
der allgemeinen Benutzbarkeit schützen. Dazu brauchen
Namespaces nicht benannt zu werden:
- namespace
{
int func1();
double func2(double);
}
Die Member des Namespaces sind damit automatisch bekannt im
Gültigkeitsbereich der Namespace-Definition, insbesondere also nicht
außerhalb der aktuellen Übersetzungseinheit. Der Vorteil der
Namenlosigkeit
liegt darin, daß kein Name für den Namespace exportiert wird, der
möglicherweise mit dem Namen eines anderen Namespace (oder einer Klasse)
aus einer anderen Übersetzungseinheit kollidiert.
Die Funktionen und Klassen aus den Standard-C- und
C++-Bibliotheken
liegen üblicherweise in einem eigenen Namensraum std. Die
folgende Präprozessor-Direktive bindet die
C-Standard-IO-Funktionen in den globalen Namensraum ein:
-
#include <stdio.h>
Verwendet man dagegen die folgende Zeile (dem Namen wird ein c
vorangestellt, die Endung .h entfällt), wird nicht der globale
Namensraum, sondern der Namespace std verwendet:
-
#include <cstdio>
Das gilt für alle Standard-Header. Im letzten Fall muß man
beispielsweise bei der Ausgabe std::printf schreiben oder mit
using namespace std; den ganzen Namensraum direkt zugänglich
machen. Bei den Namen aus std ist das meist unproblematisch und
führt selten zu Konflikten.