Namensbereiche

Einführung

In der vergangenen Lektion habe ich schon einmal von dem "globalen Namensbereich" (oder "globalem Namensraum") gesprochen. Alle Variablen, Klassen, Strukturen, Aufzählungen, "Unions" und auch Funktionen, die nicht innerhalb eines eingeschränkten Gültigkeitsbereiches (also innerhalb einer Klasse, einer Funktion etc.) deklariert werden, gehören zu dem "globalen  Namensbereich". Dieser Namensbereich ist der einzig vorgegebene. Tatsächlich lassen sich auch beliebig viele eigene erstellen. 

In Namensbereichen kann man beliebig viele Deklarationen aufnehmen und somit zusammenfassen. Will man anschließend auf diese Deklarationen zugreifen, dann muss man den Namensbereich in Verbindung mit dem Bereichsauflösungsoperator '::' mit angeben. Bei dem "globalen Namensbereich" ist dies in der Regel nicht notwendig aber keineswegs verboten. 

Deklaration

Die allgemeine Deklaration von Namensbereichen sieht in der Regel folgendermaßen aus:

namespace <name> 
{ 
	/* --- Deklarationen ---
	...
	*/ 
}

Alle bisher bekannten Deklarationen lassen sich in einem Namensbereich aufnehmen. Dazu folgendes Beispiel eines Namensbereiches:

namespace ANamespace
{
	//Aufzählung
	enum Days
	{
		Monday, 
		Tuesday,
		Wednesday,
		Thursday,
		Friday
	};

	//"Union"
	union UInt32
	{
		unsigned long DWord;
		unsigned short Words[2];
	};

	//Klasse
	class CConstDemo
	{
	private:
		int m_var1;
		double m_var2;
	public:
		mutable int m_var3;
		CConstDemo(int var1, double var2) : m_var1(var1), m_var2(var2)
		{
		}
	
		int GetVar1(void) const;
		double GetVar2(void) const;
	};

	//Implementierung von 'CCOnstDemo::GetVar1'
	int CConstDemo::GetVar1(void) const {return m_var1;}

	//Struktur
	struct Point;

	//Variable
	int Integer;

	//Funktion
	const char* id(void);
	void PrintID(void) {cout << id() << endl;}
}

//Implementierung von 'ANamespace::CCOnstDemo::GetVar2'
double ANamespace::CConstDemo::GetVar2(void) const {return m_var2;}

//Implementierung von 'ANamespace::Point'
struct ANamespace::Point
{
	int X, Y;
};

//Implementierung von 'ANamespace::id'
const char* ANamespace::id(void) {return "ANamespace";}

Zu dem Namensbereich 'ANamespace' gehören nun eine Aufzählung 'Days', eine "Union" 'UInt32', eine Klasse 'CConstDemo', eine Struktur 'Point', eine Variable 'Integer' und zwei Methoden 'id' und 'PrintID'. Wie an der Klasse 'CConstDemo' gut zu erkennen ist, ist es egal wo die Memberfunktionen 'GetVar1', 'GetVar2' sowie der Konstruktor deklariert werden, nachdem die Klasse erst einmal innerhalb des Namensbereiches deklariert wurde. So wird der Konstruktor gleich innerhalb, die Funktion 'GetVar1' außerhalb der Klasse aber noch innerhalb des Namensbereiches implementiert, wohingegen die Implementierung von 'GetVar2' sogar außerhalb des Namensbereiches stattfindet. Wie zu erkennen, muss dann aber im Falle von 'GetVar2' neben der Klasse auch der Namensbereich bei der Implementierung angegeben werden. Dazu wird der Bereichsauflösungsoperator '::' zweimal verwendet. Ähnliches gilt für die "globale" Methode 'id'. Ihre Implementierung findet (im Gegensatz zu der Methode 'PrintID') außerhalb des Namensbereiches statt. Aufgrund dessen muss auch hier bei der Implementierung der Namensbereich mit angegeben werden, wie es bereits bei Klassen üblich war. Als interessant erweist sich auch noch die Struktur 'Point'. Diese wurde nämlich nur namentlich innerhalb des Namensbereiches erwähnt. Die gesamte Implementierung findet hingegen außerhalb des Namensbereiches statt. Darum muss auch hier an entsprechender Stelle der Namensbereich mit angegeben werden.

