Teil 10: Operator Überladung. Prof. Dr. Herbert Fischer Fachhochschule Deggendorf Prof. Dr. Manfred Beham Fachhochschule Amberg-Weiden



Ähnliche Dokumente
Programmieren in C++ Überladen von Methoden und Operatoren

Projekt 3 Variablen und Operatoren

10.4 Konstante Objekte

Was Mathematiker schon vor Jahrhunderten erfunden haben, gibt es jetzt endlich in ihrer Programmiersprache:

Angewandte Mathematik und Programmierung

C++ - Operatoren. Eigene Klassen mit neuen Funktionen

5.3 Auswertung von Ausdrücken

Vokabeln. 4.1 Benutzer definierte Datentypen. Kapitel 4. Prof. Dr. Gerhard Berendt C++ oop? SS 2002 Arbeitsblatt 4

Überladen von Operatoren

Objektorientiertes Programmieren mit C++ für Fortgeschrittene

Einführung in die Programmierung mit C++

Überblick. 7. Überladen von Operatoren

7. Übung Informatik II - Objektorientierte Programmierung

Einstieg in die Informatik mit Java

11. Übung Informatik II - Operatorfunktionen

Programmierung mit C Zeiger

9. Vektoren. (auch Felder/array)

Thema heute: Vererbung und Klassenhierarchien. Abgeleitete Klassen. Vererbung von Daten und Funktionen. Virtuelle Funktionen

C++ - Objektorientierte Programmierung Polymorphie

Themen. Statische Methoden inline Methoden const Methoden this Zeiger Destruktor Kopierkonstruktor Überladen von Operatoren

4. Objektorientierte Programmierung mit C++

C++ Klassen weitere Funktionen

PROCESSING EINE ZUSAMMENFASSUNG. Created by Michael Kirsch & Beat Rossmy

Javaprogrammierung mit NetBeans. Variablen, Datentypen, Methoden

Dr. Monika Meiler. Inhalt

Einstieg in die Informatik mit Java

C++ Teil 12. Sven Groß. 18. Jan Sven Groß (IGPM, RWTH Aachen) C++ Teil Jan / 11

Einführung in C++ Operatoren überladen (Klassen)

DAP2-Programmierpraktikum Einführung in C++ (Teil 2)

Polymorphismus 179. Function.h. #include <string>

1. Referenzdatentypen: Felder und Strings. Referenz- vs. einfache Datentypen. Rückblick: Einfache Datentypen (1) 4711 r

1. Referenzdatentypen: Felder und Strings

Vorlesungsprüfung Programmiersprache 1

4.2 Programmiersprache C

Objektorientierte Programmierung mit C++ SS 2007

Systemnahe Programmierung in C/C++

Grundlagen der Informatik

Einführung in die Informatik für Naturwissenschaftler und Ingenieure (alias Einführung in die Programmierung)

Einstieg in die Informatik mit Java

6 ZEIGER UND REFERENZEN - ALLGEMEINES

Programmiertechnik. Teil 4. C++ Funktionen: Prototypen Overloading Parameter. C++ Funktionen: Eigenschaften

1 KLASSENKOMPONENTEN. Leitideen: Klassen sind aus Datenkomponenten und Komponentenfunktionen

Implementieren von Klassen

Mapra: C++ Teil 6. Felix Gruber, Sven Groß. IGPM, RWTH Aachen. 13. Juni 2017

C++ - Objektorientierte Programmierung Konstruktoren und Destruktoren

Polymorphismus 44. Function.hpp. #include <string>

Teil 8: Dynamische Speicherverwaltung. Prof. Dr. Herbert Fischer Fachhochschule Deggendorf Prof. Dr. Manfred Beham Fachhochschule Amberg-Weiden

F Zeiger, Felder und Strukturen in C

Ausdrücke (1) Grundlegende Eigenschaften

Wintersemester Maschinenbau und Kunststofftechnik. Informatik. Tobias Wolf Seite 1 von 29

Institut für Programmierung und Reaktive Systeme. Java 2. Markus Reschke

Programmieren II Abstrakte Klassen / Virtuelle Methoden. Programmieren II Abstrakte Klassen / Virtuelle Methoden

Mapra: C++ Teil 4. Felix Gruber. 6. Mai IGPM, RWTH Aachen. Felix Gruber (IGPM, RWTH Aachen) Mapra: C++ Teil 4 6.

Felder. Gerd Bohlender. Einstieg in die Informatik mit Java, Vorlesung vom

Prüfung aus PROGRAMMIEREN (2) (C++) (WS 2002/03)

7 Funktionen. 7.1 Definition. Prototyp-Syntax: {Speicherklasse} {Typ} Name ({formale Parameter});

Repetitorium Programmieren I + II

Kapitel 13. Definition von Klassen. OOP Thomas Klinker 1

Thema heute: Vererbung und Klassenhierarchien. Abgeleitete Klassen. Vererbung von Daten und Funktionen. Virtuelle Funktionen

