Operatoren

Einführung

Es existieren in C++ eine Vielzahl von Operatoren, die größtenteils bereits in vergangenen Kapiteln vorgestellt wurden. Dazu zählen u.a. die Operatoren für die vier Grundrechenarten ('+', '-', '*' und '/'). All diese Operatoren lassen sich für eigene Datentypen überladen. Dabei sollte allerdings darauf geachtet werden, dass die Operatoren sinnvoll gewählt werden, d.h. dass man bereits am Operator erkennen kann, welche Operation dieser bewegt. So würde es keinen Sinn ergeben einem Operator eine völlig neue Bedeutung zukommen zu lassen. So sollte beispielsweise der Operator '==' immer nur einen Wert vom Datentyp 'bool' zurückgeben. Außerdem sollte dann der Wert immer dann 'true' sein, wenn zwei Instanzen übereinstimmen. Im anderen Fall sollte der Operator den Wert 'false' zurückliefern. Im Prinzip lassen sich Operatoren als Funktionen verstehen, deren Funktionsname allerdings spezifisch für den entsprechenden Operator ist. Auch wenn sich Operatoren global definieren lassen, ist es meist sinnvoller sie als Memberfunktionen der entsprechenden Klasse zu definieren. 

Deklaration

Wie bereits erwähnt lässt sich ein Operator wie eine Funktion deklarieren. Abhängig vom jeweiligen Operator besitzt dieser eine Argumentliste von ein bis zwei Argumenten. Die allgemeine Deklaration sieht in etwa wie folgt aus:

<Rückgabetyp> operator <Operator> (<Argumentliste>);

Genaueres ist den folgenden Beispielen zu entnehmen.

Der Zuweisungsoperator '='

Der wohl interessanteste Operator ist wohl der Zuweisungsoperator '='. Genau wie der Kopierkonstruktor wird er bereits standardmäßig implementiert. Aber genau wie dieser sollte er in jedem Fall dann selbst implementiert werden, wenn die entsprechende Klasse einen Zeiger enthält, der auf Daten verweist, die spezifisch für eine jeweilige Instanz sind. Als ein Beispiel soll dafür soll die Klasse 'CSimpleArray' dienen, die bereits einen Kopierkonstruktor besitzt, der bereits die notwendige Implementierung besitzt. Diese Implementierung übertragen wir nun aber auf eine geschützte Funktion '_Copy' und rufen diese  vom Kopierkonstruktor und vom Operator auf, damit die Implementierung, nicht mehrfach durchgeführt werden muss. Die Klassendeklaration sieht dann wie folgt aus:

#include <memory.h>
class CSimpleArray
{
private:
	int *m_pData, m_Length;
	
	//Kopiermethode
	void _Copy(const CSimpleArray& arr)
	{
		//Zunächst prüfen ob die Instanz sich selbst kopieren soll
		if (&arr == this) return;

		//erst evtl. bestehende Daten löschen
		if (m_pData) delete[] m_pData;

		//Arraylänge übernehmen
		m_Length = arr.m_Length;
	
		//neuen Speicher reservieren
		m_pData = new int[m_Length];
	
		//Speicher kopieren 
		memcpy(m_pData, arr.m_pData, m_Length * sizeof(int));
	}
public:
	//Konstruktor
	CSimpleArray(int Length = 0)
	{
		if (Length <= 0) m_Length = 1;
		else m_Length = Length;
		
		//Speicher reservieren
		m_pData = new int[m_Length];
	}
	
	///Kopierkonstruktor
	CSimpleArray(const CSimpleArray& arr)
	{
		//Daten initialisieren
		m_pData = 0;
		m_Length = 0;

		_Copy(arr);
	}
	
	//Destruktor
	~CSimpleArray(void)
	{
		if (m_pData) 
		{
			delete[] m_pData;
			m_pData = 0;
		}
	}
	
	//Wert setzen
	int& DataAt(int Index)
	{
		if (Index < 0 || Index >= m_Length) 
		{Index = 0;}
		
		return *(m_pData + Index); 
	}

	//Operator '=' (noch zu überarbeiten)
	void operator=(const CSimpleArray& arr)
	{
		_Copy(arr);
	}
};

Nun lassen sich folgende Aufrufe durchführen:

