3.1 Allgemeines
Im Prinzip kann man Operatoren als eine besondere Form von Funktionen auffassen - nichts anderes sind sie auch. Anstatt x = a * b könnte man auch x = mal( a, b ) schreiben. Man hat sich beim Entwickeln von C/C++ für die mathematische Schreibweise entschieden.
Jetzt verstehen wir auch die Problematik hinter der Definition von Operatoren - wir wissen ja aus Kapitel 1.8 bereits, wie man Funktionen überlädt. Es existiert für den Operator + zum Beispiel eine Funktion, fü jeden bekannten Datentyp (int, double, short, char usw. ). Wollen wir für unsere eigenen Klassen solche Funktionen definieren, so müssen wir eben den gewünschten Operator in unserer Klasse selbst überladen.
In C++ gibt es jedoch beinahe 40 solcher Operatoren, so dass es sich als schwer erweisen würde, wenn ich hier alle zeigen wollte. Wichtig ist, dass man nur bereits vorhandene Operatoren überladen kann. Es ist nicht möglich, neue Operatoren zu definieren!
3.2 Der Zuweisungsoperator =
Das Überladen des Zuweisungsoperators ist besonders dann wichtig, wenn die Klasse Zeiger oder gar Listen enthält. Soll der Zeiger kopiert oder ein neuer anglegt werden, dessen Adresse den selben Wert wie die Originaladresse enthält? Soll die komplette Liste kopiert werden, oder nur der Anker? All diese Fragen sind durch den Zuweisungsoperator zu klären.
Der Rückgabewert des Operators ist eine Referenz auf die Klasse. Der einzige Parameter ist ebenfalls eine Referenz auf die Klasse, der jedoch konstant zu deklarieren ist. Ein kleines Codebeispiel:
CForm& CForm::operator= ( const CForm& quelle )
{
/* Wenn das Zielobjekt nicht leer ist, muss der alte Name
zuerst gelöscht werden um Speicherlecks zu vermeiden */
if( name_z != 0 ) { delete [] name_z; }
name_z = new char[ strlen(quelle.name_z) +1 ];
strcpy( name_z, quelle.name_z );
return *this;
}
Ich habe das Beispiel aus Kapitel 4 vorweggenommen. Bei name_z handelt es sich um einen Zeiger auf eine Zeichenkette. Beim kopieren muss zuerst überprüft werden, ob im Zielobjekt bereits ein Zieger eingerichtet ist, um diesen bei Bedarf freizugeben. Dannach wird ein neuer Zeiger angelegt und mit dem Wert des Quellobjekts gefüllt. Zuletzt gibt man noch einen Zeiger auf das Zielobjekt zurück, fertig.
3.3 Inkrement- und Dekrementoperator ++, --
Will man Inkrement- oder Dekrementoperator überladen, muss man sich zwei Dinge überlegen: Was genau soll verändert werden und wie löse ich die Unterscheidung Posfix- und Präfixverwendung des Operators!?
Ich zeige hier den Operator ++, das Dekrementieren funktioniert analog. Das Präfix bedeutet, erhöhe erst den Wert und gib dann den erhöhten Wert zurück. Beim Postfix ergibt sich das Problem, dass zwar der Wert erhöt werden muss BEVOR als letztes die return-Anweisung den noch nicht erhöhten Wert zurückgibt. Deshalb muss in der Funktion zuerst eine Kopie des Originals angelegt werden.
Um dem Compiler zu erklären, welche Art des Operators (Post- oder Präfix) wir gerade definieren, bekommt der Postfix-Op als Parameter eine Integer-Variable mit, die wir aber im Quellcode nicht weiter beachten. Der Präfix-Op bleibt parameterlos. Hier ein Beispiel mit einer Testklasse:
class Test
{
Test& operator++(); // Präfix
Test operator++(int) // Postfix
};
Test& Test::operator++()
{
// Hier Code zur Erhöhung der Member-Variable(n)
// oder was auch immer sonst passieren soll
return *this;
}
Test Test::operator++(int a)
{
Test kopie = *this; // Kopie des Aktuellen Objektes
// Hier den selben Code wie oben eintragen
return kopie;
}
3.4 Übersicht über Operator-Funktionsrümpfe
Hier möchte ich eine kurze Zusammenfassung - eine Art Formelsammlung - für das Überladen von Operatoren bieten. Man denke sich die jeweiligen Operatoren in die Deklaration einer Klasse mit dem Namen CLASS eingebunden. CLASS& ist entsprechend eine Referenz auf CLASS. Ich gebe jeweils an, was zurückgegeben werden soll. Das Label SINN sagt aus, dass ein Wert zurückgegeben wird, der dem Sinn des Operators entspricht (zum Beispiel einen Wert aus einer Liste beim Index-Operator [ ] ).
Der Zuweisungsoperator =
Funktion: CLASS& operator= ( const CLASS& quelle );
Rückgabe: return *this;
Bemerkung: Wenn die Bedingung (this == &quelle) erfüllt ist, kann die Funktion gleich abgebrochen werden (Ziel = Quelle !)
Inkrement - -, ++ (Präfix-Schreibweise)
Funktion: CLASS& operator-- ( );
CLASS& operator++( );
Rückgabe: return *this;
Bemerkung:
Inkrement - -, ++ (Postfix-Schreibweise)
Funktion: CLASS operator-- ( int a );
CLASS operator++( int a );
Rückgabe: Zu Beginn der Funktion muss eine Kopie von this erstellt werden (CLASS kopie = this;). Dann wird diese Kopie mit return kopie; zurückgegeben.
Bemerkung: Der Parameter (hier a) ist im Code nutzlos. Er identifiziert lediglich die Deklaration als Postfix-Schreibweise.
Zeigerzugriffsoperator ->
Funktion: CLASS* operator-> ();
Rückgabe: return &CLASS;
Bemerkung: Wird verwendet, um auf Mebmer-Klassen von CLASS zugreifen zu können.
Indexoperator [ ]
Funktion: TYP& operator[] (int i);
Rückgabe: SINN
Bemerkung: Die Funktion gibt im Normalfall einen Wert aus einem Array innerhalb der Klasse zurück
Der Aufrufoperator ( )
Funktion: TYP& operator() (PARAMETERLISTE) const;
Rückgabe: SINN
Bemerkung: Der Aufrufoperator wird meist wie der Indexoperator verwendet. Man kann allerdings mehrere Parameter angeben, so dass Index-Zugriffe im Basic-Stil möglich werden. Der Zugriff ist dann von der Form CLASS(x,y)
Natürlich kann man die Deklaration der Operatoren auch anders gestallten. Es ist ja der Sinn der Operatorüberladung, neue, eigene Funktionen zu definieren. Und tatsächlich kann uns kein Compiler hindern, den Indexoperator so zu überladen, das man damit eine Addition ausführt. Man muss nur den Rückgabewert und den Parameter entsprechend anpassen. Die hier gezeigten Deklarationen entsprechen lediglich der intuitiven Verwendung der jeweiligen Operatoren, und die sollten im Normalfall beibehalten werden!!!!
|