Repetitorium Programmieren I + II

Programmieren 1 C Überblick

C++ Teil 9. Sven Groß. 17. Juni Sven Groß (IGPM, RWTH Aachen) C++ Teil Juni / 17

Einstieg in die Informatik mit Java

Klassen. Kapitel Klassendeklaration

Überblick. 6. Konstruktor und Destruktor - obligatorische Elementfunktionen einer Klasse

C-Programmierung: Ausdrücke und Operatoren#Division.2F

Vorkurs Informatik WiSe 16/17

magnum C++ WALTER SAUMWEBER kompakt komplett kompetent

Abend 4 Übung : Erweitern von Klassen durch Vererbung

Kapitel 4. Programmierkurs. Datentypen. Arten von Datentypen. Datentypen und Operatoren Ganzzahlige Numerische Datentypen Logischer Datentyp

Datenkapselung: public / private

7.2 Dynamischer Speicher in Objekten/Kopierkonstruktor

C++ Teil 5. Sven Groß. 12. Nov IGPM, RWTH Aachen. Sven Groß (IGPM, RWTH Aachen) C++ Teil Nov / 16

11 Vererbung und Klassenhierarchie

17. Klassen. Überladen von Funktionen. Funktionsüberladung. Operator-Überladung (Operator Overloading) operatorop

C++ Teil 4. Sven Groß. 30. Apr IGPM, RWTH Aachen. Sven Groß (IGPM, RWTH Aachen) C++ Teil Apr / 16

Objektorientierte Programmierung mit C++

Hello World! Eine Einführung in das Programmieren Variablen

Dynamische Datentypen. Destruktor, Copy-Konstruktor, Zuweisungsoperator, Dynamischer Datentyp, Vektoren

Programmieren in C+ Einführung in den Sprachstandard C++ Springer-Verlag Berlin Heidelberg New York London Paris Tokyo Hong Kong Barcelona Budapest

C++ Teil 10. Sven Groß. 17. Dez IGPM, RWTH Aachen. Sven Groß (IGPM, RWTH Aachen) C++ Teil Dez / 14

9 Programmieren von Klassen

C++ Teil 5. Sven Groß. 13. Mai Sven Groß (IGPM, RWTH Aachen) C++ Teil Mai / 18

Einführung in die C++ Programmierung für Ingenieure

Ausdrücke der Programmiersprache Java

C++ - Objektorientierte Programmierung Konstante und statische Elemente

Arrays. Einleitung. Deklarieren einer Array Variablen

Wertebereich und Genauigkeit der Zahlendarstellung

Programmierung und Angewandte Mathematik

Operatoren und Ausdrücke

Verwendung von Klassen in C++

Klassen als Datenstrukturen

Programmierung und Angewandte Mathematik

Methoden. von Objekten definiert werden, Methoden,, Zugriffsmethoden und Read-Only

Transkript:

Teil 10: Operator Überladung Prof. Dr. Herbert Fischer Fachhochschule Deggendorf Prof. Dr. Manfred Beham Fachhochschule Amberg-Weiden

Inhaltsverzeichnis 10 Operator-Überladung... 3 10.1 Übersicht aller Operatoren... 3 10.2 Motivation zur Operatorüberladung... 5 10.3 Überladbare und nicht überladbare Operatoren... 5 10.4 Syntax der Operatorüberladung... 6 10.5 Beispiele zur Operator-Überladung: Die Klasse "Geld"... 8 10.5.1 Arithmetische Operatoren... 8 10.5.2 Vergleichsoperatoren... 10 10.5.3 Inkrement- und Dekrement-Operator... 11 10.5.4 Ein- und Ausgabeoperatoren... 12 10.5.5 Typumwandlungsoperatoren... 13 10.6 Spezielle Operatoren: Die Klasse Vector... 15 10.6.1 Zuweisung und Kopie... 16 10.6.2 Der Indexoperator [ ]... 17 10.6.3 Der Funktion-Operator ()... 18 10.6.4 Der Dereferenzierungsoperator ->... 18 Prof. Dr. Manfred Beham, FH Amberg-Weiden Seite 2

