C++ Kurs

Allgemeines Überladen von Operatoren

Die Themen:

Einleitung
Überladen von binären Operatoren
Überladen von logischen Operatoren
Überladen von unären Operatoren
Überladen der Operatoren ++, - - und des cast-Operators
const Objekte und überladene Operatoren
Sonstige Hinweise zum Überladen von Operatoren
Beispiel und Übung

Einleitung

Genauso wie in der letzten Lektion der Zuweisungsoperator '=' überladen wurde, können für ein Objekte fast alle anderen Operatoren überladen werden, mit folgenden Ausnahmen:

Ebenfalls nicht überladbar ist das Präprozessor-Symbol '#', aber das versteht sich ja fast von selbst, da die Präprozessoranweisung vor dem Compilerlauf durch den Präprozessor schon ersetzt wird.

Um einen Operator zu überladen stehen zwei prinzipielle Vorgehensweisen zur Verfügung:

Überladen von binären Operatoren

Überladen durch nicht-statische Memberfunktion

Wird ein binärer Operator (das sind Operatoren mit zwei Operanden wie z.B. '+' oder '*') überladen, so wird in der Regel folgende Memberfunktion hierfür eingesetzt:

RVAL CANY::operator OSYMBOL (DTYP);

RVAL ist der Returntyp des Operators und OSYMBOL das Symbol des zu überladenden Operators (z.B. '+' für die Addition) für die Klasse CANY. Da die Memberfunktion im Kontext des ersten, linken Operanden aufgerufen wird, gibt DTYP den Datentyp des zweiten, rechten Operanden an.

Beispiel:

Für die Klasse Complex soll der Plus-Operator definiert werden, so dass folgende Operation möglich ist:

comp3 = comp1 + comp2;

Als erstes wird der Returntyp des überladenen Operators bestimmt. Da eine Addition von zwei Complex-Objekten ebenfalls wieder ein Complex-Objekt ergibt, muss die Memberfunktion ein Complex Objekt zurückgeben, welches das Ergebnis enthält. Damit besitzt die Memberfunktion folgende Deklaration:

class Complex
{
    double real;   // Realanteil
    double imag;   // Imaginäranteil
  public:
    ....
    Complex operator + (const Complex& op2) const;
};

Die Memberfunktion ist als const Memberfunktion deklariert, da sie die Eigenschaften des linken Operanden, in dessen Kontext sie aufgerufen wird, ja nicht verändert.

Danach kann die Memberfunktion definiert werden. Eine Addition von zwei Complex-Objekten addiert einfach die Real- und Imaginäranteile der beiden Objekte. Doch hier lauert eine Fehlerfalle, die auf den ersten Blick nicht gleich ersichtlich ist. Sehen Sie sich einmal die nachfolgende korrekte Definition der Memberfunktion genau an.

// Überladener Plus-Operator
Complex Complex::operator + (const Complex& op2) const;
{
    // temp. Hilfsobjekt mit akt. Objekt initialisieren (Aufruf copy-ctor)
    Complex temp(*this);
    // 2. Operanden hinzuaddieren
    temp.real += op2.real;
    temp.imag += op2.imag;
    // temp. Objekt zurückgeben
    return temp;
}

Zuerst wird ein lokales Objekt der Klasse Complex definiert, das bei seiner Definition auch gleich mit dem aktuellen Objekt (dies ist der linke Operand des + Operators!) initialisiert wird (Aufruf des Kopier-Konstruktors). Anschließend wird zu diesem lokalen Objekt das zweite Objekt (rechter Operand der + Operators) hinzuaddiert. Sie müssen hier unbedingt ein temporäres Hilfsobjekt zu Hilfe nehmen, denn eine Addition von zwei Objekten darf niemals den Inhalt der beiden als Operanden verändern!

Warum hier ein Objekt und keine Objektreferenz zurückgegeben werden muss können Sie hier nochmals nachlesen.

Sehen wir uns ein weiteres Beispiel des überladenen Plus-Operators der Klasse Complex an. Anstelle zwei Complex-Objekte zu addieren, soll jetzt zu einem Complex-Objekt ein double-Wert addieren werden, d.h. folgende Operation soll erlaubt sein:

comp2 = comp1 + 1.2;

