8. Objektorientierung und C++ Inhalt: 8.1 Objektorientierung 8.2 Abstrakte Datentypen 8.3 Ziele der Objektorientierung 8.3 C++ - Erweiterungen 8.4 C++ - Klassen, Objekte und Methoden 8.5 C++ - Vererbung und Polymorphie Peter Sobe 1 8.1 Objektorientierung Objektorientierte Programmierung kann auf verschiedene Weise erfolgen: Prozedurale Sprachen in Verbindung mit abstrakten Datentypen Objektorientierte Sprachen, welche die Ziele der Objektorientierung durch eine spezielle Syntaxerweiterung und Verfahren bei der Übersetzung und Laufzeitunterstützung der Programme unterstützen R. Grossmann / P. Sobe 2
Objektorientierung Mit dem objektorientierten Programmier-Paradigma wird ein besonderes Herangehen bei der Modellierung eines Realitätsausschnittes praktiziert. Man sucht dort Entitäten (Autos, Mitarbeiter, Rechnungen, elektr. Bauelemente, mechanische Elemente,...), die bei Verarbeitungsprozessen eine Rolle spielen. Zu einer bestimmten Zeit wird jeweils eine Menge von Entitäten einer bestimmten Sorte, wie Autos oder Personen vorhanden sein. Vergleich mit imperativen, prozeduralem Programmierparadigma: Bisher wurde bei der Modellierung eines Realitätsausschnittes stets nach Verarbeitungsprozessen gesucht und deren Abfolge und Verflechtung als Algorithmus (Steuerfluss) entwickelt und formal beschrieben. R. Grossmann / P. Sobe 3 Objektorientierung Entitätsbegriff - Datenelemente Entität: ist ein in der Realität existierendes und eindeutig identifizierbares Exemplar (Ding, Lebewesen, Gedankenkonstrukt) als Träger von Eigenschaften (Datenelemente), die durch bestimmte Werte beschrieben sind. Beispiel Auto als Entität: Eigenschaft Typ Wert VW-Golf, Eigenschaft Eigentümer Wert Lehmann, Eigenschaft Hubraum Wert 1943, Eigenschaft Tankinhalt Wert 60... Jedes weitere Auto der betrachteten Menge hat die gleichen Eigenschaften, die aber mit anderen Werten belegt sind. R. Grossmann / P. Sobe 4
Grundlagen Methodenbegriff - Elementfunktion Sind alle relevanten Entitätsmengen im Realitätsausschnitt identifiziert, überlegt man, welche Operationen man auf den jeweiligen Entitätsmengen (Autos, Personen, Matrizen,...) benötigt. So wird man Matrizen sicher addieren und multiplizieren wollen, von Personen vielleicht das Alter bestimmen, oder ob sie in Beziehung zu anderen Entitäten stehen (Eigentümer eines Autos). Methode/Elementfunktion: ist eine wohldefinierte Operation / Funktion, die man auf Elemente einer bestimmten Entitätsmenge anwenden darf. Für jede Entitätsmenge ist eine bestimmte Menge solcher Elementfunktionen definiert und ausschließlich an die Anwendung nur auf Elemente dieser Menge gebunden. R. Grossmann / P. Sobe 5 Objektbegriff Die Zusammenfassung von Datenelementen und der über diesen Elementen definierten Elementfunktionen führt zum Objektbegriff. Objekt: ist eine in der Realität existierende Entität, die auf eine Menge von Elementfunktionen zurückgreifen kann. Objekte agieren, indem sie sich gegenseitig Nachrichten senden können, die durch Elementfunktionen bearbeitet werden. Den Bauplan eines jeden Objektes nennt man Typ oder Klasse. R. Grossmann / P. Sobe 6
8.2 Abstrakte Datentypen Bei der Entwicklung größerer Programme wird eine Modularisierung verlangt. Das bedeutet eine Aufteilung des Programmcodes im mehrere Quelldateien (Dateityp/endung.c oder.cpp). Teile eines Programms (Module) werden aus Daten und Funktionen gebildet: Lokale Daten werden dann nur innerhalb der Programmteile benutzt und sind vor den Zugriff von anderer Stelle verborgen Das Programmteil wird ausschließlich über Funktionen (Schnittstellen) angesprochen. Die Funktionen werden in einer Header Datei (Dateityp.h) für andere Programmteile bekannt gemacht. Das ist bereits eine Variante der Objektorientierung. Peter Sobe 7 Abstrakte Datentypen Ein solches Modul, das lokale Daten besitz und Funktionen zum Zugriff bzw. zur Manipulation der lokalen Daten nach außen bereitstellt wird auch abstrakter Datentyp genannt. Beispiel eines Interfaces single_timer.h : void timer_init(); char start(); // starts a timer, returns 1 when successful, otherwise 0 char stop(); // stops a timer, returns 1 when successful, otherwise 0 char get_elapsed_time(int *n_mikrosecs); // returns 1 when a timer has been started, // 0 when timer has not been started, // 2 when timer has been started and stopped Peter Sobe 8
Abstrakte Datentypen Innerhalb single_timer.c wird nun der Startzeitpunkt und der Stopzeitpunkt als lokale Variablen deklariert. Durch Aufruf von init_timer(), start(), stop() und get_elapsed_time(µsecs) kann ein anderes Modul, bzw. das Hauptprogramm den abstrakten Datentypen benutzten. Man bildet ein Objekt Timer nach. Problem: Bislang ist nur ein Timer verfügbar. Was macht man wenn man mehrere unabhängige Zeiten messen möchte? Peter Sobe 9 Abstrakte Datentypen Abstrakte Datentypen erlauben oft dem Benutzer, mehrere Exemplare d.h. Objekte eines Typs zu erzeugen und über Funktionen anzusprechen. Beispiel eines Interfaces multi_timer.h: timer_id create_timer(); char start(timer_id id); // starts the timer with the specified id, // returns 1 when successful, otherwise 0 // returns -1, if id does not specify a valid timer char stop(timer_id id); char get_elapsed_time(timer_id id, int *n_mikrosecs); char destroy_timer(timer_id id); Peter Sobe 10
8.3 Ziele der Objektorientierung Es sollen kurz die Hauptziele angegeben werden, durch die die Nutzung objektorientierter Sprachen charakterisiert wird: Datenabstraktion: Definition und Verwendung anwendungsbezogener Datentypen und der auf ihnen möglichen Operationen. Datenkapselung: Vereinbarung von Daten und den Prozeduren, die diese Daten verwalten, in einer Programmeinheit. Zugriffe sind über Zugriffsspezifizierer einschränkbar. In der Regel sind die Datenelemente eines Objektes vollständig gekapselt (private) und für den Nutzer nicht einsehbar. Nur über die Elementfunktionen (public) kann der Nutzer das Objekt manipulieren. R. Grossmann / P. Sobe 11 Ziele der Objektorientierung Trennung von Implementierung und Applikation: Durch die Datenkapselung wird es möglich, dass der Programmierer Freiheiten beim Entwurf der (gekapselten) Datenelemente hat. Er ist nur an die Signatur und die äußere Wirkung der Elementfunktionen gebunden. Der Nutzer des Typs benötigt in seiner Applikation ausschließlich das Interface, das die Elementfunktionen bilden. Damit ist eine Applikation vollkommen unabhängig von konkreten internen Kodierungen. Aus Nutzersicht liegt also ein Abstrakter Datentyp (ADT) vor. Er kann nicht mehr zwischen verschieden implementierten Datentypen unterscheiden, da er nur das äußere (identische) Verhalten sieht. R. Grossmann / P. Sobe 12
Ziele der Objektorientierung Spezialisierung und Generalisierung (Vererbung): Durch das Prinzip der Spezialisierung wird es möglich, aus einem Datentyp einen weiteren Datentyp abzuleiten, der viele Gemeinsamkeiten mit ersterem hat und nur in einigen Details abweicht. Der abgeleitete Datentyp erbt Datenstruktur und vererbbare Elementfunktionen vom Basisdatentyp. Es müssen folglich nur die Abweichungen (Spezialisierung) angegeben werden, was den Kodierungsaufwand und die mögliche Fehlerquote stark senkt. Polymorphismus: Das ist in Klassenhierarchien mit Vererbung die Fähigkeit, dass dieselbe Nachricht (Elementfunktion) gesendet an Objekte unterschiedlicher Typen, unterschiedliche Aktionen auslösen kann. R. Grossmann / P. Sobe 13 Ziele der Objektorientierung Überladen von Operatoren: Durch das Prinzip des Überladens von Operatoren ist es möglich, die in einem Namensraum eines Datentyps bekannten Operatoren (z.b. +,-,*,/,...) in einem anderen Datentyp mit zu benutzen, dort aber mit einer anderen Bedeutung. So könnten z.b. die aus den Datentypen int, float usw. bekannten arithmetischen Operatoren auch in einem Datentyp complex verwendet werden. Durch das Überladen des Operators müssen aber die Rechenregeln für den Operator im neuen Typ bekannt gemacht werden. R. Grossmann / P. Sobe 14
Möglichkeiten der Programmierung mit C++ Die Ziele werden durch die Sprache C++ unterstützt. Was ist jetzt möglich? Beispiel 1: Klassenhierarchie geometrischer Objekte Beispiel 2: Klassen mit Operatoren am Beispiel der Klassen Person und Verein Es entsteht ein anderer Programmierstil. Dieser wird durch Festlegung der Klassen und deren Beziehungen dominiert. Objektorientierter Entwurf statt prozeduraler Entwurf. R. Grossmann / P. Sobe 15 Möglichkeiten der Programmierung mit C++ Bespiel 1: Klassenhierarchie geometrischer Objekte Spezielle Klassen punkt, rechteck, kreis oder dreieck sind durch Vererbung von einer allgemeinen Klasse geofigur abgeleitet. Eine abstrakte Klasse geofigur definiert die Menge gemeinsamer Methoden für alle speziellen Objekte, zum Beispiel void zeichne(hdc &hdc); als einheitliches Interface, um das Objekt in einem Fenster zu zeichnen. Objekte anlegen: figuren[0] = new punkt(100.0, 100.0); figuren[1] = new punkt(200.0, 200.0); figuren[2] = new punkt(300.0, 300.0); figuren[3] = new kreis(punkt(400.0,400.0),10.0); figuren[4] = new rechteck(450.0,250.0,80.0,150.0); figuren[5] = new kreis(punkt(400.0, 120.0), 80.0); figuren[6] = new dreieck(200.0, 100.0, 300.0, 100.0, 250.0, 180.0 ); anz_figuren=7; R. Grossmann / P. Sobe 16
Möglichkeiten der Programmierung mit C++ In der Paint-Funktion einer WIN32-Anwendung: for(int i=0; i<anz_figuren; i++) figuren[i]->zeichne(hdc); // figuren[i] ist ein geofigur* Peter Sobe 17 Möglichkeiten der Programmierung mit C++ Beispiel 2: Klassen mit speziellen Operatoren Person schulze( Robert, Schulze,1956, Kaninchenzucht ); meier( Klaus, Meier,1976, Schachspiel ); gartenfreunde = gartenfreunde+meier ; gartenfreunde = gartenfreunde+schulze; gartenfreunde.aufloesung(); Peter Sobe 18
8.3 C++ Erweiterungen C++ stellte einige Erweiterungen bereit, die auch bei nichtobjektorientierter Programmierung benutzt werden können. streambasierte Ein- und Ausgabe Referenzen Speicherallokation und Freigabe mit new und delete (wird später eingeführt) Peter Sobe 19 Streambasierte Ein- und Ausgabe I/O-Objekte cin, cout und cerr mit Operatoren <<, >> Der folgende Programmausschnitt zeigt die typische Nutzung: #include <iostream.h> void main() { int i; float f=7.5; char c[20]= i+f ist gleich ; cout<< \nprogrammstart ; cout<< \neingabe i= ; cin>>i; cout<<c<<i+f<< \n ; } Bemerkenswert ist das Fehlen von Formatierungen (es wird mit Standards gearbeitet) und die Nutzung von Operatoren >> bzw. << für die Ein- bzw. Ausgabe. Peter Sobe 20
Referenzen (1) Eine Referenz ist ein alternativer Name (Alias) für ein Objekt. Die Hauptanwendungen in C++ sind: die Angabe von Argumenten (Parameterkonzept) und Rückkehrwerten bei Funktionen und beim Überladen von Operatoren in Klassen In C++ ist die Schreibweise X& und bedeutet Referenz auf Typ X. Beispiel:... int i=1; int& r =i; // r ist ein zweiter Name für i Peter Sobe 21 Referenzen(2) Aus C sind die beiden Parametervermittlungsarten: call by value und call by reference bekannt. Call by reference wird dabei über Zeiger realisiert. Das ist auch in C++ so möglich. Eine weitere Form des call by reference ist aber die Verwendung von Referenzen anstelle von Zeigern (vgl. Demo-Programme). Peter Sobe 22
Referenzen für Call-by-Reference Parameter void main() { int n1=2, n2, n3; int& r=n2; n2=n3=0; plus_minus(n1, n2, n3); cout <<"\n1="<<n1<<" n2="<<n2<<" n3="<<n3<<endl; //mit Referenz r das gleiche Resultat n2=n3=0; plus_minus(n1, r, n3); cout <<"\n1="<<n1<<" n2="<<n2<<" n3="<<n3<<endl; } void plus_minus(int a, int& b, int& c) { b=a+1; c=b-1; } Peter Sobe 23 8.4 C++ - Klassen, Objekte und Methoden Klassen sind der Bauplan für Objekte und müssen definiert werden. Eine Klassendefinition ist vergleichbar mit einer Type-Definition. class klassenname { private: public: Später können Objekte aus der Klasse erzeugt werden, vergleichbar mit Variablen, die aus eine Typ erzeugt werden. klassenname objekt1; // statisch Deklaration der Datenelemente Deklaration oder Definition der Elementfunktionen objekt2 = new klassenname;.. delete objekt2; // dynamisch Peter Sobe 24
Deklaration der Datenelemente Allgemein dürfen im Deklarationsteil von Datenelementen alle in C/C++ bekannten Standardtypen (auch Zeiger, Felder, Strukturen usw. benutzt werden. Sind neue Typen über Klassen definiert, dürfen diese Typen dann auch im Deklarationsteil anderer Klassen verwendet werden. Diese bezeichnet man dann als Elementobjekte. Da eine Klasse einen eigenen Gültigkeitsbereich für Namen bildet, sind Datenelemente lokal bezüglich der Klasse und nur Elementfunktionen können Zugriffe auf diese auslösen. Innerhalb einer Klasse darf niemals eine weitere Klassendeklaration vorgenommen werden. R.Grossmann / P. Sobe 25 Deklaration / Definition / Aufruf von Elementfunktionen Elementfunktionen sind einer Klasse zugeordnet und bilden die Menge der Operationen, die mit den Objekten der Klasse ausführbar sind. Damit der Nutzer Elementfunktionen zur Manipulation der Objekte in seinem Anwendungsprogramm aufrufen kann, müssen diese in aller Regel in einem public-teil verfügbar sein. Elementfunktionen sind syntaktisch gesehen - Funktionen im Sinne von C/C++. Die Deklaration und Definition dieser Elementfunktionen genügt den Regeln für normale Funktionen. Durch die Bindung an die Klasse unterscheidet sich der Aufruf von Elementfunktionen gegenüber normalen Funktionen: normale Funktion f(x) Elementfunktion f(x) der Klasse T f(y); T a; a.f(y); R.Grossmann / P. Sobe 26
Definition von Elementfunktionen Beispiel Klasse vektor (1) Deklaration und Definition der Elementfunktionen: class vektor { private: float x; float y; float z; //Deklaration Datenelemente } public: void init(float i,float j,float k) { x=i; y=j; z=k;} float betrag() {return sqrt(x*x+y*y+z*z);} R.Grossmann / P. Sobe 27 Definition von Elementfunktionen Beispiel Klasse vektor (2) Gewöhnliche Trennung von Deklaration und Definition: class vektor { private: float x; float y; float z; //Deklaration Datenelemente public: void init(float i,float j,float k) ; float betrag(); void vektor::init (float i,float j,float k) { x=i; y=j; z=k;} float vektor::betrag(){return sqrt(x*x+y*y+z*z);} R.Grossmann / P. Sobe 28
Konstruktor, Destruktor Konstruktor: eine ausgewählte Methode, die wie die Klasse benannt ist. kein Rückgabewert kann Parameter entgegen nehmen, die bei Erzeugung eines Objektes anzugeben sind mehrere Konstruktoren mit unterschiedlicher Parameterliste möglich einer der Konstruktoren wird bei Erzeugung einer Klasse ausgeführt Beispiel: class person { public: person(); person(char *vn, char *n, int gebj, char *h); Person unbekannt;// hier wird der parameterlose Konstruktor aufgerufen person *bk = new person( Helmut, Schmidt,1918, Rauchen und Politik ); R.Grossmann / P. Sobe 29 Konstruktor, Destruktor Destruktor: eine ausgewählte Methode, die wie die Klasse mit vorangestelltem ~ -Zeichen benannt ist. kein Rückgabewert, ohne Parameter nur ein Destruktor möglich Destruktor wird bei Ende der Gültigkeit eines Objekts ausgeführt Beispiel: class person { public: ~person(); Hier wird Objekt tmp freigegeben und der Destruktor aufgerufen. void tausch(person &a, person &b) { person tmp; tmp=a; a=b; b=tmp; } R.Grossmann / P. Sobe 30
Konstruktor, Destruktor Am Beispiel der Klasse Person: #define MAX_MITGLIEDER 100 class person { protected: char name[strlen], vorname[strlen], hobby[strlen]; int gebjahr; public: person() {} person(char *vn, char *n, int gebj, char *h) { gebjahr = gebj; strcpy(vorname, vn); strcpy(name, n); strcpy(hobby, h); } ~person() {} void print() { cout << vorname << " " << name << endl; } R.Grossmann / P. Sobe 31 Speicherallokation und Objekte Operatoren new und delete zur Speicherallokation bzw. -freigabe für Objekte Mit dem Operator new wird Speicher im Heap-Speicher angefordert und der Zeigervariablen zugewiesen: Beispiele: int *iptr = new int; int *kptr = new int(64); int *jptr = new int[30]; // ein Speicherbereich für 30 int Werte // wird angefordert person *p = new person( Fritz, Fuchs,1967, Fahrradfahren ); Keine Initialisierung für Standardtypen Bei Objekten wird nach Bereitstellung des Speichers der passende Klassenkonstruktor aufgerufen. Peter Sobe 32
Speicherallokation und Objekte Wird der über new angeforderte Speicherbereich nicht mehr verwendet, so muss er per delete explizit wieder freigegeben werden. int *pint = new int; int *pintfeld = new int[64]; person *pp = new person( Fritz, Fuchs,1967, Fahrradfahren ); delete pint; delete [] pintfeld; delete pp; delete kann nur in Verbindung mit new eingesetzt werden. int i, *pint = new int; int *pint2 = &i; delete pint; delete pint2; // Fehler, da nicht per new erzeugt delete auf einen Null-Pointer hat keine Auswirkungen. Peter Sobe 33 Elementobjekte Der Begriff Elementobjekt wurde zur Bezeichnung von Datenelementen im private-teil einer Klasse eingeführt, die keinen Standardtyp, wie float, int, char usw. repräsentieren, sondern Objekte einer anderen Klasse sind. Beispiel: Sie haben eine Klasse Punkt und wollen jetzt eine Klasse Linie definieren, die von einem Anfangspunkt zu einem Endpunkt geht. Das wäre mit Elementobjekten leicht zu erreichen: Im Programm Punkt_1 steht die Klassendefinition für Punkt: class Punkt { private: float x,y; public: Punkt(float a,float b){ x=a; y=b;} Punkt(){ }... R.Grossmann / P. Sobe 34
Elementobjekte Wir nehmen an, es gäbe die Klasse Punkt, mit einem Konstruktor Punkt(int xpos, int ypos). Nun wird die Klassendefinition für Linie unter Nutzung von Elementobjekten hinzugefügt: class Linie { private: Punkt anfang, ende ; public: Linie(){ }??? Wie muss der Konstruktor Linie geschrieben werden? Es muss ja der Anfangspunkt anfang und der Endpunkt ende mit zulässigen Werten belegt werden!!! Dies kann man nur über die Benutzung sogenannter Elementobjekt-Konstruktoren erreichen! R.Grossmann / P. Sobe 35 Elementobjekte Die Elementobjekt-Konstruktoren benutzen als Namen die Namen der Elementobjekte, in der Klasse Linie also anfang und ende. Der Konstruktor Linie muss deshalb mit Aufruf der Elementobjekt- Konstruktoren geschrieben werden: class Linie { private: Punkt anfang, ende ; public: Linie(float ax,float ay,float bx,float by) : anfang(ax,ay), ende (bx,by) { }... Der Aufruf der Elementobjekt-Konstruktoren muss durch einen Doppelpunkt hinter der Parameter-Liste eingeleitet werden! R.Grossmann / P. Sobe 36
Elementobjekte Besitzt eine Klassendefinition einen Standard-Konstruktor (Konstruktor ohne Parameter), so können im Programm auch Felder mit Elementen dieses Typs deklariert werden. Beispiel vektor class vektor { private: float x,y,z ; public: vektor(){ } // Standardkonstruktor vektor(float ax,float ay,float az) {x=ax;y=ay;z=az; }... In der Applikation (Hauptprogramm) kann jetzt ein Feld fv von vektoren vereinbart werden! vektor fv[4] = { v1, v2, v3, v4 v1-v4 sind vektor-objekte R.Grossmann / P. Sobe 37 Objekte als Parameter (1) C++ erlaubt, Objekte als Parameter von Methoden zu übergeben. Beispiel: Eine Zeichenfläche (canvas), der verschiedene geometrische Objekte übergeben werden. Die Zeichenfläche kann diese Objekte beispielsweise unter Berücksichtigung von 3D- Überdeckungen zeichnen. class geomobj { class canvas { public: public: geomobj() { } canvas() { } int register_geom_obj(geomobj &o) { } void draw_all() { } int num_vertexes(); int get_vertex( int *x, int *y, int *y); int draw_vertex { } R.Grossmann, P.Sobe 38
Objekte als Parameter (2) Bei Übergabe von Objekten an Methoden sind die Mechanismen der Parameterübergabe zu beachten. Bei der Übergabe als Wert würde der Compiler eine lokale Kopie des übergebenen Wertes erzeugen. Dazu wird der Kopierkonstruktor aufgerufen. Dies ist oft nicht wünschenswert (Ressourcenverschwendung, Nebeneffekte). Das bei der Wertübergabe erzeugte Objekt würde wie andere lokale Objekte am Ende der Funktion gelöscht und der zugehörige Destruktor aufgerufen. Das führt i.d.r. zu unerwünschten Wirkungen. Im allgemeinen ist es daher sinnvoll, Objekte als (konstante) Referenz zu übergeben. R.Grossmann, P.Sobe 39 Objekte als Parameter (3) Übergabe von Objekten als Referenz an Methoden: int register_geom_obj(geomobj &o) { for (int i=0; i<o.num_vertexes();i++) o.get_vertex( &geobj[a].v[i].x, &geobj[a].v[i].y, &geobj[a].v[i].z ); } // Kopiert Eckpunktkoordinaten von o auf lokales Objekt geobj[a] besser noch als konstante Referenz: int register_geom_obj(const geomobj &o) { R.Grossmann, P.Sobe 40
8.5 Vererbung und Polymorphie Verschiedene Klassen sind oft in einigen grundlegenden Eigenschaften/Funktionen gleich. Beispielsweise können verschiedene elektr. Bauelemente als Zweipole beschrieben werden. Generalisierung: Ohmscher Widerstand, Diode, Spule usw. sind Zweipole Spezialisierung: Vom Zweipol ausgehend erfolgt eine Spezialisierung als Ohmscher Widerstand durch ein lineares Strom/Spannungs-Verhältnis. als Diode durch eine Sperrfunktion und die Diodenkennlinie als Spule durch einen Induktiven Widerstand und ein phasenabhängiges Strom/Spannungsverhältnis ZP Generalisierung Peter Sobe 41 Generalisierung und Spezialisierung Gemeinsame Eigenschaften und Funktionen können in einer Basisklasse definiert werden. Eine Spezialisierung erfolgt durch Erzeugung einer neuen abgeleiteten Klasse, die die Eigenschaften und Funktion der Basisklasse erbt. Die abgeleitete Klasse kann Neues hinzufügen zusätzliche Elementobjekte und zusätzliche Methoden Die abgeleitete Klasse kann Methoden und Operationen verändern (überschreiben) Objekte der Basisklasse und abgeleiteter Klassen sind zuweisungskompatibel. R.Grossmann, P. Sobe 42
Begriff Klassenhierarchie Definition: Eine Menge von Klassen, deren Elemente durch die Relation Spezialisierung bzw. Generalisierung miteinander in Beziehung gesetzt sind, bezeichnet man als Klassenhierarchie, wenn jede Klasse höchstens eine Basisklasse besitzt. Basisklasse heißt eine Klasse von der durch Spezialisierung eine abgeleitete Klasse gebildet wurde. R.Grossmann, P. Sobe 43 Abgeleitete Klassen in C++Programmen Abgeleitete Klassen müssen in C++ Programmen durch Angabe ihrer Basisklasse gekennzeichnet werden. Beispiel: class A {... class B : public A{... class C: public B{... class D: public A{... //Basis-Klasse //abgeleitete Klasse und gleich- // zeitig Basis-Klasse für C //abgeleitete Klasse //abgeleitete Klasse Alle Datenelemente einer Klasse, sowie alle Methoden mit Ausnahme der unten angegebenen werden an die abgeleitete Klasse vererbt. Nicht vererbt werden alle Konstruktoren und der Destruktor. R.Grossmann, P. Sobe 44
Zugriffsspezifizierer in Klassenhierarchien Alle privaten Datenelemente einer Klasse können nur von den Methoden der Klasse benutzt werden. Sollen diese Datenelemente auch von Methoden der abgeleiteten Klassen erreichbar sein, muss anstelle von private der Spezifizierer protected benutzt werden. Veranschaulichung: class myclass : public yourclass { public: myclass(); ~myclass(); // öffentlich für alle anderen Objekte, // auch für abgeleitete Klassen protected: double calculate(); // nicht öffentlich, kann aber von Klasse // selbst und von abgeleiteter Klasse aufgerufen werden private: void internalaction(); // nur von Klasse selbst aufrufbar, kein Zugriff durch // abgeleitete Klassen R.Grossmann, P. Sobe 45 Konstruktoren abgeleiteter Klassen Konstruktoren werden nicht vererbt. Konstruktoren abgeleiteter Klassen müssen den Konstruktor der Basisklasse aufrufen, der für die Instanziierung der geerbten Anteile sorgt. Beispiel: class A //Basis-Klasse { private: int i; public: A(int k){i=k;} //Konstruktor class B : public A //abgeleitete Klasse { private: char c; public: B(char s,int j): A(j) {c=s;} // : A(j) ruft Basisklassen-Konstruktor auf R.Grossmann, P. Sobe 46
Elementobjektkonstruktoren in Konstruktoren abgeleiteter Klassen Enthalten abgeleitete Klassen Elementobjekte, so müssen neben dem Konstruktor der Basisklasse auch der Elementobjekt- Konstruktor aufgerufen werden. Beispiel: class A { private: int i; public: A(int k){i=k;} //Basis-Klasse //Konstruktor class B : public A //abgeleitete Klasse { private: char c; A a; //Elementobjekt public: B(char s,int j,int m) //Konstruktor abgl. Klasse : a(m),a(j) {c=s;} // A(j) Basisklassen-Konstruktor // a(m) ist Aufruf Elementobjekt-Konstr. R.Grossmann, P. Sobe 47 Elementobjektkonstruktoren in Konstruktoren abgeleiteter Klassen Beachten Sie, dass unabhängig von der Notation die Rufreihenfolge festgelegt ist: 1. Ruf des Konstruktors der Basisklasse 2. Ruf des Elementobjekt-Konstruktors 3. Ruf des Konstruktors der abgeleiteten Klasse Beispiel: class A { //Basis-Klasse class B : public A //abgeleitete Klasse { private: int a, c; public: B(..., int m, int s, ) : a(m),a(j) {c=s;} // zuerst wird A(), dann a() und zuletzt B() ausgeführt R.Grossmann, P.Sobe 48
Vererbung und dynamisches Binden Durch Einfügen der Compilerdirektive virtual wird dynamisches Binden von Methoden erzwungen: class A //Basis-Klasse {public: virtual void Druck(){cout<< Klasse A ;} void Nachricht(){cout<< Nachricht von ; Druck() ;} class B : public A //abgeleitete Klasse { public: void Druck(){cout<< Klasse B ;} void main() {A a; B b; a.nachricht(); b.nachricht(); } Jetzt wird ausgegeben: Nachricht von Klasse A Nachricht von Klasse B R.Grossmann, P. Sobe 49 Beispiel für Polymorphie (1) class B { public: B(int n): b(n) {} virtual void f() {cout << b;} private: int b; class C: public B { public: C(int n1, int n2): B(n1), c(n2) {} virtual void f() {B::f(); cout << c; } private: int c; class D: public C { public: D(int n1, int n2, int n3): C(n1,n2), d(n3) {} virtual void f() {C::f(); cout << d;} private: int d; int main { B* b_array[3]; B b(6); C c(9,12); D d (16,18,20); b_array[0] = &b; b_array[1] = &c; b_array[2] = &d; for(int i=0;i<3;i++) b_array[i]->f(); } R.Grossmann, P. Sobe 50
Beispiel für Polymorphie (2) b_array Ausgabe des Programms: B b(6) 6 9 12 16 18 20 C c(9,12) D d(16,18,20) Es reicht aus, den Typ der Basisklasse zu kennen und einheitliche Methodennamen für Basis- und abgleitete Klassen zu verwenden. Zur Laufzeit werden die Methoden der entsprechenden Objekte (Klassen) richtig ausgewählt, ohne dass sich der Programmierer darum kümmern muss. R.Grossmann, P. Sobe 51 Grafische Veranschaulichung von Klassen (1) Klassen können untereinander verschiedene Beziehungen eingehen: Klasse B als Elementobjekt von Klasse A allgemein kann eine Klasse viele verschiedene Elementobjekte besitzen, die möglicherweise auch Instanzen verschiedener Klassen sind. Elementobjekte können ebenfalls aus verschiedenen Elementobjekten bestehen. Es ergibt sich eine Hierarchie von Elementen, die die Beziehung enthalten in wiederspiegeln. Klasse B als abgeleitete Klasse von A eine Klasse wird typischerweise von einer Basisklasse abgeleitet. Die Basisklasse kann wiederum eine abgeleitete Klasse sein. Es ergibt sich eine Schichtung im Sinne von erbt von. Mehrfachvererbung ist aber auch möglich, d.h. eine Klasse kann von mehreren Basisklassen erben. Klassen können sich über Referenzen assoziieren, d.h. Beziehungen aufbauen, die nicht durch die Elementobjekt-Beziehung abgedeckt ist. Diese Assoziation ist beim Entwurf zu berücksichtigen. Die grafische Darstellung der Hierarchien und Schichtung erfolgt durch Klassendiagramme, die als Teil der UML (Unified Modelling Language) vereinheitlicht wurden. R.Grossmann, P.Sobe 52
Grafische Veranschaulichung von Klassen (2) Objektorientierter Entwurf als Schritt, der vor der eigentlichen Programmierung erfolgt UML Einzelne Klasse: Vererbung: Name der Klasse Zweipol Elementobjekte Methoden BerechneU() spezialisiert OhmscherWiderst Diode BerechneU() BerechneU() R.Grossmann, P.Sobe 53 Grafische Veranschaulichung von Klassen (3) UML Komposition (Ganzes enthält Teile) ElektrSchaltung 1 1..* Zweipol Simuliere() BerechneU() Die Komposition durch Pfeil mit ausgefüllter Raute, wobei Teile nicht ohne das Ganze existieren können. In C++ wird die Komposition z.b. durch Elementobjekte realisiert. Am Pfeil sind Multiplizitäten vermerkt Die Aggregation ist ähnlich, wobei Teile auch ohne das Ganze existieren dürfen. Fahrgemeinschaft 0..1 2..* Fahrgast Reise() Reise () R.Grossmann, P.Sobe 54
Grafische Veranschaulichung von Klassen (4) UML Assoziation eine inhaltliche Beziehung Konto 0.. * 1..* Kunde BucheAb() Kaufe() Klassen können Verweise (Zeiger, Referenzen) auf Objekte verwalten, um inhaltliche Beziehungen zu verwalten. R.Grossmann, P.Sobe 55 Vorgefertigte Klassenbibliotheken Beispiele: Klassenbibliotheken für grafische Ausgabe und Window-Systeme Microsoft MFC, Qt (Unix/Linux-Welt) Klassenbibliotheken für Datenstrukturen (Felder, Arrays, Listen), z.b. Standard Template Library.NET Klassenbibliothek, nutzbar für Programme in C#, C++, Visual Basic Klassenbibliotheken zur Simulation, z.b. SystemC R.Grossmann, P. Sobe 56