10 Operator-Überladung Die Verwendung der arithmetischen Operatoren +, -, * oder \ ist uns allen geläufig. Darüber hinaus kennt C++ noch eine ganze Menge weiterer Operatoren. Doch damit nicht genug, wir können Operatoren für unsere eigenen Klassen neu definieren. Zuerst werden wir uns einen Überblick aller C++ Operatoren verschaffen und Ordnung in die große Menge von verschiedenen Operationen bringen. Wir werden dann einen Mechanismus zum Überladen von Operatoren kennen lernen. Ausgehend von einem neu zu definierenden Zahlentyp der Festkommazahl werden wir alle benötigten Operatoren neu definieren. Auch elementare Vorgänge, wie Zuweisung und Initialisierung, können vom Programmierer selbst definiert werden, wenn das für spezielle Klassen nötig ist. 10.1 Übersicht aller Operatoren C++ kennt eine große Menge an Operatoren, die mit allen möglichen Sonderzeichen abgekürzt werden. Offensichtlich waren die Erfinder von C bzw. C++ schreibfaul. Anstatt sinnreiche Namen für die jeweilige Operation zu verwenden, haben sie alle Sonderzeichen verwendet, die auf der Tastatur zu finden sind. Und damit nicht genug, sie haben auch noch Kombinationen dieser Sonderzeichen verwendet. Wir können mit Hilfe der Operatoren beliebig komplexe Ausdrücke in unserem Programm aufbauen. Es gibt hier sogar Programmierer die Spaß daran haben, möglichst viele Operationen in eine Programmzeile zu schreiben, was nicht gerade die Lesbarkeit eines Programms erhöht. Zum Verständnis eines komplexen Ausdrucks sind folgende Eigenschaften eines Operators hilfreich: Die Stelligkeit eines Operators gibt die Anzahl der an der Operation beteiligten Operanden an. Es gibt: o unäre Operatoren: - (Vorzeichen), ++, --, o binäre Operatoren: +, -, *, /, &&,, o einen tertiären Operator:? : Die Priorität eines Operators legt in Ausdrücken mit verschiedenen Operatoren die Reihenfolge fest, in der die einzelnen Operationen ausgeführt werden. Bei mehreren Operatoren gleicher Priorität muß man wissen ob der Ausdruck von links nach rechts oder rechts nach links ausgewertet wird (Assoziativität). Außerdem muß man natürlich die Bedeutung eines Operators und die Datentypen auf die er angewandt werden kann kennen. Folgende Tabelle zeigt alle C++ Operatoren unter Angabe ihrer Eigenschaften und Funktionalität: Prof. Dr. Manfred Beham, FH Amberg-Weiden Seite 3

Prof. Dr. Manfred Beham, FH Amberg-Weiden Seite 4

10.2 Motivation zur Operatorüberladung Idee: Verwende die einheitliche Notation für das Verknüpfen bzw. Manipulieren von Standarddatentypen auch für Klassenobjekte. int ia = 1, ib = 2, ic; ic = ia + ib; Geld A(1.12), B(2.34), C; C = A + B; Vorteil: Programme werden übersichtlicher und verständlicher. Prinzip: Die Operator- oder Infix-Notation ist in C++ nur eine Kurzschreibweise für eine spezielle Funktions- Notation: Operatoren können deshalb ähnlich wie Funktionen über die Parametertypen überladen werden. 10.3 Überladbare und nicht überladbare Operatoren Prof. Dr. Manfred Beham, FH Amberg-Weiden Seite 5

Einschränkungen: Die Priorität und die Ausführungsreihenfolge der Operatoren kann nicht geändert werden. Binäre Operatoren bleiben binär und unäre Operatoren bleiben unär. Neue Operatorsymbole können nicht definiert werden. Ein Operand muß immer ein Klassentyp sein, d.h. man kann die Operatoren für die Standardtypen nicht umdefinieren. Operatorfunktionen dürfen keine default-parameter haben. 10.4 Syntax der Operatorüberladung Operatorüberladung: // Deklaration: RückgabeTyp operator op (Parameterliste); // Definition: RückgabeTyp [KlassenName::]operator op (Parameterliste) // Funktionsrumpf (op steht als Platzhalter für den zu überladenden Operator) Grundsätzlich hat der Programmierer die Wahl, einen Operator als Memberfunktion (Elementfunktion) einer Klasse oder als externe Funktion zu definieren. Um diesen wichtigen Unterschied zu demonstrieren, wollen wir den Operator + (Addition) für eine selbst definierte Klasse Currency (Währung) überladen und anwenden 1. Klasse "Currency" operator + als externe Funktion: class Currency //... Attribute, sonstige Methoden ; // externe Funktion (non Member of Currency) Currency operator + (Currency leftop, Currency rightop) // Funktionsrumpf int main () Currency a, b, c; //... c = a + b; // Infix Notation c = operator + (a, b); // Funktions Notation Prof. Dr. Manfred Beham, FH Amberg-Weiden Seite 6

2. Klasse "Currency" operator + als Memberfunktion: class Currency //... Attribute, sonstige Methoden ; // Memberfunktion Deklaration Currency operator + (Currency rightop) const; // Memberfunktion Definiton Currency Currency::operator + (Currency rightop) const // Funktionsrumpf, leftop *this int main () Currency a, b, c; //... c = a + b; c = a.operator+ (b); // Infix Notation // Funktions Notation Beachte: Die Infix- und die Funktions-Notation sind vollkommen gleichartig und können nebeneinander verwendet werden. Natürlich kommt uns die Infix-Notation entgegen und sollte bevorzugt werden. Syntaxübersicht 1. Externe Funktion: Ist die Funktion keine Methode der Klasse, so hat die Funktion so viele Parameter wie der Operator Operanden. 2. Memberfunktion: Ist die Funktion eine Methode der Klasse, so ist der linke Operand immer das aktuelle Objekt (this-zeiger). Ist der linke Operand, d.h. der 1. Parameter kein Klassenobjekt, so kann die Überladung nicht als Memberfunktion deklariert werden. Sie muß, falls trotzdem auf private-komponenten zugegriffen werden soll, als friend-funktion deklariert werden. Prof. Dr. Manfred Beham, FH Amberg-Weiden Seite 7