Am Returntyp der Memberfunktion ändert sich gegenüber vorher nichts, da auch hier das Ergebnis der Addition wiederum ein Complex-Objekt ist. Lediglich der Datentyp des Parameters der Memberfunktion ändert sich, da er den Datentyp des zweiten (rechten) Operanden widerspiegelt. In unserem Fall hier ist der Datentyp ein double. Im nachfolgenden Beispiel wird der double-Wert nur zum Realanteil der komplexen Zahl addiert.


class Complex
{
    double real;   // Realanteil
    double imag;   // Imaginäranteil
  public:
    ....
    Complex operator+ (double val) const;
};
// Überladener Plus-Operator
Complex Complex::operator + (double val) const
{
    // temp. Hilfsobjekt definieren und mit akt. Objekt initialisieren
    Complex temp(*this);
    // Operand hinzuaddieren
    temp.real += val;
    // temp. Objekt zurückgeben
    return temp;
}

Die umgekehrte Operation

comp2 = 1.2 + comp1;

können Sie nicht mit einer Memberfunktion realisieren! Wie bereits erwähnt, wird die Operator-Memberfunktion im Kontext des linken Operanden aufgerufen, und das ist hier im Kontext von double. Da Sie für die Standard-Datentypen keine Operatoren überladen können, müssen Sie für diese Operation auf eine normale Funktion zurückgreifen. Mehr dazu nun.

Überladen durch Funktionen

Betrachten wir nun den Fall, dass der Operator durch eine normale Funktion überladen wird und nicht durch eine Memberfunktion. Wie bereits am Anfang der Lektion erwähnt, wird hierfür eine friend-Funktion benötigt. Die Besonderheit einer friend-Funktion liegt darin, dass sie Zugriff auf alle Member einer Klasse (auch auf die private-Member!) besitzen. Mehr zu friend-Funktionen später im Kurs noch.

Wird ein binärer Operator durch eine normale Funktion überladen, so wird in der Regel folgende Funktion hierfür eingesetzt:

RVAL operator OSYMBOL (DTYP1 Op1, DTYP2 Op2);

RVAL ist der Returntyp des Operators und OSYMBOL das Symbol des zu überladenden Operators (z.B. '+' für die Addition). Da die Funktion nun nicht mehr zur Klasse gehört, und damit implizit der Datentyp des linken Operanden definiert ist, benötigt sie auch zwei Parameter. Der erste Parameter gibt hierbei den linken Operanden und dessen Datentyp an und der zweite Parameter den rechten Operanden und dessen Datentyp.

Sehen wir uns an, wie der Plus-Operator für die Klasse Complex durch eine normale Funktion überladen wird, um zu einem Complex-Objekt einen double-Wert zu addieren. Beachten Sie bitte die Deklaration der friend-Funktion innerhalb der Klasse Complex. Die friend-Deklaration gleich bis auf das vorangestellte Schlüsselwort friend der Deklaration der Operatorfunktion.


class Complex
{
    double real;   // Realanteil
    double imag;   // Imaginäranteil
    ....
    // Funktion als friend-Funktion deklarieren
    friend Complex operator + (const Complex& op1, double val);
};
// Überladener Plus-Operator
Complex operator+ (const Complex& op1, double val)
{
    // temp. Hilfsobjekt
    Complex temp(op1);
    // 2. Operanden hinzuaddieren
    temp.real += val;
    // temp. Objekt zurückgeben
    return temp;
}

Die Funktion muss als Returnwert ein Complex-Objekt mit dem Ergebnis der Addition liefern. Im ersten Parameter erhält die Funktion eine Referenz auf den linken Operanden des Operators, in unserem Fall also eine Referenz auf ein Complex-Objekt. Und da der zweite Parameter den rechten Operanden repräsentiert, muss hier ein double-Parameter stehen. Der Ablauf der Funktion selbst entspricht dem vorherigen Beispiel.

Aber Achtung!

Haben Sie den Plus-Operator wie oben angegeben überladen, so können Sie damit nur Operationen des folgenden Typs durchführen:

comp2 = comp1 + 1.2;

Wollen Sie auch Operationen vom Typ

comp2 = 1.2 + comp1;

durchführen, so müssen Sie eine weitere friend-Funktion definieren, bei der die beiden Parameter vertauscht sind, d.h.

Complex operator+ (double val, const Complex& op2);

Überladen von logischen Operatoren

