Überladen |
[Bruchrechnung mit überladener Funktion (bruch.cpp)]
class tBruch
{
public:
tBruch() {zaehler=0; nenner=1;}
long GetNenner() {return nenner;}
long GetZaehler() {return zaehler;}
void SetNenner(long p) {if (p!=0) nenner=p;}
void SetZaehler(long p) {zaehler=p;}
void Addiere(long);
void Addiere(tBruch);
void Show();
private:
long zaehler;
long nenner;
};
void tBruch::Addiere(long summand)
{
zaehler+=summand*nenner;
}
void tBruch::Addiere(tBruch summand)
{
zaehler = zaehler*summand.GetNenner()
+ summand.GetZaehler()*nenner;
nenner = nenner * summand.GetNenner();
}
Welche der Additionsfunktionen verwendet wird, entscheidet der Compiler anhand der verwendeten Parameter beim Aufruf.
new + % ~ > /= |= <<= >= -- () delete - ^ ! += %= << == && , [] new[] * & = -= ^= >> != || ->* delete[] / | < *= &= >>= <= ++ ->
tBruch tBruch::operator+(long summand); // Elementfunktion Bruch = Bruch.operator+(Long); // Aufruf Bruch = Bruch + Long; // Aufruf
Benötigt der Operator einen zweiten Operanden, wird er als Parameter übergeben. Das Beispiel zeigt eine Addition. Diese hat immer zwei Operanden. Also wird der zweite Operand als Parameter der Funktion operator+() übergeben, hier also vom Typ long. Der Anwender einer Addition wird erwarten, dass er die beiden Parameter beliebig tauschen kann. Dann wäre der erste Operand allerdings long und damit ein Standardtyp für den es natürlich keine Klasse gibt. In solch einem Fall muss eine globale Operatorfunktion geschrieben werden. Diese Funktion hat dann zwei Parameter:
tBruch operator+(long, const tBruch& o2); // globale Funktion Bruch = operator+(Long, Bruch); // Aufruf Bruch = Long + Bruch; // Aufruf
Liebgewonnene Zusammenhänge gelten nicht automatisch, wenn Operatoren überladen werden. So ergibt sich aus dem Überladen des Operators + nicht, dass sich += aus dem Aufaddieren des zweiten Operanden ergibt. Wollen Sie, dass diese Regel auch für Ihren Datentyp gilt, so müssen Sie explizit den +=-Operator implementieren. Das Gleiche gilt für ++.
[Bruchklasse mit überladenen Operatoren]
#include <iostream>
using namespace std;
class tBruch
{
public:
tBruch() {zaehler=0; nenner=1;}
long GetNenner() {return nenner;}
long GetZaehler() {return zaehler;}
void SetNenner(long p) {if (p!=0) nenner=p;}
void SetZaehler(long p) {zaehler=p;}
tBruch operator+(long);
tBruch operator+(tBruch);
void Show();
private:
long zaehler;
long nenner;
};
tBruch tBruch::operator+(long summand)
{
zaehler+=summand*nenner;
return *this;
}
tBruch tBruch::operator+(tBruch summand)
{
zaehler = zaehler*summand.GetNenner()
+ summand.GetZaehler()*nenner;
nenner = nenner * summand.GetNenner();
return *this;
}
Sie können die Funktion durchaus direkt aufrufen. Dazu benötigen Sie eine Objekte vom Typ tBruch, die das Ergebnis aufnimmt, ein Objekt vom Typ tBruch, über die die Funktion aufgerufen wird, und ein Objekt vom Typ tBruch oder long, die als zweiter Operand dient.
tBruch Summe, Summand1, Summand2; Summe = Summand1.operator+(Summand2); Summe = Summand1 + Summand2;
Die beiden letzten Zeilen sind äquivalent. Der erste Operand wird durch die Klasse bestimmt, deren Elementfunktion operator+() ist. Der zweite Operand wird durch den Typ des Parameters bestimmt. Der Rückgabetyp der Funktion operator+() legt den Typ der Variablen fest, der das Ergebnis zugewiesen wird.
[Überladen des Operators]
class tDatum
{
public:
tDatum operator+(int Tage);
};
tDatum tDatum::operator+(int Tage)
{
// Berechnung des Datums
return *this;
}
Die Addition liefert ein neues Datum zurück, und als rechter Operand ist eine ganze Zahl zulässig. So kann das Datum durch einfache Addition um 14 Tage weitergeschoben werden.
[Verwendung des Operators]
tDatum heute; heute = heute + 14;
Vielleicht ist es Ihnen auch so gegangen, dass Sie spontan überlegt hatten, statt dieser Zeile heute += 14 zu schreiben. Der Operator += ist aber gar nicht definiert. Das wäre jedoch konsequenterweise zu fordern, wenn bereits der Operator + definiert ist. Dasselbe gilt natürlich auch für den Operator ++.
tBruch operator+(long o1, const tBruch& o2)
{
tBruch summe;
summe.SetZaehler(o2.GetZaehler()+o1*o2.GetNenner());
summe.SetNenner(o2.GetNenner());
return summe;
}
class tBruch
{
...
// die globale Funktion operator+ darf auf
// Elementvariablen zugreifen
friend tBruch operator+(long o1, const tBruch& o2);
...
};
tBruch operator+(long o1, const tBruch& o2)
{
tBruch summe;
summe.zaehler = o2.zaehler+o1*o2.nenner;
summe.nenner = o2.nenner;
return summe;
}
a = 5; a = 5; b = ++a; b = a++; // b==6 // b==5
Um Präfix und Postfix in der operator++-Definition zu unterscheiden, erhält die Postfix-Variante zusätzlich einen Integer-Parameter. Dieser wird aber tatsächlich überhaupt nicht ausgewertet. Die Postfix-Operation benötigt immer eine lokale Variable zum Sichern des alten Stands, der ja nach Erhöhung der Variablen noch zurückgegeben werden muss. Daraus ergibt sich eine geringfügig bessere Performance der Präfix-Variante.
[Inkrementieren mit Präfix und Postfix (bruch.cpp)]
class tBruch
{
tBruch& operator++(); // Präfix
tBruch operator++(int); // Postfix
...
};
tBruch& tBruch::operator++()
// Praefix-Inkrement
{
// berechne den neuen Bruch
// geht auch: *this = *this + 1;
zaehler += nenner;
return *this;
}
tBruch tBruch::operator++(int)
// Postfix-Inkrement
{
tBruch oldBruch =*this; // alten Stand sichern
zaehler += nenner; // Variable erhöhen
return oldBruch; // return alten Stand
}
Der Rückgabewert der Funktion operator++ ist das Ergebnis der Auswertung. Der Postfix-Operator darf keine Referenz zurückgeben, da ansonsten eine Referenz auf eine lokale Variable entstünde, die sofort nach dem Verlassen der Funktion ungültig würde.
Hier steht im Buch die Abbildung "Kopieren von Objekten" (abbobjkopie2).
[Zuweisungsoperator definieren (zuweis.cpp)]
#include <iostream>
using namespace std;
class tAuto
{
public:
tAuto() { Kennzeichen=0; Raeder=4; }
~tAuto() { delete Kennzeichen; Kennzeichen = 0; }
void SetKennzeichen(char *a);
char *GetKennzeichen() const { return Kennzeichen; }
tAuto(const tAuto& k); // Kopierkonstruktor
tAuto &operator=(const tAuto &k); // Zuweisungsoperator
private:
// Zwei private Funktionen für den Umgang mit den Strings
int StringLaenge(char *s)
{
int len=0;
while (*s++) len++;
return len+1;
}
void StringKopie(char *Quelle)
{
char *Ziel = Kennzeichen;
while (*Quelle) *Ziel++ = *Quelle++;
*Ziel=0; // Abschluss-Null
}
char *Kennzeichen; // Zeiger auf externen Speicher
int Raeder; // Normale Elementvariable
};
// Normale Belegung des Kennzeichens
void tAuto::SetKennzeichen(char *Kz)
{
if (Kennzeichen) delete Kennzeichen; // Speicher freigeben
int len = StringLaenge(Kz); // Größe bestimmen
Kennzeichen = new char[len]; // Neuen Speicher anfordern
StringKopie(Kz); // Daten übernehmen
}
// Kopierkonstruktor
tAuto::tAuto(const tAuto& k)
{
cout << "Kopierkonstruktor" << endl;
int len = StringLaenge(k.GetKennzeichen()); // Größe?
Kennzeichen = new char[len]; // Neuen Speicher anfordern
StringKopie(k.GetKennzeichen()); // Daten übernehmen
Raeder = k.Raeder; // Alle anderen Daten kopieren
}
// Zuweisungsoperator
tAuto &tAuto::operator=(const tAuto &k)
{
if (this != &k) // wenn es keine Zuweisung an sich selbst ist
{
cout << "Zuweisung:" << k.GetKennzeichen() << endl;
delete Kennzeichen; // lösche bisherige Daten
int len = StringLaenge(k.GetKennzeichen()); // Größe?
Kennzeichen = new char[len]; // Neuen Speicher anfordern
StringKopie(k.GetKennzeichen()); // Daten übernehmen
Raeder = k.Raeder; // Alle anderen Daten kopieren
}
return *this; // zurück mit dem aktuellen Objekt
}
// Zum Test von Kopierkonstruktor und Zuweisungsoperator
tAuto Uebergebe(tAuto para)
{
cout << "Funktion:" << para.GetKennzeichen() << endl;
return para;
}
int main()
{
tAuto Objekt;
tAuto Ziel;
Objekt.SetKennzeichen("HG-AW 409");
Ziel = Uebergebe(Objekt);
cout << Objekt.GetKennzeichen() << endl;
cout << Ziel.GetKennzeichen() << endl;
}
[Gleichheitsoperator bei tBruch]
bool tBruch::operator==(tBruch vgl)
{
kuerze();
vgl.kuerze();
return (zaehler==vgl.zaehler && nenner==vgl.nenner);
}
Für die Klasse tDatum würde einfach nur verglichen, ob Tag, Monat und Jahr übereinstimmen. Bei einer Klasse mit Zeigern würde der Inhalt der Speicher verglichen, auf den die Zeiger verweisen.
[Ungleichheit]
class tBruch
{
...
bool operator==(tBruch op2);
bool operator!=(tBruch op2)
{
return !(*this == op2);
}
...
};
Auf ähnliche Weise lässt sich der Operator >= aus der Negation des Operators < erzeugen.
[Ausgabe eines Bruchs (bruch.cpp)]
ostream& operator<<(ostream& Stream, const tBruch& B)
{
return Stream << B.GetZaehler() << "/" << B.GetNenner();
}
[Konstante Funktionen (bruch.cpp)]
class tBruch
{
...
long GetNenner() const {return nenner;}
long GetZaehler() const {return zaehler;}
...
};
[Konstante Funktionen (bruch.cpp)]
class tBruch
{
...
friend ostream& operator<<(ostream&, const tBruch&);
friend istream& operator>>(istream&, tBruch&);
...
};
ostream& operator<<(ostream& Stream, const tBruch& B)
{
return Stream << B.zaehler << '/' << B.nenner;
}
[Safer String (safestr.cpp)]
#include <iostream>
using namespace std;
class tSafeString // String-Klasse mit abgesichertem Index
{
public:
tSafeString(int len) // Max. Länge muss vorgegeben werden
{
maxLen = len;
safestr = new char[len]; // Externe Speicheranforderung
mist = 0; // Dummy
}
~tSafeString()
{
delete[] safestr;
safestr=0;
}
char& operator[](int i);
// In einer realen Klasse müsste auch ein Kopierkonstruktor
// und ein Zuweisungsoperator implementiert werden.
private:
char *safestr; // der String ist extern!
char mist; // Dummy fuer Fehlzugriffe
int maxLen;
};
char& tSafeString::operator[](int i)
{
if (i<maxLen && i>=0)
{
return *(safestr+i); // Buchstabe gefunden!
}
return mist; // return 0 wegen Referenz nicht möglich!
}
int main()
{
tSafeString str(6); // 6 Zeichen, von 0 bis 5
str[5] = 'A'; // Schreiben
char c = str[5]; // Lesen
cout << c << endl; // Test, ob es wirklich ein 'A' ist
}
Sofern der Index korrekt ist, liefert die Operatorfunktion eine Referenz auf das passende Element des internen Strings safestr. Dieses Element kann dann ausgelesen oder belegt werden, je nachdem, auf welcher Seite des Zuweisungsoperators der Ausdruck steht. Für den Fall, dass der Index außerhalb des zulässigen Bereichs liegt, wird die Elementvariable mist verwendet. Die Funktion kann nicht einfach 0 zurückgeben, weil der zurückgegebenen Referenz gegebenenfalls ein Wert zugewiesen werden soll.
KfzKennzeichen["HH"] = "Hansestadt Hamburg";
Auf diese Weise funktioniert auch der STL-Container map.
[Aufrufoperator]
class tRufMich
{
public:
int operator() (int)
{ /* tu was */ }
...
};
Die Verwendung dieses Operators sieht erwartungsgemäß wie ein Funktionsaufruf auf ein Objekt aus. Hat die Funktion keine Rückgabewerte, könnte man den Aufruf auch mit einem Konstruktoraufruf verwechseln. Von Letzterem unterscheidet sich der Aufrufoperator vor allem darin, dass er nicht bei der Initialisierung des Objekts aufgerufen wird. Das folgende Beispiel zeigt den Aufruf eines Konstruktors und eines Aufrufoperators.
[Aufrufoperator]
tRufMich ruf(23); // Aufruf des Konstruktors ... ruf(23); // Aufruf des Funktionsoperators
class tBruch
{
public:
operator double()
{
return double(zaehler)/double(nenner);
}
...
|
Diese Seite basiert auf Inhalten aus dem Buch
Arnold Willemer: Einstieg in C++ Mit freundlicher Genehmigung und Unterstützung des Verlags galileo computing |
| Informatik-Ecke Einstieg in C++ |
(C) Copyright 2005 Arnold Willemer
|