Folgende Operatoren müssen als Memberfunktionen der Klasse deklariert werden: = Zuweisung [] Indexoperator () Funktionsaufruf -> Zugriffsoperator ->* Zeigerzugriffsoperator (1. Parameter ist damit garantiert ein lvalue.) 10.5 Beispiele zur Operator-Überladung: Die Klasse "Geld" Wir werden einen neuen Zahlentyp definieren, den es so noch nicht in C++ gibt. Wir haben bisher den Datentyp float verwendet, um unseren Kontostand zu speichern. Das ist sicher sinnvoll, da der Kontostand auch krumme Beträge von DM und Pfennigen bzw. Euro und Cent annehmen kann. Der Datentyp float ergibt aber eine Genauigkeit von ca. 7 Dezimalstellen nach dem Komma, was für unsere Anwendung etwas übertrieben ist. Der Kontostand sollte durch eine sogenannte Festkommazahl repräsentiert werden, die immer nur 2 Stellen nach dem Komma berücksichtigt. 10.5.1 Arithmetische Operatoren Definition der Klasse "Geld" #include <iostream.h> class Geld private: int Euro; int Cent; void normalize (); public: // Konstruktor Geld (int eur=0, int cnt=0) : Euro(eur), Cent(cnt) normalize (); // Zusammengesetzte arithmetische Operatoren const Geld& operator += (const Geld& r) const Geld& operator -= (const Geld& r) // Zugriff auf Attribute int geteuro () const return Euro; int getcent () const return Cent; ; // externe Operator-Funktionen Geld operator + (const Geld& l, const Geld& r); Geld operator - (const Geld& l, const Geld& r); Geld operator - (const Geld& l); Die Methode "normalize" dient der normalisierten Darstellung eines Geldbetrages. Sie sorgt dafür, daß der Cent-Wert 0 Cent < 100 ist. Größere Beträge an Cent werden in ganze Euro umgerechnet. Die zusammengesetzten Zuweisungsoperatoren += und -= werden als Memberfunktionen der Klasse Geld definiert (Zuweisung!). Die "normale" Addition, Subtraktion und Negation werden als externe Funktion deklariert. Sie verwenden die bereits definierten zusammengesetzten Zuweisungsoperatoren. Prof. Dr. Manfred Beham, FH Amberg-Weiden Seite 8

Implementation der Klasse "Geld" #include "geld.h" // Normalisieren der Zahlendarstellung (0 <= Cent < 100) void Geld::normalize () if (Cent >= 100 Cent <= -100) int eur = Cent / 100; Euro += eur; Cent -= eur * 100; if (Cent < 0) Cent += 100; Euro -= 1; // Addition und Zuweisung const Geld& Geld::operator += (const Geld& r) Euro += r.euro; Cent += r.cent; normalize (); return *this; // Subtraktion und Zuweisung const Geld& Geld::operator -= (const Geld& r) Euro -= r.euro; Cent -= r.cent; normalize (); return *this; //-------- externe Funktionen -------- // Addition Geld operator + (const Geld& l, const Geld& r) return Geld(l) += r; // Subtraktion Geld operator - (const Geld& l, const Geld& r) return Geld(l) -= r; // Vorzeichen Geld operator - (const Geld& l) return Geld (0) -= l; Beachten sie bitte die Vorgehensweise: Definieren sie zuerst die zusammengesetzten Zuweisungsoperatoren als Memberfunktion: const Geld& operator += (const Geld& rechterop); Prof. Dr. Manfred Beham, FH Amberg-Weiden Seite 9

Damit können sie dann die einfachen Operatoren als externe Funktion implementieren: Geld operator + (const Geld& linkerop, const Geld& rechterop) return Geld(linkerOp) += rechterop; Der Ausdruck hinter dem return erzeugt eine namenlose Kopie des linken Operanden, addiert den rechten Operanden dazu und gibt das Ergebnis als Kopie (by value) zurück. 10.5.2 Vergleichsoperatoren Die Vergleichsoperatoren liefern als Ergebnis einen boolschen Wert. Sie sollten immer als externe Funktionen nach folgenden Muster definiert werden: Deklaration der Vergleichsoperatoren: #include <iostream.h> class Geld //... ; bool operator == (const Geld&, const Geld&); bool operator!= (const Geld&, const Geld&); bool operator < (const Geld&, const Geld&); bool operator <= (const Geld&, const Geld&); //... etc. Üblicherweise werden nur == und < implementiert und die anderen Operatoren davon abgeleitet. Implementation der Vergleichsoperatoren: #include "geld.h" bool operator == (const Geld& li, const Geld& re) return li.geteuro() == re.geteuro() && li.getcent() == re.getcent(); bool operator!= (const Geld& li, const Geld& re) return!(li == re); bool operator < (const Geld& li, const Geld& re) return li.geteuro() == re.geteuro()? li.getcent() < re.getcent() : li.geteuro() < re.geteuro(); bool operator <= (const Geld& li, const Geld& re) return li < re li == re; // etc. Prof. Dr. Manfred Beham, FH Amberg-Weiden Seite 10