Oftmals ist es erforderlich, zwei Objekte miteinander vergleichen zu können. Da im C++ Standard nicht definiert ist, wie Objekte miteinander verglichen werden, müssen auch hierfür die entsprechenden Operatoren überladen werden, um z.B. folgende Anweisungen schreiben zu können:

if (comp1 == comp2)
    ...
if (comp1 != comp2)
    ....

Beide Operatoren müssen als Ergebnis einen bool-Wert zurückliefern. Und damit ergeben sich die nachfolgend dargestellten Vergleichs-Memberfunktionen. Sehen Sie sich einmal an wie der != Operator überladen wurde. Er ruft den überladenen == Operator auf und negiert lediglich dessen Ergebnis. Verwenden Sie soweit wie möglich bereits bestehenden Code!


class Complex
{
    double real;   // Realanteil
    double imag;   // Imaginäranteil
  public:
    ....
    bool operator == (const Complex& op2) const;
    bool operator != (const Complex& op2) const;

};
// Überladener == Operator
bool Complex::operator == (const Complex& op2) const
{
    return ((real == op2.real) &&
            (imag == op2.imag));
}
// Überladener != Operator
bool Complex::operator != (const Complex& op2) const
{
    return !operator==(op2);
}

Überladen von unären Operatoren

Kommen wir nun zum Überladen von unären Operatoren wie z.B. dem NOT-Operator '!'. Auch diese Operatoren können entweder durch eine nicht-statische Memberfunktion der Klasse oder eine Funktion überladen werden.

Überladen durch Memberfunktionen

Um einen unären Operator durch eine nicht-statische Memberfunktion zu überladen wird in der Regel folgende Memberfunktion verwendet:

RVAL CANY::operator OSYMBOL ();

RVAL ist der Returntyp des Operators und OSYMBOL wiederum das Symbol des zu überladenden Operators (z.B. '!' für die NOT-Operation) der Klasse CANY. Da unäre Operatoren nur einen Operanden besitzen, benötigt die Memberfunktion keine weiteren Parameter.

Für die bekannte Klasse Complex soll der Vorzeichenoperator überladen werden, so dass folgende Operation definiert ist:

comp2 = -comp1;

Beachten Sie bitte, dass es sich hier um den Vorzeichenoperator und nicht um den Minus-Operator handelt! Beide besitzen zwar das gleiche Symbol, der Minus-Operator benötigt jedoch zwei Operanden und der Vorzeichenoperator nur einen. Da das Ergebnis des Vorzeichenoperators immer vom Typ des Operanden ist, muss die Operator-Memberfunktion auch ein Complex-Objekt zurückliefern. Im Beispiel kehrt der Vorzeichenoperator die Vorzeichen des Real- und Imaginäranteils um. Achten Sie beim Überladen von Operatoren auch immer darauf, wann Sie Operanden verändern dürfen und wann nicht!


class Complex
{
    double real;   // Realanteil
    double imag;   // Imaginäranteil
  public:
    ....
    Complex operator -() const;
};
// Überladener Vorzeichenoperator
Complex Complex::operator- () const
{
    Complex temp;
    temp.real = -real;
    temp.imag = -imag;
    return temp;
}

Überladen durch Funktionen

Wird ein unärer Operator durch eine normale Funktion überladen, so wird in der Regel folgende Funktion hierfür eingesetzt:

RVAL operator OSYMBOL (CAny& Op1);

RVAL ist wieder der Returntyp des Operators und OSYMBOL das Symbol des zu überladenden Operators. Da die Funktion nun nicht mehr zur Klasse gehört, muss sie wiederum eine friend-Funktion der Klasse sein und benötigt einen Parameter. Dieser Parameter gibt den Operanden an und ist immer vom Typ der Klasse, für die der Operator überladen wird.

Überladen wir den NOT-Operator '!' für die Klasse Complex durch eine normale Funktion um folgende Abfrage zu ermöglichen:

if (!comp1)
    ....

Der NOT-Operator muss laut C++ Standard einen bool-Wert zurückliefern, denn entweder ist die Bedingung erfüllt oder nicht. Als Parameter erhält die Funktion eine const Referenz auf den Operanden. Der NOT-Operator liefert im Beispiel dann true zurück, wenn sowohl der Real- wie auch der Imaginäranteil 0.0 enthält.


