Überladen von Operatoren

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:



Unterabschnitte
AnyWare@Wachtler.de