10.5.3 Inkrement- und Dekrement-Operator C++ kennt spezielle Operatoren um ganze Zahlen um eins zu erhöhen (increment) oder eins zu erniedrigen (decrement). Der Incrementoperator '++' ist nichts anderes als eine verkürzte Schreibweise: int a = 5; a++; // entspricht: a = a + 1; Den In- / Decrementoperator gibt es in einer Pre- und Postfix Version. Sie unterscheiden sich lediglich durch den Rückgabewert. Folgender Programmausschnitt int a = 5; cout << a++ << endl; cout << a << endl; cout << ++a << endl; cout << a << endl; liefert die Ausgabe: 5 6 7 7 Vorüberlegung zur Überladung: Eine Unterscheidung zwischen Prefix und Postfix wird durch einen nicht verwendeten (dummy) int- Parameter ermöglicht. Die unäre Operation wird als Memberfunktion deklariert: RückgabeTyp operator++(); //Prefix: ++A RückgabeTyp operator++(int); //Postfix: A++ Prefix: Rückgabewert ist die um 1 erhöhte Zahl (vom Typ CRatio) Postfix: Rückgabewert ist die noch unveränderte Zahl Deklaration und Implementation von Increment- / Decrementoperator: (Fortsetzung) #include <iostream.h> class Geld... // Prefix Increment (plus ein Euro) const Geld& operator++ () return *this += Geld(1); // Postfix Increment Geld operator++ (int) Geld t = *this; *this += Geld(1); return t; ; Prof. Dr. Manfred Beham, FH Amberg-Weiden Seite 11

10.5.4 Ein- und Ausgabeoperatoren Strenggenommen kennt die Programmiersprache C++ gar keine Ein- und Ausgabeoperatoren. Zusätzlich zum C++ Sprachumfang gibt es noch eine sogenannte Standardbibliothek, die umfangreiche Funktionalität geschrieben in C++ zur Verfügung stellt unter anderem Ein- und Ausgabe (siehe auch Folge 13). Die standardisierte Ein- und Ausgabe erfolgt über zeichenorientierte Datenströme (streams). Die Standardbibliothek definiert zwei Basisklassen für die beiden Richtungen des Datenverkehrs: ostream: Zeichenweise Ausgabe istream: Zeichenweise Eingabe Da die einzelnen ASCII-Zeichen der Datenströme in interne Datentypen gewandelt werden müssen, stellen die beiden Klassen ostream und istream Aus- bzw. Eingabeoperatoren zur Verfügung. Da C++ selbst keinen besonderen Operator für die Ein- und Ausgabe kennt, mußte ein bestehender Operator 'mißbraucht' (überladen) werden. Man entschied sich für operator << den bitshift-left-operator zur Ausgabe und für operator >> den bitshift-right-operator zur Eingabe. Damit können wir die bereits verwendete Schreibweise zur Ein- / Ausgabe verstehen. In folgendem Programmfragment int a = 5; cout << a; handelt es sich bei cout um eine Objekt vom Typ ostream. Auf der linken Seite des Operators << steht also ein ostream, auf der rechten ein int. Der entsprechende Operator ist ebenso wie für alle anderen eingebauten Standarddatentypen bereits in der Klasse ostream wie folgt definiert: class ostream ostream& operator << (int);... Interessant ist, daß der Operator << selbst wieder einen ostream als Ergebnis liefert. Da dieser Operator links assoziativ ist, kann man mehrere Ausgaben in einem Ausdruck aneinanderreihen: int a; double b; char c; cout << a << b << c << endl; // entspricht (((cout << a) << b) << c) << endl; Überladen von << und >> für selbst definierte Klassen Idee: Die gleiche Form der Eingabe wie für die Standarddatentypen soll auch für eigene Klassen möglich sein. Beispiel: Geld a; cin >> a; cout << a; Beachten sie: Auf der linken Seite des Ausdrucks befindet sich ein Objekt vom Typ ostream bzw. istream. Deshalb kann der linke Operand kein Klassenobjekt vom Typ Geld sein und damit können Einoder Ausgabeoperatoren nicht Memberfunktionen der Klasse sein! => Ein- bzw. Ausgabeoperatoren als externe Funktionen Falls diese externe Funktion Zugriff auf private Elemente der Klasse benötigt, müssen diese Operatoren als friend der Klasse deklariert werden Prof. Dr. Manfred Beham, FH Amberg-Weiden Seite 12

