Überblick 7. Überladen von Operatoren
Transcription
Überblick 7. Überladen von Operatoren
Überblick 1. 2. Einführung C++ / Entwicklung/ Sprachfamilie Nicht objektorientierte Erweiterungen von C 2.1 Das Ein-/Ausgabekonzept von C++ 2.2 Referenzen in C++ 2.3 Heap-Allokatoren in C++ 3. Grundlagen des Typkonzepts von C++ 4. Ziele der Objektorientierung 5. Objekt und Klasse, Elementfunktionen (Methoden) 6. Konstruktoren, Copy-Konstruktor, Destruktor und Wertzuweisung 7. Operatoren, Überladen von Operatoren 8. Elementobjekte und Elementobjektkonstruktoren 9. Klassenhierarchien und Vererbung 10. Polymorphie 11. Abstrakte Klassen R.Grossmann / P. Sobe 1 7. Überladen von Operatoren Betrachten wir noch einmal die Methode add vektor add(vektor a) {return vektor(x+a.x,y+a.y,z+a.z);} und ihre Nutzung z.B. neu = n.add(a).add(b).add(d); Jetzt möchte man statt dieser etwas umständlichen Notation die übliche Schreibweise mit Infix-Operatoren benutzen: neu = n + a + b + d; Dies erreicht man mit dem Konzept des Überladens von Operatoren. Dazu braucht man die obige Definition nur geringfügig ändern: vektor operator +(vektor a) {return vektor(x+a.x,y+a.y,z+a.z);} R.Grossmann / P. Sobe 2 Überladen von Operatoren Demonstration einer Klasse mit klassischen Methoden und mit Operatoren zur Verdeutlichung des Unterschieds: vektor_4.cpp und vektor_5.cpp Probleme: 1. Man darf nur Operatoren überladen, die der Compiler bereits für Standardtypen kennt. Es können keinen neuen Symbole hinzugefügt werden. Beispiel Kreuzprodukt von Vektoren: Man hätte auf die Idee kommen können zu schreiben vektor operator #(vektor a){return ....} Das geht nicht, da # kein Operator ist, wie auch §, $ usw. Deshalb wurde im Beispielprogramm der Modulo-Operator % für das Kreuzprodukt verwendet. R.Grossmann / P. Sobe 3 Überladen von Operatoren Probleme (Fortsetzung): 2. Die Priorität des Operators bleibt laut Definition erhalten und kann nicht verändert werden. 3. Man darf folgende Operatoren nicht überladen: . .* :: ? : (.* bedeutet Zugriff auf ein Element als Zeiger und gleichzeitige Dereferenzierung, selten benutzt!) 4. Überladene binäre Operatoren als Elementfunktionen dürfen nur einen Parameter besitzen, eine Definition wie vektor add(float f,vektor a) {return falsch ! vektor(f*a.x,f*a.y,f*a.z);} ist verboten. Solche Überladungen sind nur als NichtElementfunktionen außerhalb der Klasse gestattet. R.Grossmann / P. Sobe 4 Überladen von Operatoren Beispiel: Überladung des <<-Operators in der Klasse ostream Die Ausgabenotation ist bekannt. int x; x=5; cout << x; Der Compiler weiß, was er tun muss. Wollen Sie aber schreiben: vektor x(-2,3,5); cout << x; dann weiß der Compiler nicht , wie er einen Vektor ausgeben soll! Dann muss der <<-Operator überschrieben werden. Dazu sind aber zwei Parameter nötig, da das cout-Objekt vom Typ ostream ist, aber x vom Typ vektor. R.Grossmann / P. Sobe 5 Überladen von Operatoren Deshalb muss der <<-Operator als Nicht-Elementfunktion außerhalb der Klasse überladen werden, zum Beispiel: // Ausgabe eines Vektors in der Form ( x , y , z ) ostream& operator << (ostream& s, vektor v) { return s << ” (”<<v.i()<< ”, ”<<v.j()<< ”, ”<<v.k()<< ” )”;} Beachten Sie dabei, dass Sie von außerhalb keinen direkten Zugriff auf die privaten Daten innerhalb vektor v haben, also auf x, y und z. Es müssen deshalb Methoden (hier i(), j() und k()) geschrieben werden, die Zugriff auf die Komponenten des vektor v zulassen. Siehe dazu Programmcode vektor_6.cpp. R.Grossmann / P. Sobe 6 Überladen von einstelligen Operatoren Einstellige Operatoren können als Präfix- oder als PostfixOperatoren entworfen werden. Beispiel: Das Vorzeichen (-,+) einer komplexen Zahl als Präfix. Präfix-Operatoren werden parameterlos überladen complex operator -() { return complex(-real(),-imag() );} complex a(-3,9),x; x = - a ; Achtung: Im Beispiel wird der Wert der komplexen Zahl nicht geändert. Es gibt auch Prefix-Operatoren, die den Wert des Objekts ändern, z.B. -- zahl; für Integer-Werte. Dann unterscheidet sich ein Prefix-Operator von einem Postfix-Operator, z.B. zahl--; R.Grossmann / P. Sobe 7 Überladen von einstelligen Operatoren Beispiel: Der Inkrementierungsoperator ++ als Postfix-Operator. Postfix-Operatoren werden mit Parameter int überladen. “int“ ist dabei nur ein Dummy-Parameter, um eine Unterscheidung zwischen Präfix-++ und Postfix-++ zu ermöglichen. Im Beispiel vektor soll der Postfix-++ Operator den alten Wert des vektor-Objektes übergeben und danach zum alten Wert den Einheitsvektor addieren. vektor operator ++( int ) { vektor h=*this; ++x; ++y; ++z; return h; } Die Bedeutung von *this wird auf den nächsten Folien erklärt! R.Grossmann / P. Sobe 8 Übersicht (1) Zweistellige Operatoren: werden mit einem Parameter definiert, der den Typ des jeweiligen zweiten Operanden (hier obj2) angibt. Benutzung: erg = obj + obj2; Definition am Beispiel vektor: vektor operator +(vektor a) {return vektor(x+a.x,y+a.y,z+a.z);} Einstellige Operatoren: Prefix-Operatoren: ++x … werden mit einer leeren Parameterliste definiert. Postfix-Operatoren: x++ … werden mit einem Dummy-“int“ in der Parameterliste definiert. R.Grossmann / P. Sobe 9 Übersicht (2) Einstellige Prefix-Operatoren: … werden mit einer leeren Parameterliste definiert. Benutzung: erg = ++obj; Definition am Beispiel vektor: vektor operator ++( ) { ++x; ++y; ++z; return *this;} Einstellige Postfix-Operatoren: … werden mit einem Dummy-“int“ in der Parameterliste definiert. Benutzung: erg = obj++; Definition am Beispiel vektor: vektor operator ++( int ) { vektor h=*this; ++x; ++y; ++z; return h; } R.Grossmann / P. Sobe 10 Der *this - Zeiger Nachdem wir den <<-Operator als Nicht-Elementfunktion außerhalb der Klasse überladen haben, wollen wir diesen gleich in der Elementfunktion drucke() so verwenden. Da aber die Definition des Überladens ostream& operator << (ostream& s, vektor v) .... in der Klasse nicht bekannt ist, müssen wir drucke() auch außerhalb, hinter der ostream& operator << -Definition plazieren: void vektor::drucke(){ cout << “ \n “<< ??? ; } An der Stelle ??? müsste eigentlich das auszugebende Objekt stehen! Wie heißt aber das Objekt in der Klasse selbst? Dafür wurde der this-Zeiger eingeführt. Durch Dereferenzierung kommt man zum Wert des Objekts: void vektor::drucke(){ cout << “ \n “<< *this ; } R.Grossmann / P. Sobe 11 Der *this - Zeiger Erklärung zu Programm vektor_7.cpp! Da in jeder Klasse zwar die Datenstruktur (private-Teil) definiert ist, aber das Objekt als Ganzes innerhalb der Klasse keinen Namen hat, wurde dafür der this-Zeiger eingeführt. Über *this kann man auf den Wert des Objektes zugreifen. Der this-Zeiger kann auch bei Element-Funktionen eingesetzt werden, die sonst den Typ void zurückbringen würden. Durch return *this; kann jetzt der klassenspezifische Typ zurückgegeben werden. Beispiel: Klasse complex Sie haben eine Methode konj(), die den konjugiert komplexen Wert des Objektes erzeugen soll und man kann in der Klasse complex definieren void konj() {i=-i;} // i sei der Imaginärteil in der Klasse R.Grossmann / P. Sobe 12 Der *this - Zeiger Besser wäre die Nutzung des this-Zeigers , also: statt void konj() {i=-i;} kann man schreiben complex konj() { i=-i; return *this; } Das ist wesentlich eleganter, da jetzt die Methoden auf Objekte mehrfach hintereinander ausgeführt werden können, da sie den Klassentyp zurückbringen, was wir bereits bei den Element-Funktionen add, sub,... erörtert haben ! Hinweis: Das Programm vektor_8.cpp implementiert einige interessante Methoden, Einheitsvektor, Parallelität, Senkrechtstehen und die Gleichheit von Vektoren unter Nutzung des thisZeigers. R.Grossmann / P. Sobe 13