Verwenden eines Namensbereiches

Soll nun eine der Deklarationen außerhalb des Namensbereiches verwendet werden, dann muss genau wie bei den Implementierungen jeweils der Namensbereich mit angegeben werden. Dazu folgendes Quelltextbeispiel:

//Verwenden der Aufzählung 'ANamespace::Days'
ANamespace::Days today = ANamespace::Tuesday;

//Verwenden der "Union" 'ANamespace::UInt32'
ANamespace::UInt32 ui;
ui.Words[0] = 65;
ui.Words[1] = 0;
cout << "ui: " << ui.DWord << endl;

//Instanz der Klasse 'ANamespace::CConstDemo'
ANamespace::CConstDemo cd(3, 3.14);
cd.m_var3 = 65;

//Instanz der Struktur 'ANamespace::Point'
ANamespace::Point pt = {0, 0};

//Verwenden der Variablen 'ANamespace::Integer'
ANamespace::Integer = 0; 

//Aufrufen der Methode 'ANamespace::id'
cout << "ID: " << ANamespace::id() << endl;

Erweitern von Namensbereichen

Ein bestehender Namensbereich kann jederzeit erweitert werden. So lässt sich ein Namensbereich auch auf mehrere Dateien aufteilen.

Verschachtelte Namensbereiche

Es ist durchaus möglich Namensbereiche ineinander zu verschachteln

Die 'using'-Direktive

Namensbereiche werden oft verwendet um zu verhindern, dass Deklarationen mehrere Male verwendet werden. So könnte es durchaus vorkommen, dass man beispielsweise eine Headerdatei (die man vielleicht nicht selbst geschrieben hat) einbindet und diese beispielsweise eine Klasse enthält, deren Namen man selbst für eine eigne Klasse verwendet. Verwendet man also beispielsweise die aus den vorherigen Lektionen hervorgegangenen Klassen 'CString' und 'CArray', dann kann es leicht passieren, dass diese Namen in einer anderen Headerdatei ebenso enthalten sind. So sind Klassen mit genau diesen Namen beispielsweise Bestandteil der MFC. Nun ist bekannt, dass nicht zwei Klassen mit den selben Namen existieren können. Eine mögliche Lösung wäre die Verwendung eines Namensbereiches. So können beide Klassen nebeneinander existieren und auch beide verwendet werden. Gerade in der STL werden auch des öfteren Namensbereiche für die Klassen verwendet. Allerdings ist es u.U. all die Klassen aus der STL mitsamt den Namen der Namensbereiche zu verwenden. Es besteht die Möglichkeit Namensbereiche im Prinzip aufzulösen. Dazu dient die 'using'-Direktive. Man verwendet sie wie folgt:

using <name_of_namespace>;

Anschließend sind alle Deklarationen innerhalb des Namensbereiches auch ohne Angabe des Namen und des Operators  '::' voll nutzbar. Obiges Beispiel ließe sich also wie folgt abändern:

//Namespace 'ANamespace' verwenden
using namespace ANamespace;

//Verwenden der Aufzählung 'ANamespace::Days'
Days today = ANamespace::Tuesday;

//Verwenden der "Union" 'ANamespace::UInt32'
UInt32 ui;
ui.Words[0] = 65;
ui.Words[1] = 0;
cout << "ui: " << ui.DWord << endl;

//Instanz der Klasse 'ANamespace::CConstDemo'
CConstDemo cd(3, 3.14);
cd.m_var3 = 65;

//Instanz der Struktur 'ANamespace::Point'
Point pt = {0, 0};

//Verwenden der Variablen 'ANamespace::Integer'
Integer = 0; 

//Aufrufen der Methode 'ANamespace::id'
cout << "ID: " << id() << endl;

Tatsächlich wurde eher unbewusst ein Namensbereich verwendet. Die Objekte 'cin' für Eingaben und 'cout' für Ausgaben gehören dem Namensbereich 'std' an. Dieser wird allerdings in der Datei 'iostream.h' bereits aufgelöst.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