Deklaration von Ein- / Ausgabeoperator: #include <iostream.h> class Geld... // Ein- und Ausgabe friend ostream& operator<< (ostream&, const Geld&); friend istream& operator>> (istream&, Geld&); ; Implementation von Ein- / Ausgabeoperator: #include "geld.h" // Ausgabe Operator ostream& operator << (ostream& os, const Geld& r) int cnt; if (r.euro >= 0 r.cent == 0) os << r.euro << '.'; cnt = r.cent; else os << '-' << -(r.euro + 1) << '.'; cnt = -(r.cent - 100); if (cnt < 10) os << '0' << cnt; else os << cnt; return os; // Eingabe Operator istream& operator >> (istream& is, Geld& r)... 10.5.5 Typumwandlungsoperatoren C++ ist eine sehr streng typorientierte Sprache. Alle Objekte, die wir in unseren Programmen verwenden, haben einen bestimmten Datentyp. Und C++ wacht penibel darüber, dass einem Objekt eines Datentyps nur gleichartige Werte zugewiesen werden. Diese strenge Regel, kann der Programmierer aufweichen, indem er sogenannte Typumwandlungs-Operatoren für seine eigenen Klassen definiert. Wie auch in anderen Bereichen ist C++ hier höchst flexibel. Grundsätzlich ist es zwar verboten Datentypen wild zu vermengen. Aber der Programmierer kann dort, wo es ihm sinnvoll erscheint, wohl definierte Umwandlungen zulassen. Prof. Dr. Manfred Beham, FH Amberg-Weiden Seite 13

Wir müssen zwei Arten der Konvertierung von Typen für eine von uns selbst definierte Klasse unterscheiden. Für unsere Klasse Geld käme zum Beispiel eine Umwandlung von und in den Standarddatentyp double in Frage: 1. andere Typen Klassentyp; mit Typumwandlungs-Konstruktor 2. Klassentyp andere Typen; mit Cast-Operator Zu 1.: Wir haben einen neuen Typ in unserem Fall die Klasse Geld geschaffen und wollen andere Datentypen in unsere Klasse wandeln. Also die Umwandlung anderer Datentypen in unsere selbst definierte Klasse Geld. Diese Umwandlung von double nach Geld kann ein passender Konstruktor, den wir dann auch Typumwandlungskonstruktor nennen, übernehmen. Er muss aus einem double -Wert ein Objekt vom Typ Geld konstruieren. Der Konstruktor hat also genau einen Parameter vom Typ double. Zu 2.: In der anderen Richtung müssen wir einen geeigneten Umwandlungsoperator schreiben, den wir auch Cast-Operator nennen. Angewandt auf ein Objekt vom Typ Geld liefert er einen double -Wert als Ergebnis zurück. Die Syntax zur Deklaration des Cast-Operators sehen sie nachfolgend. Bei der Deklaration eines Cast-Operators muss man beachten, dass der Name des Operators sich aus dem Schlüsselwort Operator und dem Zieldatentyp zusammensetzt. Eine weitere Angabe eines Ergebnistyps ist nicht nötig. Deklaration der Typumwandlungsoperatoren : (Fortsetzung) #include <iostream.h> class Geld... // Konstruktor Geld (double); // cast-operator operator double () const; ; Sobald ein geeignete Typumwandlungs-Operatoren existieren können wir vielfältig zwischen float und Geld wandeln. Dabei ist die Konstruktorschreibweise und die Cast-Operator-Schreibwiese vollkommen gleichartig: Prof. Dr. Manfred Beham, FH Amberg-Weiden Seite 14

Anwendung der Typumwandlungsoperatoren : (Ausschnitt) double d; Geld a; Geld b = 3.14; // Typumwandlungskonstruktor 3.14 -> Geld a = b + Geld (2.50); // Typumwandlungskonstruktor 2.50 -> Geld a = b + (Geld) 2.50; // Typumwandlungskonstruktor 2.50 -> Geld d = 1.1 + (double) b; // Cast-Operator b -> 3.55 d = 1.1 + double(b); // Cast-Operator b -> 3.55 d = sqrt (b); // Implizite Typwandlung b -> 3.55 d = 1.1 + b // Ups! Mehrdeutig: Geld + Geld / double + double Beachte: Generell ist es klug, Konvertierungsoperatoren sparsam einzusetzen. 1. Die meisten Umwandlungen machen gar keinen Sinn oder führen zu Informationsverlust. 2. Es kann zu Mehrdeutigkeiten kommen, falls mehrere verschiedene Typumwandlungen existieren. 10.6 Spezielle Operatoren: Die Klasse Vector Neben den bereits überladenen Operatoren der Klasse 'Geld' kann man in C++ auch sehr spezielle Operatoren überladen. Für bestimmte Klassen ist es essentiell einen eigenen Zuweisungsoperator = zu definieren. Da dies für unsere Klasse Geld nicht unbedingt nötig ist, wollen wir im folgenden eine Klasse 'Vector' zur Repräsentation von eindimensionalen Feldern von double-werten realisieren. Bevor wir uns an die Realisierung der Klasse 'Vector' machen, wollen wir einige Vorüberlegungen zur Verwendung dieser Klasse anstellen: Vektoren sollen wie andere elementare Datentypen verwendbar sein, z.b.: #include "vector.h" int main () Vector a(3); // 3 dim. Vektor Vector b(3); // 3 dim. Vektor Vector c; // 1 dim. Vektor cin >> a >> b; // Eingabe zweier 3 dim. Vektoren c = a * b; // Scalarprodukt der Vektoren und Zuweisung Die Klasse 'Vector' muß also einen Konstruktor der Art Matrix (dim) besitzen, um dynamisch den nötigen Speicher zu reservieren. Die arithmetischen Operatoren können falls definiert auf Objekte vom Typ 'Vector' angewandt werden. Ebenso muß die Zuweisung das gewünschte Ergebnis liefern. Der Zugriff auf ein Element des Vektors erfolgt mit dem [] Operator: cout << c[1]; // Element 1 (= zweites) des Vektor c Ein Teilvektor von einem Startindex bis zu einem Endindex kann wie folgt gebildet werden: c = a(1,2); // Elemente von 1 bis 2 (einschließlich) Prof. Dr. Manfred Beham, FH Amberg-Weiden Seite 15