CSimpleArray arr1(5), arr2;
arr1.DataAt(0) = 3;
arr2 = arr1;
cout << "arr2[0]: " << arr2.DataAt(0) << endl;	//Ausgabe: "arr2[0]: 3"

In C++ ist es möglich auch mehrere Zuweisungen hintereinander durchzuführen, also beispielsweise:

int i, j, k;
i = j = k = 3; 

Dabei wird der Wert von rechts nach links übergeben. Dies Operation ist mit der Klasse 'CSimpleArray' derzeit noch nicht möglich (deswegen auch der Kommentar "noch zu überarbeiten"). Damit dies möglich wird, müsste die Klasse irgendwie sich selbst zurückgeben, damit eine weitere Zuweisung mit Hilfe eben dieser Instanz erfolgen kann. Zu diesem Zweck dient (wie im vorigen Kapitel bereits angekündigt) der 'this'-Zeiger. Die Implementierung des Operators ist also wie folgt zu erweitern: 

class CSimpleArray
{
private:
	/* --- Deklaration der Membervariablen ---
	...
	*/
public:
	/* --- Implementierung von Funktionen ---
	...
	*/

	//Operator '='
	CSimpleArray& operator=(const CSimpleArray& arr)
	{
		_Copy(arr);
		
		//Instanz zurückgeben
		return *this;
	}
};

Nun lässt sich auch der folgende Aufruf realisieren:

CSimpleArray arr1(5), arr2, arr3;
arr1.DataAt(0) = 3;
arr3 = arr2 = arr1;
cout << "arr3[0]: " << arr3.DataAt(0) << endl;	//Ausgabe: "arr3[0]: 3"

Der Zuweisungsoperator sollte für alle Klassen so deklariert werden: Als Parameter sollte eine Referenz der Klasse übergeben werden (am besten als 'const', damit der Operator auch für konstante Objekte verwendet werden kann). Der Rückgabetyp sollte ebenfalls eine Instanz der Klasse in Form einer Referenz sein. Innerhalb der Implementierung sollten dann die Daten von der übergebenen Instanz kopiert und die eigene Instanz wieder zurückgegeben werden. 

Der Zuweisungsoperator ermöglicht es, ein Objekt zu kopieren. Er sollte immer dann überschrieben werden, wenn ein einfaches (bitweises) Kopieren der Member aus bereits zuvor genannten Gründen nicht ausreicht. Der Zuweisungsoperator wird nur verwendet, wenn bereits beide Objekte bestehen, die für den Kopiervorgang vorgesehen sind. Soll ein Objekt aber gleich als Kopie eines bereits existierenden Objekts erstellt werden, dann wird nicht der Zuweisungsoperator sondern der Kopierkonstruktor verwendet. Aus diesem Grunde sollte immer dann, wenn der Zuweisungsoperator überschrieben wird auch der Kopierkonstruktor überschrieben werden und umgekehrt.

In der Funktion '_Copy' wurde zunächst überprüft, ob nicht evtl. eine Instanz sich selbst kopiert. So ist in C++ folgender Aufruf durchaus möglich:

CSimpleArray c1;
c1 = c1;

In einem solchen Fall brauchen zum einen keine Daten kopiert werden und zum anderen könnte dies ein ungewünschtes Ergebnis nach sich ziehen. So gibt die Funktion '_Copy' die eigenen Daten zunächst frei. Da die Instanz, die die Methode '_Copy' aufruft und die die übergeben wird, identisch sind ist auch der Speicher der selbe. Nun wird innerhalb der Funktion '_Copy' zunächst der Speicher der aufrufenden Instanz freigegeben. Gleichzeitig würde dies aber auch bedeuten, dass am Ende auch der Speicher der übergebenen Instanz gelöscht wurde und somit keine Daten mehr kopiert werden können. Aus diesem Grunde sollte man bei Zuweisungen zunächst prüfen, ob eine Instanz nicht auf sich selbst zuweist. Dies geschieht mit dem ersten Aufruf innerhalb der Funktion. Dort wird einfach überprüft, ob die Adresse der übergebenen Instanz mit der der aufrufenden Instanz übereinstimmt.

Der Operator '[ ]'

Dieser Operator bietet sich vor allem dann an, wenn man eine Klasse schreibt, die irgendwie mit einem Array verwandt ist. Denn so lässt sich auf bequeme Art und Weise so auf die einzelnen Elemente zugreifen als handele es sich tatsächlich um einen gewöhnliche C++-Array. Es lässt sich nun wieder das Beispiel der Klasse 'CSimpleArray' verwenden:

class CSimpleArray
{
private:
	/* --- Deklaration der Membervariablen ---
	...
	*/
public:
	/* --- Implementierung von Funktionen ---
	...
	*/

	//Operator '[]'
	int& operator[](int Index)
	{
		return DataAt(Index);
	}
};

Nun sind also auch folgende Aufrufe möglich:

CSimpleArray arr(5);
arr[0] = 3;
cout << "arr[0]: " << arr[0] << endl;	//Ausgabe: "arr[0]: 3"

Der Operator '+' und '+='

Man stelle sich eine Klasse 'CComplex' vor, welche eine komplexe Zahl (bestehend aus einem Real- und einem Imaginärteil) verkörpert. Dort macht eine Addition durchaus Sinn. Die vollständige Implementierung sähe dann in etwa wie folgt aus:

class CComplex
{
public:
	//Real- und Imaginärteil
	double re, im;
	
	//Konstruktor
	CComplex(double r = 0, double i = 0)
	{
		re = r;
		im = i;
	}

	//Addition
	CComplex operator+ (const CComplex& c)
	{
		CComplex crtn(re + c.re, im + c.im);
		return crtn;
	}

	//Addition und anschließende Zuweisung
	CComplex& operator+= (const CComplex& c)
	{
		re += c.re;
		im += c.im;
		return *this;
	}
};

Diese beiden Operatoren stehen in ihren Deklarationen stellvertretend für alle anderen Operationen, bei der zwei Operanden durch einen Operator verknüpft werden (z.B. '-', '*', '/', '%', '<<', '>>', '|', '&', etc.). 

Der Operator '++'

Dieser Operator unterscheidet sich insofern von allen andern, als dass es zwei Varianten gibt. Zum einen existiert die Postfixvariante und zum anderen die Präfixvariante. Dazu folgendes Beispiel:

int i, j;
i = j = 0;
i++;	//Postfix;
++j;	//Präfix

Dafür existieren zwei verschiedene Deklarationen. Es ließe sich erneut die 'CComplex'-Klasse verwenden:

class CComplex
{
public:
	//Real- und Imaginärteil
	double re, im;
	
	//Konstruktor
	CComplex(double r = 0, double i = 0)
	{
		re = r;
		im = i;
	}

	//Addition
	CComplex operator+ (const CComplex& c)
	{
		CComplex crtn(re + c.re, im + c.im);
		return crtn;
	}

	//Addition und anschließende Zuweisung
	CComplex& operator+= (const CComplex& c)
	{
		re += c.re;
		im += c.im;
		return *this;
	}
	
	//Präfix Inkrementierung
	CComplex& operator++ (void)
	{
		re++;
		return *this;
	}
	
	//Postfix Inkrementierung
	CComplex operator++ (int)
	{
		CComplex crtn(re++, im);
		return crtn;
	}
};

Nun sind folgende Aufrufe möglich:

CComplex c1(1, 1), c2(2, 2), c3;
c3 = c1++ + ++c2;

Die zweite Zeile wäre dann gleichbedeutend mit:

++c2; c3 = c1 + c2; c1++;

Bei diesem Operator ist vor allem die Logik bedingt durch die zwei Varianten zu beachten. Bei der Postfixvariante muss im Prinzip erst die Variable zurückgegeben werden und dann die Inkrementierung durchgeführt werden. Da dies nicht funktioniert, muss erst eine Variable erzeugt, dann der interne Zähler inkrementiert und schließlich die Variable zurückgegeben werden. Die Präfixvariante erweist sich als einfacher. Dort braucht nur der Zähler inkrementiert und anschließend die eigene Instanz zurückgegeben werden. Aus diesem Grund kann auch nur bei der Präfixvariante eine Referenz zurückgegeben werden, die es ermöglicht den Wert der Variablen noch nachträglich zu ändern. Bei der Postfixvariante wird lediglich eine Kopie zurückgegeben. Dieser Unterschied wird v.a. an folgendem Beispiel deutlich:

CComplex c1, c2;
++c1 = c2;	//Funktioniert, da Präfixvariante eine Referenz auf c1 zurückgibt (c1: (0, 0))
c1++ = c2;	//Funktioniert nicht, da Postfixvariante nur eine Kopie zurückgibt (c1: (1, 0))