class Complex
{
    double real;   // Realanteil
    double imag;   // Imaginäranteil
    ....
    // Funktion als friend-Funktion deklarieren
    friend bool operator ! (const Complex& op);
};
// Überladener NOT-Operator
bool operator! (const Complex& op)
{
   return ((op.real == 0.0) && (op.imag == 0.0));
}

Überladen der Operatoren ++, - - und des cast-Operators

Sehen wir uns nun einmal das Überladen von einigen besonderen Operatoren an. Verwiesen sei an dieser Stelle noch auf die nächste Lektion, die ebenfalls das Überladen von speziellen Operatoren behandelt.

Überladen der Operatoren ++ und - -

Einen Sonderfall beim Überladen von unären Operatoren stellen die Operatoren ++ und -- dar. Da sie sowohl als Präfix (++X) wie auch als Postfix (X++) auftreten können, müssen hier beim Überladen zwei verschiedene Memberfunktionen verwendet werden.

Um den Präfixoperator ++ zu überladen, wird die im nachfolgenden Beispiel angegebene Memberfunktion eingesetzt. Diese Memberfunktion muss eine Referenz auf das aktuelle Objekt zurückgeben, damit z.B. folgende Operation möglich ist:

comp2 = ++comp1;


class Complex
{
    double real;   // Realanteil
    double imag;   // Imaginäranteil
  public:
    ....
    Complex& operator ++();
};
// Überladener Präfixoperator ++
Complex& Complex::operator++ ()
{
    ++real;
    ++imag;
    return *this;
}

Beachten Sie beim Präfixoperator, dass zuerst die Addition ausgeführt und das Ergebnis daraus zurückgeliefert wird. Außerdem muss der Operator hier den Operanden (sprich das Objekt, in dessen Kontext die Memberfunktion aufgerufen wurde) verändern, so dass hier kein temporäres Hilfsobjekt, wie z.B. beim + Operator, benötigt wird.

Soll der Postfixoperator überladen werden, so erhält die Memberfunktion einen Dummy-Parameter vom Typ int. Auch hier muss der überladene Operator wieder ein entsprechendes Complex-Objekt zurückliefern.

comp2 = comp1++;


class Complex
{
    double real;   // Realanteil
    double imag;   // Imaginäranteil
  public:
    ....
    Complex operator ++(int);
};
// Überladener Postfixoperator ++
Complex Complex::operator++ (int)
{
    Complex temp(*this); //copy-ctor
    ++real;
    ++imag;
    return temp;
}

Beachten Sie beim Überladen dieses Operators, dass der Ursprungswert des Objekts zurückgegeben werden muss, da der Postfixoperator die Addition erst nach der Auswertung des Objekts durchführen darf. Wenn Sie sich nicht mehr sicher über den Unterschied zwischen X++ und ++X sind, dann hier nachschauen.

Beachten Sie bitte, dass diese Memberfunktion keine const-Memberfunktionen sind, da sie den Operanden ja verändern!

Überladener cast-Operator

Durch die Definition von cast-Operatoren kann dem Compiler mitgeteilt werden, wie ein Objekt in einen anderen Datentyp zu konvertieren ist. Die allgemeine Syntax für eine solche Typkonvertierung mittels einer nicht-statischen Memberfunktion lautet:

CANY::operator NEWDTYP ();

 

NEWDTYP gibt den Datentyp an, in den ein Objekt der Klasse CANY konvertiert werden soll. Am Besten sehen wir uns dies auch anhand eines Beispiels an.

Ausgangsbasis für dieses Beispiel ist wiederum die Klasse Complex. Ziel der Typkonvertierung ist es, folgende Anweisung schreiben zu können:

double val = comp;

wobei comp ein Objekt der Klasse Complex sein soll. Damit obige Anweisung durch den Compiler richtig aufgelöst werden kann, müssen wir ihm mitteilen, wie ein Complex-Objekt in einen double-Wert zu konvertieren ist. Bei dieser Konvertierung ist NEWDTYP ist der Datentyp double und damit ergibt sich die im Beispiel dargestellte Operator-Memberfunktion. Die Typkonvertierung erfolgt hier durch ziehen der Wurzel aus der Addition der quadrierten Anteile.