Definition der Klasse Vector: #include <iostream.h> class Vector private: unsigned int vecdim; // Anzahl der Elemente double * vecptr; // Zeiger auf erstes Element (Feld) public: Vector (unsigned int dim = 1); // Konstruktor ~Vector (); // Destruktor //... ; 10.6.1 Zuweisung und Kopie C++ läßt dem Programmierer größtmögliche Freiheiten bei der Gestaltung seiner Objekte. Deshalb legt C++ auch nicht die Art und Weise fest, wie Objekte einander zugewiesen werden. Da nur der Programmierer die Bedeutung seiner Objekte kennt, kann auch nur er entscheiden, welches Resultat eine Zuweisung ergibt. Das macht C++ unter anderem so universell einsetzbar. Dieser Vorteil hat wie so vieles leider auch eine Kehrseite. Der Programmierer muss die Mechanismen kennen und er muss sehr sorgfältig damit umgehen. Würden wir eine Checkliste zur Klassendefinition aufstellen, dürfen folgende zwei Punkte nie fehlen: Braucht meine Klasse einen besonderen Zuweisungsoperator Benötigt meine Klasse eine spezielle Initialisierung durch Kopie Um den Unterschied zwischen Zuweisung und Kopie zu verstehen, sehen wir uns folgenden Programmausschnitt an. Vector a; Vector b(3); Vector c = b; a = b + c; // Konstruktion ohne explizite Initialisierung // Initialisierung mit spez. Konstruktor // Initialisierung durch Kopie // Zuweisung Offensichtlich erfolgt die Initialisierung nur wenn neue Objekte angelegt werden. Sie erhalten mit der initialen Zuweisung einen Startwert. Während die Zuweisung für bereits bestehende Objekte erfolgt und für ein Objekt beliebig oft wiederholt werden kann. C++ unterscheidet diese beiden Arten, da die Zuweisung im allgemeinen komplexer ist als die Initialisierung. Damit kann die Initialisierung unabhängig von der Zuweisung optimiert werden. Initialisierung durch Kopie Der Compiler sorgt standardmäßig dafür, das Kopieren eines Objekts möglich ist. Er kopiert dafür einfach alle einzelnen Elemente eines Objekts. Falls dies nicht das gewünschte Verhalten ist, kann der Programmierer den sogenannten "Copy-Konstructor" selbst definieren. Für unsere Klasse 'Vector' ist dieser Copy-Konstruktor unbedingt nötig, da eine elementweise Kopie nur den Zeiger auf die Elemente, nicht aber die Elemente selbst kopiert. Prof. Dr. Manfred Beham, FH Amberg-Weiden Seite 16