struct RECTANGLE 
{ 
	unsigned long ulWidth; 
	unsigned long ulHeight; 	
};

Ein eigener Datentyp 

Im folgenden kann 'RECTANGLE' praktisch als eigener Datentyp aufgefasst werden. Genauso wie mit allen herkömmlichen Datentypen lassen sich auch hier Variablen (bzw. Instanzen oder Objekte) deklarieren. Im Programm könnte das etwa so aussehen:

RECTANGLE rect;

Im Prinzip sind 'ulWidth' und 'ulHeight' ebenfalls Variablen. Allerdings sind sie in einem Block bzw. in einer Struktur zusammengefasst. Man nennt sie auch "Membervariablen" der Struktur 'RECTANGLE'

Soll das Rechteck gleich mit Daten initialisiert werden, so z.B. mit der Breite 600 und der Höhe 480, dann lässt sich das wie folgt realisieren:

RECTANGLE rect = {600, 480};

Dann muss allerdings die Reihenfolge bekannt sein, in der Breite und Höhe gespeichert sind. Nach der Deklaration einer Variablen (bzw. Instanz), lassen sich die Daten auch einfacher schreiben und lesen. 

Zugriff auf die Membervariablen

Soll beispielweise die Breite eines Rechtecks nachträglich geändert werden, dann erreicht man dies über die folgende Notation:

//Deklaration einer RECTANGLE-Instanz
RECTANGLE rect = {600, 480};

//Ändern der Breite
rect.ulWidth = 500;

Indem man den Namen einer Instanz und den Namen irgendeiner ihrer Membervariablen durch einen Punkt trennt, ermöglicht man einen einfachen Zugriff auf diese Membervariable.

Übrigens ist es durchaus möglich, dass eine Membervariable einer Instanz ebenfalls wiederum eine Instanz mit eigenen Membervariablen ist. So ist folgende Deklaration durchaus legitim:

struct FIGURE
{
	RECTANGLE 	rect;
	unsigned long	color;
};

Wird jetzt eine "Figur" deklariert, dann könnte das in etwa so aussehen:

//Deklaration
FIGURE figure;

//Setzen der Membervariablen
figure.rect.ulWidth	=	600;
figure.rect.ulHeight	=	480;
figure.color		=	0;

Dies bedarf wohl keiner weiteren Erklärung.

Zeiger auf eine Instanz

Natürlich lassen sich auch Zeiger (und auch Referenzen) von Strukturen bilden:

//Deklaration einer RECTANGLE-Instanz
RECTANGLE rect = {600, 480};

//Zeiger auf die Instanz
RECTANGLE *prect = &rect;

Für den Zugriff auf die einzelnen Membervariablen gibt es zwei Möglichkeiten. Die naheliegende Methode ist, dass man zunächst den Zeiger dereferenziert und dann wie gewohnt auf die einzelnen Membervariablen zugreift, also:

(*prect).ulWidth	=	800;
(*prect).ulHeight	=	600; 

Speziell für Zeiger gibt es aber noch eine andere Möglichkeit. Für Zeiger, welche auf Objekte von Strukturen (oder Klassen) zeigen, existiert der Operator '->' um auf die einzelnen Membervariablen zugreifen zu können. Das letzte Beispiel lässt sich somit auch wie folgt notieren:

prect->ulWidth		=	800;
prect->ulHeight		=	600;

Für die nächsten Lektionen

Neben der Deklaration und Aufbau einer Struktur wurde hier auch der Begriff der Membervariablen geklärt.  Außerdem wurden zwei neue Operatoren für den Zugriff auf Membervariablen definiert. Die gesamte Funktionalität einer Struktur, die hier aufgezeigt wurde, soll nur eine Einführung sein. In den folgenden Lektionen werden Klassen eingeführt, die die gesamte Funktionalität einer Struktur besitzen aber darüber hinaus noch ein ganzes Stück eigene für Klassen spezifische Funktionalität mitbringen.

Zurück Nach oben