class Complex
{
    double real, imag;
    ....
  public:
    ....
    operator double () const;
};
// Typkonvertierung Complex -> double
Complex::operator double () const
{
    return static_cast<double>(sqrt(real*real + imag*imag));
}

Beachten Sie, dass die Operator-Memberfunktion keinen Returntyp besitzt und eine const-Memberfunktion ist. Würden Sie hier keine const-Memberfunktion verwenden, so könnten Sie kein const Complex Objekt in einen double-Wert konvertieren.

const Objekte und überladene Operatoren

Sehen wir uns das Überladen von Operatoren noch einmal genauer an, und zwar im Zusammenhang mit const-Objekten. Wie Sie ja bereits wissen, lautet die allgemeine Memberfunktion zum Überladen von Operatoren:

RVAL CANY::operator OSYMBOL (DTYP);

Wie bereits des Öfteren erwähnt, erfolgt der Aufruf des Operators immer im Kontext des linken Operanden und der rechte Operand wird als Parameter an die Memberfunktion übergeben. Nehmen wir jetzt einmal an, Sie wollen für eine Klasse CAny den Plus-Operator überladen und hätten zwei Objekte dieser Klasse definiert und wollen die angegebenen Operationen durchführen:

CAny ncObject1, CRes;
const CAny cObject2;

CRes = ncObject1 + ncObject1;
CRes = ncObject1 + cObject2;
CRes = cObject2 + ncObject1;
CRes = cObject2 + cObject2;

Welche Operator-Memberfunktionen müssen Sie letztendlich definieren damit auch alle Operationen definiert sind?

Nachfolgend sehen Sie die Operator-Memberfunktionen in der Reihenfolge, in der Sie für die obigen Operationen aufgerufen werden. Ist der zweite Operand ein const-Objekt, so muss damit der Parameter der Operator-Memberfunktion auch ein const sein. Ist dagegen der erste Operand ein const, so muss die Operator-Memberfunktion selbst const sein. Da ein const-Objekt laut Definition nicht verändert werden, kann es also nur Memberfunktionen verwenden die dies auch sicherstellen. Und das sind const-Memberfunktionen.


class Complex
{
    double real;   // Realanteil
    double imag;   // Imaginäranteil
  public:
    ....
    Complex operator + (Complex& op2);
    Complex operator + (const Complex op2);
    Complex& operator + (Complex& op2) const;
    Complex& operator + (const Complex op2) const;
};

Aber keine Panik! Sie müssen nicht immer alle vier Operator-Memberfunktionen für überladene Operatoren definieren. Ein nicht-const Objekt kann jederzeit durch den Compiler in ein const-Objekt konvertiert werden (aber nicht umgekehrt). Und damit würde im Prinzip die letzte Memberfunktion alleine ausreichen. Ob dies aber für alle Operatoren ausreicht hängt vom Anwendungsfall ab.

Vergessen Sie einmal die Operator-Memberfunktion für ein const Objekt zu definieren und Sie rufen den Operator mit einem const Objekt auf, so erhalten Sie eine Fehlermeldung in der Art, dass kein Operator für const class CLASS definiert ist.

Sonstige Hinweise zum Überladen von Operatoren

Zum Schluss dieser Lektion noch drei Hinweise:

  1. Das Überladen eines Operators ändert niemals die Rangfolge der Operatoren, d.h. es gilt z.B. auch weiterhin, dass eine Multiplikation immer vor einer Addition ausgeführt wird.
  2. Auch die Anzahl der Operanden eines Operators ist fest vorgegeben. So benötigt der Plus-Operator immer zwei Operanden.
  3. Haben Sie die Operatoren für = und * überladen, so können Sie trotzdem nicht die Kurzschreibweise *= verwenden. Sie müssen hierzu den Operator *= explizit überladen.

Und sollten Sie noch Zweifel haben, wann ein überladener Operator eine Referenz zurückliefern muss und wann ein Objekt, so können Sie sich an folgender Daumregel orientieren:

Ein überladener Operator liefert in der Regel dann eine Referenz zurück, wenn der Operand selbst verändert wird. In allen anderen Fällen ist ein Objekt zurückzuliefern.

Beispiel und Übung

Beispiel:

Das Beispiel demonstriert verschiedene überladene Operatoren anhand einer Klasse Rect zur Abspeicherung von Rechteckdaten.