Die Deklarations-Syntax eines Copy-Constructors sollten sie sich gut einprägen. Deklaration eines Copy-Konstruktors: (Fortsetzung) class Vector public: Vector (const Vector&); ; Der Parameter des Copy-Constructors muß als Referenz übergeben werden. Ansonsten müßte bereits bei der Parameterübergabe eine Kopie des Wertes angelegt werden. Aber genau dieses Kopieren wollen wir ja gerade mit diesem Copy-Construktur definieren. Deshalb darf hier der Vector nicht als Kopie (by value), sondern muß als Referenz übergeben werden. Der Zuweisungsoperator Ähnlich wie der Operator + ist auch die Zuweisung ein zweistelliger Operator. Dem Operanden auf der linken Seite des "=Zeichens" wird der Wert der rechten Seite zugewiesen. Das ist ja bereits bekannt. Genau wie beim Operator + ist der Operand der linken Seite das Objekt auf den der Zuweisungsoperator angewandt wird. Der Wert der rechten Seite wird als Parameter übergeben. Mit diesem Wissen können wir uns an die Deklaration des Zuweisungsoperators wagen. Deklaration eines Zuweisungsoperators: (Fortsetzung) class Vector public: const Vector& operator = (const Vector&); ; Das Ergebnis einer Zuweisung ist der Wert, der gerade zugewiesen wurde. Diesen Wert kann man in Ausdrücken weiter verwenden, um z.b. Ketten von Zuweisungen zu ermöglichen. Wir dürfen in C++ in einem Ausdruck gleich mehreren Variablen einen Wert zuweisen: a = b = c = 1. Alle drei Variablen erhalten hier den Wert 1. Und das funktioniert nur deshalb, weil dieser Ausdruck von rechts nach links ausgewertet wird; und das Ergebnis der Zuweisung von der rechten Seite weiter nach links gereicht wird. Beachte: Zuweisung und Initialisierung müssen nicht für alle Klassen definiert werden. Sie werden von C++ automatisch definiert, falls wir keinen dieser Operatoren definieren. Da der Compiler aber nicht die Bedeutung eines Objekts und seiner Attribute kennt, werden stur die einzelnen Attribute elementweise kopiert. Wenn das für uns o.k. ist, können wir diese Standard-Zuweisung verwenden. Leider ergeben sich daraus oft schwerwiegende Programmierfehler: Wenn der Programmierer vergisst einen speziellen Zuweisungsoperator zu definieren. Meldet der Compiler keinen Fehler, sondern verwendet statt dessen die Standard-Zuweisung. 10.6.2 Der Indexoperator [ ] Der Indexoperator [] wird meist überladen, wenn indizierungsähnliche Operationen an Objekten, die sich aus mehreren Elementen zusammensetzen, vorgenommen werden sollen, wie z.b. der Zugriff auf ein Element unseres Vektors. Die Deklaration sieht dabei wie folgt aus: RückgabeTyp operator[](indextyp); Prof. Dr. Manfred Beham, FH Amberg-Weiden Seite 17

Der IndexTyp ist beliebig und muß nicht unbedingt ganzzahlig sein. Deklaration eines Indexoperators: class Vector public: double& operator [] (unsigned int); const double& operator [] (unsigned int) const; ; Hinweise: Der Indexoperator muß als Memberfunktion überladen werden. Über VectorObj[Index] soll auf den double-wert des entsprechenden Elements lesend und schreibend zugegriffen werden können. Der lesende Zugriff sollte auch für konstante Objekte const Vector möglich sein. Es sollten die Indexgrenzen automatisch überwacht werden. Rückgabewert soll im Fehlerfall eine Referenz auf das letzte Element des Vektors sein (willkürlich festgelegt) 10.6.3 Der Funktion-Operator () Die Operator-Funktion () ist eine Elementfunktion und kann im Gegensatz zu vielen anderen Operatoren eine beliebige Anzahl von Argumenten entgegennehmen: RückgabeTyp operator() (Parameter1, Parameter2,... ); Anwendungen für das Überladen des ()-Operators sind z.b. Der Zugriff auf mehrdimensionale Arrays in der Form Matrix(1,4,6);. Klassen als Funktionsobjekte in der Form Function less; less(x,y); Für unsere Klasse 'Vector' wollen wir diesen Operator mißbrauchen, um aus einem Vector-Objekt einen Teilvektor beginnend ab einem Startindex bis einschließlich eines Endindex zu bilden. Deklaration eines Funktionsoperators: class Vector public: Vector operator () (unsigned int von, unsigned int bis) const; ; Das Ergebnis des ()-Operators ist hier ein neuer Vektor, der by-value zurückgegeben werden muß. 10.6.4 Der Dereferenzierungsoperator -> Das Hauptanwendungsgebiet für das Überladen von -> ist die Realisierung von Smart Pointers (sicheren Zeigern). Dies sind Zeigerobjekte, die die Indirektion, d.h. den Zugriff auf Objektkomponenten oder Objekte über Zeiger sicher gestalten (keine undef. Werte, keine 0-Zeiger,...). Der Operator muß als Elementfunktion überladen werden. Der Operator ist als einstelliger Postfixoperator zu deklarieren: RückgabeTyp operator->(); Prof. Dr. Manfred Beham, FH Amberg-Weiden Seite 18

Bei der Verwendung wird A->B ggf. interpretiert als (A.operator->())->B. Beachte: Die Operatorfunktion muß entweder einen Zeiger auf ein Klassenobjekt oder ein Objekt einer Klasse, für die der Operator -> definiert ist zurückgeben. Die Interpretation von A->B läuft folgendermaßen ab: a) Ist A ein Zeiger auf ein Klassenobjekt, dann wird der "normale" Dereferenzierungsoperator verwendet. b) Ist A jedoch ein Klassenobjekt, dann wird die Operatorfunktion der entsprechenden Klasse aufgerufen. Auf das Ergebnis wird wiederum nach a) oder b) verfahren. Prof. Dr. Manfred Beham, FH Amberg-Weiden Seite 19