Die Operatoren 'new' und 'delete'

Auch die Operatoren, die dazu dienen ein Objekt dynamisch zu erstellen, bzw. es wieder freizugeben, lassen sich überladen. Dies macht unter umständen schon einmal dann sinhn, wenn genau bestimmt werden soll, wo Speicher reserviert werden soll. So existieren vor allem in der WIN32-API mehrere verschiedene Funktionspaare (z.B. 'GlobalAlloc' / 'GlobalFree' oder 'CoTaskMemAlloc' / 'CoTaskMemFree') um Speicher dynamisch zu erstellen und diesen wieder freizugeben. So könnte explizit festgelegt werden, welches Funktionspaar verwendet wird, denn so ist beispielsweise "global" reservierter Speicher (durch 'GlobalAlloc') mehreren Programmen zugänglich. Auch wäre es denkbar bereits vorher Speicher zur Verfügung zu stellen und diesen zunächst zu verwenden. Es sollte jedoch immer darauf geachtet werden, dass entweder beide Operatoren überladen werden oder gar keiner, denn nur so kann sichergestellt werden, dass Speicher, der von einer bestimmten Funktion reserviert wird auch wieder korrekt freigegeben wird. Angenommen eine Klasse soll das bereits bekannte Funktionspaar 'malloc' / 'free' für die dynamische Speicherverwaltung verwenden. Die Implementierung sollte in etwa wie folgt aussehen:

#include <malloc.h>
class CSpecialHeap
{
public:
	void* operator new (unsigned int cbAlloc)
	{
		return malloc(cbAlloc);
	}
	void operator delete (void* pv)
	{
		free(pv);
	}
};

Der Operator 'new' hat die Aufgabe soviel Speicher bereitzustellen, wie es ihm hier über den Parameter 'cbAlloc' mitgeteilt wird. Anschließend muss er dann ein Zeiger auf diesen Speicher zurückgeben. Der Operator 'delete' muss einfach den Speicher mit der entsprechenden Funktion wieder freigeben. Dazu wird ihm als Parameter ein Zeiger (hier 'pv') mitgegeben, der die Adresse des Speichers enthält, der zuvor mit 'new' reserviert wurde. Das Überladen von 'new' und 'delete' bietet auch die Möglichkeit mehr Speicher als notwendig zu reservieren und diesen dann nach belieben zu verwenden. Zum Beispiel könnte man zu Debug-Zwecken die Größe des Speichers mit angeben. Dazu folgendes Beispiel:

#include <malloc.h>
class CSpecialHeap
{
public:
	void* operator new (unsigned int cbAlloc)
	{
		unsigned int* pmem = (unsigned int*)malloc(cbAlloc + sizeof(cbAlloc));
		*pmem = cbAlloc;
		return (void*)(pmem + 1);
	}
	void operator delete (void* pv)
	{
		unsigned int* pmem = (unsigned int*)pv;
		pmem--;
		free(pmem);
	}
};

Hier werden nun beim Aufruf des Operators 'new' vier Bytes ('sizeof(unsigned int)') extra reserviert und in diesen vier Bytes wird die Speichergröße angegeben. Diese steht am Anfang des Speichers. Der Speicher, der dann für die Klasse verwendet werden kann, beginnt dann schließlich genau hinter der Größenangabe. Aus diesem Grunde wird nicht die Adresse zurückgegeben, welche die Funktion 'malloc' liefert, sondern diese Adresse addiert mit dem Wert vier. Beim Aufruf des Operators 'delete' wird zuerst wieder der wert vier von der Adresse abgezogen,da ja der gesamte Speicher freigegeben werden soll, der zuvor reserviert wurde. Mit Hilfe dieser Überladungen kann jederzeit, die Größe des Speichers bestimmt werden, den eine Instanz der Klasse 'CSpecialHeap' auf dem Heap einnimmt:

CSpecialHeap *pc = new CSpecialHeap;

unsigned int size = *((unsigned int*)pc - 1);	//size: 1

delete pc; pc = 0;