Außer dem notwendigen Konstruktor (zur Initialisierung der Rechteckdaten) und einer Ausgabe-Memberfunktion PrintRect(...), besitzt die Klasse vier überladene Operatoren deren Funktion in der nachfolgenden Tabelle aufgeführt sind:

Operator

Funktion

++

Verschiebt ein Rechteck um eine Einheit in X- und Y-Richtung.

&

Verundet zwei Rechtecke. Es wird hierbei die gemeinsame Fläche der beiden Rechtecke berechnet und als Rect-Objekt zurückgegeben.

==

Vergleicht ob zwei Rechtecke identisch sind, d.h. die gleiche Position und Ausdehnung besitzen.

!

Stellt fest ob das Rect-Objekt eine Fläche besitzt.

In der main() Funktion werden zwei nicht identische Rechtecke definiert. Diese Rechtecke werden mithilfe des überladenen == Operators auf Gleichheit überprüft und das Ergebnis der Überprüfung dann ausgegeben. Anschließend wird das erste Rechteck mit dem Operator ++ in X/Y-Richtung um eine Einheit verschoben. Aus den beiden Rechtecken wird dann mittels des & Operators die Schnittfläche gebildet und diese einem weiteren Rechteck Objekt zugewiesen. Zum Schluss wird noch durch Anwendung des ! Operators überprüft, ob die ermittelte Schnittfläche nicht leer ist und diese ausgegeben.

1. Rechteck:
Rechteck auf (10,20) Grösse: (100,200)
2. Rechteck:
Rechteck auf (50,50) Grösse: (100,200)
Rechtecke sind unterschiedlich
1. Rechteck um eins verschoben:
Rechteck auf (11,21) Grösse: (100,200)
Rechtecke überschneiden sich!
Gemeinsame Fläche: Rechteck auf (50,50) Grösse: (61,171)


// Beispiel zu überladenen Operatoren

// Zuerst Dateien einbinden
#include <iostream>

using std::cout;
using std::endl;

// Klassendefinition
class Rect
{
    short   xPos, yPos;             // Position
    short   width, height;          // Breite und Höhe
  public:
    Rect();                         // ctors
    Rect(short x, short y, short w, short h);
    Rect(const Rect& source);
    void PrintRect()const;          // Ausgabe des Rechtecks
    Rect operator ++(int);          // Verschiebt Rechteck um eine X/Y-Position
    Rect operator & (const Rect& op2) const;  // Verundet zwei Rechtecke
    bool operator ==(const Rect& op2) const;  // Vergleicht zwei Rechtecke
    bool operator !() const;        // Prüft auf leeres Rechteck ab
};
// Definition der Memberfunktionen
// 1. Konstruktor (Standard Konstruktor)
Rect::Rect()
{
    xPos = yPos = width = height = 0;
}
// 2. Konstruktor
// Erhält als Parameter die Rechteck-Daten als 4 short Werte
Rect::Rect(short x, short y, short w, short h)
{
    xPos = x; yPos = y;
    width = w; height = h;
}
// 3. Konstruktor
// copy-ctor
Rect::Rect(const Rect& source)
{
    xPos = source.xPos; yPos = source.yPos;
    width = source.width; height = source.height;
}
// Ausgabe der Rechteck Daten
void Rect::PrintRect() const
{
    cout << "Rechteck auf (" << xPos << "," << yPos << ") ";
    cout << "Grösse: (" << width << "," << height << ")\n";
}
// Verschiebt Rechteck um eine X/Y-Position
// Achtung Postfixoperator!
// Darf keine const-Memberfunktion sein da das Objekt
// ja verändert wird!
Rect Rect::operator ++(int)
{
    // Hilfsobjekt zur Aufnahme der akt. Werte
    // Ruft copy-ctor auf
    Rect orig(*this);
    // Nun erst Position verändern
    xPos++;
    yPos++;
    // Ursprungswerte zurückgeben
    return orig;
}
// Verundet zwei Rechtecke
// Das daraus resultierende Rechtecke enthält die Fläche,
// die beiden Rechtecken gemeinsam ist
Rect Rect::operator & (const Rect& op2) const
{
    // Resultierendes Rechteck
    Rect result;
    // Hilfsvariablen
    short end1, end2;
    // Grösste X-Position ist resultierende X-Position
    result.xPos = (op2.xPos>xPos) ? op2.xPos : xPos;
    // X-Positionen der rechten Kante berechnen
    end1 = xPos+width;
    end2 = op2.xPos+op2.width;
    // Kleinste X-Position bestimmte Breite des result. Rechtecks
    result.width = (end1>end2) ? end2-result.xPos : end1-result.xPos;
    // Nun das gleiche Spiel mit der Y-Position und der Höhe
    result.yPos = (op2.yPos>yPos) ? op2.yPos : yPos;
    end1 = yPos+height;
    end2 = op2.yPos+op2.height;
    result.height = (end1>end2) ? end2-result.yPos : end1-result.yPos;
    // Falls keine gemeinsame Fläche vorhanden ist
    // leeres Rechteck setzen
    if ((result.width <= 0) || (result.height <= 0))
    {
        result.width = result.height = 0;
        result.xPos = result.yPos = 0;
    }
    return result;
}
// Vergleicht zwei Rechecke
bool Rect::operator ==(const Rect& op2) const
{
    // Rechtecke sind gleich wenn Koordinaten und Ausdehnung gleich sind
    if ((xPos == op2.xPos) && (width == op2.width) &&
        (yPos == op2.yPos) && (height == op2.height))
        return true;
    else
        return false;
}
// Prüft ab, ob das Rechteck eine Fläche besitzt
bool Rect::operator ! () const
{
    if ((width == 0) || (height == 0))
        return true;
    else
        return false;
}

