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