Angenommen innerhalb des Quellcodes immer wieder die Instanz einer Klasse gebildet und wieder freigegeben, um dann erneut Speicher zu reservieren. Dies ist sehr ungünstig, denn ein ständiges Reservieren und Freigeben von Speicher kostet Zeit, die u.U. wertvoller genutzt werden sollte. Insbesondere, wenn immer wieder gleich viel Speicher reserviert werden muss, wäre es günstiger den Speicher einmal zu reservieren und diesen immer wieder neu zu benutzen. Zu diesem Zweck existiert die Möglichkeit den Operator 'new' zu überladen. So könnte man beispielsweise einen Zeiger übergeben, welcher die Adresse von bereits reserviertem Speicher enthält, der dann für die Klasse verwendet werden kann. Der Operator 'delete' sollte weiterhin aufgerufen werden, damit es auch zu einem Detruktoraufruf kommt. Wenn beide Varianten von 'new' existieren (einmal mit Zeigerübergabe, einmal ohne), dann muss der Operator 'delete' irgendwie ermitteln, ob der Speicher freigegeben werden soll oder nicht. Zu diesem Zweck wird bei jeder Speicherzuweisung ein Byte extra reserviert, in dem dann gespeichert wird, ob der Speicher gelöscht werden soll oder nicht. Die Implementierung könnte so aussehen:

#include <malloc.h>
class CSpecialHeap
{
public:
	static unsigned int ExtraSize(void)
	{
		return sizeof (unsigned char);
	}
	void* operator new (unsigned int cbAlloc)
	{
		unsigned char* pmem = (unsigned char*)malloc(cbAlloc + ExtraSize());
		*pmem = 1;		//Speicher bei delete löschen
		return (pmem + sizeof (unsigned char));
	}
	void* operator new (unsigned int cbAlloc, void* pv)
	{
		if (!pv) return 0;
		unsigned char* pmem = (unsigned char*)pv;
		*pmem = 0;		//Speicher bei delete nicht löschen
		return (pmem + sizeof (unsigned char));

	}
	void operator delete (void* pv)
	{
		unsigned char* pmem = (unsigned char*)pv;
		pmem--;
		if (*pmem) free(pmem);
	}

};

Nun lässt sich auf zwei verschiedenen Wegen eine Instanz auf dem Heap erstellen. Wird der Speicher schon vorher reserviert, dann sollte der Speicher mindestens so groß sein, wie die Klasse wobei die Extradaten mit berücksichtigt werden müssen. Die Größe der Extradaten ist zu diesem Zweck durch die Funktion 'ExtraSize' ermittelbar. Dazu folgendes Beispiel:

//einmalig Speicher reservieren
void* pv = malloc(sizeof(CSpecialHeap) + CSpecialHeap::ExtraSize());

int i = 0;
while (i++ < 1000)
{
	//Instanz im gegebenen Speicher erstellen
	CSpecialHeap *pc = new (pv) CSpecialHeap;

	//Instanz zerstören (Destruktor wird aufgerufen, Speicher bleibt bestehen)
	delete pc; pc = 0;
}

//Speicher wieder freigeben
free(pv); pv = 0;

Nun existieren noch die Operatoren 'new[]' und 'delete[]', die dann aufgerufen werden, wenn mehrere Instanzen auf einmal auf dem Heap erstellt werden sollen. Sie lassen sich aber genauso überschreiben, wie die Operatoren 'new' und 'delete' und in diesem Falle unterscheidet sich die Implementierung auch nicht voneinander:

#include <malloc.h>
class CSpecialHeap
{
public:
	static unsigned int ExtraSize(void)
	{
		return sizeof (unsigned char);
	}
	void* operator new (unsigned int cbAlloc)
	{
		unsigned char* pmem = (unsigned char*)malloc(cbAlloc + ExtraSize());
		*pmem = 1;		//Speicher bei delete löschen
		return (pmem + sizeof (unsigned char));
	}
	void* operator new (unsigned int cbAlloc, void* pv)
	{
		if (!pv) return 0;
		unsigned char* pmem = (unsigned char*)pv;
		*pmem = 0;		//Speicher bei delete nicht löschen
		return (pmem + sizeof (unsigned char));

	}
	void operator delete (void* pv)
	{
		unsigned char* pmem = (unsigned char*)pv;
		pmem--;
		if (*pmem) free(pmem);
	}

