C++-Übungen
Transcription
C++-Übungen
Prof. Dr. Helmut Herold C++-Übungen Inhaltsverzeichnis 1 Einführung in die Welt der Objektorientierung 1 1.1 Aussagen zur Objektorientierung . . . . . . . 1 1.2 OO-Modelle der realen Welt . . . . . . . . 2 1.2.1 Tiere mit und ohne Beine . . . . . . . 2 1.2.2 Die C-Datentypen . . . . . . . 2 1.2.3 Unterschiedliche Menschen . . . . . . . 2 . 2 Nicht objektorientierte Erweiterungen in C++ 3 2.1 Default-Parameter und Überladen von Funktionen 2.2 Vertauschen von int-, double- und char *-Variablen 2.3 Rotieren eines Arrays 2.4 Array-Verwaltung in traditioneller Modul-Technik 2.5 Strukturen in C++ – Erster Schritt zur OO . . . . . . . . . . . 3 . . 5 . . . 6 . . . 7 . . . 9 2.5.1 Eine Byte-Struktur mit 8-Bits zum Rechnen . . . 9 2.5.2 Aufrufe von Konstruktoren und Destruktoren . . . 11 2.5.3 Programm, das nur Deklarationen enthält . . . 12 2.5.4 Array-Verwaltung mit Funktionen in Strukturen . . 13 2.5.5 Game of Life . . 15 . . . . . . . . . 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen 3.1 Was ist UML und warum UML? 3.2 Verwendung einer Klasse . 19 . . . . . . . 19 . . . . . . . . 19 . 3.2.1 Konstruktoren . . . . . . . 19 3.2.2 Allgemeine Aussagen zu C++ . . . . . . 20 3.2.3 Initialisierung von Membervariablen . . . . . 20 3.2.4 Methoden einer Klasse . . . . . 20 . . I Inhaltsverzeichnis 3.3 3.4 3.5 3.6 II 3.2.5 Addition, Substraktion und Multiplikation sehr grosser Zahlen 21 3.2.6 Realisierung einer Queue (Warteschlange) . . . . 23 3.2.7 Realisierung einer Klasse CDoubleArray . . . 26 3.2.8 Konstruktoren, Destruktoren, Kopierkonstruktor und Zuweisungsoperator . . . . . . . . . . 27 3.2.9 Eine Klasse für Bitoperationen . . . . 28 3.2.10 Vergabe und Löschen von Kundennummern . . . 32 Mehrere Klassen . . . . . . . . . . . . . 34 . . . . . 34 . . . . . 35 . . . . 37 . . . 39 . . 41 3.3.1 Realisierung eines Bruchrechners 3.3.2 Gemeinsamer Termin 3.3.3 Das d’Hondtsche Höchstzählverfahren 3.3.4 Kästners Kubikkilometer für alle Menschen 3.3.5 Wortstatistik zu Textdateien (mittels Binärbaum) 3.3.6 Geldscheine stapeln . . . . . . . . . . . 42 Beziehungen zwischen Objekten . . . . . . . 44 3.4.1 Klassendiagramm und Implementierung zu einem Vieleck 44 3.4.2 Klassendiagramm und Implementierung zu einem Polygonzug 45 3.4.3 Assoziationen zum Monopoly-Spiel . . . 47 3.4.4 Rekursive und aufgelöste rekursive Assoziationen . . 49 3.4.5 Mastermind gegen den Computer Vererbung . . . . . . . . . . . . 50 . . . . . . . 52 3.5.1 Überschreiben von Funktionen . . . . . . 52 3.5.2 Konstruktor-/Destruktoraufrufe . . . . . 53 3.5.3 Linie in Graphikbibliothek . . . . . 55 3.5.4 Klassenmodell zu Autos, Dreirädern usw. . . . . 56 3.5.5 Erweitern des Klassenmodells zu Fahrzeugen . . . 57 3.5.6 Ableiten eines Lkws von einem Auto . . . . 58 3.5.7 Speicherbedarf bei virtuellen Funktionen . . . . 59 3.5.8 Speicherbedarf bei abgeleiteten Klassen . . . . 59 3.5.9 Ein Suppenteller in Franken . . . . . . . . . 60 3.5.10 Franken und Berliner . . . . . . . . 61 3.5.11 Tierhierarchie . . . . . . . . 63 3.5.12 Parkplatz mit Polymorphismus . . . . . . 64 Abstrakte Klassen . . . . . . 65 . . . . . Inhaltsverzeichnis 3.7 3.8 3.6.1 Sinnlose Code-Erzeugung bei “Dummy“-Methoden in Basisklasse . . . . . . . . . . . . 65 3.6.2 Zoo mit zufälligen Tieren . . . . . . . 66 . . . . . . . 67 3.7.1 Ehe, mehrfach abgeleitet von Mensch über Mann/Frau . 67 3.7.2 Hai als fleischfressender Fisch Mehrfachvererbung Schnittstellen . . . . . . . . . . . . . . 68 . . . . . . 69 3.8.1 Schnittstelle ISprechweise für unterschiedliche Dialekte 3.8.2 Eine allgemein verwendbare verkettete Liste . . 69 . 4 Weitere Features in C++ 4.1 4.2 69 73 Friends (Befreundete Funktionen und Klassen) . . . . 73 4.1.1 Addieren von Brüchen . . . . . 73 4.1.2 Array von ungekürzten und gekürzten Brüchen . . 74 Operatoren überladen . . . . . . . . . . . . 76 4.2.1 Ein Uhrzeit-Rechner . . . . . . . . 76 4.2.2 Bruchrechner mit überladenen Operatoren . . . 77 4.2.3 Rechner für sehr grosse Zahlen mit überladenen Operatoren 80 4.2.4 Realisierung von Mengenoperationen über ein Array . 82 4.2.5 Rechner für Dezimal- und Dualzahlen . 84 . . . III Inhaltsverzeichnis IV Kapitel 1 Einführung in die Welt der Objektorientierung 1.1 Aussagen zur Objektorientierung Welche der folgenden Aussagen sind richtig? 1. Ein Objekt ist ein Exemplar einer Klasse. 2. Eine Klasse ist ein Objekt. 3. Bei der Vererbung erbt die übergeordnete Klasse alle Eigenschaften der Unterklasse. 4. Ein Objekt ist eine Klasse. 5. Eine Unterklasse erbt nur bestimmte, vom Benutzer wählbare Eigenschaften von ihrer Oberklasse. 6. Ein Objekt ist eine Instanz einer Klasse. 7. Eine Unterklasse ist eine Klasse, die durch Vererbung aus einer anderen Klasse entsteht. 8. Das Neue an der Objektorientierung ist die klare Trennung zwischen Daten und Funktionen, so dass beliebige Funktionen jederzeit auf globale Daten zugreifen können. 9. Eine Unterklasse erbt alle Eigenschaften von ihrer Oberklasse. 10. Eine Klasse ist ein Exemplar eines Objekts. 11. Die Objektorientierung ermöglicht die Wiederverwendbarkeit von Software. 1 1 Einführung in die Welt der Objektorientierung 1.2 OO-Modelle der realen Welt In welchem Zusammenhang stehen jeweils die folgenden Begriffe (Klasse, Objekt, Unterklasse)? Objektorientierte Sprache, PASCAL, Gesprochene Sprache, Sprache, Java, Englisch, Latein, Computersprache, Altgriechisch, C, Prozedurale Sprache, C++, Ausgestorbene Sprache, Deutsch Zeichnen Sie hierzu korrespondierende Modelle in UML-Notation! Abbildung 1.1 zeigt die Lösung zu dieser Aufgabenstellung. Sprache Gesprochene Sprache Computersprache <<instance of>> Prozedurale Sprache Objektorient. Sprache Englisch <<instance of>> Ausgestorbene Sprache <<instance of>> Deutsch Altgriech. <<instance of>> Latein <<instance of>> <<instance of>> <<instance of>> <<instance of>> PASCAL C C++ Java Abbildung 1.1: Objektorientiertes Modell zur Aufgabenstellung Zeichnen Sie nun das korrespondierende Modell (in UML-Notation) zu folgenden Begriffen! 1.2.1 Tiere mit und ohne Beine Bello, Beinlos, Katze, “Ringel, meine Schlange“, Zweibeiner, Hund, Fisch, Mensch, Lebewesen, Vierbeiner, “Mein Haustier“, Hai, Schlange, Vogel, Rex, Mausi, “Mein Kater“ 1.2.2 Die C-Datentypen Datentyp, Ganzzahl, Gleitpunktzahl, short, int, alphanumerisch, numerisch, char, float, char zeichen, double, int zaehler, float pi 1.2.3 Unterschiedliche Menschen Adenauer, Seefahrer, Bundeskanzler, “Hans Meier“, “H. Hesse“, Pirat, Mensch, Nobelpreisträger, Politiker, Physik, Nixon, “A. Einstein“, Literatur, Sindbad, amerik. Präsident 2 Kapitel 2 Nicht objektorientierte Erweiterungen in C++ 2.1 Default-Parameter und Überladen von Funktionen Erstellen Sie ein Programm konvert.cpp, das über folgende Funktionalität verfügt: 1. Zählen in einem beliebigen Zahlensystem von einem Start- bis zu einem Endwert 2. Zählen eines Zeichens in einem String Für beide Zählarten soll eine überladene Funktion namens zaehl() existieren. Für die erste Form des Zählens (Zählen in einem beliebigen Zahlensystem) sollen folgende Voreinstellungen gelten, wenn beim Aufruf die entsprechenden Argumente fehlen: ❏ Startwert: 1 ❏ Endwert: 5 ❏ Zahlensystem: 10 Erstellen Sie diese beiden Varianten der zaehl()-Funktion, indem Sie den folgenden Codeausschnitt entsprechend ergänzen: #include <stdio.h> #include <string.h> #include <ctype.h> void zaehl( ... ) // Zählen in einem beliebigen Zahlensystem { ....... } void zaehl( ... ) // Zählen eines Zeichens in einem String { ....... } 3 2 Nicht objektorientierte Erweiterungen in C++ int main(void) { zaehl(); zaehl( 3, 9, 2 ); zaehl( 20, 30, 16 ); zaehl( 2 ); zaehl( "Westernleder", ’e’ ); zaehl( "Abraham", ’a’ ); return 0; } Das Programm konvert.cpp sollte dann folgende Ausgabe liefern: ---- Zähle von 1 bis 5 im 10er-System 1 2 3 4 5 ---- Zähle von 3 bis 9 im 2er-System 11 100 101 110 111 1000 1001 ---- Zähle von 20 bis 30 im 16er-System 14 15 16 17 18 19 1A 1B 1C 1D 1E ---- Zähle von 2 bis 5 im 10er-System 2 3 4 5 ---- Das Zeichen ’e’ kommt im String "Westernleder" 4 mal vor ---- Das Zeichen ’a’ kommt im String "Abraham" 3 mal vor 4 2.2 Vertauschen von int-, double- und char *-Variablen 2.2 Vertauschen von int-, double- und char *-Variablen Erstellen Sie ein Programm swap.cpp, das eine überladene Funktion swap() anbietet, mit der man sowohl int- und double-Variablen als auch char-Zeiger vertauschen kann. Das Vertauschen in der jeweiligen swap() Funktion soll dabei mit echtem call-by-reference erfolgen. Erstellen Sie diese drei Varianten der swap()-Funktion, indem Sie den folgenden Codeausschnitt entsprechend ergänzen: #include <stdio.h> void swap(...) // Vertauschen von zwei int-Variablen { .... } void swap(...) // Vertauschen von zwei double-Variablen { .... } void swap(...) // Vertauschen von zwei char-Zeiger { .... } int main(void) { int a=5, b=10; double x=5.2, y=10.7; char *str1 = "Eins", *str2 = "Zwei"; swap( a, b ); swap( x, y ); swap( str1, str2 ); printf("a=%d, b=%d\n", a, b); printf("x=%.1f, y=%.1f\n", x, y); printf("str1=%s, str2=%s\n", str1, str2); return 0; } Das Programm swap.cpp sollte dann folgende Ausgabe liefern: a=10, b=5 x=10.7, y=5.2 str1=Zwei, str2=Eins 5 2 Nicht objektorientierte Erweiterungen in C++ 2.3 Rotieren eines Arrays Erstellen Sie ein Programm arrayrotate.cpp, das eine Funktion rotiereArray() anbietet, mit der man den Inhalt eines Arrays rotieren lassen kann. Das zu rotierende Array ist dabei als Referenzparameter zu deklarieren. Über weitere Argumente soll der Aufrufer der Funktion steuern können, wie viele Elemente des Arrays zu rotieren sind und ob diese Elemente rechts oder links zu rotieren sind. Das rotierte Array soll die Funktion rotiereArray() als Referenz zurückgeben, so dass man diese Rückgabe wieder direkt als Argument an eine Funktion, wie z.B. an die Funktion rotiereArray() selbst übergeben kann. Erstellen Sie die Funktion rotiereArray(), indem Sie den folgenden Codeausschnitt entsprechend ergänzen: #include <stdio.h> const int groesse = 20; struct arrayStruct { int x[groesse]; }; ..... rotiereArray( ... ) { ........ } void ausgabe( const arrayStruct &a, const char *str ) { printf(".............%s....\n\n", str); for (int i=0; i<groesse; i++) printf("%d ", a.x[i]); printf("\n\n"); } int main(void) { arrayStruct array; for (int i=0; i<groesse; i++) array.x[i] = i+1; ausgabe( array, "Am Anfang" ); rotiereArray( array ); // Voreinstellung: 1 Element rechts rotieren ausgabe( array, "rotiereArray(array)" ); rotiereArray( array, 5, false ); // 5 Elemente links rotieren ausgabe( array, "rotiereArray(array, 5, false)" ); rotiereArray( array, 4 ); // 4 Elemente rechts rotieren ausgabe( array, "rotiereArray(array, 4)" ); // Zunächst 10 Elemente links rotieren und dann 5 Elemente rechts rotiereArray( rotiereArray(array, 10, false), 5 ); ausgabe( array, "rotiereArray(rotiereArray(array, 10, false), 5)" ); return 0; } Das Programm arrayrotate.cpp sollte dann folgende Ausgabe liefern: .............Am Anfang: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 .............rotiereArray(array): 20 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 .............rotiereArray(array, 5, false): 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 1 2 3 4 .............rotiereArray(array, 4): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 .............rotiereArray(rotiereArray(array, 10, false), 5): 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 1 2 3 4 5 6 2.4 Array-Verwaltung in traditioneller Modul-Technik 2.4 Array-Verwaltung in traditioneller Modul-Technik Erstellen Sie ein Programm array.cpp, das über folgende Funktionalität verfügt: ❏ Anlegen und Füllen eines double-Arrays mit Zufallszahlen ❏ Neues Füllen eines bereits existierenden double-Arrays ❏ Sortieren eines double-Arrays ❏ Ausgeben eines double-Arrays Dazu soll die Verwaltung des Arrays in traditioneller Modultechnik durchgeführt werden: ❏ Deklarieren der nach aussen sichtbaren Schnittstellen in einer Headerdatei (hier array.h) ❏ Implementieren der Schnittstellen in der .cpp-Datei ((hier array.cpp) Andere Module, die diese Array-Verwaltung nutzen wollen, müssen dann die Headerdatei array.h inkludieren. Zugriffe auf die Internas des Arrays haben die Module dabei nur über die in der Headerdatei angebotenen Funktionen. Dies ist vergleichbar zu einem Kaffeeautomaten, bei dem der Kunde immer nur die aussen angebrachten Tasten drücken kann und niemals die Tür öffnen darf, um sich selbst seinen Kaffee zuzubereiten. Dieser “Array-Automat“ ist mit einem solchen Kaffeautomaten vergleichbar, wie es in Abbildung 2.1 gezeigt ist. Der Array−Automat initArray() en Arbeit eren im Inn maten to u A s de ur kann n teller rs der He hren durchfü fuelleArray() sortiereArray() gibausArray() Abbildung 2.1: Der Array-Automat Nachfolgend die Headerdatei array.h für diesen “Array-Automaten“: 7 2 Nicht objektorientierte Erweiterungen in C++ #ifndef ARRAY_HEADER #define ARRAY_HEADER typedef struct { char *name; // Name des Arrays (als String) bool sortiert; // Array sortiert oder nicht int anzahl; // Anzahl der Elemente des Arrays double *meinArray; // Anfangsadresse des Arrays } array; extern void initArray( array &a, char *name, int anzahl ); // initialisiert das Array, indem es Speicherplatz für // ’anzahl’-Elemente alloziert und dann auch dieses Array mit // ’anzahl’ zufälligen double-Zahlen füllt extern void fuelleArray( array &a ); // füllt ein bereits existierendes Array mit neuen zufälligen double-Zahlen extern void sortiereArray( array &a, bool aufwaerts=true ); // sortiert ein bereits existierendes Array abh. vom dritten Argument // entweder aufsteigend oder absteigend extern void gibausArray( array &a ); // gibt alle Elemente eines existierenden Arrays und zusätzliche // Information (Name, Elementzahl und ob sortiert oder nicht) aus #endif Mit dem folgenden Programm arraymain.cpp lassen wir uns nun zwei Arrays mit unserem “Array-Automaten“ verwalten: #include <stdio.h> #include "array.h" int main(void) { array array1, array2; initArray( array1, "array1", 10 ); gibausArray( array1 ); sortiereArray( array1 ); gibausArray( array1 ); sortiereArray( array1, false ); gibausArray( array1 ); fuelleArray( array1 ); gibausArray( array1 ); initArray( array2, "array2", 5 ); sortiereArray( array2 ); gibausArray( array2 ); sortiereArray( array1, false ); gibausArray( array1 ); return 0; } Linkt man dieses Programm arraymain.cpp mit dem noch zu erstellenden Modul array.cpp zusammen, sollte das Programm z.B. folgende Ausgabe liefern: 8 2.5 Strukturen in C++ – Erster Schritt zur OO --------------------------------------------- array1 ---Groesse des Arrays: 10 Array sortiert: nein 3.41 3.67 1.86 2.01 0.62 2.33 0.08 0.45 1.04 2.16 --------------------------------------------- array1 ---Groesse des Arrays: 10 Array sortiert: ja 0.08 0.45 0.62 1.04 1.86 2.01 2.16 2.33 3.41 3.67 --------------------------------------------- array1 ---Groesse des Arrays: 10 Array sortiert: ja 3.67 3.41 2.33 2.16 2.01 1.86 1.04 0.62 0.45 0.08 --------------------------------------------- array1 ---Groesse des Arrays: 10 Array sortiert: nein 0.25 0.40 0.62 0.59 3.32 1.71 0.86 1.20 2.14 7.48 --------------------------------------------- array2 ---Groesse des Arrays: 5 Array sortiert: ja 0.62 1.86 2.01 3.41 3.67 --------------------------------------------- array1 ---Groesse des Arrays: 10 Array sortiert: ja 7.48 3.32 2.14 1.71 1.20 0.86 0.62 0.59 0.40 0.25 2.5 2.5.1 Strukturen in C++ – Erster Schritt zur OO Eine Byte-Struktur mit 8-Bits zum Rechnen Erstellen Sie ein Programm dualbyte.cpp, das zunächst die folgende bit-Struktur definiert: struct bit { public: //............................................... Konstruktor bit() { bitwert = 0; } //.......................................... Memberfunktionen void setBit(int wert) { bitwert = wert; } int getBit(void) { return bitwert; } private: int bitwert; // enthaelt immer aktuellen Bitwert }; Zudem soll dieses Programm eine Struktur byte enthalten, die ein bit-Strukturarray der Größe 8 enthält. Diese Struktur byte soll dann folgende Funktionalität anbieten: 9 2 Nicht objektorientierte Erweiterungen in C++ Einen Konstruktor mit einem int-Parameter. Der dabei übergebene Zahlenwert soll dann dual in den einzelnen Bits des jeweiligen Bytes abgelegt werden. ❏ eine Methode add(byte byt2), welche das übergebene Byte auf den Inhalt des eigenen Bytes aufaddiert. Dazu müssen die einzelnen Bits der beiden Bytes mit Berücksichtigung von Überläufen addiert werden. ❏ eine Methode getBitOfByte(int i), die zum Byte den Wert des Bits mit der Nummer i (i=0,1,...,7) liefert. Diese Methode wird benötigt, damit man in der Methode add(byte byt2) auf die einzelnen Bits des als Argument übergebenen Bytes zugreifen kann. ❏ ❏ zwei Methode printDual() und printDezimal(), die den aktuellen Byteinhalt als duale bzw. als dezimale Zahl ausgeben. Ergänzen Sie dazu den folgenden Codeausschnitt des Programms dualbyte.cpp: #include <stdio.h> struct bit { public: //............................................... Konstruktor bit() { bitwert = 0; } //.......................................... Memberfunktionen void setBit(int wert) { bitwert = wert; } int getBit(void) { return bitwert; } private: int bitwert; // enthaelt immer aktuellen Bitwert }; struct byte { public: ......... private: bit byt[8]; }; int main(void) { byte b1 = 45, b2 = 127; b1.printDual(); printf(" ("); b1.printDezimal(); printf(")"); printf(" + "); b2.printDual(); printf(" ("); b2.printDezimal(); printf(")"); b1.add(b2); printf(" = "); b1.printDual(); printf(" ("); b1.printDezimal(); printf(")\n"); return 0; } Das Programm dualbyte.cpp sollte dann folgende Ausgabe liefern: 00101101 (45) + 01111111 (127) = 10101100 (172) 10 2.5 Strukturen in C++ – Erster Schritt zur OO 2.5.2 Aufrufe von Konstruktoren und Destruktoren Was würde das folgende Programm dualbyte2.cpp ausgeben? #include <stdio.h> struct bit { public: bit() { printf("B"); } // Konstruktor ~bit() { printf("b"); } // Destruktor void setBit(int wert) { bitwert = wert; } int getBit(void) { return bitwert; } private: int bitwert; // enthaelt immer aktuellen Bitwert }; struct byte { public: byte(int z) { printf(" BYTE(%d)\n", zahl=z); } ~byte() { printf("\nbyte(%d) ", zahl); } // Destruktor void add( byte byt2 ) { printf("\nadd()"); } private: bit byt[8]; int zahl; }; int main(void) { byte b1 = 1, b2 = 2; { //... Lokaler Block byte b3(3); // Anlegen eines temporären byte-Objekts printf("--------\n"); } // temporäres byte-Objekt wird mit Verlassen des Blocks wieder zerstört b1.add(b2); printf("\n......."); return 0; } 11 2 Nicht objektorientierte Erweiterungen in C++ 2.5.3 Programm, das nur Deklarationen enthält Im folgenden Programm konstrdestr.cpp befindet sich in der main()-Funktion keine einzige Ausgabeanweisung. Gibt das Programm konstrdestr.cpp trotzdem etwas aus? Wenn ja, warum und was wird ausgegeben? #include <stdio.h> struct Applikation { Applikation() { printf("Applikation wird geladen...\n"); run(); } void run() { for (int i=0; i < 3; i++) printf("Applikation wird ausgeführt...\n"); } ~Applikation() { printf("Applikation wird beendet; Tschuess...\n"); } }; struct Bruch { public: Bruch(int z=0, int n=1) { zaehler = z; nenner = n; printf("Bruch: %d / %d wurde erzeugt\n", zaehler, nenner); } ~Bruch() { printf("Bruch: %d / %d wurde entfernt\n", zaehler, nenner); } private: int zaehler; int nenner; }; Applikation myApp; int main(void) { Bruch *b1, b2(1,2), b3; b1 = new Bruch(3,4); { Bruch b4(4); } return 0; } 12 2.5 Strukturen in C++ – Erster Schritt zur OO 2.5.4 Array-Verwaltung mit Funktionen in Strukturen In der Übung von Kapitel 2.4 auf Seite 7 sollte eine Array-Verwaltung in traditioneller Modultechnik realisiert. Hier soll nun diese Array-Verwaltung mit den in diesem Kapitel neu kennengelernten Konstrukten realisiert werden. Nachfolgend ist die Headerdatei arraystruct.h für diesen neuen “Array-Automaten“ gegeben: #ifndef ARRAY_HEADER #define ARRAY_HEADER struct array { public: array( char *name, int anzahl ); void fill(void); void sort(bool aufwaerts=true); void print(void); private: char *name; // Name des Arrays (als String) bool sortiert; // Array sortiert oder nicht int anzahl; // Anzahl der Elemente des Arrays double *meinArray; // Anfangsadresse des Arrays void tausch( double &a, double &b ) { double h = a; a = b; b = h; } }; #endif Mit dem folgenden Programm arraystructmain.cpp lassen wir uns nun wieder zwei Arrays mit unserem neuen “Array-Automaten“ verwalten. #include <stdio.h> #include "arraystruct.h" int main(void) { array array1("array1", 10 ), array2("array2", 5); array1.print(); array1.sort(); array1.print(); array1.sort(false); array1.print(); array1.fill(); array1.print(); array2.sort(); array2.print(); array1.sort(false); array1.print(); return 0; } 13 2 Nicht objektorientierte Erweiterungen in C++ Linkt man dieses Programm arraystructmain.cpp mit dem noch zu erstellenden Modul arraystruct.cpp zusammen, sollte das Programm z.B. folgende Ausgabe liefern: --------------------------------------------- array1 ---Groesse des Arrays: 10 Array sortiert: nein 0.22 3.98 1.45 37.55 1.02 44.84 0.10 16.98 1.05 2.38 --------------------------------------------- array1 ---Groesse des Arrays: 10 Array sortiert: ja 0.10 0.22 1.02 1.05 1.45 2.38 3.98 16.98 37.55 44.84 --------------------------------------------- array1 ---Groesse des Arrays: 10 Array sortiert: ja 44.84 37.55 16.98 3.98 2.38 1.45 1.05 1.02 0.22 0.10 --------------------------------------------- array1 ---Groesse des Arrays: 10 Array sortiert: nein 44.84 0.10 16.98 1.05 2.38 0.36 3.87 3.83 36.24 0.49 --------------------------------------------- array2 ---Groesse des Arrays: 5 Array sortiert: ja 0.22 1.02 1.45 3.98 37.55 --------------------------------------------- array1 ---Groesse des Arrays: 10 Array sortiert: ja 44.84 36.24 16.98 3.87 3.83 2.38 1.05 0.49 0.36 0.10 14 2.5 Strukturen in C++ – Erster Schritt zur OO 2.5.5 Game of Life Die wesentliche Eigenschaft lebender Organismen ist ihre Fähigkeit zur Selbstreproduktion. Jeder Organismus kann Nachkommen erzeugen, die – bis auf Feinheiten – eine Kopie des erzeugenden Organismus sind. John von Neumann stellte folgende Fragen: „Sind auch Maschinen (z. B. Roboter) zur Selbstreproduktion fähig? Welche Art logischer Organisation ist dafür notwendig und hinreichend?“ S. M. Ulam schlug die Verwendung so genannter zellularer Automaten vor. Einen zellularen Automaten kann man sich anschaulich als eine in Quadrate (Zellen) aufgeteilte Ebene vorstellen. Auf jedem Quadrat befindet sich ein endlicher Automat, dessen Verhalten von seinem eigenen Zustand und von den Zuständen gewisser Nachbarn (Zellen) abhängt. Alle Automaten sind gleich und arbeiten im gleichen Takt. Ein berühmtes Beispiel für einen zellularen Automaten ist das Game of Life (Lebensspiel) des englischen Mathematikers John H. Conway. Jede Zelle hat zwei Zustände (lebend, tot), und die Umgebung der Zelle besteht aus den angrenzenden acht Nachbarquadraten. Die Zeit verstreicht in diskreten Schritten. Von einem Schlag der kosmischen Uhr bis zum nächsten verharrt die Zelle im zuvor eingenommenen Zustand, beim Gong aber wird nach den folgenden Regeln erneut über Leben und Tod entschieden: ❏ Geburt: Eine tote Zelle feiert Auferstehung, wenn drei ihrer acht Nachbarn leben. ❏ Tod durch Überbevölkerung: Eine Zelle stirbt, wenn vier oder mehr Nachbarn leben. ❏ Tod durch Vereinsamung: Eine Zelle stirbt, wenn sie keinen oder nur einen lebenden Nachbarn hat. Eine lebende Zelle bleibt also genau dann am Leben, wenn sie zwei oder drei lebende Nachbarn besitzt. Der Reiz dieses Spiels liegt in seiner Unvorhersehbarkeit. Nach den oben angegebenen Regeln kann eine Population aus lebenden Zellen grenzenlos wachsen, sich zu einem periodisch wiederkehrenden oder stabilen Muster entwickeln oder aussterben. Erstellen Sie ein Programm life.cpp, das dieses Spiel simuliert. Dieses Programm soll zunächst zufällig eine Population verteilen und dann mit der Simulation starten. Lebende Zellen sollen dabei als Sternchen angezeigt und tote Zellen als Leerzeichen dargestellt werden. Das Spielfeld soll dabei eine Kugel darstellen, was bedeutet, dass die Zellen in der oberen Zeile Nachbarn zu den Zellen in der unteren Zeile und Zellen in der linken Spalte Nachbarn zu den Zellen in der rechten Spalte sind. Ergänzen Sie nun den folgenden Ausschnitt des Programms life.cpp: #include <stdio.h> #include <stdlib.h> #include <time.h> struct zelle { public: zelle( bool lebt = false ) { this->lebt = lebt; } void setZelle( bool lebt ) { this->lebt = lebt; } bool getZelle( void ) { return lebt; } private: bool lebt; }; 15 2 Nicht objektorientierte Erweiterungen in C++ struct feld { public: ..................... private: int breite, hoehe; int n; // Nummer der Generation zelle **lifeFeld; // Zweidim. Array von ’zelle’-Elementen zelle **hilfFeld; // Zweidim. Array von ’zelle’-Elementen (Hilfsfeld) ..................... }; int main(void) { const int breite = 30, feld hoehe = 10, n = (hoehe*breite)/2; spielFeld( hoehe, breite ); srand( time(NULL) ); for (int i=0; i<n; i++) spielFeld.set( rand()%hoehe, rand()%breite, true); spielFeld.ausgabe(); while (getchar() == ’\n’) { spielFeld.naechsteGeneration(); spielFeld.ausgabe(); } return 0; } Nachfolgend ist Ablaufbeispiel zum Programm life.cpp gezeigt: 0. Generation +------------------------------+ |*** * | ** * | **** * * |** * * * * * * * * ** ** * |** * * ** * |* * |* * *** * ***** | ** * * ** * * * *| * ** | *** * * * | ** **** * * *| ** ** ** ** ** | * ***| ** ** | * **** ** ** * * * ** ** * * * * | | | | * * * *****| +------------------------------+ ←- 1. Generation +------------------------------+ | | | | 16 * *** * * * *** ** * * ** * * *** ** * ***** ** * *| ** | * **| * | 2.5 Strukturen in C++ – Erster Schritt zur OO | * | ** | * ******** * ***| * | | *** * | * * * * ** **** * *** * * | ***** ** ** * | * | ** ** * * * * * ** | * * *| +------------------------------+ ←- 2. Generation +------------------------------+ |* ** | * * | ** * **** *** ** | * **** * | *** **| * * * | ** * ** * ** | * * *** | | * * * | * ***| * * ** | *| | * *** * |* *** * |***** * * ****** * * * ** | * | * * | +------------------------------+ ←- ................... ................... 80. Generation +------------------------------+ | * *** |* ** * |* *| | * *| |* **| | ** |* * |* ** | |* | *| ** ** ** * * * * | | * * | **** * * | * | * ** +------------------------------+ ←- 81. Generation +------------------------------+ |* ** |* * * * *** | * * | | * | |* **| |* | *| **| *** ** **** ** | * | 17 2 Nicht objektorientierte Erweiterungen in C++ | * * | * | * * * ** | * ** ** *** * ** | * | * * +------------------------------+ ←- 211. Generation +------------------------------+ | | | | | | | | | | | * | | * | | * | | | | | +------------------------------+ ←- 212. Generation +------------------------------+ | | | | | | | | | | | | | *** | | | | | | | +------------------------------+ ←- 213. Generation +------------------------------+ | | | | | | | | | | | * | | * | | * | | | | | +------------------------------+ ...... Man hat nun ein sich wiederholendes Muster 18 Kapitel 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen 3.1 Was ist UML und warum UML? keine Übung dazu 3.2 3.2.1 Verwendung einer Klasse Konstruktoren Welche Voraussetzungen müssen erfüllt sein, damit die beiden Objekte punkt1 und punkt2 im folgenden Codeausschnitt angelegt werden können? int main(void) { CPunkt punkt1(10,20), punkt2; } 1. Alle Konstruktoren der Klasse CPunkt müssen public sein. 2. Der Standardkonstruktor muss explizit definiert werden. 3. Es muss ein Konstruktor definiert sein, der zwei Parameter vom Typ int besitzt. 4. Keine der obigen Voraussetzungen muss unbedingt erfüllt sein. 19 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen 3.2.2 Allgemeine Aussagen zu C++ Welche der folgenden Aussagen ist/sind richtig? 1. Klassennamen müssen immer mit C anfangen, sonst meldet der Compiler einen Fehler. 2. Wenn Funktionen innerhalb der Klassendefinition definiert werden, sind sie automatisch inline. 3. Inline-Funktionen sind automatisch public. 4. Eine Klasse ist ein komplexer, selbst definierter Datentyp, der nur aus einfachen C++ Datentypen wie int, char usw. bestehen darf. 5. Alle Methoden einer Klasse können von allen anderen Klassen aufgerufen werden. 6. Keine der Antworten ist richtig. 3.2.3 Initialisierung von Membervariablen Wo initialisiert man die (nicht statischen) Membervariablen eines Objektes? 1. In der Klasse. 2. Im Destruktor. 3. Im Konstruktor. 4. Wahlweise in der Klasse oder im Konstruktor. 5. Keine der Antworten ist richtig. 3.2.4 Methoden einer Klasse Welche der folgenden Aussagen trifft für Methoden einer Klasse zu? 1. Alle haben den gleichen Rückgabedatentyp. 2. Alle haben den gleichen Rückgabewert. 3. Sie können auf alle Membervariablen der Klasse zugreifen. 4. Sie können nur auf die private-Membervariablen der Klasse zugreifen. 5. Steht ihre Definition außerhalb der Klassendefinition, muss vor dem Funktionsnamen der Klassennamen gefolgt von einem doppelten Doppelpunkt :: angegeben werden. 6. Keine der Antworten ist richtig. 20 3.2 Verwendung einer Klasse 3.2.5 Addition, Substraktion und Multiplikation sehr grosser Zahlen Erstellen Sie eine Klasse CZahl, die Zahlen als Strings verwaltet. Diese Klasse soll aber auch in der Lage sein, mit solchen Zahlen zu rechnen, indem sie diese addieren, subtrahieren und multiplizieren kann. Geben Sie zum nachfolgenden Programm bigadd.cpp, das nur eine main()-Funktion zeigt, die Klasse CZahl an, so dass dieses Programm die folgende Ausgabe liefert: 12 + 352 = 364 352 - 12 = 340 -20 * 12 = -240 -240 -350 = -590 364 * 12345678 = 4493826792 4493826792 * 12345678 = 55479338561804976 55479338561804976 * -3 = -166438015685414928 1234567890123456897938347784734676346736346436634634634923900009439439\ 4943949499494394390344904390439043934994823884384843189999999999999999\ 9999999999372732737373737 * 9238189489484832284399248384343483344838484398438944389899423184384384\ 8484843894489894389438948948934893484388894384787854785789192300832737\ 237372373278237372722727282784378437437817474377478334748943894 = 1140517210659398479729310616025945896322122231988087836159776722797615\ 4425733791383710899745452092715604142534140865341296244069195112306113\ 6594051314603013044340379760643705191250060349655149572805989750598815\ 9054584834694375386301548330751334457495742799074533284035929138400363\ 1828475472798914559540279619940070957534669663478148744226364125541647\ 791864123922111878 21 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen Die zugehörige main()-Funktion ist hierbei folgende: ...... const int maxStellen = 10000; // Zahlen bis 10000 Stellen möglich class CZahl { ...... }; int main(void) { CZahl a("12"), b("352"), c("1234567890123456897938347784734676346736346436634634634" "9239000094394394943949499494394390344904390439043934994" "8238843848431899999999999999999999999999372732737373737"), d("9238189489484832284399248384343483344838484398438944389" "8994231843843848484843894489894389438948948934893484388" "8943847878547857891923008327372373723732782373727227272" "82784378437437817474377478334748943894"); // Angabe einer Zahl bei Methode print --> Ausgabe von Zeilenvorschüben a.print(); printf(" + "); b.print(); printf(" = "); a.add(b); a.print(2); b.print(); printf(" - 12 = "); b.sub("12"); b.print(2); b.set("-20"); b.print(); printf(" * 12 = "); b.mult("12"); b.print(2); b.print(); printf(" -350 = "); b.add("-350"); b.print(2); a.print(); printf(" * 12345678 = "); a.mult("12345678"); a.print(2); a.print(); printf(" * 12345678 = "); a.mult("12345678"); a.print(2); a.print(); printf(" * -3 = "); a.mult("-3"); a.print(2); c.print(); printf(" *\n"); d.print(); printf(" =\n"); c.mult(d); c.print(1); return 0; } } 22 3.2 Verwendung einer Klasse 3.2.6 Realisierung einer Queue (Warteschlange) Eine weitere grundlegende Datenstruktur in der Informatik neben dem Stack ist die so genannte Queue, welche eine Schlange vor einem Postschalter oder einem Eintritt in eine Veranstaltung simuliert. Es handelt sich dabei wieder um eine Datenstruktur, für die nur zwei Operationen definiert sind: ❏ Put() zum Einfügen eines Elements am Ende der Queue und ❏ Get() zum Entfernen des Elements am Anfang der Queue. Abbildung 3.1 verdeutlicht die Funktionsweise einer Queue nochmals. Put(5) 5 5 2 2 5 4 4 2 7 7 4 7 Get() Abbildung 3.1: Funktionsweise einer Queue Während ein Stack nach dem LIFO-Prinzip („last in, first out“) arbeitet, arbeitet eine Queue nach dem FIFO-Prinzip („first in, first out“). Ist die maximale Größe einer Queue konstant, bietet sich als eine einfache Realisierung ein eindimensionales Array an. Erstellen Sie nun ein Programm queue.cpp, das die Verwaltung einer Queue wie z. B. vor einem Postschalter oder in einem Wartezimmer übernimmt. Dem Bediener des Programms sollen dabei folgende Möglichkeiten angeboten werden: ❏ Ankunft eines Kunden mit Eingabe des Namens, welcher der Einfacheit halber nur aus einem Zeichen besteht (entspricht Einordnen am Ende der Queue) ❏ Bedienung eines Kunden (entspricht Entfernen aus der Queue) ❏ Auflisten der aktuellen Warteschlange, entsprechend der Reihenfolge Dieses Programm queue.cpp soll eine Klasse CQueue definieren, die für die Verwaltung einer Queue von Namen (hier nur Buchstaben) verantwortlich ist. Dazu soll sie die folgenden Methoden anbieten: bool isEmpty() { return m_iCount==0; bool isFull() } // Queue leer ? (inline) { return m_iCount==m_iMax; } // Queue voll ? (inline) char Get(); bool Put(char name); void Contents(); // Auflisten der Queue entsprechend der Reihenfolge Zur internen Verwaltung dieser Queue sollten Sie vier Variablen benutzen: 23 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen ❏ m_iMax enthält maximale Anzahl von Personen, die die Warteschlange aufnehmen kann ❏ m_iCount enthält aktuelle Anzahl der Personen, die sich in der Warteschlange befinden ❏ m_iFirst ist der Index der Person im Array, die sich an erster Stelle in der Warteschlange befindet und als nächste zu bedienen ist. ❏ m_iLast ist Index der Person im Array, die am Ende der Warteschlange steht. Ist das Ende des Array erreicht, müssen wieder die vorderen freien Plätze benutzt werden. Das Aussehen der Funktion main() ist nachfolgend gezeigt. ..... int main(void) { const int iMaxQueue = 10; CQueue queue(iMaxQueue); char eingabe, name; while (1) { printf("\n\n" "A Ankunft eines neuen Patienten\n" "B Bedienung des naechsten Patienten\n" "Q Aktuellen Queue-Inhalt anzeigen\n" "E Ende des Programms\n\n" " Deine Wahl: "); eingabe = getchar(); getchar(); printf("\n"); if (eingabe == ’e’) break; else if (eingabe == ’a’) { if (!queue.isFull()) { printf("... Name des Patienten (als Buchstabe): "); name = getchar(); getchar(); queue.Put(name); } else printf("...... Wartezimmer ist voll ....\n"); } else if (eingabe == ’b’) { if (queue.isEmpty()) printf("...... Wartezimmer ist leer ....\n"); else printf("...... ’%c’ wird nun bedient\n", queue.Get()); } else if (eingabe == ’q’) { queue.Contents(); } } return 0; } ..... 24 3.2 Verwendung einer Klasse Ihre Aufgabe ist es nun, das Programm queue.cpp um die fehlenden Konstrukte zu ergänzen, so dass es z. B. folgenden Ablauf zeigt. Bevor Sie mit der Implementierung beginnen, sollten Sie ein Klassen- und ein Zustandsdiagramm zu dieser Klasse zeichnen. A B Ankunft eines neuen Patienten Bedienung des naechsten Patienten Q Aktuellen Queue-Inhalt anzeigen E Ende des Programms Deine Wahl: a ←- ... Name des Patienten (als Buchstabe): h A ←- Ankunft eines neuen Patienten ....... Deine Wahl: a ←- ... Name des Patienten (als Buchstabe): x ....... Deine Wahl: a ←- ... Name des Patienten (als Buchstabe): m ....... Deine Wahl: q ←- Deine Wahl: b ←- ←- ←- 1. h 2. x 3. m ....... ...... ’h’ wird nun bedient ....... Deine Wahl: b ←- ...... ’x’ wird nun bedient ....... Deine Wahl: a ←- ... Name des Patienten (als Buchstabe): c ....... Deine Wahl: a ←- ... Name des Patienten (als Buchstabe): y ....... Deine Wahl: q ←- Deine Wahl: b ←- ←- ←- 1. m 2. c 3. y ....... ...... ’m’ wird nun bedient ....... Deine Wahl: b ←- ...... ’c’ wird nun bedient ....... Deine Wahl: b ←- ...... ’y’ wird nun bedient ....... Deine Wahl: b ...... Wartezimmer ist ....... Deine Wahl: e ←- leer .... ←- 25 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen 3.2.7 Realisierung einer Klasse CDoubleArray Erstellen Sie eine Klasse CDoubleArray, die ein Array von double-Zahlen kapselt. Diese Klasse soll sicherstellen, dass es zu keinerlei Speicherüber- oder unterschreitung kommt. Die Größe des Arrays legt der Nutzer der Klasse beim Anlegen eines CDoubleArray-Objektes fest. Zum Setzen und Erfragen eines Arrayelements bietet diese Klasse die beiden Methoden Set() und Get() an. Außerdem verfügt diese Klasse über eine Methode GetLen(), um die Größe des Arrays erfragen zu können. Da es auch möglich sein soll, dass man ein Array-Objekt bereits beim Anlegen mit dem Inhalt eines anderen Array belegen kann, sollten Sie auch den Kopierkonstruktor überladen. Um eine Zuweisung eines Arrays an ein anderes Array zu ermöglichen, sollte zudem auch der Zuweisungsoperator überladen werden. Erstellen Sie zunächst ein Klassendiagramm, bevor Sie mit der Implementierung beginnen. Testen Sie Ihre Klasse mit einer geeigneten main()-Funktion, die neben typischen Anwendungsfällen natürlich auch Grenzfälle testen sollte, wie z.B. einen bewussten Versuch einer Speicherüberschreitung. Es wird hier und auch an anderen Stellen oft bewusst kein main() oder ein möglicher Programmablauf vorgegeben. Denn man kann es nicht früh genug üben, sich vernünftige Testfälle (hier in main()) auszudenken, um die Funktionsweise von Klassen zu gewährleisten. 26 3.2 Verwendung einer Klasse 3.2.8 Konstruktoren, Destruktoren, Kopierkonstruktor und Zuweisungsoperator Geben Sie zum nachfolgenden Programm klasse.cpp, das nur eine main()-Funktion zeigt, die Klasse CKlasse an, so dass dieses Programm die folgende Ausgabe liefert: Konstruktor CKlasse(A) Kopierkonstruktor: Klasse(A) Zuweisungsoperator: x = A Konstruktor CKlasse(C) Zuweisungsoperator: x = A Destruktor CKlasse(C) Destruktor CKlasse(B) Destruktor CKlasse(A) Die zugehörige main()-Funktion ist hierbei folgende: #include <stdio.h> #include <string.h> class CKlasse { .............. }; int main(void) { CKlasse A("A"), // Standardkonstruktor B(A), // Kopierkonstruktor C("C"); // Standardkonstruktor C = B; // Zuweisungsoperator B.setName("B"); C.setName("C"); return 0; } // Destruktor von A,B,C 27 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen 3.2.9 Eine Klasse für Bitoperationen Die folgende Headerdatei bitmap.h zeigt eine Klasse für Bitoperationen. #ifndef BITMAP_H #define BITMAP_H class Bitmap { public: Bitmap(); void Zero(void) { bitWord = 0; } // setzt alle Bits auf 0 void SetBits(int bitstoset[], int num); // setzt alle Bits, deren Nummer im // Array (Länge n) angegeben sind void SetBit(int bitnum); // setzt das Bit mit der Nummer bitnum auf 1 void ClearBit(int bitnum); // setzt das Bit mit der Nummer bitnum auf 0 void SetAs(int bitnum, int setting); // setzt das Bit mit der Nummer bitnum // auf ’setting’ (0 oder 1) int TestBit(int bitnum); // liefert 1, wenn Bit mit Nummer bitnum // gesetzt ist, und ansonsten 0 void FlipBit(int bitnum); // invertiert das Bit mit der Nummer bitnum void Invert(void); // invertiert das ganze Bitmuster Bitmap And(const Bitmap& other); // Bitmuster AND other Bitmap Or(const Bitmap& other); // Bitmuster OR other Bitmap Xor(const Bitmap& other); // Bitmuster XOR other Bitmap Not(void); // liefert invertiertes Bitmuster bool Equals(const Bitmap& other); // liefert true, wenn Bitmuster gleich // other ist, und ansonsten false int Count(void); // liefert Anzahl gesetzter Bits void Print(const char *str); // gibt Bitmuster aus private: unsigned int bitWord; int maxBits; }; #endif Qualifizieren Sie nun zunächst die Memberfunktionen im Programm bitmap.h mit const, bei denen dies möglich ist. Anschließend sollten Sie noch in der Datei bitmap.cpp die Implementierung zu diesen Memberfunktionen angeben. Zum Testen Ihrer Implementierung können Sie das folgende Programm bitmapmain.cpp verwenden. #include <stdio.h> #include "bitmap.h" void test1(void) { int somebits[] = { 0, 3, 4, 6, 9, 14, 21, 31 }; int n = sizeof(somebits) / sizeof(int); Bitmap b1; 28 3.2 Verwendung einer Klasse b1.SetBits(somebits, n); b1.Print("Bits 0, 3, 4, 6, 9, 14, 21, 31 sollten gesetzt sein"); printf(" Bit 20 ist%s gesetzt\n", b1.TestBit(20) ? "" : " nicht"); printf(" Bit 21 ist%s gesetzt\n", b1.TestBit(21) ? "" : " nicht"); printf(" Bit 6 ist%s gesetzt\n", !b1.TestBit(6) ? " nicht" : ""); printf(" Bit 7 ist%s gesetzt\n", !b1.TestBit(7) ? " nicht" : ""); printf(" Insgesamt sind %d Bits gesetzt\n", b1.Count()); b1.Invert(); b1.Print("Bits sollten nun alle invertiert sein"); b1.ClearBit(24); b1.FlipBit(6); b1.FlipBit(7); b1.Print("24.Bit gelöscht; 6. und 7.Bit umgedreht"); Bitmap b2 = b1; b1.Print("Beide Bitmuster sollten gleich sein"); b2.Print(""); if (b1.Equals(b2)) printf(" .... und sie sind es auch\n"); else printf(" .... und sie sind es nicht (irgendwas falsch)\n"); } void test2(void) { int somebits[] = { 2, 4, 6, 8, 16 }; int n = sizeof(somebits) / sizeof(int); Bitmap b1; b1.SetBits(somebits, n); int otherbits[] = { 2, 3, 6, 9, 16, 23, 25, 27 }; int m = sizeof(otherbits) / sizeof(int); Bitmap b2; b2.SetBits(otherbits, m); Bitmap b3 = b1.Not(); b1.Print("Bitmuster (negiert)"); printf("----------------------------------------------\n"); b3.Print(""); Bitmap b4 = b1.And(b2); b1.Print("Zwei Bitmuster (And)"); b2.Print(""); printf("----------------------------------------------\n"); b4.Print(""); Bitmap b5 = b1.Or(b2); 29 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen b1.Print("Zwei Bitmuster (Or)"); b2.Print(""); printf("----------------------------------------------\n"); b5.Print(""); Bitmap b6 = b1.Xor(b2); b1.Print("Zwei Bitmuster (Xor)"); b2.Print(""); printf("----------------------------------------------\n"); b6.Print(""); b2.Print("Bitmuster (negiert)"); printf("----------------------------------------------\n"); b2.Invert(); b2.Print(""); } int main(void) { test1(); test2(); return 0; } Kompiliert und linkt man dieses Programm bitmapmain.cpp und Ihre Implementierung im Programm bitmap.cpp z.B. wie folgt user@linux: user@linux: > g++ -o bitmap bitmap.cpp bitmapmain.cpp > _ ←- so sollte ein Start dieses Programms die folgende Ausgabe liefern: ====================================================== Bits 0, 3, 4, 6, 9, 14, 21, 31 sollten gesetzt sein | 0 | 8 | 16 | 24 | | 10011010 | 01000010 | 00000100 | 00000001 | Bit 20 ist nicht gesetzt Bit 21 ist gesetzt Bit 6 ist gesetzt Bit 7 ist nicht gesetzt Insgesamt sind 8 Bits gesetzt ====================================================== Bits sollten nun alle invertiert sein | 0 | 8 | 16 | 24 | | 01100101 | 10111101 | 11111011 | 11111110 | ====================================================== 24.Bit gelöscht; 6. und 7.Bit umgedreht | 0 | 8 | 16 | 24 | | 01100110 | 10111101 | 11111011 | 01111110 | ====================================================== 30 3.2 Verwendung einer Klasse Beide Bitmuster sollten gleich sein | 0 | 8 | 16 | 24 | | 01100110 | 10111101 | 11111011 | 01111110 | | 01100110 | 10111101 | 11111011 | 01111110 | .... und sie sind es auch ====================================================== Bitmuster (negiert) | 0 | 8 | 16 | 24 | | 00101010 | 10000000 | 10000000 | 00000000 | ---------------------------------------------| 11010101 | 01111111 | 01111111 | 11111111 | ====================================================== Zwei Bitmuster (And) | 0 | 8 | 16 | 24 | | 00101010 | 10000000 | 10000000 | 00000000 | | 00110010 | 01000000 | 10000001 | 01010000 | ---------------------------------------------| 00100010 | 00000000 | 10000000 | 00000000 | ====================================================== Zwei Bitmuster (Or) | 0 | 8 | 16 | 24 | | 00101010 | 10000000 | 10000000 | 00000000 | | 00110010 | 01000000 | 10000001 | 01010000 | ---------------------------------------------| 00111010 | 11000000 | 10000001 | 01010000 | ====================================================== Zwei Bitmuster (Xor) | 0 | 8 | 16 | 24 | | 00101010 | 10000000 | 10000000 | 00000000 | | 00110010 | 01000000 | 10000001 | 01010000 | ---------------------------------------------| 00011000 | 11000000 | 00000001 | 01010000 | ====================================================== Bitmuster (negiert) | 0 | 8 | 16 | 24 | | 00110010 | 01000000 | 10000001 | 01010000 | ---------------------------------------------| 11001101 | 10111111 | 01111110 | 10101111 | 31 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen 3.2.10 Vergabe und Löschen von Kundennummern Erstellen Sie eine Klasse CKunde. Jeder Kunde soll über eine eindeutige Kundennummer identifiziert werden können, die automatisch beim Anlegen des Kunden vergeben wird. Sehen Sie für das Anlegen eines neuen Kunden eine eigene Methode Anlegen() vor und verwenden Sie nicht den Konstruktor, da dieser z.B. auch dann verwendet wird, wenn ein Kundenobjekt nur temporär angelegt wird, z.B. für eine Vertauschung innerhalb einer Methode. Analoges gilt für das Löschen eines Kunden, genauer gesagt für das Freigeben einer Kundennummer; verwenden Sie auch hier eine eigene Methode Loeschen(). Geben Sie zum nachfolgenden Programm kunde.cpp, das nur eine main()-Funktion zeigt, die Klasse CKkunde und eventuell weitere Konstrukte an, so dass dieses Programm die folgende Ausgabe liefert: Liste aller bisherigen Kunden: 1 2 3 4 5 6 7 Liste aller aktuellen Kunden (nach Loeschen und Anlegen zweier neuer Kunden): 1 2: fehlt 3 4 5: fehlt 6 7 8 9 Naechste zu vergebende Nummer: 10 32 3.2 Verwendung einer Klasse Vor der Implementierung der Klasse CKunde sollten Sie ein Klassendiagramm zeichnen. Die zugehörige main()-Funktion ist hierbei folgende: #include <stdio.h> class CKunde { ..... }; ...... int main(void) { CKunde kunde[8]; CKunde neuKunde1, neuKunde2; unsigned long kdnr; int i; for(i=1; i < 8; i ++) kunde[i].Anlegen(); printf("Liste aller bisherigen Kunden:\n"); for (i=1; i < 8; i++) printf("%d\n", kunde[i].GetKdnr()); kunde[2].Loeschen(); kunde[5].Loeschen(); neuKunde1.Anlegen(); neuKunde2.Anlegen(); printf("Liste aller aktuellen Kunden\n" "(nach Loeschen und Anlegen zweier neuer Kunden):\n"); for (i=1; i < 8; i++) if (kdnr = kunde[i].GetKdnr()) printf("%d\n", kdnr); else printf("%d: fehlt\n", i); printf("%d\n", neuKunde1.GetKdnr()); printf("%d\n", neuKunde2.GetKdnr()); printf("Naechste zu vergebende Nummer: %d\n", CKunde::GetNaechstNr()); return 0; } 33 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen 3.3 Mehrere Klassen 3.3.1 Realisierung eines Bruchrechners Es ist ein Bruchrechner zu erstellen, der Brüche addieren, subtrahieren, multiplizieren und dividieren kann. Nach jeder Operation ist der Ergebnis-Bruch gekürzt auszugeben. Machen Sie sich Gedanken über Ihre Klassenaufteilung, wobei Sie zu jeder Klasse zwei Dateien angeben sollten: Eine Header-Datei, in der sich die Klassen-Deklaration befindet und eine .cpp-Datei, in der sich die Implementierung zu dieser Klasse befindet. Natürlich benötigen Sie auch eine Datei, wie z.B. bruchmain.cpp, in der sich die main-Funktion befindet. Nachfolgend ist ein möglicher Ablauf Ihres Programms gezeigt: Bruchrechner mit folgenden Operationen + : Addieren - : Subtrahieren * : Multiplizieren / : Dividieren k : Kuerzen e : Ende Geben Sie Ihren ersten Bruch ein (wie z.B. 3/15): 3/8 Operation: + ←- Nächster Bruch: 1/8 ←- = 1 / 2 ------------------- ←- Bruch: 1/4 ←- Operation: Nächster = 1 / 4 ------------------- ←- Bruch: 3/1 ←- Operation: * Nächster = 3 / 4 ------------------- ←- Bruch: 1/2 ←- Operation: * Nächster = 3 / 8 ------------------- ←- Bruch: 1/4 ←- Operation: / Nächster = 3 / 2 ------------------- ←- Bruch: 1/6 ←- Operation: * Nächster = 1 / 4 ------------------ Operation: e ←- 34 ←- 3.3 Mehrere Klassen 3.3.2 Gemeinsamer Termin In Gemeinschaften (Vereinen, Abteilungen,...) gibt es oft das Problem, dass ein gemeinsamer Termin gefunden werden muss (z.B. für die gemeinsame Weihnachtsfeier). Üblicherweise wird dazu eine Tabelle erstellt, in der alle Kandidaten eintragen können, welcher Termin für sie möglich wäre und welcher nicht. Der Termin, an dem die meisten Personen Zeit haben, wird dann genommen. Erstellen Sie eine Klasse CTerminFinder, die diese Aufgabe übernimmt. Diese Klasse soll über folgende Methoden verfügen: streichen(TerminNr), freigeben(TerminNr), getBestTermine(). Der Einfachheit halber wird nicht gespeichert, welche Person ihre Stimme bereits abgegeben hat, sondern mit jedem freigeben() wird ein Zähler für diesen Termin hochgezählt und für jedes streichen() der Zähler für den Termin dekrementiert. Die Anzahl der Termine, die zur Auswahl stehen, wird beim Konstruktor angegeben. Berücksichtigen Sie, dass es auch mehrere Termine geben kann, an denen die meisten Personen Zeit haben. Die Methode getBestTermine() muss daher ein Array aus Terminnummern zurückliefern statt nur eines Wertes. Hier bietet sich eine eigene Klasse CIntArray an, die die Verwaltung eines int-Arrays übernimmt: class CIntArray { public: CIntArray(int max) { ... } // Konstruktor (legt int-Array der Größe ’max’ an bool set(int i, int value) { ... } // schreibt an Index i Wert (value) bool get(int i, int& value) { ... } // liefert zum Index i den Wert (value) int getLen() { ... } // liefert die max. Anzahl von moegl. Werten im Array CIntArray(const CIntArray& array) { ... } // Kopierkonstruktor CIntArray& operator=(const CIntArray& array) { ... } // Zuweisungsoperator private: int* m_array; int m_max; // maximaler Index (Groesse des Arrays) }; Diese Klasse CIntArray kann man in einer eigenen Headerdatei intarray.h angeben, so dass man diese Headerdatei bei Bedarf immer nur inkludieren muss. Implementieren Sie nun diese beiden Klassen CIntArray und CTerminFinder (im Programm terminfinder.cpp). Erstellen Sie zusätzlich eine geeignete main()-Funktion (in terminfinder.cpp), um diese Klassen zu testen, Z.B. könnten Sie folgende Situation fest in main() vorgeben: ❏ Es wird jeweils ein Wochentag (5 mögliche Tage: MO-FR) gesucht, an dem man sich zum Kegeln treffen kann, und ein Tag zum Skatspielen. ❏ Hugo ist Kegler und Skatspieler und kann Montags und Mittwochs nie. ❏ Fritz ist nur Kegler und kann am Dienstag nicht ❏ Anna spielt nur Skat und kann am Dienstag und Mittwoch nicht ❏ Nachdem Anna aus einem Verein ausgeschieden ist, kann sie nun doch dienstags 35 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen Geben Sie die jeweils besten Tage zum Kegeln und zum Skatspielen aus. Das Programm sollte dann in etwa Folgendes ausgeben: Beste Termine fuer Kegeln: DO FR Beste Termine fuer Skat: DI DO FR Da die beiden “TerminFinder“ nicht synchronisiert sind, hätte Hugo nun ein Problem, wenn beide Termine auf Donnerstagabend festgelegt werden. Aber so ist das eben im Leben ;-) Noch ein Hinweis zu dieser Übung: Es kommt immer darauf an, wie die Aufgabe einer Klasse definiert ist. Natürlich hätte hier auch die Ausgabe der besten Termine direkt in CTerminFinder erfolgen können, etwa in einer Methode printBestTermine(). Dann wäre der Einsatz der zweiten Klasse CIntArray überflüssig. Der Nachteil ist dann aber, dass die Klasse nicht so flexibel eingesetzt werden kann, etwa in GUIApplikationen. In unserem Fall dagegen kümmert sich die Klasse lediglich um die Verwaltung der Terminliste und nimmt die Auswertung vor, ohne sie jedoch optisch aufzubereiten. 36 3.3 Mehrere Klassen Initialisierung von Membervariablen mit Initialisierungslisten 3.3.3 Das d’Hondtsche Höchstzählverfahren Gemäß Bundeswahlgesetz Paragraph 6 Abs. 1 werden die Sitze auf die Landeslisten nach dem dem „Höchstzählverfahren d’Hondt“ verteilt. Es heißt so nach seinem Urheber, dem Belgier Victor d’Hondt und wird in den Erläuterungen zum Bundeswahlgesetz wie folgt beschrieben: Dieses Höchstzählverfahren ist ein Rechenverfahren, durch das auf verhältnismäßig einfache Weise auf Grund der Stimmenzahl die proportionale Sitzverteilung ermittelt wird. Das Ergebnis entspricht nicht ganz, aber annähernd der mathematischen Proportion. Es besteht darin, dass die auf eine Wahlvorschlagsliste entfallenen Stimmen so oft durch 1, 2, 3 usw. geteilt werden, bis so viele ’Höchstzahlen’ ermittelt sind, als Sitze zu verteilen sind. In der Reihenfolge der so ermittelten Höchstzahlen werden jeder Partei dann die Sitze zugewiesen. Angenommen, es seien insgesamt abgegeben: für die Liste A B C D E 650500 541600 461500 89200 64800 Stimmen Stimmen Stimmen Stimmen Stimmen und es seien insgesamt 31 Abgeordnete zu wählen. Folgende Tabelle enthält dann die Quotienten und in Klammern die Reihenfolge der Sitzzuteilung: A B C 650500(1) 541600(2) 461500(3) durch 2 325250(4) 270800(5) 230750(6) durch 3 216833(7) 180533(8) 153833(10) durch 4 162625(9) 135400(11) 115375(13) durch 5 130100(12) 108320(15) 92300(17) .................................................. D 89200(19) 44600 E 64800(27) 32400 Würde man diese Rechnung fortsetzen, dann erhält die Partei A 12 Sitze, B erhält 9, C erhält 8 und D und E erhalten je einen Sitz. Erstellen Sie nun ein C-Programm dhondt.cpp das für Bundestags-, Landestags- und Kommunalwahlen die Sitzverteilung nach dem d’Hondtschen Höchstzählverfahren berechnet. Die Namen der Parteien seien dabei fest im Programm vorgegeben. Im Programm dhondt.cpp sollten Sie zwei Klassen angeben: ❏ CPartei für die Parteien ❏ CdHondt für das d’Hondtsche Höchstzählverfahren Die Membervariablen dieser beiden Klassen sollten Sie dabei mittels Initialisierungslisten initialisieren. Möglicher Ablauf des Programms dhondt.cpp ist z.B.: Sitzverteilung nach d’Hondt =========================== 37 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen Wieviele Sitze sind zu vergeben: 31 ←- Geben Sie die abgegebenen Stimmen pro Partei ein ←- SPT: 541600 ←- FTB: 461500 ← Greens: 89200 ← PTS: 64800 ←- ZTU: 650500 Soll jede einzelne Sitzzuteilung ausgegeben werden (j/n) ? j ←- ...Sitz 1 geht an: ZTU (650500) ...Sitz 2 geht an: SPT (541600) ...Sitz 3 geht an: FTB (461500) ...Sitz 4 geht an: ZTU (325250) ...Sitz 5 geht an: SPT (270800) ...Sitz 6 geht an: FTB (230750) ...Sitz 7 geht an: ZTU (216833) ...Sitz 8 geht an: SPT (180533) ...Sitz 9 geht an: ZTU (162625) ...Sitz 10 geht an: FTB (153833) ...Sitz 11 geht an: SPT (135400) ...Sitz 12 geht an: ZTU (130100) ...Sitz 13 geht an: FTB (115375) ...Sitz 14 geht an: ZTU (108417) ...Sitz 15 geht an: SPT (108320) ...Sitz 16 geht an: ZTU (92929) ...Sitz 17 geht an: FTB (92300) ...Sitz 18 geht an: SPT (90267) ...Sitz 19 geht an: Greens (89200) ...Sitz 20 geht an: ZTU (81312) ...Sitz 21 geht an: SPT (77371) ...Sitz 22 geht an: FTB (76917) ...Sitz 23 geht an: ZTU (72278) ...Sitz 24 geht an: SPT (67700) ...Sitz 25 geht an: FTB (65929) ...Sitz 26 geht an: ZTU (65050) ...Sitz 27 geht an: PTS (64800) ...Sitz 28 geht an: SPT (60178) ...Sitz 29 geht an: ZTU (59136) ...Sitz 30 geht an: FTB (57688) ...Sitz 31 geht an: ZTU (54208) Sitzverteilung: Partei | Sitze | Prozent (Sitze) | Prozent (Stimmen) | -----------+-------+----------------------+----------------------| 38 ZTU | 12 | 38.71 | 35.99 | SPT | 9 | 29.03 | 29.96 | FTB | 8 | 25.81 | 25.53 | Greens | PTS | 1 | 1 | 3.23 | 3.23 | 4.93 | 3.58 | 3.3 Mehrere Klassen 3.3.4 Kästners Kubikkilometer für alle Menschen Der Kinderbuchautor Erich Kästner hat in einem seiner Bücher die interessante These aufgestellt, dass in einen Kubikkilometer alle Menschen passen würden. Ergänzen Sie das folgende C-Programm kaestner.cpp um eine Initialisierungsliste, so dass es nach dem Einlesen der durchschnittlichen Höhe, Breite und Tiefe eines Menschen, ausgibt, wieviel Milliarden Menschen bei solchen Maßen in einen Kubikkilometer passen würden. #include <stdio.h> class CBerech { public: CBerech(float l, float b, float t) : // ...fehlender Code... { printf("\n\nBei diesen Mass-Angaben passen\n"); printf(" %f Mrd. Menschen in einen Kubik-Kilometer\n", mensch_zahl/mrd); } private: const double kubik_km_in_cm, mrd; double mensch_volumen, mensch_zahl; }; int main(void) { float laenge, breite, tiefe; printf("Wie lang ist durchschnittlich ein Mensch (in cm): "); scanf("%f", &laenge); printf("Wie breit ist durchschnittlich ein Mensch (in cm): "); scanf("%f", &breite); printf("Wie tief ist durchschnittlich ein Mensch (in cm): "); scanf("%f", &tiefe); CBerech menschKubikKm( laenge, breite, tiefe ); return 0; } Mögliche Abläufe des Programms kaestner.cpp: ←- breit ist durchschnittlich ein Mensch (in cm): 55 ← tief ist durchschnittlich ein Mensch (in cm): 20 ←- Wie lang ist durchschnittlich ein Mensch (in cm): 150 Wie Wie Bei diesen Mass-Angaben passen 6.060606 Mrd. Menschen in einen Kubik-Kilometer 39 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen ←- breit ist durchschnittlich ein Mensch (in cm): 45 ← tief ist durchschnittlich ein Mensch (in cm): 17 ←- Wie lang ist durchschnittlich ein Mensch (in cm): 135 Wie Wie Bei diesen Mass-Angaben passen 9.682885 Mrd. Menschen in einen Kubik-Kilometer Korrigieren Sie das folgende Programm kaestner2.cpp, so dass es fehlerfrei kompiliert werden kann. Dazu kommentieren Sie die entsprechenden fehlerhaften Zeilen aus bzw. entfernen den C++-Kommentar // aus den erforderlichen Zeilen in der Initialisierungsliste: #include <stdio.h> class CBerech { public: CBerech(float l, float b, float t) // : kubik_km_in_cm(1e3 * 1e3 * 1e3 * 1e6), // mrd(1e9), // mensch_volumen(l*b*t), // mensch_zahl(kubik_km_in_cm / mensch_volumen) { kubik_km_in_cm = 1e3 * 1e3 * 1e3 * 1e6; mrd = 1e9; mensch_volumen = l*b*t; mensch_zahl = kubik_km_in_cm / mensch_volumen; printf("\n\nBei diesen Mass-Angaben passen\n"); printf(" %f Mrd. Menschen in einen Kubik-Kilometer\n", mensch_zahl/mrd); } private: const double kubik_km_in_cm, mrd; double mensch_volumen, mensch_zahl; }; int main(void) { .... // siehe vorheriges Programm \cmd{kaestner.cpp} .... } 40 3.3 Mehrere Klassen 3.3.5 Wortstatistik zu Textdateien (mittels Binärbaum) Erstellen Sie ein Programm wortstat.cpp, das zu Textdateien, die beim Aufruf auf der Kommandozeile angegeben sind, eine alphabetisch geordnete Wortstatistik erstellt, was bedeutet, dass zu jedem Wort die Häufigkeit seines Vorkommens in den angegebenen Textdateien ausgegeben wird. Verwenden Sie zur Lösung dieser Aufgabenstellung einen Binärbaum. Hat man z.B. die folgenden Texte in den Dateien text1.txt und text2.txt: ❏ text1.txt: Heute sind die Voraussetzungen für eine gute Tat sehr günstig; solche Voraussetzungen wünscht man sich. Nur weiter so bis Ende des Jahres; vielleicht auch einen guten Umsatz, der dreistellig ist; dann werden sich auch schwarze Zahlen sehen lassen; Jeder kann etwas dazu beitragen. ❏ text2.txt: Heute wünscht man sich Voraussetzungen, die sich bis Ende des Jahres günstig auswirken. So soll es sein und wir wünschen einen guten Jahresabschluss. Der Umsatz, der besser sein könnte, möge sich weiter erhöhen, und dann sehen wir in eine rosige Zukunft. Nun wollen wir das Jammern lassen und jeder soll das tun, was er kann. und man ruft das Programm wortstat wie folgt auf: wortstat text1.txt text2.txt so sollte z.B. folgendes ausgegeben werden: auch : 2 auswirken : 1 beitragen : 1 besser : 1 bis : 2 dann : 2 das : 2 dazu : 1 der : 3 des : 2 die : 2 dreistellig : 1 ............................ ............................ sich : 5 sind : 1 so : 2 solche : 1 41 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen soll : 2 tat : 1 tun : 1 umsatz : 2 und : 3 vielleicht : 1 voraussetzungen : 3 was : 1 weiter : 2 werden : 1 wir : 3 wollen : 1 wünschen : 1 wünscht : 2 zahlen : 1 zukunft : 1 3.3.6 Geldscheine stapeln In der Politik ist häufig von zig Milliarden die Rede. Viele Menschen können sich gar nicht vorstellen, um welche gigantische Menge von Geld es sich dabei handelt. In diesem Beispiel wird nun angenommen, dass Geldscheine übereinander gestapelt werden, um eine bestimmte Summe zu bilden. Das folgende Programm geldstap.cpp liest zunächst ein, welche Scheine (10 Euro oder 50 Euro oder ...) zum Stapeln verwendet werden sollen. Nachdem es noch den zu stapelnden Betrag eingelesen hat, gibt dieses Programm aus, wie hoch der Stapel in Zentimeter, Meter und Kilometer wäre, wobei es für 1000 Scheine eine Höhe von 15 cm annimmt. 1 #include <stdio.h> 2 3 class CGeldStapel 4 { 5 public: 6 CGeldStapel(float scheinWert, float betrag) 7 : cm_zu_meter(0.01), 8 meter_zu_km(0.001), 9 scheinDicke(15.0/1000), // 15 cm fuer 1000 Scheine genommen 10 scheinZahl(betrag/scheinWert), 11 12 hoehe(scheinZahl*scheinDicke) { 13 printf("\nMit %.0f Euro-Scheine werden %.0f Euro gestapelt\n", 14 scheinWert, betrag); 15 printf("Der Stapel wäre hoch: %.2f cm = %.2f m = %.2f km\n", 16 17 18 42 hoehe, hoehe*cm_zu_meter, hoehe*cm_zu_meter*meter_zu_km); } private: 3.3 Mehrere Klassen 19 float cm_zu_meter; 20 float meter_zu_km; 21 float hoehe; 22 float scheinZahl; 23 24 float scheinDicke; }; 25 26 int 27 { 28 main(void) float schein_wert, betrag; 29 30 printf("Welche Scheine sollen zum Stapeln verwendet werden: "); 31 scanf("%f", &schein_wert); 32 printf("Welcher Betrag soll mit diesen Scheinen gestapelt werden: "); 33 scanf("%f", &betrag); 34 35 CGeldStapel stapel(schein_wert, betrag); 36 37 38 return 0; } Allerdings zeigen die folgenden Programmabläufe, dass an diesem Programm etwas falsch ist: Welche Scheine sollen zum Stapeln verwendet werden: 10 ←- Welcher Betrag soll mit diesen Scheinen gestapelt werden: 1000000 ←- Mit 10 Euro-Scheine werden 1000000 Euro gestapelt Der Stapel wäre hoch: 5.33 cm = 0.05 m = 0.00 km Welche Scheine sollen zum Stapeln verwendet werden: 5 ←- Welcher Betrag soll mit diesen Scheinen gestapelt werden: 1e10 ←- Mit 5 Euro-Scheine werden 10000000000 Euro gestapelt Der Stapel wäre hoch: 5.33 cm = 0.05 m = 0.00 k Wo liegt in diesem Programm der Fehler? 43 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen 3.4 3.4.1 Beziehungen zwischen Objekten Klassendiagramm und Implementierung zu einem Vieleck Realisieren Sie eine Klasse zur Verwaltung und Darstellung eines Vielecks. Ein Vieleck ist eine graphische Figur, bei der die einzelnen Ecken über Punkte angegeben sind und über Linien verbunden werden. Im Gegensatz zu einem Polygonzug (siehe nächste Übung) ist bei einem Vieleck von Anfang an klar, wie viele Ecken die Figur besitzen kann. Außerdem ist ein Vieleck immer eine geschlossene Figur, während es auch offene Polygonzüge gibt. Die Vieleck-Klasse kann daher beispielsweise wie im folgenden Programm vieleck.cpp eingesetzt werden: #include <stdio.h> #include "punkt.h" // enthält Klasse CPunkt, die einen Punkt repräsentiert enum farbe { rot, gruen, blau, gelb, weiss, schwarz }; const char *farbName[] = { "rot", "grün", "blau", "gelb", "weiss", "schwarz" }; class CEigenschaften // repräsentiert Rand- und Füllfarbe { ...... }; class CVieleck // repräsentiert ein Vieleck { ...... }; int main(void) { CVieleck dreieck("Dreieck", 3), fuenfeck("Fünfeck", 5, blau, gelb); dreieck.set(0, CPunkt(2,3)); // 1. Punkt des Dreiecks dreieck.set(1, CPunkt(4,5)); // 2. Punkt des Dreiecks dreieck.set(2, CPunkt(2,8)); // 3. Punkt des Dreiecks // Der Anwender setzt 2.Punkt von dreieck um dreieck.set(1, CPunkt(4,6)); dreieck.zeichnen(); fuenfeck.set(0, CPunkt(1,2)); // 1. Punkt des Fünfecks fuenfeck.set(1, CPunkt(3,5)); // 2. Punkt des Fünfecks fuenfeck.set(2, CPunkt(4,6)); // 3. Punkt des Fünfecks fuenfeck.set(3, CPunkt(2,7)); // 4. Punkt des Fünfecks fuenfeck.set(4, CPunkt(1,8)); // 5. Punkt des Fünfecks fuenfeck.zeichnen(); return 0; } 44 3.4 Beziehungen zwischen Objekten Geben Sie zunächst ein Klassendiagramm zum Vieleck an, bevor Sie die Klassen CPunkt, CEigenschaften und CVieleck implementieren. Das Programm vieleck.cpp sollte dann folgende Ausgabe liefern: Dreieck: Randfarbe = schwarz, Füllfarbe = weiss Punkte: P(2, 3) P(4, 6) P(2, 8) Fünfeck: Randfarbe = blau, Füllfarbe = gelb Punkte: P(1, 2) 3.4.2 P(3, 5) P(4, 6) P(2, 7) P(1, 8) Klassendiagramm und Implementierung zu einem Polygonzug Realisieren Sie eine Klasse zur Verwaltung und Darstellung eines Polygonzuges. Die einzelnen Punkte des Polygonzuges werden zur Laufzeit eingegeben, wie etwa in einer graphischen Oberfläche durch Mausklicks auf die entsprechende Position. Die Polygon-Klasse könnte daher beispielsweise wie im folgenden Programm polygon.cpp eingesetzt werden: #include <stdio.h> #include <stdlib.h> #include "punkt.h" //================================================================ // class CListKnoten: Speichert einen Punkt und kennt Nachfolger. // Uebertragene Aufgaben werden, wenn es Sinn macht, // auch an Nachfolger weitergemeldet (Prinzip: Stille Post) //---------------------------------------------------------------class CListElem { ...... }; //========================================================================= // class CPolygon: Repraesentiert ein Polygon nach aussen. // Ein Polygon ist eine verkettete Liste von Punkten. // Intern wird nur ein Zeiger auf erstes Element gespeichert. // Alle Funktionsaufrufe werden an erstes Element in der Liste delegiert. //------------------------------------------------------------------------class CPolygon { ...... }; int main(void) { CPolygon poly; // Polygon anlegen; hier Groesse noch nicht festgelegt poly.insert(CPunkt(0,4)); 45 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen poly.insert(CPunkt(3,7)); poly.insert(CPunkt(5,5)); poly.insert(CPunkt(3,4)); poly.insert(CPunkt(5,2)); poly.insert(CPunkt(3,0)); poly.zeichnen(); // Polygonzug drucken return 0; // hier wird Destruktor von poly durchlaufen und rekursiv gesamter // Polygonzug mit allen gespeicherten Punkten geloescht } Da die Anzahl der Punkte beim Anlegen des Polygonzuges noch nicht bekannt ist und der Polygonzug während der Laufzeit an Punkten zunehmen kann, bietet sich als Verwaltungsstruktur eine einfach verkettete Liste an, bei der jedes Element lediglich seinen Nachfolger kennt (über einen Zeiger auf das nächste Element). Die Klasse CPunkt hat aber an sich nichts mit einer verketteten Liste zu tun. Es ist daher unpassend, CPunkt direkt um einen Zeiger auf den nächsten Punkt zu ergänzen. Wir führen stattdessen eine neue Klasse CListElem ein, die ein Element der verketteten Liste darstellt. CListElem enthält damit einen Zeiger auf das nächste Element und als eigentliches Datum ein CPunkt-Objekt. Die Elemente selbst werden bei Bedarf dynamisch CPolygon::insert(...) auf dem Heap (mit new) angelegt und jeweils vorne in die Liste eingehängt. Dies ist performanter als jedesmal beim Einhängen die Liste bis zum Ende zu durchlaufen. Das Drucken der Liste mit CPolygon::zeichnen() und das Löschen der Liste im Destruktor ~CPolygon sollten hier jeweils an den Nachfolger (rekursiv) weitergemeldet. Geben Sie zunächst ein Klassendiagramm zum Polygon an, bevor Sie die Klassen CPunkt, CListElem und CPolygon implementieren. Das Programm polygon.cpp sollte dann folgende Ausgabe liefern: P(0, 4) P(3, 7) P(5, 5) P(3, 4) P(5, 2) P(3, 0) 46 3.4 Beziehungen zwischen Objekten 3.4.3 Assoziationen zum Monopoly-Spiel ❏ Klassendiagramm 1 Geben Sie ein Klassendiagramm mit entsprechenden Assoziationen zu den folgenden Begriffen aus dem Monopoly-Spiel an: Teilnehmer, Strasse, besitzt, Spieler, Spiel, nimmt teil, Besitzer, befindet sich auf ❏ Klassendiagramm 2 Geben Sie ein Klassendiagramm zu folgenden Sätzen an: Ein Monopoly-Spiel hat insgesamt 22 Strassen. Diese sind in acht Strassengruppen aufgeteilt, wobei zu einer Strassengruppe entweder zwei oder drei Strassen gehören, die ihre Strassengruppe kennen. Die Strassengruppen kennen jedoch nicht ihre Strassen. ❏ Klassendiagramm 3 Geben Sie ein Klassendiagramm zu folgenden Sätzen an: Ein Monopoly-Spiel hat insgesamt 22 Strassen und acht Strassengruppen, wobei einer Strassengruppe zwei oder drei Strassen zugeordnet sind. Die Strassen kennen dabei ihre Strassengruppe, und umgekehrt kennt jede Strassengruppe ihre Strassen. ❏ Klassendiagramm 4 Geben Sie ein Klassendiagramm zu folgenden Satz an: An einem Monopoly-Spiel können zwischen 2 und 6 Spieler teilnehmen ❏ Klassendiagramm 5 Geben Sie ein Klassendiagramm zu folgenden Satz an: Ein Monopoly-Spielbrett hat 40 Spielfelder, 16 Ereigniskarten und 16 Gemeinschaftskarten, wobei das Ziehen von Ereignis- bzw. Gemeinschaftskarten durch das Betreten bestimmter Spielfelder ausgelöst wird Erstellen Sie zusätzlich noch zwei C++-Programme zum Klassendiagramm 2 (monopclass1.cpp) und zum Klassendiagramm 3 (monopclass2.cpp), wobei diese Programme dann z.B. die folgenden Ausgaben liefern: Ausgabe durch das Programm monopclass1.cpp: --------- Strassen (Strassengruppen): 0 ( 0) 1 ( 1) 2 ( 2) 3 ( 3) 4 ( 4) 5 ( 5) 6 ( 6) 7 ( 7) 8 ( 0) 9 ( 1) 10 ( 2) 11 ( 3) 12 ( 4) 13 ( 5) 47 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen 14 ( 6) 15 ( 7) 16 ( 0) 17 ( 1) 18 ( 2) 19 ( 3) 20 ( 4) 21 ( 5) Ausgabe durch das Programm monopclass2.cpp: --------- Strassen (Strassengruppen): 0 ( 0) 1 ( 1) 2 ( 2) 3 ( 3) 4 ( 4) 5 ( 5) 6 ( 6) 7 ( 7) 8 ( 0) 9 ( 1) 10 ( 2) 11 ( 3) 12 ( 4) 13 ( 5) 14 ( 6) 15 ( 7) 16 ( 0) 17 ( 1) 18 ( 2) 19 ( 3) 20 ( 4) 21 ( 5) --------- Strassengruppen: 48 0: 0, 8, 16, 1: 1, 9, 17, 2: 2, 10, 18, 3: 3, 11, 19, 4: 4, 12, 20, 5: 5, 13, 21, 6: 6, 14, 7: 7, 15, 3.4 Beziehungen zwischen Objekten 3.4.4 Rekursive und aufgelöste rekursive Assoziationen Erstellen Sie zunächst drei Klassendiagramme mit Assoziationszusicherungen zu dem Objektdiagramm in Abbildung 3.2. 1. Klassendiagramm soll die rekursive Darstellung zeigen. 2. Klassendiagramm soll eine nicht-rekursive Darstellung zeigen. 3. Klassendiagramm soll eine rekursive Darstellung mit Vorgänger und Nachfolger zeigen. Danach sollten Sie ein C++-Programm spielreihe.cpp erstellen, das alle drei Klassendigramme realisiert. Objektdiagramm hans:CSpieler ist Nachfolger von emil:CSpieler ist Nachfolger von ist Nachfolger von inge:CSpieler ist Nachfolger von anna:CSpieler Abbildung 3.2: Rekursive Assoziation zur Reihenfolge der Spieler bei einem Spiel Das Programm spielreihe.cpp sollte z.B. für die drei unterschiedlichen Klassendigramme die folgende Ausgabe liefern: 1. Klassendigramm: hans --> emil --> anna --> inge --> hans --> emil --> anna --> inge --> 2. Klassendigramm: hans --> emil --> anna --> inge --> hans --> emil --> anna --> inge --> 3. Klassendigramm: Vorwärts: hans --> emil --> anna --> inge --> hans --> emil --> anna --> inge --> Rückwärts: hans --> inge --> anna --> emil --> hans --> inge --> anna --> emil --> 49 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen 3.4.5 Mastermind gegen den Computer Das Spiel “Moo“ ist eine Computerversion zu dem bekannten Spiel “Superhirn“ (auch unter dem Namen “Mastermind“ bekannt). Stimmen Ziffern überein, werden diese als ’Kühe’ bezeichnet. Befinden sich die Ziffern sogar an der richtigen Stelle, werden sie als ’Bullen’ bezeichnet (und nicht als Kühe gezählt). Bei einer vierstelligen Zahl entsprechen also 4 ’Bullen’ der gesuchten Zahl. Erstellen Sie ein Programm moo.cpp, das versucht, eine vom Spieler (Anwender) gedachte Zahl zu erraten. Wie viele Stellen die Zahl hat, soll beim Aufruf des Programms als Kommandozeilenargument angegeben werden. Realisieren Sie diese Aufgabenstellung vollkommen objektorientiert unter Einsatz mehrerer Klassen und Objekte. Stellen Sie zuvor ein Klassen- und ein Sequenzdiagramm zu dieser Aufgabenstellung auf. Nachfolgend sind mögliche Abläufe dieses Programms moo.cpp gezeigt, wobei der Spieler sich beim ersten Ablauf die Zahl 4711 und beim zweiten Ablauf die Zahl 4321 gedacht hat: user@linux: > ./moo Falscher Aufruf; ←- richtiger Aufruf: ./moo stellenzahl user@linux: > ./moo 4 ←- Das Spiel Moo =============== Denken Sie sich eine beliebige Kombination aus 4 Ziffern aus Ich werde versuchen Sie zu erraten Mein 1. Rateversuch: 0000 ← ← Bullen: 0 Kuehe: 0 Mein 2. Rateversuch: 1111 ← ←- Bullen: 2 Kuehe: 0 Mein 3. Rateversuch: 1122 ← ← Bullen: 0 Kuehe: 2 Mein 4. Rateversuch: 3311 ← ← Bullen: 2 Kuehe: 0 Mein 5. Rateversuch: 4411 ← ←- Bullen: 3 Kuehe: 0 Mein 6. Rateversuch: 4511 ← ← Bullen: 3 Kuehe: 0 Mein 7. Rateversuch: 4611 ← ←- Bullen: 3 Kuehe: 0 50 3.4 Beziehungen zwischen Objekten Mein 8. Rateversuch: 4711 Bullen: 4 ←- .... Die Lösung ist also: 4711 user@linux: > ./moo 4 ←- Das Spiel Moo =============== Denken Sie sich eine beliebige Kombination aus 4 Ziffern aus Ich werde versuchen Sie zu erraten Mein 1. Rateversuch: 0000 ← ←- Bullen: 0 Kuehe: 0 Mein 2. Rateversuch: 1111 ← ← Bullen: 1 Kuehe: 0 Mein 3. Rateversuch: 1222 ← ←- Bullen: 1 Kuehe: 1 Mein 4. Rateversuch: 3123 ← ← Bullen: 1 Kuehe: 2 Mein 5. Rateversuch: 3214 ← ← Bullen: 0 Kuehe: 4 Mein 6. Rateversuch: 4132 ← ←- Bullen: 1 Kuehe: 3 Mein 7. Rateversuch: 4321 Bullen: 4 ←- .... Die Lösung ist also: 4321 user@linux: > _ Ein kleiner Tipp noch zu diesem Programm moo.cpp: Erstellen Sie zunächst ein Array, das alle möglichen Kombinationen bei der über die Kommandozeile angegebenen Stellenzahl speichert. Aus diesem Array bieten Sie dem Spieler dann die erste Kombination (0000) an. Nachdem dieser seine Bullen und Kühe eingegeben hat, sind aus dem Array alle Kombinationen zu streichen, bei denen die aktuell gesetzte Zahl (Kombination) nicht die gleichen Bullen und Kühe ergäbe, da es sich bei diesen Kombinationen nicht um die Lösung handeln kann. Nun bieten Sie dem Spieler im zweiten Durchgang die erste noch nicht gestrichene Kombination aus dem Array und lassen sich die Bullen und Kühe zu dieser Kombination vom Spieler mitteilen. Nun streichen Sie wieder alle Kombinationen aus dem Array, die bei dieser Vorgabe nicht in Frage kommen usw. Es muss schliesslich eine Kombination übrig bleiben, die dann auch die Lösung ist. Bleibt keine Kombination im Array übrig, hat der Spieler falsche Bewertungen einer oder mehrerer Stellungen vorgenommen. 51 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen 3.5 Vererbung 3.5.1 Überschreiben von Funktionen Was gibt das folgende Programm dezahexa.cpp aus? #include <stdio.h> //-------------------------------------------------------------- Dezimalziffer class Dezimalziffer { public: Dezimalziffer(int z) : m_Ziffer(z) { } void ausgeben(void) { printf("Dezimal: %d\n", m_Ziffer); } private: int m_Ziffer; }; //----------------------------------------------------------------- Hexaziffer class Hexaziffer : public Dezimalziffer { public: Hexaziffer(int z) : Dezimalziffer(z<10 ? z : z-’A’+10), m_Ziffer(z) { } void ausgeben(void) { Dezimalziffer::ausgeben(); // Aufrufen der Methode aus Basisklasse printf("Hexadezimal: %c\n", m_Ziffer); } private: int m_Ziffer; }; //----------------------------------------------------------------------- main int main(void) { Dezimalziffer d(10); d.ausgeben(); Hexaziffer h(’D’); h.ausgeben(); return 0; } 52 3.5 Vererbung 3.5.2 Konstruktor-/Destruktoraufrufe Was geben die drei folgenden Programme aus? autofahr.cpp: #include <stdio.h> //--------------------------------------class CFahrzeug { public: CFahrzeug() { printf("KF->"); } ~CFahrzeug() { printf("DF->"); } }; //--------------------------------------class CAuto : public CFahrzeug { public: CAuto() { printf("KA->"); } ~CAuto() { printf("DA->"); } }; //--------------------------------------int main(void) { CAuto myAuto; return 0; } blumen.cpp: #include <stdio.h> //-----------------------------------------------------// Makro zur Definition von Konstruktor und Destruktor. #define KD(KLASSE) \ public: KLASSE() \ { printf("Konstruktor " #KLASSE "\n"); } \ ~KLASSE() { printf("Destruktor " #KLASSE "\n"); } //-----------------------------------------------------class Pflanze { KD(Pflanze) }; class Bluetenblatt { KD(Bluetenblatt) }; //-----------------------------------------------------class Blume: public Pflanze { Bluetenblatt m_bluete[2]; KD(Blume); }; //-----------------------------------------------------class Rose: public Blume { KD(Rose) }; //-----------------------------------------------------int main(void) { Rose Rosenstrauss[3]; printf("---------------------\n"); printf("Alle Blumen welken...\n"); 53 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen printf("---------------------\n"); return 0; } dackel.cpp: #include <stdio.h> //-----------------------------------------------------class Tier { public: Tier() { printf("\n+Tier\n"); } ~Tier() { printf("\n-Tier\n"); } }; //-----------------------------------------------------class Bein { public: Bein() { printf("+Bein"); } ~Bein() { printf("-Bein"); } }; //-----------------------------------------------------class Hund : public Tier { Bein m_beine[4]; public: Hund() { printf("\n+Hund\n"); } ~Hund() { printf("\n-Hund\n"); } }; //-----------------------------------------------------class Dackel : public Hund { char m_name[100]; public: Dackel(char *name) { strcpy(m_name, name); printf("+Dackel (%s)\n", m_name); } ~Dackel() { printf("\n-Dackel (%s)", m_name); } }; //-----------------------------------------------------int main(void) { Dackel d1("Hansi"), d2("Maxi"); printf("\n*****************\n"); printf("Alle Hunde bellen\n"); printf("*****************\n"); return 0; } 54 3.5 Vererbung 3.5.3 Linie in Graphikbibliothek Erweitern Sie die Graphikbibliothek um eine Klasse für Linien. Bei genauerer Betrachungsweise ist dabei erkennbar, dass auch eine Linie sich über einen Mittelpunkt und Deltas (ähnlich wie beim Rechteck) darstellen lässt. Stellen Sie sich dazu ein Rechteck um die Linie vor; die Deltas sind dann die Abstände zwischen dem Mittelpunkt und den Endpunkten der Linie. Erstellen Sie nun zum folgenden Programm graphline.cpp die fehlende Headerdatei graphline.h, so dass das Programm graphline.cpp die folgende Ausgabe liefert: Rechteck(xd,yd) = 1,1; P(x,y) = 4,5 Kreis(radius) = 2; P(x,y) = 2,3 Linie(xd,yd) = 2,1; P(x,y) = 5,4 #include "graphline.h" int main(void) { CRechteck r(4, 5); CKreis k(2, 3, 2); CLinie l(5, 4, 2); r.draw(); k.draw(); l.draw(); return 0; } 55 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen 3.5.4 Klassenmodell zu Autos, Dreirädern usw. Erstellen Sie ein Klassenmodell zu folgenden Erkenntnissen: ❏ Autos, Dreiräder, Fahrräder, Motorräder können alle fahren. ❏ Wenn ein Auto fährt, macht es “Brumm brumm, tüt tüt“ und ein Fahrrad macht “Kling kling“. Setzen Sie Ihr Klassenmodell anschließend in C++ um. Das Fahren und die Geräusche simulieren Sie durch entsprechende Ausgaben. Erstellen Sie zum folgenden Programm fahrzeuge.cpp die fehlende Headerdatei fahrzeuge.h, so dass dieses Programm die folgende Ausgabe liefert: ---------------------------------...faehrt und faehrt und faehrt... ......Brumm brumm, tuet tuet. ---------------------------------...faehrt und faehrt und faehrt... ---------------------------------...faehrt und faehrt und faehrt... ......Kling kling. ---------------------------------...faehrt und faehrt und faehrt... ---------------------------------...faehrt und faehrt und faehrt... ......Brumm brumm, tuet tuet. #include <stdio.h> #include "fahrzeuge.h" int main(void) { CAuto auto1, auto2; CDreirad dreiradPauli; CFahrrad blechEsel; CMotorrad mySuzi; auto1.fahren(); dreiradPauli.fahren(); blechEsel.fahren(); mySuzi.fahren(); auto2.fahren(); return 0; } 56 3.5 Vererbung 3.5.5 Erweitern des Klassenmodells zu Fahrzeugen Autos, Dreiräder usw. besitzen Räder. Wenn eines dieser Fahrzeuge fährt, bedeutet dies, dass sich seine Räder drehen. Bauen Sie diese Erkenntnisse in Ihr Klassenmodell aus der vorherigen Übung ein. Überlegen Sie gut, an welcher Stelle in der Klassenhierarchie Sie das Drehen der Räder ansiedeln wollen und warum gerade da. Setzen Sie Ihr Klassenmodell anschließend in C++ um. Erstellen Sie zum folgenden Programm fahrzeuge2.cpp, das bis auf das Inkludieren der neuen Headerdatei fahrzeuge2.h identisch zum vorherigen Programm fahrzeuge.cpp ist, die fehlende Headerdatei fahrzeuge2.h, so dass dieses Programm die folgende Ausgabe liefert: --------------------------------Rad-Drehung Rad-Drehung Rad-Drehung Rad-Drehung ......Brumm brumm, tuet tuet. --------------------------------Rad-Drehung Rad-Drehung Rad-Drehung --------------------------------Rad-Drehung Rad-Drehung ......Kling kling. --------------------------------Rad-Drehung Rad-Drehung --------------------------------Rad-Drehung Rad-Drehung Rad-Drehung Rad-Drehung ......Brumm brumm, tuet tuet. #include <stdio.h> #include "fahrzeuge2.h" int main(void) { CAuto auto1, auto2; CDreirad dreiradPauli; CFahrrad blechEsel; CMotorrad mySuzi; auto1.fahren(); dreiradPauli.fahren(); blechEsel.fahren(); mySuzi.fahren(); auto2.fahren(); return 0; } 57 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen 3.5.6 Ableiten eines Lkws von einem Auto Erstellen Sie ein Programm lkw.cpp, dass das Programm erb10.cpp um eine Klasse CLkw erweitert, die von der Klasse CAuto abgeleitet ist. Diese Klasse Lkw soll eine private-Membervariable m_anhGewicht für das Gewicht des Anhängers enthalten. Daneben soll diese Klasse noch die beiden folgenden public-Memberfunktionen enthalten: void setGewicht(int gew); // setzt das Anhängergewicht (m_anhGewicht) int getGewicht(void); // liefert das Gesamtgewicht vom Lkw // (sein eigenes Gewicht + Gewicht des Anhängers) Für die folgende main()-Funktion: ............. int main(void) { CLkw laster(1000, 120, "Brummi", 2000); printf("%s:\n Gewicht: %d\n Geschw.: %d\n Gesamtgewicht: %d\n", laster.getName(), laster.CAuto::getGewicht(), laster.getSpeed(), laster.getGewicht()); return 0; } sollte das Programm lkw.cpp die folgende Ausgabe liefern: Brummi: Gewicht: 1000 Geschw.: 120 Gesamtgewicht: 3000 58 3.5 Vererbung 3.5.7 Speicherbedarf bei virtuellen Funktionen Was würde das folgende Programm virtbytes.cpp ausgeben? 1. 32 2. 28 3. 24 4. 16 5. 8 #include <stdio.h> // ---------------------------------------------class CGraphObj { public: CGraphObj() {} virtual ~CGraphObj() {} virtual void draw() {} protected: char m_farbe[20]; }; // ---------------------------------------------class CPunkt : public CGraphObj { public: CPunkt(int x, int y) : m_x(x), m_y(y) { } virtual ~CPunkt() {} private: int m_x; int m_y; }; // ---------------------------------------------int main(void) { CPunkt myPoint(0, 0); printf("%d\n", sizeof(myPoint)); return 0; } 3.5.8 Speicherbedarf bei abgeleiteten Klassen Welche Beziehung zwischen dem Speicherbedarf eines Objektes der Basisklasse und einer abgeleiteten Klasse ist richtig, wenn CAuto von CFahrzeug abgeleitet ist? 1. sizeof(CAuto) <= sizeof(CFahrzeug) 2. sizeof(CAuto) > sizeof(CFahrzeug) 3. sizeof(CAuto) >= sizeof(CFahrzeug) 4. sizeof(CAuto) < sizeof(CFahrzeug) 59 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen 3.5.9 Ein Suppenteller in Franken Was gibt das folgende Programm virtfrank.cpp aus? 1. Suppenteller---Subbendeller---Suppenteller--2. Suppenteller---Suppenteller---Suppenteller--3. Keine der Antworten ist richtig. #include <stdio.h> #include <string.h> //-------------------------------------class CMensch { public: void sprechen(char *str) { printf("%s", str); } }; //-------------------------------------class CFranke : public CMensch { public: void sprechen(char *str) { for (int i=0; i<strlen(str); i++) switch (str[i]) { case ’p’: printf("b"); break; case ’P’: printf("B"); break; case ’t’: printf("d"); break; case ’T’: printf("D"); break; default: printf("%c", str[i]); break; } } }; //-------------------------------------int main(void) { CMensch *Entwickler[3], Heinrich, Ursula; CFranke Schorsch; Entwickler[0] = &Heinrich; Entwickler[1] = &Schorsch; Entwickler[2] = &Ursula; for (int i=0; i<3; i++) { Entwickler[i]->sprechen("Suppenteller"); printf("---"); } printf("\n"); return 0; } 60 3.5 Vererbung 3.5.10 Franken und Berliner Das folgende Programm dialekt.cpp liefert die folgende Ausgabe: ................................... Mensch Alle: Ich hole jetzt ein Taxi. Alle: Wir machen eine ganz tolle Party. ................................. Berliner Alle: Ich hole jetzt ein Taxi. Alle: Wir machen eine ganz tolle Party. ................................... Franke Alle: Ich hole jetzt ein Taxi. Alle: Wir machen eine ganz tolle Party. 1 #include <stdio.h> 2 3 //-------------------------------------- 4 class CMensch 5 { 6 public: 7 void satz1() { printf("Alle: Ich hole jetzt ein Taxi.\n"); 8 void satz2() { printf("Alle: Wir machen eine ganz tolle Party.\n"); } 9 void beide_saetze_sprechen() { 10 } satz1(); 11 satz2(); 12 } 13 }; 14 //-------------------------------------- 15 class CBerliner : public CMensch 16 { 17 public: 18 void satz1() { printf("Berliner: Ick hole jetzt ne Droschke.\n"); 19 void satz2() { printf("Berliner: Wir machen ne janz tolle Sause.\n"); } 20 }; 21 //-------------------------------------- 22 class CFranke : public CMensch 23 { 24 public: 25 void satz1() { printf("Franke: I hol etz a Daxi.\n"); 26 } } void satz2() { printf("Franke: Mer macha a ganz dolla Bardi.\n"); } 27 }; 28 //-------------------------------------- 29 int main (void) 30 { 31 CMensch 32 CBerliner berliner; mensch; 61 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen 33 CFranke franke; 34 35 printf("................................... Mensch\n"); 36 mensch.beide_saetze_sprechen(); 37 printf("\n................................. Berliner\n"); 38 berliner.beide_saetze_sprechen(); 39 printf("\n................................... Franke\n"); 40 franke.beide_saetze_sprechen(); 41 42 43 return 0; } Das Programm dialekt.cpp soll nun folgende Ausgabe liefern: ................................... Mensch Alle: Ich hole jetzt ein Taxi. Alle: Wir machen eine ganz tolle Party. ................................. Berliner Berliner: Ick hole jetzt ne Droschke. Berliner: Wir machen ne janz tolle Sause. ................................... Franke Franke: I hol etz a Daxi. Franke: Mer macha a ganz dolla Bardi. Welche Funktionen muss man dazu im Programm dialekt.cpp mindestens als virtual deklarieren? 1. Funktion in Zeile 7 2. Funktion in Zeile 8 3. Funktion in Zeile 9 4. Funktion in Zeile 18 5. Funktion in Zeile 19 6. Funktion in Zeile 25 7. Funktion in Zeile 26 62 3.5 Vererbung 3.5.11 Tierhierarchie Was gibt das folgende Programm raubtiere.cpp aus? #include <stdio.h> //---------------------------------------------------------------------class Tier { public: Tier() {} void ichBin() { printf("Ich bin ein Tier\n"); void iAm() } { printf("...und ein Lebewesen\n"); } protected: char m_name[100]; }; //---------------------------------------------------------------------class Raubtier : public Tier { public: Raubtier() {} virtual void ichBin() { printf("Ich bin ein Raubtier\n"); void iAm() } { printf("...fresse andere Tiere\n"); } }; //---------------------------------------------------------------------class Katze : public Raubtier { public: Katze() {} void ichBin() { printf("Ich bin eine Katze\n"); void iAm() } { printf("...und immer auf der Jagd\n"); } }; //---------------------------------------------------------------------class Hund : public Raubtier { public: Hund() {} void ichBin() { printf("Ich bin ein Hund\n"); void iAm() } { printf("...und jage gern im Rudel\n"); } }; //---------------------------------------------------------------------class Loewe : public Katze { public: Loewe(char *name) { strcpy(m_name, name); } void ichBin() { printf("Ich bin %s, ein Löwe\n", m_name); } virtual void iAm() { printf("...und der König der Tiere\n"); } }; //---------------------------------------------------------------------class Tiger : public Katze { public: Tiger(char *name) { strcpy(m_name, name); } void ichBin() { printf("Ich bin %s, ein Tiger\n", m_name); } 63 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen virtual void iAm() { printf("...und habe ein gestreiftes Fell\n"); } }; //---------------------------------------------------------------------class Kojote : public Hund { public: Kojote(char *name) { strcpy(m_name, name); } void ichBin() { printf("Ich bin %s, ein Kojote\n", m_name); } virtual void iAm() { printf("...und heule nachts gerne\n"); } }; //---------------------------------------------------------------------int main(void) { Raubtier *zoo[] = { new Loewe("Leo"), new Tiger("Terminator"), new Kojote("Kolja") }; for (int i=0; i<3; i++) { printf("----------------------------------\n"); zoo[i]->ichBin(); zoo[i]->iAm(); } return 0; } 3.5.12 Parkplatz mit Polymorphismus Erstellen Sie ein Programm parkplatz.cpp mit einer main()-Funktion, die folgende Situation widerspiegelt: Vor einem Geschäft gibt es einen Parkplatz mit 10 Parkbuchten. Bei Geschäftsschluss fahren alle dort parkenden Fahrzeuge vom Parkplatz. Die Fahrzeugklassen können Sie aus der Übung in Kapitel 3.5.5 auf Seite 57 übernehmen, indem Sie die Datei fahrzeuge2.h in die Datei fahrzeuge3.h kopieren und eventuell dieser Aufgabenstellung anpassen. Parken auf dem Parkplatz z.B. ein Fahrrad, ein Auto, ein Motorrad und noch ein Auto, so sollte das Programm parkplatz.cpp für den Geschäftsschluss folgendes ausgeben: --------------------------------Rad-Drehung Rad-Drehung ......Kling kling. --------------------------------Rad-Drehung Rad-Drehung Rad-Drehung Rad-Drehung ......Brumm brumm, tuet tuet. --------------------------------Rad-Drehung Rad-Drehung --------------------------------Rad-Drehung Rad-Drehung Rad-Drehung Rad-Drehung ......Brumm brumm, tuet tuet. 64 3.6 Abstrakte Klassen 3.6 Abstrakte Klassen 3.6.1 Sinnlose Code-Erzeugung bei “Dummy“-Methoden in Basisklasse Im folgenden Programm quadkreis.cpp wird eine “Dummy“-Methode flaeche() in der Basisklasse CFigur angegeben, um diese dann an die beiden Unterklassen CKreis und CQuadrat vererben zu können, wo sie dann neu definiert wird. Wie könnte man im Programm quadkreis.cpp diese sinnlose Code-Erzeugung in der Basisklasse CFigur vermeiden? 1 #include <stdio.h> 2 //---------------------------------------------------------------------- 3 class CFigur { 4 public: 5 virtual double flaeche() const { return 0; } 6 }; 7 //---------------------------------------------------------------------- 8 class CKreis : public CFigur { 9 public: 10 CKreis(double r) 11 double flaeche() const { return 3.14159265 * radius * radius; } 12 { radius = r; } private: 13 double radius; 14 }; 15 //---------------------------------------------------------------------- 16 class CQuadrat : public CFigur { 17 public: 18 CQuadrat(double l) 19 double flaeche() const { return laenge * laenge; } 20 { laenge = l; } private: 21 double laenge; 22 }; 23 //---------------------------------------------------------------------- 24 int main(void) 25 { 26 CKreis 27 CQuadrat q(12); k(12); 28 CFigur *figurZgr; 29 30 figurZgr = &k; 31 printf("Fläche des Kreises: %g\n", figurZgr->flaeche()); 32 33 figurZgr = &q; 34 printf("Fläche des Quadrats: %g\n", figurZgr->flaeche()); 35 36 37 return 0; } 65 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen 3.6.2 Zoo mit zufälligen Tieren Erstellen Sie ein Programm (zoo.h und zoo.cpp), das einen Zoo simuliert, bei dem sich die einzelnen Bewohner wie folgt vorstellen: Hallo, mein Name ist .... Ich lebe ... (im Wasser / auf der Erde / in der Luft) Ich bin ....Tierart Folgende Tierarten sollen im Zoo vorhanden sein: Hund, Katze, Maus, Schmetterling, Biene, Vogel, Wal, Hai Der Zoo soll zufällig mit Tieren gefüllt werden. Die Namen werden dabei den Tieren ebenfalls zufällig zugeordnet. Als Namen könnten z.B. verwendet werden: Wotan, Pluto, Maya, Lukas, Flipper, Tarzan, Kucki, Slappi, Fury, Moritz. Erstellen Sie vor der Implementierung ein Klassendiagramm. Möglicher Ablauf des Programms zoo.cpp (hier für 5 Tiere): Wie viele Tiere sollen im virtuellen Zoo sein: 5 Die Tiere aus dem Zoo stellen sich vor: ............................. 1. Tier Hallo, mein Name ist Maya. Ich lebe auf dem Land. Ich bin eine Katze. ............................. 2. Tier Hallo, mein Name ist Kucki. Ich lebe in der Luft. Ich bin ein Vogel. ............................. 3. Tier Hallo, mein Name ist Moritz. Ich lebe im Wasser. Ich bin ein Hai. ............................. 4. Tier Hallo, mein Name ist Wotan. Ich lebe im Wasser. Ich bin ein Wal. ............................. 5. Tier Hallo, mein Name ist Kucki. Ich lebe in der Luft. Ich bin ein Schmetterling. 66 ←- 3.7 Mehrfachvererbung 3.7 3.7.1 Mehrfachvererbung Ehe, mehrfach abgeleitet von Mensch über Mann/Frau Erstellen Sie ein Programm ehe.cpp, das die Vererbungshierarchie aus Abbildung 3.3 realisiert. Die Methode ehedaten() soll zur Ausgabe der einzelnen Partnerdaten die beiden Methoden fraudaten() und manndaten() aus ihren beiden Basisklassen aufrufen, bevor die Methode ehedaten() ihrerseits ausgibt, wie lange die Ehe schon besteht. Die beiden Methoden fraudaten() und manndaten() sollen ihrerseits die Methode gibNameAlterAus() aus ihrer gemeinsamen Basisklasse zur Ausgabe des Namens und des Alters des jeweiligen Ehepartners aufrufen, bevor sie ihrerseits geschlechtsspezifisch ausgeben, ob Kinder bzw. ein Bart vorhanden ist. Ergänzen Sie dazu das nachfolgende Programm ehe.cpp: M e n sc h # m _ N a m e :c h a r [5 0 ] # m _ A lte r: in t + M e n s c h (c h a r * n a m e , in t a lte r) + g ib N a m e A lte rA u s () F r a u M a n n - m _ K in d e r: b o o l - m _ B a rt: b o o l + F ra u (c h a r * n a m e , in t a lte r, b o o l k in d e r) + v o id fra u d a te n () + F ra u (c h a r * n a m e , in t a lte r, b o o l b a rt) + v o id m a n n d a te n () E h e - m _ J a h re : in t + E h e (c h a r * fn a m e , in t fa lte r, b o o l k in d e r, c h a r * m n a m e , in t m a lte r, b o o l b a rt, in t ja h re ) + v o id e h e d a te n () Abbildung 3.3: Ehe, mehrfach abgeleitet von Mensch über Mann/Frau ....... int main(void) { Ehe e1("Vera", 35, true, "Hans", 39, false, 12), e2("Kerstin", 63, true, "Otto", 66, true, 34); e1.ehedaten(); e2.ehedaten(); return 0; } so dass es folgende Ausgabe liefert: 67 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen Vera: ist 35 Jahre alt und hat Kinder Hans: ist 39 Jahre alt und hat keinen Bart Sie sind seit 12 Jahre verheiratet --------------------------------------------Kerstin: ist 63 Jahre alt und hat Kinder Otto: ist 66 Jahre alt und hat einen Bart Sie sind seit 34 Jahre verheiratet 3.7.2 Hai als fleischfressender Fisch Erstellen Sie zu dem in Abbildung 3.4 gezeigten Klassendiagramm ein Programm hai.cpp, so dass der Name (m_name) von CTier in der Klasse CHai nur einmal vorhanden ist. CTier # m_name: char[20] CHund # m_reinrassig: bool CFisch # m_salzwasser: bool CFleischfresser # m_saeugetier: bool CHai + printName() Abbildung 3.4: Hai als fleischfressender Fisch Ergänzen Sie dazu das nachfolgende Programm hai.cpp: .......... int main(void) { CHai h1("Killer"), h2("Torpedo"); h1.printName(); h2.printName(); return 0; } so dass es folgende Ausgabe liefert: Killer Torpedo 68 CPflanzenFresser # m_kannfliegen: bool 3.8 Schnittstellen 3.8 Schnittstellen 3.8.1 Schnittstelle ISprechweise für unterschiedliche Dialekte In Übung 3.5.9 auf Seite 60 wurde die Klasse CFranke von CMensch abgeleitet und die Methode sprechen() überlagert. Wäre es vorteilhafter gewesen, stattdessen eine Schnittstelle ISprechweise zu definieren, die von CFranke entsprechend implementiert worden wäre? Wenn ja, warum? 3.8.2 Eine allgemein verwendbare verkettete Liste Entwickeln und implementieren Sie ein Klassenmodell, das es jedem C++-Programm mit einem Minimum an Erweiterung ermöglicht, eine verkettete Liste als Speicherstruktur für seine Objekte zu verwenden. Die Herausforderung ist hier also, dass der Typ der verwalteten Objekte nicht fix ist. Als Beispiel möge das folgende Programm polymain.cpp dienen, das Punkte für ein Polygon in der Liste einhängt und ausgibt. #include <stdio.h> #include "liste.h" #include "pkt.h" #include "polygon.h" int main(void) { CPolygon poly; // Polygon anlegen; noch nicht festgelegt, wie gross es wird poly.einfuegen( CPunkt(0,4) ); // mit jedem neuen Punkt wächst der Polygonzug poly.einfuegen( CPunkt(3,7) ); poly.einfuegen( CPunkt(5,5) ); poly.einfuegen( CPunkt(3,4) ); poly.einfuegen( CPunkt(5,2) ); poly.einfuegen( CPunkt(3,0) ); poly.ausgeben(); // Polygon zeichnen return 0; // hier wird der Destruktor von poly durchlaufen // und rekursiv der gesamte Polygonzug mit allen // gespeicherten Punkten gelöscht } Die folgende Headerdatei polygon.h zeigt die Klasse CPolygon. 1 2 #ifndef POLYGON_H #define POLYGON_H 3 4 #include "liste.h" 5 #include "pkt.h" 6 69 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen 7 class CPolygon 8 { 9 public: 10 CPolygon() { } 11 virtual ~CPolygon() { } 12 void ausgeben() 13 bool einfuegen(CPunkt pkt) { return(m_list.insert(pkt)); } 14 { print(m_list.getHead()); } private: 15 CListe m_list; 16 17 void print(CListElem* pElem) { 18 if (pElem) { 19 print(pElem->getNext()); 20 pElem->getObj()->show(); 21 printf("\n"); 22 } 23 24 25 } }; #endif Das Programm polymain.cpp liefert dann folgende Ausgabe: P(0, 4) P(3, 7) P(5, 5) P(3, 4) P(5, 2) P(3, 0) Möchte man nun statt der Punkte eines Polygons Strings mittels der verketteten Liste verwalten, so sollte dies mit einem Minimum an Aufwand möglich sein. Man müsste nun statt der Headerdatei pkt.h (Klasse CPunkt) eine Headerdatei str.h mit einer Klasse CStr erstellen und die Headerdatei polygon.h – wie in der folgenden Headerdatei strliste.h gezeigt – ändern. 1 2 #ifndef STRLISTE_H #define STRLISTE_H 3 4 #include "liste.h" 5 #include "str.h" 6 7 class CStrListe 8 { 9 public: 10 CStrListe() { } 11 virtual ~CStrListe() { } 12 void ausgeben() 13 bool einfuegen(CStr str) { return(m_list.insert(str)); } 14 70 private: { print(m_list.getHead()); } 3.8 Schnittstellen 15 CListe m_list; 16 17 void print(CListElem* pElem) { 18 if (pElem) { 19 print(pElem->getNext()); 20 pElem->getObj()->show(); 21 printf("\n"); 22 } 23 } 24 25 }; #endif Man könnte dann z.B. das folgende Programm stringmain.cpp verwenden, um Strings über die allgemein verwendbare verkettete Liste verwalten zu lassen. #include <stdio.h> #include "liste.h" #include "str.h" #include "strliste.h" int main(void) { CStrListe strList; // StrListe anlegen; noch nicht festgelegt, wie viele strList.einfuegen( CStr("Hans") ); // mit jedem neuen String wächst Liste strList.einfuegen( CStr("Antonia") ); strList.einfuegen( CStr("Hans Peter") ); strList.einfuegen( CStr("Michaela") ); strList.einfuegen( CStr("Kerstin") ); strList.einfuegen( CStr("Marie Luise") ); strList.ausgeben(); // Stringliste ausgeben return 0; // hier wird der Destruktor von strList durchlaufen // und rekursiv die gesamte Stringliste mit allen // gespeicherten Strings gelöscht } Das Programm polymain.cpp liefert dann folgende Ausgabe: Hans Antonia Hans Peter Michaela Kerstin Marie Luise 71 3 Objektorientierte Erweiterungen in C++ und UML-Grundlagen 72 Kapitel 4 Weitere Features in C++ 4.1 Friends (Befreundete Funktionen und Klassen) 4.1.1 Addieren von Brüchen Gegeben sei das folgende noch unvollständige Programm bruchadd.cpp: #include <stdio.h> //............................................................ Klasse CBruch class CBruch { public: CBruch( int z = 1, int n = 1 ) { // Konstruktor zur Initialisierung m_zaehler = z; m_nenner = n; } ................ private: int m_zaehler; int m_nenner; int ggT( int n, int m) { return (m==0) ? n : ggT(m, n%m); } void kuerzen( void ) { int ggTeiler = ggT( m_zaehler, m_nenner ); m_zaehler /= ggTeiler; m_nenner /= ggTeiler; } }; //..................................................................... main int main(void) { CBruch b1( 1, 4 ); // 1. Bruch (1/4) CBruch b2( 3, 8 ); // 2. Bruch (3/8) CBruch b3; b1.add( b2 ); // 3. Bruch (1/1) // Addiere b2 zu b1 73 4 Weitere Features in C++ printf("b1 = "); // Ausgabe von b1 b1.print(); printf("\n"); b3 = add( b1, b2 ); // Addiere b1 und b2 --> b3 printf("b3 = "); // Ausgabe von b3 b3.print(); printf("\n"); return 0; } Ergänzen Sie dieses Programm bruchadd.cpp, so dass es folgende Ausgabe liefert: b1 = 5/8 b3 = 1/1 Bei Ihrer Ergänzung dürfen Sie allerdings keine get-Funktionen in CBruch angeben, die m_zaehler bzw. m_nenner zurückliefern. 4.1.2 Array von ungekürzten und gekürzten Brüchen Gegeben sei das folgende noch unvollständige Programm brucharray.cpp: #include <stdio.h> #include <stdlib.h> #include <time.h> //............................................................ Klasse CBruch class CBruch { public: CBruch( int z=1, int n=1 ) : m_zaehler(z), m_nenner(n) { } ....... private: int m_zaehler; int m_nenner; int m_zaehlerGekuerzt; int m_nennerGekuerzt; int ggT( int n, int m) { return (m==0) ? n : ggT(m, n%m); } void kuerzen( void ) { int ggTeiler = ggT( m_zaehler, m_nenner ); m_zaehlerGekuerzt = m_zaehler / ggTeiler; m_nennerGekuerzt = m_nenner / ggTeiler; } }; //....................................................... Klasse CBruchArray class CBruchArray { public: 74 4.1 Friends (Befreundete Funktionen und Klassen) CBruchArray() { .... } void print(void) { .... } private: CBruch bruchArray[20]; }; //..................................................................... main int main(void) { srand(time(NULL)); CBruchArray b; b.print(); return 0; } Ergänzen Sie dieses Programm brucharray.cpp, indem Sie beim Anlegen der Klasse CBruchArray die Zähler und Nenner der einzelnen Brüche mit Zufallszahlen zwischen 1 und 100 setzen. Zusätzlich soll noch im Konstruktor von CBruchArray die Methode kuerzen() für jeden einzelnen zufällig erzeugten Bruch aufgerufen werden. In der Methode print() der Klasse CBruchArray sollen dann alle Brüche ausgegeben werden, wobei bei Brüchen, die kürzbar waren, noch der gekürzte Bruch dazu in Klammern mit auszugeben ist. Programm brucharray.cpp sollte z.B. eine Ausgabe wie die folgende liefern: 3/97 7/98 (1/14) 25/7 86/75 63/3 (21/1) 22/45 87/2 48/14 (24/7) 83/9 42/42 (1/1) 72/83 47/78 29/16 48/63 (16/21) 34/92 (17/46) 88/87 36/88 (9/22) 95/94 20/12 (5/3) 14/32 (7/16) Bei Ihrer Ergänzung dürfen Sie allerdings keine get-Funktionen in CBruch angeben, die die Werte dessen private-Membervariablen zurückliefern. 75 4 Weitere Features in C++ 4.2 Operatoren überladen 4.2.1 Ein Uhrzeit-Rechner Geben Sie im folgenden Programm uhrzeit.cpp die fehlende Klasse CUhrzeit an. Die Klasse sollte Uhrzeiten addieren und subtrahieren können. #include <stdio.h> class CUhrzeit { ........ }; int main(void) { CUhrzeit mittag(12, 0, 0); // Mittag, punkt 12 Uhr mittag.print("Mittag"); CUhrzeit kurznachMittag = mittag; kurznachMittag += CUhrzeit(0, 15); // Nun ist es 15 Minuten später kurznachMittag.print("15 Minuten später"); CUhrzeit ziemlichFrueh; ziemlichFrueh = mittag - CUhrzeit(3, 20, 53); ziemlichFrueh.print("3 Std, 20 Min, 53 Sek vor Mittag war es"); return 0; } Startet man das Programm uhrzeit.cpp, so sollte es die folgende Ausgabe liefern: Mittag: 12:00:00 15 Minuten später: 12:15:00 3 Std, 20 Min, 53 Sek vor Mittag war es: 08:39:07 76 4.2 Operatoren überladen 4.2.2 Bruchrechner mit überladenen Operatoren Erstellen Sie eine Headerdatei bruchop.h, in der sich eine Klassendeklaration CBruch für einen Bruchrechner befindet, der folgende Operatoren anbietet: = + - (Vorzeichen) += ++ < - -= * -> + (Vorzeichen) *= / /= (Präfix- und Postfix-Variante) == != <= >= (Vergleichsoperatoren) In der Datei bruchop.cpp sollten Sie dann die Implementierung dieser Klasse angeben. Testen Sie dann Ihr Programm mit dem folgenden Programm bruchopmain.cpp. #include <stdio.h> #include "bruchop.h" CBruch b1(1, 2), b2(3, 4), b3(2, 5), b4; void printAll(char *text) { printf("%20s : b1=%2d/%-2d, b2=%2d/%-2d, b3=%2d/%-2d, b4=%2d/%-2d\n", text, b1.getZaehler(), b1.getNenner(), b2.getZaehler(), b2.getNenner(), b3.getZaehler(), b3.getNenner(), b4.getZaehler(), b4.getNenner()); } int main(void) { printAll("Am Anfang"); printf("------------------------------------------------------ Zuweisung\n"); b4 = b1; printAll("b4 = b1"); printf("----------------------------------------------------- Vorzeichen\n"); b4 = -b2; printAll("b4 = -b2"); b4 = - -b2; printAll("b4 = - -b2"); b4 = +-b2; printAll("b4 = +-b2"); printf("------------------------------------------------------- Addition\n"); b1 += b2; printAll("b1 += b2"); b1 += 5; printAll("b1 += 5"); b1 = b2 + b3; printAll("b1 = b2 + b3"); b1 = b2 + 5; printAll("b1 = b2 + 5"); b1 = 6 + b2; printAll("b1 = 5 + b2"); printf("---------------------------------------------------- Subtraktion\n"); b1 -= b2; printAll("b1 -= b2"); b1 -= 3; printAll("b1 -= 3"); b1 = b2 - b3; printAll("b1 = b2 - b3"); b1 = b2 - 2; printAll("b1 = b2 - 2"); b1 = 4 - b2; printAll("b1 = 4 - b2"); printf("------------------------------------------------- Multiplikation\n"); b1 *= b3; printAll("b1 *= b3"); b1 *= 5; printAll("b1 *= 5"); b1 = b2 * b3; printAll("b1 = b2 * b3"); 77 4 Weitere Features in C++ b1 = b3 * 10; printAll("b1 = b3 * 10"); b1 = 8 * b2; printAll("b1 = 8 * b2"); printf("------------------------------------------------------- Division\n"); b1 /= b3; printAll("b1 /= b3"); b2 /= 3; printAll("b2 /= 3"); b1 = b2 / b3; printAll("b1 = b2 / b3"); b1 = b3 / 4; printAll("b1 = b3 / 4"); b1 = 2 / b2; printAll("b1 = 2 / b2"); printf("------------------------------------------- Komplexere Ausdrücke\n"); b4 += b1 + b2 * b3; printAll("b4 += b1 + b2 * b3"); b4 -= b1 + b2 * b3 - CBruch(7, 20); printAll("b4 -= b1 + b2 * b3 - 7/20"); printf("------------------------------------------------ Postfix-/Präfix\n"); b1 = ++b4 - b3; printAll("b1 = ++b4 - b3"); b1 = b4++ + ++b3; printAll("b1 = b4++ + ++b3"); b1 = b4-- - --b3; printAll("b1 = b4-- - --b3"); printf("----------------------------------------------------- Vergleiche\n"); b4 = b3; printAll(".............."); printf(" b1 > b3 ist %s\n", (b1 > b3) ? "wahr" : "falsch"); printf(" b2 > b3 ist %s\n", (b2 > b3) ? "wahr" : "falsch"); printf(" b2 < b4 ist %s\n", (b2 < b4) ? "wahr" : "falsch"); printf(" b4 < b2 ist %s\n", (b4 < b2) ? "wahr" : "falsch"); printf(" b3 == b4 ist %s\n", (b3 == b4) ? "wahr" : "falsch"); printf(" b3 == b2 ist %s\n", (b3 == b2) ? "wahr" : "falsch"); printf(" b3 != b2 ist %s\n", (b3 != b2) ? "wahr" : "falsch"); printf(" b3 != b4 ist %s\n", (b3 != b4) ? "wahr" : "falsch"); printf(" b3 <= b4 ist %s\n", (b3 <= b4) ? "wahr" : "falsch"); printf(" b2 <= b4 ist %s\n", (b2 <= b4) ? "wahr" : "falsch"); printf(" b1 <= b4 ist %s\n", (b1 <= b4) ? "wahr" : "falsch"); printf(" b3 >= b4 ist %s\n", (b3 >= b4) ? "wahr" : "falsch"); printf(" b4 >= b2 ist %s\n", (b4 >= b2) ? "wahr" : "falsch"); printf(" b4 >= b1 ist %s\n", (b4 >= b1) ? "wahr" : "falsch"); return 0; } Nachdem Sie Ihr Programm zusammen mit dem Programm bruchopmain.cpp kompiliert und gelinkt haben, sollte es die folgende Ausgabe liefern: Am Anfang : b1= 1/2 , b2= 3/4 , b3= 2/5 , b4= 0/1 ------------------------------------------------------ Zuweisung b4 = b1 : b1= 1/2 , b2= 3/4 , b3= 2/5 , b4= 1/2 ----------------------------------------------------- Vorzeichen b4 = -b2 : b1= 1/2 , b2= 3/4 , b3= 2/5 , b4=-3/4 b4 = - -b2 : b1= 1/2 , b2= 3/4 , b3= 2/5 , b4= 3/4 b4 = +-b2 : b1= 1/2 , b2= 3/4 , b3= 2/5 , b4=-3/4 ------------------------------------------------------- Addition b1 += b2 : b1= 5/4 , b2= 3/4 , b3= 2/5 , b4=-3/4 b1 += 5 : b1=25/4 , b2= 3/4 , b3= 2/5 , b4=-3/4 78 4.2 Operatoren überladen b1 = b2 + b3 : b1=23/20, b2= 3/4 , b3= 2/5 , b4=-3/4 b1 = b2 + 5 : b1=23/4 , b2= 3/4 , b3= 2/5 , b4=-3/4 b1 = 5 + b2 : b1=27/4 , b2= 3/4 , b3= 2/5 , b4=-3/4 ---------------------------------------------------- Subtraktion b1 -= b2 : b1= 6/1 , b2= 3/4 , b3= 2/5 , b4=-3/4 b1 -= 3 : b1= 3/1 , b2= 3/4 , b3= 2/5 , b4=-3/4 b1 = b2 - b3 : b1= 7/20, b2= 3/4 , b3= 2/5 , b4=-3/4 b1 = b2 - 2 : b1=-5/4 , b2= 3/4 , b3= 2/5 , b4=-3/4 b1 = 4 - b2 : b1=13/4 , b2= 3/4 , b3= 2/5 , b4=-3/4 ------------------------------------------------- Multiplikation b1 *= b3 : b1=13/10, b2= 3/4 , b3= 2/5 , b4=-3/4 b1 *= 5 : b1=13/2 , b2= 3/4 , b3= 2/5 , b4=-3/4 b1 = b2 * b3 : b1= 3/10, b2= 3/4 , b3= 2/5 , b4=-3/4 b1 = b3 * 10 : b1= 4/1 , b2= 3/4 , b3= 2/5 , b4=-3/4 b1 = 8 * b2 : b1= 6/1 , b2= 3/4 , b3= 2/5 , b4=-3/4 ------------------------------------------------------- Division b1 /= b3 : b1=15/1 , b2= 3/4 , b3= 2/5 , b4=-3/4 b2 /= 3 : b1=15/1 , b2= 1/4 , b3= 2/5 , b4=-3/4 b1 = b2 / b3 : b1= 5/8 , b2= 1/4 , b3= 2/5 , b4=-3/4 b1 = b3 / 4 : b1= 1/10, b2= 1/4 , b3= 2/5 , b4=-3/4 b1 = 2 / b2 : b1= 8/1 , b2= 1/4 , b3= 2/5 , b4=-3/4 ------------------------------------------- Komplexere Ausdrücke b4 += b1 + b2 * b3 : b1= 8/1 , b2= 1/4 , b3= 2/5 , b4=147/20 b4 -= b1 + b2 * b3 - 7/20 : b1= 8/1 , b2= 1/4 , b3= 2/5 , b4=-2/5 ------------------------------------------------ Postfix-/Präfix b1 = ++b4 - b3 : b1= 1/5 , b2= 1/4 , b3= 2/5 , b4= 3/5 b1 = b4++ + ++b3 : b1= 2/1 , b2= 1/4 , b3= 7/5 , b4= 8/5 b1 = b4-- - --b3 : b1= 6/5 , b2= 1/4 , b3= 2/5 , b4= 3/5 ----------------------------------------------------- Vergleiche .............. : b1= 6/5 , b2= 1/4 , b3= 2/5 , b4= 2/5 b1 > b3 ist wahr b2 > b3 ist falsch b2 < b4 ist wahr b4 < b2 ist falsch b3 == b4 ist wahr b3 == b2 ist falsch b3 != b2 ist wahr b3 != b4 ist falsch b3 <= b4 ist wahr b2 <= b4 ist wahr b1 <= b4 ist falsch b3 >= b4 ist wahr b4 >= b2 ist wahr b4 >= b1 ist falsch 79 4 Weitere Features in C++ 4.2.3 Rechner für sehr grosse Zahlen mit überladenen Operatoren Erstellen Sie eine Headerdatei rechgross.h, in der sich eine Klassendeklaration CZahl für einen Rechner befindet, der grundlegende Rechenoperationen für ganze Zahlen mit Tausenden von Stellen durchführen kann. Der Rechner soll folgende Operatoren anbieten: + += ++ < - -= * *= -> == (Präfix- und Postfix-Variante) != <= >= (Vergleichsoperatoren) In der Datei rechgross.cpp sollten Sie dann die Implementierung dieser Klasse angeben. Testen Sie dann Ihr Programm mit dem folgenden Programm rechgrossmain.cpp. #include <stdio.h> #include "rechgross.h" void print(CZahl z) { char *zahl = z.getZahl(); //... Zahlen werden rückwärts gespeichert for (int i=z.getStellen()-1; i>=0; i--) //... um Rechnen zu erleichtern printf("%d", zahl[i]); } CZahl a("12"), b("352"), c, d; int main(void) { print(a); printf(" + "); print(b); printf(" = "); a += b; print(a); printf("\n"); print(b); printf(" - 12 = "); b -= "12"; print(b); printf("\n"); print(a); printf(" * 61263762733250949 = "); a *= "61263762733250949"; print(a); printf("\n"); printf("a * 827327191620020029716 = "); c = a * "827327191620020029716"; print(c); printf("\n"); if ( (d = a * "8474747747") > c ) { print(d); printf(" ist grösser als "); print(c); } else { print(d); printf(" ist kleiner als "); print(c); } 80 4.2 Operatoren überladen printf("\n"); c *= d *= c * c * c; printf("d = "); print(d); printf("\n"); printf("c = "); print(c); printf("\n"); return 0; } Nachdem Sie Ihr Programm zusammen mit dem Programm rechgrossmain.cpp kompiliert und gelinkt haben, wie z.B.: c++ -o rechgross rechgrossmain.cpp rechgross.cpp sollte es die folgende Ausgabe liefern: 12 + 352 = 364 352 - 12 = 40 364 * 61263762733250949 = 22300009634903345436 a * 827327191620020029716 = 18449404344343972972568408824016532976176 188986956411475419296503732692 ist kleiner als 18449404344343972972568408824016 532976176 d = 118680366354009072311427849732459579543994049124893069068182338649575599410 0866106155414184747877048765312347229204782703111333190830483208151732436992 c = 218958206659998925891816336388767735582678032528245838695812451494370563624 8924666463202093835804489404417474850121337970821478428458874503601596941539787 6222965812868832217458781915157102592 81 4 Weitere Features in C++ 4.2.4 Realisierung von Mengenoperationen über ein Array Erstellen Sie eine Headerdatei mengenarray.h, in der sich eine Klassendeklaration CMengArray befindet, die Mengenoperationen zur Verfügung stellt. Die Elemente einer Menge sollen dabei int-Zahlen sein. Folgende Mengenoperationen soll die Klasse CMengArray anbieten: ❏ Zuweisung einer ganzen Menge bzw. eines Elements (Operator =) ❏ Vereinigung zweier Mengen bzw. einer Menge mit einem Element (Operatoren +=, +) ❏ Durchschnitt zweier Mengen bzw. einer Menge und einem Element (Operatoren /=, /) ❏ Restmenge aus zwei Mengen bzw. aus einer Menge und einem Element (Operatoren -=, -) ❏ Prüfung, ob eine Menge Teilmenge einer anderen bzw. ein Element in einer Menge enthalten ist (Operator <=) ❏ Prüfung, ob eine Menge bzw. ein Element eine echte Teilmenge einer anderen sind (Operator <) ❏ Prüfung, ob zwei Mengen gleich bzw. ungleich sind, wobei auch ein Element bei dieser Prüfung zugelassen ist (Operatoren ==, !=) In der Datei mengenarray.cpp sollten Sie dann die Implementierung dieser Klasse angeben. Testen Sie dann Ihr Programm mit dem Programm mengenarraymain.cpp. #include <stdio.h> #include "mengenarray.h" void print(char *name, CMengArray& a) { printf("%s: ", name); for (int i=0; i<a.getMax(); i++) printf("%d, ", a.getElement(i)); printf("\n"); } int main(void) { CMengArray int eins, zwei, drei, vier, fuenf; i; for (i=0; i<8; i++) eins = eins + (i+i); print("eins", eins); zwei += 5; zwei += 9; print("zwei", zwei); drei = eins + zwei; print("drei = eins + zwei", drei); vier = drei - eins + zwei; print("vier = drei - eins + vier", vier); fuenf = eins / drei; print("fuenf = eins / drei", fuenf); vier -= zwei; print("vier -= zwei", vier); if (eins < drei) printf("eins ist echte Teilmenge von drei\n"); 82 4.2 Operatoren überladen if (fuenf <= eins) { printf("fuenf ist Teilmenge von eins, "); if (fuenf < eins) printf("und zwar eine echte\n"); else printf("aber keine echte\n"); } if (fuenf == eins) printf("Die Mengen fuenf und eins sind gleich\n"); if (drei != eins) printf("Die Mengen drei und eins sind nicht gleich\n"); if (9 <= drei) printf("9 ist in drei enthalten\n"); if (drei - vier == fuenf) printf("drei - vier == fuenf\n"); if (zwei / drei == vier) printf("Durchschnitt von zwei und drei == vier\n"); return 0; } Nachdem Sie Ihr Programm zusammen mit dem Programm mengenarraymain.cpp kompiliert und gelinkt haben, wie z.B.: c++ -o mengenarray mengenarraymain.cpp mengenarray.cpp sollte es die folgende Ausgabe liefern: eins: 0, 2, 4, 6, 8, 10, 12, 14, zwei: 5, 9, drei = eins + zwei: 0, 2, 4, 5, 6, 8, 9, 10, 12, 14, vier = drei - eins + vier: 5, 5, 9, 9, fuenf = eins / drei: 0, 2, 4, 6, 8, 10, 12, 14, vier -= zwei: 5, 9, eins ist echte Teilmenge von drei fuenf ist Teilmenge von eins, aber keine echte Die Mengen fuenf und eins sind gleich Die Mengen drei und eins sind nicht gleich 9 ist in drei enthalten drei - vier == fuenf Durchschnitt von zwei und drei == vier 83 4 Weitere Features in C++ 4.2.5 Rechner für Dezimal- und Dualzahlen Ergänzen Sie das folgende Programm dualrech.cpp um die Klasse CDual, so dass es ein gleichzeitiges – also auch gemischtes – Rechnen mit Dezimal- und Dualzahlen ermöglicht. #include <stdio.h> #include <stdlib.h> #include <string.h> ......... int main(void) { CDual zehn = 10, einundzwanzig = 21, zwoelf = "1100", tausend, million20 = 1e6, neunzig; long twentyone, thirtyone, hundert = 100, million52 = (long)1e6; CDual(hundert).print("hundert"); einundzwanzig.print("einundzwanzig"); zwoelf.print("zwoelf"); zwoelf += hundert; zwoelf.print("zwoelf += hundert"); tausend = zehn + 990; tausend.print("tausend"); million20 = million20 + "10100"; million20.print("million20"); twentyone = (long)einundzwanzig; //... long-Cast von CDual printf("%20s: %d\n", "twentyone", twentyone); thirtyone = long(zehn + einundzwanzig); //... long-Cast von CDual printf("%20s: %d\n", "thirtyone", thirtyone); million52 = (long)million52 + twentyone + thirtyone; // long-Cast für million52 CDual(million52).print("million52"); neunzig = (long)twentyone + (long)thirtyone + 38; // long-Casts neunzig.print("neunzig"); return 0; } Nachdem Sie das Programm dualrech.cpp um die Klasse CDual ergänzt haben, sollte es die folgende Ausgabe liefern: hundert: 00000000000000000000000001100100 (100) einundzwanzig: 00000000000000000000000000010101 (21) zwoelf: 00000000000000000000000000001100 (12) zwoelf += hundert: 00000000000000000000000001110000 (112) tausend: 00000000000000000000001111101000 (1000) million20: 00000000000011110100001001010100 (1000020) twentyone: 21 thirtyone: 31 million52: 00000000000011110100001001110100 (1000052) neunzig: 00000000000000000000000001011010 (90) 84