C++ - Operatoren überladen
Transcription
C++ - Operatoren überladen
FB Informatik Prof. Dr. R.Nitsch C++ - Operatoren überladen Reiner Nitsch [email protected] Überladen von Operatoren FB Informatik Prof. Dr. R.Nitsch • Ziel: Die üblichen Operatoren (z.B. +, -, /, *, %, (), [], <<, >>, <=, >=, =, ==, …) auch auf Objekte eigener Klassen anwenden können. Beispiel: Rechnen mit rationalen Zahlen Zähler Nenner void main() { Bruch b1(2,3),b2(3,4),b3; const Bruch kb(4,3); b3 = b1.mal(b2); geht, weil read-only b3 = kb.mal(b1); Methode b3 = b1.mal(kb); geht nur, weil const-Parameter //oder eleganter b3 = b1 * b2; äquivalente Anweisungen b3 = b1.operator*(b2); b3 = kb * b1; b3 = b1 * kb; geht, weil read-only Methode geht nur, weil const-Parameter } Principle of least privilege 19.10.2007 Bruch::Bruch(long Z,long N):z(Z),n(N) { } ~Bruch::Bruch() { } Bruch Bruch::mal(const Bruch & b) const { Bruch tmp(z*b.z,n*b.n); return tmp; //oder besser return Bruch(z*b.z,n*b.n); } lokales temporäres Objekt; wird auf Stack erzeugt, zurückgegeben, und danach Destruktoraufruf Bruch Bruch::operator*(const Bruch &b)const { return Bruch(z*b.z,n*b.n); } Farbcodierung class Bruch { erklären! long z,n; public: Bruch(long Z=0,long N=1); ~Bruch(); Bruch mal(const Bruch &b) const; Bruch operator*(const Bruch &b) const; }; C++ Operatoren überladen 2 Überladen von Operatoren FB Informatik Prof. Dr. R.Nitsch Aufgabe: Ergänzen Sie die Klasse Bruch so, dass die folgenden Anweisungen compilieren Keine Ergänzung notwendig: Bruch b4(5); Bruch(long=0, long=1) kann auch mit nur einem Parameter aufgerufen werden! b3 = b4 * 3; b3.operator*(const Bruch&) Typumwandlungskonstruktor aufrufen implizit Fortsetzung von main b3 = b4.operator*(3).operator*(b2); b3 = b4 * 3 * b2; // OK Bruch Welche Massnahme erlaubt es, den '*'-Operator hier mehrfach anzuwenden? Bruch b3 = 3 * b4 * b2; // Fehler entspricht b3 = 3.operator*(b4) long und nicht Bruch! Abhilfe: Globale operator* Funktion 19.10.2007 C++ Operatoren überladen 3 Globale operator-Funktion FB Informatik Prof. Dr. R.Nitsch Beispiel: Globale operator* Funktion Bruch& operator*( const int i, const Bruch &b) { return Bruch( i*b.z , n ); Zugriffsschutz, weil private // deshalb Bruch::operator* aufrufen return b*i; entspricht b.operator*(i) } jetzt geht auch b3 = 3 * b4 in main Warum? besser, weil universeller, ist aber die globale Funktion Bruch& operator*( const Bruch &a, const Bruch &b) { return Bruch( a.z*b.z , a.n*b.n ); } Problem 1: In Anweisung b3 = b4 * 3; kann Compiler nun nicht mehr entscheiden, ob ::operator* oder Bruch::operator* aufgerufen werden soll. Bruch::operator* löschen Problem 2: Zugriff auf private Attribute von aussen! public Attribute friend-Deklaration 19.10.2007 ☺ class Bruch { int z,n; Bruch(long Z=0,long N=1); ~Bruch(); Bruch& operator*(const Bruch &b) const; friend Bruch& operator*(const Bruch &a, const Bruch &b); }; C++ Operatoren überladen Anwendung in main ⇒ 4 Globale operator-Funktion FB Informatik Prof. Dr. R.Nitsch Anwendung in main b3 = 3 * b4; entspricht Aufruf operator*( Bruch(3), b4 ) b3 = b4 * 3; entspricht Aufruf operator*( b4 , Bruch(3) ) 19.10.2007 C++ Operatoren überladen 5 Was ist bei der Überladung von Operatoren für Klassen zu beachten? FB Informatik Prof. Dr. R.Nitsch • Es können keine neuen Operatoren erfunden werden • Linker Operand ist immer ein Objekt der Klasse • Die Operanden-Anzahl kann nicht geändert werden; unär bleibt unär, binär bleibt binär • Der Vorrang eines Operators bleibt erhalten • Die Operatoren . , :: , .* , ?: und sizeof() sind nicht überladbar • Soll der linke Operand kein Objekt einer Klasse sein, dann muß eine Globale Operatorfunktion definiert werden. aber: der rechte Operand muß dann ein Klassenobjekt sein. Grund: für Standardtypen können die Operatoren nicht umdefiniert werden. 19.10.2007 C++ Operatoren überladen 6 Globale Operatorfunktionen FB Informatik Prof. Dr. R.Nitsch • Einsatzgebiete: – Der Operator ist binär und der linke Operator kein Objekt einer Klasse (z.B. '+', '*',...) – Operator soll für fremde Klasse überladen werden ohne die Klasse selbst zu ändern, z.B. operator<< für Klasse ostream (kommt gleich) • Globale Operatorfunktionen sind nicht möglich für – die Operatoren '=', '()', '[]' und '->' 19.10.2007 C++ Operatoren überladen 7 Überladen des Operators << für ostream-Klasse FB Informatik Prof. Dr. R.Nitsch Zweck: Ausgeben von Objekten eigener Klassen, z.B. // cout ist Objekt der ostream-Klasse cout.operator<<(b1); // oder kompakter cout << b1; 1. Realisierungsmöglichkeit: die operator<< - Methode der ostream-Klasse überladen Syntax: // in <ostream> void ostream::operator<<(const Bruch &b){ (*this) << b.z << '/' << b.n; return; Problem? Zugriffschutz aufheben mittels friend-Deklaration! } Nachteil: 19.10.2007 Eingriff in eine System-Library notwendig! Finger weg C++ Operatoren überladen 8 Überladen des Operators << FB Informatik Prof. Dr. R.Nitsch 2. Realisierungsmöglichkeit: als operator<< -Methode der Klasse Bruch Syntax: void Bruch::operator<<( ostream& os ) { os << z << '/' << n; return; } Nachteil: ungewohnte Anwendung in Aufrufumgebung b1.operator<<(cout) entspricht b1 << cout; 3. Realisierungsmöglichkeit: Globale Funktion operator<< cout << b1; void operator<<( ostream &os, const Bruch &b ){ os << b.z << '/' << b.n; return; Problem? Zugriffschutz aufheben mittels friend-Deklaration! } 19.10.2007 C++ Operatoren überladen // Anwendung // Implementation 9 Ausgeben von mehreren Objekten in einer Anweisung FB Informatik Prof. Dr. R.Nitsch cout.operator<<(b1).operator<<('\n'); cout // oder kompakter cout << b1 << '\n'; cout Realisierungsmöglichkeit: cout cout << b1 << '\n'; ostream& void operator<<( ostream &os, const Bruch &b ){ os << b.z << '/' << b.n; return return;os; Zugriffschutz aufheben mittels friend-Deklaration! } 19.10.2007 C++ Operatoren überladen // Anwendung // Implementation 10 Globale Funktion operator>> für Klasse istream Zweck: Einlesen in eigene Klassen z.B. cin.operator>>(b3); cin >> b3; // oder kompakter cin ist Objekt der istream-Klasse FB Informatik Prof. Dr. R.Nitsch Trennzeichen sind - Leerzeichen ' ', - tab '\t', - CRLF-Zchn '\n' und - CR-Zchn '\r'. istream& operator>>( istream& is, Bruch &b) { // Implementation ohne Fehlerbehandlung // Zwischen Zähler- und Nenner-Einabe werden ausschliesslich Trennzeichen erwartet. char c; long Z, N; cout << "gib Bruch: Zaehler[Trennzeichen]Nenner "; is >> Z >> N; istream&::operator>>(int&) entfernt führende Trennzeichen; liest danach alle Ziffern bis zur 1. Nicht-Ziffer Fehlerflags (ios::failbit) werden bei unerwarteten Eingaben gesetzt, z.B. keine Ziffer(n) im Puffer, wenn solche erwartet werden. Klasse istream verwaltet folgende Statusbits vom Typ iostate ios::failbit: für einfache (behebbare) Fehler ios::badbit: für schwere (nicht behebbare) Fehler ios::eofbit: Das Ende des Bitstroms wurde erreicht ios::goodbit: alles ok 19.10.2007 Methoden zum Lesen der Statusbits bool istream::good() bool istream::fail() bool istream::bad() bool istream::eof() Methoden zum Setzen der Statusbits void iostream::setstate(iostate state) void iostream::clear() C++ Operatoren überladen 11 Probleme mit istream::operator(istream&, … ) Beispiel: Tastatureingabe 1/2 1 2 FB Informatik Prof. Dr. R.Nitsch 1.e2 2 int i1=0, i2=0; char c; cout << boolalpha; cin >> i1; cout << i1; cout << cin.good(); 1 true 1 true 1 true cin >> c; cout << c; cout << cin.good(); / true 2 true . true cin >> i2; cout << i2 cout << cin.good(); 2 true 0 false Nächstes Zeichen im Puffer ist 'e' 'e' ist kein int -> ios::fail wird gesetzt und operator>>(istream&,int) beendet i2 wurde nicht geändert -> i2 bleibt 0 Um mit cin wieder einlesen zu können muss das fail-Bit gelöscht werden: cin.fail(); Damit operator>>(istream&,int) wieder int-Eingaben verarbeitet muss zusätzlich - entweder das 'e' aus dem Lesepuffer entfernt werden: cin >> c; - oder der Eingabepuffer gelöscht werden: cin.sync(); 19.10.2007 C++ Operatoren überladen 12 Globale Funktion operator>> für Klasse istream FB Informatik Prof. Dr. R.Nitsch istream& operator>>( istream& is, Bruch &b) { // Implementation mit Fehlerbehandlung // Zwischen Zähler- und Nenner-Einabe werden ausschliesslich Trennzeichen erwartet. char c; long Z, N; cout << "gib Bruch: Zaehler[Trennzeichen]Nenner "; is >> Z >> N; if(!is.good()) return is; 1. Vorbedingung // Alternative dazu if(!( is >> Z >> N ).good()) return is; is // hier ggf. zusätzliche Vorbedingungen prüfen // z.B. nur pos. Brüche oder // nur echte Brüche // im Fehlerfall: is.setstate(ios::failbit) setzt das fail-Bit b.z = Z; Alle Vorbedingungen erfüllt! Jetzt wird Objektzustand verändert b.n = N; return is; 19.10.2007 C++ Operatoren überladen 13 Globale Funktion operator>> für Klasse istream FB Informatik Prof. Dr. R.Nitsch Ebenfalls gleichwertig, aber weniger selbstkommentierend ist if( is >> Z >> N ) { … } istream → bool Erklärung: Zur impliziten Typumwandlung von istream& nach bool enthält die Klasse istream eine Konvertierungsfunktion. Diese Funktion fragt mittels des Aufrufes this->good() die Statusbits des istream-Objekts ab und erzeugt den returnWert true nur dann, wenn kein Fehlerbit gesetzt ist. Das eigentliche Einlesen ist hier ein Seiteneffekt. 19.10.2007 C++ Operatoren überladen 14 Anwendung der globalen Funktion operator>> für Klasse istream FB Informatik Prof. Dr. R.Nitsch void main() { Bruch b1(2,3), b2(3,4), b3; const Bruch kb(4,3); b3 = b1 * b2; Die Bedingung !(cin >> b3) ruft operator!() der istreamKlasse auf, der den Wert true liefert, wenn ein Fehlerbit gesetzt ist. In diesem Fall müssen die Fehlerflags gelöscht werden und der while( !(cin >> b3) ) resetcin(); Eingabepuffer geleert werden. Dieser Aufgabe übernimmt die userdefinierte globale Funktion resetcin() // oder Operatoren-Hierarchie . (oben) while( !(cin >> b3 ).good()) resetcin(); ! } >> (unten) void resetcin() { cin.clear(); cin.sync(); } 19.10.2007 löscht die Fehlerflags leert den Eingabe-Puffer C++ Operatoren überladen 15 Fall-Studie: Dynamisches Array FB Informatik Prof. Dr. R.Nitsch • Arrays in C++ – Keine Indexprüfung – Vergleich von Arrays mit == nicht definiert – Zuweisung nicht definiert (Array-Namen sind const Zeiger) – können nur elementweise ein- und ausgegeben werden. • Beispiel: Implementierung einer (dynamischen) Arrayklasse mit – Copy-Konstruktor – Indexoperator operator[], der auch den Index prüft – Kenntnis der Anzahl gespeicherter Elemente – Zuweisungs operator= – Eingabe/Ausgabe des gesamten Arrays mit operator<< und operator>> – Array-Vergleichsoperatoren operator== und operator!= 19.10.2007 C++ Operatoren überladen 16 Fall-Studie: Dynamische Array-Klasse DynArray FB Informatik Prof. Dr. R.Nitsch // DynArray für Elemente vom Typ 'float' class DynArray { Anzahl der Elemente public: defaultFunktion constructor class Array Welche hat für diese Komponente? DynArray( int arraySize=0, float value=0.0 ); // Array Welche Funktionfür hatclass diese Komponente? DynArray( const DynArray& toCopy ); // copy-constructor // destructor für class Array ~DynArray(); int getSize() const { return size; } // Überladener Zuweisungsoperator; DynArray& operator= ( const DynArray& toAssign ); // return-Wert erlaubt a1= a2 = a3 float& operator[] ( int i ); private: int size; float* pArr; // Überladener Index-Operator für nicht-const Arrays // return "by reference" erzeugt einen lvalue (read&write) Größe des Arrays Zeiger auf ein C-Array mit Elementen vom Typ float }; // DynArray 19.10.2007 C++ Operatoren überladen 17 Testanwendung DynArray void main() { DynArray float1(3); DynArray float2(0); FB Informatik Size of array float1 is 3 Prof. Dr. R.Nitsch Array after initialization: 0 0 0 // 3-element Array // 0-element Array by default // Größe cout << << << und Inhalt von float1 ausgeben "Size of array float1 is " float1.getSize() "\nArray after initialization:\n" << float1; // Größe cout << << << und Inhalt von float2 ausgeben "\nSize of array float2 is " float2.getSize() "\nArray after initialization:\n" << float2; // float1 und float2 einlesen cout << "\nInput 3 float:\n"; for( int i=0; i<3; i++) cin >> float1[i]; cout << "\nInput 5 float:\n"; for( i=0; i<5; ++i) cin >> float2[i]; Size of array float1 is 0 Array after initialization: Input 3 float: 1.1 2.2 3.3 Input 5 float: 4.4 5.5 6.6 7.7 8.8 After input, the arrays contain: float1: 1.1 2.2 3.3 float2: 4.4 5.5 6.6 7.7 8.8 // float1 und float2 ausgeben cout << "\nAfter input, the arrays contain:\n" << "float1:\n" << float1 << "float2:\n" << float2; 19.10.2007 C++ Operatoren überladen 18 Auswerten: float1 != float2 float1 und float2 sind nicht gleich Testanwendung DynArray FB Informatik Prof. Dr. R.Nitsch float3 hat die Groesse 3 Arraywerte nach der Initialisierung: 1.1 2.2 3.3 // Testen des überladenen Ungleich-Operators (!=) cout << "\nAuswerten: float1 != float2\n"; if ( float1 != float2 ) cout << "float1 und float2 sind nicht gleich\n"; Zuweisung float2 = float1 : float1: // Test des Copy-Konstruktors 4.4 5.5 6.6 7.7 // array float3 erzeugen und mit Werten 8.8 // aus float1 initialisieren float2: DynArray float3( float1 ); 4.4 5.5 6.6 7.7 8.8 // Größe und Inhalt von float3 ausgeben cout << "\nfloat3 hat die Groesse " << float3.getSize() << "\nArraywerte nach der Initialisierung:\n" << float3; // Testen des überladenen Zuweisungsoperator (=) cout << "\nZuweisung float2 = float1 :\n"; float1 = float2; // beachte, dass float1 nur aus 3 Elementen besteht. cout << "float1:\n" << float1 << "float2:\n" << float2; 19.10.2007 C++ Operatoren überladen 19 Implementation der Array-Klasse DynArray FB Informatik Prof. Dr. R.Nitsch #include "DynArray.h" // default Konstruktor für Klasse Array DynArray::DynArray( int arraySize, float value ) { if( arraySize > 0) // Wenn Array nicht leer sein soll { size = arraySize; pArr = new float[arraySize]; // Speicherplatz vom System anfordern for( int i=0; i< arraySize; i++ ) pArr[i]=value; // elementweise mit value initialisieren } else { size = 0; pArr = NULL; } } // Ende Array default Konstruktor // Destruktor für Klasse Array DynArray::~DynArray() { if (pArr!=NULL) delete [] pArr; } 19.10.2007 C++ Operatoren überladen // Speicherplatz freigeben 20 Implementation der Array-Klasse DynArray FB Informatik Prof. Dr. R.Nitsch Warum genügt hier nicht die default-Zuweisung, die elementweise kopiert? DynArray A1(3), A2(2); A1 A2[1] = 1; A1 = A2; A2 size=3 pArr size=2 pArr Heap verwitwetes Objekt Heap 0 0 size=2 0 0 A1 pArr 0 0 0 1 A2 size=2 pArr 0 1 A1 size=2 pArr Heap 0 1 A2 size=2 pArr 0 1 "flache" Zuweisung (default) "tiefe" Zuweisung. // Zuweisungsoperator // Rückgabewert ermöglicht: a1 = a2 = a3 DynArray& DynArray::operator= (const DynArray& toAssign) { if ( &toAssign != this ) { // check for self-assignment // Bei verschiedenen Array-Größen, linkes Orginal löschen, // dann Speicherplatz für neues linksseitiges Array anfordern if ( size>0 && size != toAssign.size ) { delete [] pArr; // bisherigen Speicherplatz freigeben size = toAssign. size; // Größe des neuen Objekts festlegen pArr = new float[ size ]; // Speicherplatz gemäß neuer Größe anfordern } for ( int i=0; i<size; i++ ) pArr[i] = toAssign.pArr[i]; // Array elementweise kopieren } return *this; } 19.10.2007 C++ Operatoren überladen 21 Implementation der Array-Klasse DynArray FB Informatik Prof. Dr. R.Nitsch // Copy-Konstruktor für class DynArray; // call "by reference", um nicht abbrechenden rekursiven Aufruf zu vermeiden DynArray::DynArray( const DynArray& arrayToCopy ) { size = arrayToCopy.size; pArr = new float[ size ]; // Speicherplatz für neues Array anfordern for ( int i = 0; i < size; i++ ) // Arraywerte kopieren pArr[ i ] = arrayToCopy.pArr[ i ]; } // end DynArray copy constructor // globale operator<< Funktion für class DynArray ostream& operator<<( ostream& os, const DynArray& a ) { int i; // Ausgabe aller Arrayelemente for ( i = 0; i < a.size; i++ ) { os << setw( 12 ) << a.pArr[ i ]; if ( ( i + 1 ) % 4 == 0 ) // 4 Werte pro Ausgabezeile os << endl; } // end for if ( i % 4 != 0 ) // Zeilenvorschub am Zeilenende os << endl; return os; // ermöglicht cout << x << y; } // end function operator<< 19.10.2007 C++ Operatoren überladen 22 Implementation der Array-Klasse DynArray FB Informatik Prof. Dr. R.Nitsch // Überladener Index-Operator für r/w-Arrays // gibt r/w-Referenz auf gewünschtes Element zurück // Keine Index-Bereichskontrolle float& DynArray::operator[] ( int i ) { return *(pArr + i); // Referenz auf Element mit Index i zurückgeben // oder alternativ return pArr[i]; } // Überladener operator!= bool DynArray::operator!= ( const DynArray& other ) const { if( size != other.size ) return true; for( int i=0; i<size; ++i ) { if( pArr[i] != other.pArr[i] ) return true; } return false; } // Überladener operator!= bool DynArray::operator== ( const DynArray& other ) const { return *this != other; } 19.10.2007 C++ Operatoren überladen 23 Testanwendung DynArray void main() { DynArray float1(3); DynArray float2(0); FB Informatik Prof. Dr. R.Nitsch Size of array float1 is 3 Array after initialization: // 3-element Array // 0-element Array by default // Größe cout << << << und Inhalt von float1 ausgeben "Size of array float1 is " float1.getSize() "\nArray after initialization:\n" << float1; // Größe cout << << << und Inhalt von float1 ausgeben "\nSize of array float2 is " float2.getSize() "\nArray after initialization:\n" << float2; // float1 und float2 einlesen cout << "\nInput 3 float-values:\n"; for( int i=0; i<3; i++) cin >> float1[i]; cout << "\nInput 5 float:\n"; for( int i=0; i<5; ++i) cin >> float2[i]; 0 0 0 Size of array float2 is 0 Array after initialization: Input 3 float-values: 1.1 2.2 3.3 Input 5 float-values: 4.4 5.5 6.6 7.7 8.8 After input, the arrays contain: float1: 1.1 float2: 4.4 8.8 2.2 3.3 5.5 6.6 7.7 // float1 und float2 ausgeben cout << "\nAfter input, the arrays contain:\n" << "float1:\n" << float1 << "float2:\n" << float2; 19.10.2007 C++ Operatoren überladen 25 Auswerten: float1 != float2 float1 und float2 sind nicht gleich Testanwendung DynArray FB Informatik Prof. Dr. R.Nitsch float3 hat die Groesse 3 // Testen des überladenen Ungleich-Operators (!=) Arraywerte nach der Initialisierung: cout << "\nAuswerten: float1 != float2\n"; 1.1 2.2 3.3 if ( float1 != float2 ) cout << "float1 und float2 sind nicht gleich\n"; Zuweisung float2 = float1 : float1: // Test des Copy-Konstruktors 4.4 5.5 6.6 7.7 // array float3 erzeugen und mit Werten 8.8 // aus float1 initialisieren float2: DynArray float3( float1 ); 4.4 5.5 6.6 7.7 8.8 // Größe und Inhalt von float3 ausgeben cout << "\nfloat3 hat die Groesse " << float3.getSize() << "\nArraywerte nach der Initialisierung:\n" << float3; // Testen des überladenen Zuweisungsoperator (=) cout << "\nZuweisung float2 = float1 :\n"; float1 = float2; // beachte, dass float1 nur aus 3 Elementen besteht. cout << "float1:\n" << float1 << "float2:\n" << float2; 19.10.2007 C++ Operatoren überladen 26 Testanwendung DynArray FB Informatik Prof. Dr. R.Nitsch // Testen des überladenen Gleichheitsoperators (==) cout << "\nAuswerten: float1 == float2\n"; if ( float1 == float2 ) cout << "float1 und float2 sind gleich\n"; // Testen des überladenen Index-Operators ([]) cout << "\nfloat1[4] = 1000;\n"; float1[4] = 1000; // schreibender Zugriff (rvalue) cout << "float1:\n" << float1; // lesender Zugriff (lvalue) } // end main Auswerten: float1 == float2 float1 und float2 sind gleich float1[5]=1000; float1: 4.4 5.5 6.6 1000 7.7 Press any key to continue 19.10.2007 C++ Operatoren überladen 27