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