// main () Funktion
int main()
{
    // Zwei Rect Objekte erstellen
    Rect *pFirstRect = new Rect(10,20,100,200);
    Rect *pSecondRect = new Rect(50,50,100,200);

    // Rechteckdaten ausgeben
    cout << "1. Rechteck:\n";
    pFirstRect->PrintRect();
    cout << "2. Rechteck:\n";
    pSecondRect->PrintRect();

    // Rechtecke vergleichen
    if (*pFirstRect == *pSecondRect)
        cout << "Rechtecke sind gleich\n";
    else
        cout << "Rechtecke sind unterschiedlich\n";

    // Erstes Objekt verschieben
    // Klammerung unbedingt beachten!
    (*pFirstRect)++;
    cout << "1. Rechteck um eins verschoben:\n";
    pFirstRect->PrintRect();

    // Neues Rechteck aus der gemeinsamen Fläche der beiden
    // Rechtecke bilden
    Rect mergedRect = *pFirstRect & *pSecondRect;
    // Abprüfen, ob beide Rechtecke eine gemeinsame Fläche hatten
    if (!mergedRect)
        cout << "Keine Überschneidung der beiden Rechtecke!\n";
    else
    {
        cout << "Rechtecke überschneiden sich! Gemeinsame Fläche:\n";
        mergedRect.PrintRect();
    }

    delete pFirstRect;
    delete pSecondRect;
}

Übung:

Erweitern Sie die in vorherigen Übung erstellte Klasse CString um den Operator ==, um zwei CString-Objekte vergleichen zu können.

Ersetzen Sie dann die beiden AddString(...) Memberfunktionen der Klasse durch entsprechendes Memberfunktionen, die den Operator + überladen.

Erstellen Sie im main() zwei CString-Objekte, wobei das erste CString-Objekt bei seiner Definition mit dem Text "Es waren" zu initialisieren ist und das zweite leer bleibt. Geben Sie beide Objekte zur Kontrolle aus.

'Addieren' Sie zum ersten CString-Objekt den Text " einmal zwei Ameisen" und zum zweiten den Text "die wollten nach Amerika reisen" hinzu. Geben Sie beide Objekte erneut aus.

Vergleichen Sie dann beide CString-Objekte mittels einer if-Abfrage auf Gleichheit und geben das Ergebnis der Abfrage aus.

Zum Schluss weisen Sie dem ersten CString-Objekt das zweite CString-Objekt zu und vergleichen nun nochmals die beiden Objekte.

Ausgangs-Strings
1.String: Es waren
2.String:

Nach Addition
1.String: Es waren einmal zwei Ameisen
2.String: die wollten nach Amerika reisen
Die Strings sind ungleich

Strings einander zugewiesen!
1.String: die wollten nach Amerika reisen
2.String: die wollten nach Amerika reisen
Die Strings sind gleich

Lösung ansehen!