	void* operator new[] (unsigned int cbAlloc)
	{
		unsigned char* pmem = (unsigned char*)malloc(cbAlloc + ExtraSize());
		*pmem = 1;		//Speicher bei delete löschen
		return (pmem + sizeof (unsigned char));
	}
	void* operator new[] (unsigned int cbAlloc, void* pv)
	{
		if (!pv) return 0;
		unsigned char* pmem = (unsigned char*)pv;
		*pmem = 0;		//Speicher bei delete nicht löschen
		return (pmem + sizeof (unsigned char));

	}
	void operator delete[] (void* pv)
	{
		unsigned char* pmem = (unsigned char*)pv;
		pmem--;
		if (*pmem) free(pmem);
	}

};

Nun lässt sich obiger Quelltext auch hier anwenden. Stattdessen werden hier aber jeweils zehn Instanzen erstellt:

//einmalig Speicher reservieren
void* pv = malloc(10 * sizeof(CSpecialHeap) + CSpecialHeap::ExtraSize());
	
int i = 0;
while (i++ < 1000)
{
	//Instanz im gegebenen Speicher erstellen
	CSpecialHeap *pc = new (pv) CSpecialHeap[10];

	//Instanz zerstören (Destruktor wird aufgerufen, Speicher bleibt bestehen)
	delete[] pc; pc = 0;
}
	
//Speicher wieder freigeben
free(pv); pv = 0;

Eine andere Variante den Operator 'new' zu überladen wäre beispielsweise bei dem Aufruf einen Wert zu übergeben, mit dem der gesamte Speicher dann initialisiert wird. 

Operatoren zum Casten

Stellenweise macht es durchaus Sinn, wenn man zwei zunächst scheinbar "nicht kompatible" Datentypen ineinander überführen kann. Es lassen sich natürlich Methoden definieren, die dann einen entsprechenden Wert zurückgeben. Manchmal bietet es sich jedoch an, ein einfaches Casting durchzuführen (u.U. sogar implizit).

Dazu deklariert man einen entsprechenden Operator mit dem ein solches Casting ermöglicht wird. Die allgemeine Deklaration sieht in etwa wie folgt aus:

operator <Datentyp> (void);

Da man bei diesem Operator den Datentyp angibt in den "gecastet" werden soll, ist keine weitere Angabe des Rückgabetyps notwendig, wie es bei sonstigen Memberfunktionen üblich ist.

So könnte in dem Beispiel der 'CComplex'-Klasse durchaus erwünscht sein, dass man die komplexe Zahl in eine reale Zahl umwandelt. Das Ergebnis einer solchen Umwandlung könnte etwa der Betrag der komplexen Zahl sein. Die Implementierung sähe dann wie folgt aus:

#include <math.h>	//für sqrt (Quadratwurzel)
class CComplex
{
public:
	/* --- Deklaration der Membervariablen ---
	...
	*/

	/* --- Implementierung der Operatoren ---
	...
	*/

	//Casting: Ergebnis ist der Betrag
	operator double (void)
	{
		return sqrt(re * re + im * im);
	}
};

Nun lässt sich eine 'CComplex'-Instanz ohne weiteres in ein 'double'-Wert unwandlen. Die Umwandlung liefert dann als Ergebnis den Betrag der in der 'CComplex'-Instanz enthaltenen komplexen Zahl:

CComplex c(3, 4);
double d = (double)c;	//d: 5.0

Allerdings wird die Umwandlung sogar implizit durchgeführt, d.h. auch folgender Aufruf wäre legitim:

CComplex c(3, 4);
double d = c;		//d: 5.0

Nun lässt sich die 'CComplex'-Instanz auch in eine Ganzzahl umwandeln. Dabei bedarf es allerdings einer expliziten Umwandlung (da beim Umwandeln von Kommazahlen in Ganzzahlen ein Datenverlust nicht ausgeschlossen werden kann):

CComplex c(3, 4);
int i = (int)c;		//i: 5 

Für die nächsten Lektionen

Operatoren sind nicht immer zwingend notwendig, aber in Einzelfällen sind sie einfach eleganter als ihre Alternativen. Der Zuweisungsoperator sollte aber zumindest bekannt sein, da dieser im Zweifelsfall (genau wie der Kopierkonstruktor) selbst implementiert werden muss, um evtl. Fehler von vorneherein bei der Anwendung einer Klasse auszuschließen. Alle anderen Operatoren lassen gewisse Klassen lediglich eleganter erscheinen. Sie sind im folgenden weniger wichtig. Die nächsten Lektionen werden sich auf die Erweiterung von Klassen mit Hilfe von Vererbung stützen.  

Zurück Nach oben Weiter