In C++ kann man für alle selbstgeschaffenen Klassen die meisten Operatoren überladen.
Im einzelnen sind dies die Rechenoperatoren
+
,
-
,
*
,
/
,
%
,
^
,
&
,
|
,
+
,
!
,
<<
,
>>
,
die Vergleichsoperatoren
<
,
>
,
==
,
<=
,
>=
,
!=
,
&&
,
||
,
die Zuweisungen
=
,
+=
,
-=
,
*=
,
/=
,
%=
,
die Inkrement- und Dekrementoperatoren
++
,
--
,
sowie der Feldzugriff
[]
,
der Klammeroperator
()
,
und die Speicheroperatoren
new
,
delete
,
sowie
->
.
Schließlich lassen sich auch explizite und implizite Typkonversionen
ebenso wie Konstruktoren und Destruktoren überladen.
Durch Überladen der Operatoren mit eigenen Funktionen kann man nicht
die Prioritäten der Operatoren untereinander verändern.
Ebenfalls kann man nicht die Anzahl der Operanden verändern.
Operatoren, die sowohl unär als auch binär existieren (zum Beispiel -
),
können dementsprechend getrennt voneinander zweimal überladen werden mit
Funktionen, die jeweils ein oder zwei Argumente haben.
Mehrfaches Überladen mit Funktionen unterschiedlicher Parametertypen ist natürlich erlaubt.
Ein Überladen ist prinzipiell sowohl mit
Elementfunktionen, als auch mit
normalen Funktionen möglich.
Nur die Operatoren []
,
()
, ->
und Konversionsfunktionen müssen Elementfunktionen sein.
Mindestens einer der beteiligten Operanden muß ein Klassenobjekt sein;
Dadurch kann man die Operationen, an denen ausschließlich elementare
C-Objekte beteiligt sind, nicht neu definieren.
Eine Nichtelementfunktion, die einen Operator überlagert, hat
logischerweise ebensoviele
Parameter, wie der entsprechende Operator Operanden hat.
(Einzige Ausnahme sind die Operatoren ++
und --
.
Diese können vor oder nach ihrem Operanden stehen (Präfixoperator,
Postfixoperator) und liefern dementsprechend den Wert nach oder vor
der Änderung zurück. Sowohl beide Versionen von ++
als auch
beide Versionen von --
können getrennt durch eine Funktion
überladen werden. Da jetzt aber die beiden Versionen nicht anhand
ihrer Parameterliste unterschieden werden könnten, wird für die
jeweilige Postfixversion, also für Operand++
und
Operand--
, ein zusätzlicher int-Parameter
übergeben, der nur zur Unterscheidung der Parameterlisten gegenüber
der Präfixversion dient, und
ansonsten nicht verwendet wird.)
Bei der Definition als Elementfunktion entfällt der erste Parameter; stattdessen wird der erste Operand als zugehöriges Klassenobjekt angesprochen.
Der Name einer Operatorfunktion ist immer operator, gefolgt vom
gewünschten Operatorzeichen, beispielsweise operator+
für die
Addition. Anzahl und Typen der Parameter ergeben sich aus den Operanden.
Als Beispiel soll eine neue Klasse für Kardinalzahlen, also nichtnegative Zahlen geschaffen werden. Der Einfachheit halber werden die Elementfunktionen gleich innerhalb der Klassendeklaration definiert, auch wenn das normalerweise nicht unbedingt sinnvoll ist (siehe Aufteilen auf Quelltexte):
// Time-stamp: "15.01.04 01:27 card.cpp klaus@wachtler.de" #include <iostream> #include <cstdlib> using namespace std; // Die Deklaration der Klasse Cardinal. class Cardinal { friend ostream &operator<<( ostream &s, Cardinal &c ); private: unsigned long wert; public: // parameterloser Konstruktor: Cardinal( void ) { cout << "Konstruktor Cardinal( void )" << "\n"; wert = 0; } // Konstruktor mit einem ganzzahligen Parameter: Cardinal( long int i ) { cout << "Konstruktor Cardinal( long int i=" << i << " )" << "\n"; if( (i<0) ) { cerr << "Cardinal(): " << i << " ist negativ!\n"; exit( 2 ); } wert = i; } // Zuweisung unsigned int an Cardinal: Cardinal &operator=( unsigned int i ) { cout << "operator=( unsigned int i=" << i << " )" << "\n"; wert = i; return *this; } // Zuweisung int an Cardinal: Cardinal &operator=( int i ) { cout << "operator=( int i=" << i << " )" << "\n"; if( (i<0) ) { cerr << "Cardinal(): " << i << " ist negativ!\n"; exit( 2 ); } wert = i; return *this; } // Zuweisung Cardinal an Cardinal: Cardinal &operator=( Cardinal c2 ) { cout << "operator=( Cardinal c2=" << c2.wert << " )" << "\n"; wert = c2.wert; return *this; } // Operator Cardinal+Cardinal: Cardinal operator+( Cardinal c2 ) { cout << "Objekt " << wert << ", operator+( Cardinal c2=" << c2.wert << " )" << "\n"; return wert + c2.wert; } }; // Operator ostream<<Cardinal (Ausgabe von Cardinal nach ostream). // Kann nicht als Elementfunktion von class Cardinal geschrieben werden, // da der erste Parameter nicht vom Typ Cardinal ist: ostream &operator<<( ostream &s, Cardinal &c ) { cout << "operator<<( ostream &s, Cardinal &c ) mit c=" << c.wert << "\n"; s << c.wert; return s; } int main( int nargs, char *args[] ) { Cardinal c = 0, // Aufr. d. Konstruktors Cardinal( long int i ) mit i=0 d; // Aufr. d. Konstruktors Cardinal( void ) int j = 15; c = j; // Aufr. v. c.operator=( int i ) mit i=j d = c; // Aufr. v. d.operator=( Cardinal c2 ) mit c2=c d = c + 1; // Mit einem Konstruktor Cardinal( long int i ) // wird ein temporäres Objekt erzeugt, dieses mit // c.operator+( Cardinal c2 ) mit dem temp. Objekt als c2 // verknüpft, dann mit d.operator=( Cardinal c2 ) an d // zugewiesen. cout << c << "\n"; cout << d << "\n"; }
Die Ausgabe dieses Programms sieht so aus:
Konstruktor Cardinal( long int i=0 ) Konstruktor Cardinal( void ) operator=( int i=15 ) operator=( Cardinal c2=15 ) Konstruktor Cardinal( long int i=1 ) Objekt 15, operator+( Cardinal c2=1 ) Konstruktor Cardinal( long int i=16 ) operator=( Cardinal c2=16 ) operator<<( ostream &s, Cardinal &c ) mit c=15 15 operator<<( ostream &s, Cardinal &c ) mit c=16 16
Zu diesem Programm folgende Anmerkungen:
ostream &operator<<( ostream &s, Cardinal &c )
nicht als Elementfunktion der Klasse Cardinal vereinbart
werden. Theoretisch wäre es möglich, die Funktion als Elementfunktion
von ostream zu definieren. Da aber ostream im Nachhinein
nicht geändert werden kann, und geschrieben wurde, als Cardinal
nicht bekannt war, scheidet diese Möglichkeit praktisch aus.
Wenn eine solche Funktion trotzdem auf die Elemente der Klasse zugreifen können soll, dann muß sie als friend
vereinbart werden.
Cardinal operator=( unsigned int i )
und
Cardinal operator=( int i )
).