Struktur und OOP-Scr.. - Webseiten der Mitarbeiter der Universität

Werbung
BBS III Mainz
Objektorientierte Programmierung
für IT-Berufe
mit C++
von
Gerold Mader
D:\75807564.doc vom 13.10.99
BBS III Mainz
OOP für IT-Berufe mit C++
Inhaltsverzeichnis
1
LITERATUR ZU C++ ............................................................................................................................... 1-1
2
ENTWICKLUNGSUMGEBUNG VON VISUAL C++ 6.0..................................................................... 2-2
3
GRUNDLEGENDE PROGRAMMSTRUKTUREN IN C++ ................................................................. 3-3
3.1
AUFBAU EINES PROGRAMMES IN C++ ................................................................................................... 3-3
3.1.1
Kommentare ................................................................................................................................. 3-3
3.1.2
Datentypen ................................................................................................................................... 3-4
3.1.3
Variablen ...................................................................................................................................... 3-5
3.1.4
Konstanten ................................................................................................................................... 3-6
3.1.5
Anweisungen ................................................................................................................................ 3-7
3.1.6
Wertzuweisung, Rechenoperationen ............................................................................................ 3-8
3.1.7
Implizite und explizite Typkonvertierung ..................................................................................... 3-9
3.1.8
Inkrement- und Dekrementoperatoren ...................................................................................... 3-10
3.2
FOLGE (SEQUENZ) ............................................................................................................................... 3-16
3.2.1
Grundlegende Übungsaufgaben ................................................................................................. 3-17
3.2.2
Anfangsprobleme und deren Lösungen ...................................................................................... 3-18
3.3
AUSWAHL (SELEKTION) ...................................................................................................................... 3-20
3.3.1
Einseitige Auswahl ..................................................................................................................... 3-20
3.3.2
Probleme und deren Lösung ...................................................................................................... 3-23
3.3.3
Zweiseitige Auswahl ................................................................................................................... 3-24
3.3.4
Mehrseitige Auswahl .................................................................................................................. 3-28
3.3.5
Probleme und deren Lösung ...................................................................................................... 3-35
3.4
WIEDERHOLUNG (REPETITION) ........................................................................................................... 3-37
3.4.1
Schleife mit Anfangsabfrage....................................................................................................... 3-37
3.4.2
Probleme und deren Lösung ...................................................................................................... 3-39
3.4.3
Schleife mit Endabfrage ............................................................................................................. 3-40
3.4.4
Zählschleife ................................................................................................................................ 3-42
3.4.5
Zählschleife mit variabler Schrittweite ...................................................................................... 3-43
3.4.6
Anweisungen für Schleifenabbruch und -wiederholung ............................................................ 3-45
3.5
MODUL (UNTERPROGRAMM) .............................................................................................................. 3-47
3.5.1
Funktionen ohne Parameter ....................................................................................................... 3-48
3.5.2
Funktionen mit Werteparametern .............................................................................................. 3-49
3.5.3
Funktionen mit Variablenparametern ........................................................................................ 3-51
3.5.4
Funktionen mit Rückgabetyp ...................................................................................................... 3-53
3.5.5
Sprachspezifische Besonderheiten bei Funktionen .................................................................... 3-55
3.6
VERBUNDE UND OBJEKTE: STRUKTUREN UND KLASSEN .................................................................... 3-63
3.6.1
Strukturen ................................................................................................................................... 3-63
3.6.2
Funktionen zum Zugriff auf Strukturelemente ............................................................................ 3-66
3.6.3
Kapselung von Struktur und Zugriffsfunktionen: Objekt ............................................................ 3-68
3.6.4
Objektklasse und Objektinstanz.................................................................................................. 3-70
4
OBJEKTORIENTIERTE PROGRAMMIERUNG .............................................................................. 4-72
4.1
PRINZIPIEN DER OBJEKTORIENTIERTEN PROGRAMMIERUNG ................................................................ 4-72
4.2
DAS OOP-PRINZIP KAPSELUNG .......................................................................................................... 4-73
4.2.1
Klassen ....................................................................................................................................... 4-73
4.2.2
Methoden .................................................................................................................................... 4-74
4.2.3
Der Bereichsoperator :: ............................................................................................................. 4-75
4.2.4
Konstruktoren ............................................................................................................................. 4-76
4.2.5
Destruktoren ............................................................................................................................... 4-78
4.2.6
Objekte als Rückgabewert von Funktionen ................................................................................ 4-79
4.2.7
Klassen, Objekte und Speicher ................................................................................................... 4-80
4.2.8
Operator-Überladung ................................................................................................................ 4-80
4.2.9
Übungsaufgaben: ....................................................................................................................... 4-85
D:\75807564.doc vom 13.10.99
BBS III Mainz
OOP für IT-Berufe mit C++
4.3
VERERBUNG........................................................................................................................................ 4-87
4.3.1
Das Prinzip der Vererbung ........................................................................................................ 4-87
4.3.2
Protected .................................................................................................................................... 4-93
4.3.3
Konstruktoren in abgeleiteten Klassen ....................................................................................... 4-93
4.3.4
Überschreiben von Methoden .................................................................................................... 4-94
4.3.5
Klassenhierarchie....................................................................................................................... 4-97
4.3.6
Vererbung von Zugriffsrechten ................................................................................................ 4-100
4.3.7
Mehrere Stufen der Vererbung ................................................................................................. 4-101
4.3.8
Mehrfachvererbung .................................................................................................................. 4-103
4.3.9
Übungsaufgaben ...................................................................................................................... 4-106
4.4
POLYMORPHIE................................................................................................................................... 4-107
4.4.1
Virtuelle Methoden/Funktionen................................................................................................ 4-107
4.4.2
Virtuelle Basisklassen .............................................................................................................. 4-113
4.4.3
Friend-Funktionen ................................................................................................................... 4-114
4.4.4
This-Zeiger ............................................................................................................................... 4-115
4.4.5
Übungsaufgaben ...................................................................................................................... 4-115
D:\75807564.doc vom 13.10.99
BBS III Mainz
OOP für IT-Berufe mit C++
1 Literatur zu C++
Herdt-Verlag: Objektor. Pro. mit C++ 30,-DM
Arnush(SAMS) Borland C++ in 21 Tagen
Breymann (Hanser): C++ Einführung 74 DM Beispiele auf hohem Niveau
Louis (Markt+Technik):
C/C++-Kompendium
B. Stronstrup (Add.-Wesley): Die C++ Prog.sprache 100,- DM
C++ Workshop (Add.-Wesley)
Rororo Verlag OOP mit C++ 20,- DM
Booch
Objektorientierte Analyse und Design 90,- DM
Chapman, David Teach Yourself Visual C++ 6 in 21 Days
D:\75807564.doc vom 13.10.99
1-1
110,- DM
BBS III Mainz
OOP für IT-Berufe mit C++
2 Entwicklungsumgebung von Visual C++ 6.0
1. DATEI - NEU: Leeren Arbeitsbereich ITSK97A anlegen.
2. DATEI - NEU - PROJEKTE - WIN32-Konsoleanwendung : Hinzufügen zu
aktivem Arbeitsbereich (anklicken)
NAME: TEIL1
3. Dateien dem Projekt TEIL1 hinzufügen
4. DATEI - NEU - PROJEKTE - WIN32-Konsoleanwendung : Hinzufügen zu
aktivem Arbeitsbereich (anklicken)
NAME: TEIL2
5. Dateien dem Projekt TEIL2 hinzufügen
A. Teil1 aktivieren (Als aktives Projekt festlegen)
B. Projekt Einstellungen - ALLGEMEIN
Bis auf zu kompilierende Quelldatei alle anderen Quelldateien
mit Haken versehen bei: DATEI VON ERSTELLUNG AUSSCHLIESSEN
Mit F7 wird das Projekt erstellt: EXE-Datei wird dabei unter
dem Projektnamen erzeugt.
Einzelne Quelldateien können wie oben beschrieben von der Erstellung
ausgeschlossen werden. Jedoch erhält die EXE-Datei die mit F7 erzeugt
wird immer den Projektnamen (Projekt-Einstellungen - Projektnamen anklicken - DEBUG).
D:\75807564.doc vom 13.10.99
2-2
BBS III Mainz
OOP für IT-Berufe mit C++
3 Grundlegende Programmstrukturen in C++
3.1 Aufbau eines Programmes in C++
// Programm Erstprog.cpp
// Einbinden einer Header-Datei
// iostream.h ist notwendig, um Ein-/Ausgabe nutzen zu koennen
#include <iostream.h>
/* Definitionsteil */
// main() enthaelt das Hauptprogramm zwischen den geschweiften Klammern {}
void main()
{
// Deklaration der Variablen
float a,b,c;
/* Anweisungsteil */
// Startmeldung
cout << "Programm zum Addieren von zwei Zahlen." << endl;
// Eingabeaufforderungen
cout << "Geben Sie die erste Zahl ein: ";
cin >> a;
cout << "Geben Sie die zweite Zahl ein: ";
cin >> b;
// Berechnung der Summe, Ergebnis in c ablegen
c=a+b;
// Ergebnis ausgeben
cout << "Die Summe von " << a << " und " << b << " ist " << c << "."
<< endl;
}
An diesem Beispiel soll der Aufbau eines Programmes in C++ erläutert werden.
Ein Programm in C++ besteht zunächst aus 2 Teilen.
1.
Einbinden evtl. benötigter „Headerdateien“ (Vorspanndateien). Durch diese werden neue Befehle in
C++ zugänglich. Es gibt eine Reihe von Standard-Headerdateien, die bei jeder C++-Implementierung
mitgeliefert werden. Eine davon ist die Datei
iostream.h, durch die Befehle zur Eingabe und Ausgabe verfügbar werden.
2.
Im Definitionsteil werden benötigte Variablen und Funktionen definiert. Im hier dargestellten
einfachsten Fall wird nur die Funktion main() definiert, die das Hauptprogramm enthält. Funktionen
und damit auch das Hauptprogramm main bestehen wiederum aus zwei Teilen.
a)
Im Variablendeklarationsteil werden die benötigten Variablen und Konstanten eingeführt
(deklariert).
b)
Im Anweisungsteil wird der eigentliche Algorithmus in der Sprache C++ formuliert.
Grobanalyse des Beispielprogrammes Erstprog.cpp
Im Deklarationsteil werden 3 Variablen mit den Namen a, b und c deklariert. Diese können bestimmte
Datentypen („Datentypen“ siehe einen der nächsten Abschnitte) speichern, hier „float“, also Fließkommazahlen.
Im Anweisungsteil werden verschiedene Anweisungen zur Ausgabe (cout <<) und Eingabe (cin >>)
verwendet. Auch eine Berechnungsvorschrift (c=a+b) ist zu finden.
3.1.1 Kommentare
Um den Überblick über ein größeres Programm zu behalten ist es wichtig, Programmteile optisch voneinander
trennen zu können und bestimmte Anmerkungen anbringen zu können. Dies geschieht durch Kommentare, die
D:\75807564.doc vom 13.10.99
3-3
BBS III Mainz
OOP für IT-Berufe mit C++
vom restlichen Text auf zwei Arten abgetrennt werden können. Durch // wird ein Kommentar eingeleitet, der
bis zum Ende der Zeile geht. Kommentare können aber auch in /* ... */ eingeschlossen werden. Der Text
in Kommentarklammern unterliegt keinen Einschränkungen und ist absolut beliebig.
3.1.2 Datentypen
C++ kennt verschiedene Datentypen, die jeweils einen bestimmten Platzbedarf und verschiedene Wertebereiche
haben.
Typname
Bereich
Platzbedarf in Bytes
bool
true, false
1
char
-128 bis 127 bzw. Zeichen nach ASCII-Tabelle, 1
z.B. ’A’ oder ’@’
unsigned char
0 bis 255 bzw. Zeichen nach ASCII-Tabelle, 1
z.B. ’A’ oder ’@’
int
-32768 bis +32767
2
long
-2 147 483 648 bis +2 147 483 647
4
unsigned int
0 bis 65535
2
unsigned long
0 bis 4.294.967.295
4
float
3,4*10-38 bis 3,4*10+38
4
double
1,7*10-308 bis 1,7*10+308
8
long double
3,4*10-4932 bis 1,1*10+4932
10
// Deklaration von Variablen
float a,a2;
// reelle Zahlen
char b;
// Zeichen
unsigned long l; // lange vorzeichenlose Ganzzahl
long double x;
// sehr genaue relle Zahl mit großem Zahlenbereich
Darüber hinaus können bei Bedarf weitere Typen definiert werden, z.B. Aufzählungstypen oder Verbunde.
// Definition neuer Typen
enum Wochentage {Mo,Di,Mi,Do,Fr,Sa,So}; // Aufzählungstyp
struct Datum { int Tag,Monat,Jahr } ;
// Verbund
// darauf aufbauende Deklaration von Variablen
Wochentage t; // Wert von Mo bis So
Datum d;
// Variable zusammengesetzt aus 3 Integer-Zahlen
D:\75807564.doc vom 13.10.99
3-4
BBS III Mainz
OOP für IT-Berufe mit C++
3.1.3 Variablen
In den Variablen eines Programms werden die zu verarbeitenden Daten, Ergebnisse und Zwischenergebnisse
gespeichert.
Variablen werden ebenso wie Typen über ihre symbolischen Namen angesprochen. Namen müssen mit einem
Buchstaben beginnen und dürfen ansonsten Buchstaben, Ziffern und den Unterstrich „_“ enthalten; Groß- und
Kleinschreibung wird beachtet, name, NaMe und NAME meinen also 3 verschiedene Variablen. Leerzeichen
können in Variablennamen nicht vorkommen.
Variablen können nur Daten eines bestimmten Typs speichern. Dieser Typ wird im Deklarationsteil festgelegt;
er kann im Lauf des Programmes nicht verändert werden.
Variablen bekommen einen Wert zugewiesen, indem man schreibt
Variablenname=Ausdruck;
a=1.2345; // Variable a erhält den Wert 1.2345
a2=a*5+7; // Variable a2 erhält den Wert, der sich aus der Formel berechnet
b=’z’;
// Variable b speichert das Zeichen ’z’
Konstante ASCII-Zeichen werden dabei in einfachen Apostrophen „’“ eingeschlossen. Bei reellen Zahlen (float
oder double) wird ein Dezimal-Punkt, kein Dezimal-Komma verwendet.
D:\75807564.doc vom 13.10.99
3-5
BBS III Mainz
OOP für IT-Berufe mit C++
3.1.4 Konstanten
Konstanten können wie Variablen angesprochen werden, verändern aber während des Programmlaufs ihren
Wert nicht. Wird dies trotzdem versucht, reagiert der Compiler mit einer Fehlermeldung. KonstantenBezeichner werden üblicherweise in BLOCKSCHRIFT deklariert.
Beispiele:
const double PI=3.1415;
// Kreiszahl PI als double-Konstante
const float E=2.7172;
// Eulersche Zahl
const unsigned int ZWEIUNDVIERZIG=42; // ob das sinnvoll ist?
Zeichen und Zeichenketten (auch Literale genannt) werden in Apostrophen eingeschlossen. Einzelne Zeichen
werden dabei in einfachen Apostrophen „’“und Zeichenketten werden in doppelten Apostrophen „"“
eingeschlossen; letzteres wird häufig bei Ausgaben verwendet (cout << "Hallo"). Bei reellen float- oder
double-Zahlen wird ein Dezimal-Punkt, kein Dezimal-Komma verwendet.
Bestimmte ASCII-Zeichen sind nicht direkt ausgebbar; wie lasse ich z.B. den doppelten Apostrophen ausgeben,
wo der doch als Begrenzung für die auszugebende Zeichenkette dient? Deshalb werden diese Zeichen über
besondere sogenannte Escape-Sequenzen dargestellt, die alle mit dem Backslash „\“ beginnen:
Zeichen
\t
\v
\r
\n
\a
\b
\\
\’
\"
\xzz, \xZZ
Bedeutung
Tabulator
Zeilensprung
Zeilenrücklauf
neue Zeile
Signalton
Backspace, löscht das Zeichen links von der Eingabemarke
der Backslash selbst
’ einfacher Apostroph
" doppelter Apostroph
Zeichen aus Sedezimalziffern, z.B. \x40 ist der ’@’
D:\75807564.doc vom 13.10.99
3-6
BBS III Mainz
OOP für IT-Berufe mit C++
3.1.5 Anweisungen
Über Anweisungen wird der Ablauf des Programmes gesteuert. Mit der Anweisung
cout << ... << ... ;
beispielsweise werden Zeichen, Zeichenketten und Zahlen auf dem Bildschirm ausgegeben. Mit
cin >> ... >> ... ;
werden Daten von der Tastatur in Variablen eingelesen.
Anweisungen werden immer mit einem Strichpunkt ; abgeschlossen. Mehrere Anweisungen werden mit den
geschweiften Klamern {...} zu einer Anweisungs-Gruppe zusammengefaßt.
AnweisungA;
{ Anweisung1; Anweisung2; Anweisung3; }
AnweisungC;
Die Anweisungsgruppen werden bei den weiteren Strukturen benötigt; aber auch das Hauptprogramm main
besteht aus einer Anweisungsgruppe.
C++ stellt selbst bereits viele Anweisungen zur Verfügung. Eine besondere Art der Anweisung ist die
Wertzuweisung, die meist mit der Ausführung von Rechenoperationen verbunden ist.
D:\75807564.doc vom 13.10.99
3-7
BBS III Mainz
OOP für IT-Berufe mit C++
3.1.6 Wertzuweisung, Rechenoperationen
Die Wertzuweisung an eine Variable erfolgt über den Operator „=“, z.B.
a=5;
ergebnis=x*faktor+abstand-12;
Das erste Beispiel weist der Variablen a den Wert 5 zu. Im zweiten Beispiel wird eine Formel berechnet, das
Ergebnis der Berechnung wird der Variablen mit Namen ergebnis zugewiesen.
C++ bietet die Grundrechenarten Addition, Subtraktion, Multiplikation und Division an.
Addition, Subtraktion, Multiplikation und Division werden durch die Operatoren +, -, * und / ausgeführt; der
Ergebnistyp richtet sich dabei immer nach dem größten beteiligten Operandentyp. int+int ergibt z.B. int, aber
long+float ergibt float.
Ergänzend zur Ganzzahl-Division gibt es noch den Operator %, der den Rest der ganzzahligen Division
errechnet.
C++ beachtet die Punkt-vor-Strich-Regel, so daß Multiplikation und Division vor Addition und Subtraktion
ausgeführt werden. Auch runde Klammern (...) können wie gewohnt eingesetzt werden.
Formel
17+5
12*1.5
12/4.0
12/4
17/3
17/3.0
17%3
D:\75807564.doc vom 13.10.99
Ergebnis
22
18.0
3.0
3
5
5.66666666667
2
Ergebnistyp
(int)
(float)
(float)
(int)
(int)
(float)
(int)
3-8
Bemerkung
int*float
17:3=5 Rest 2
BBS III Mainz
OOP für IT-Berufe mit C++
3.1.7 Implizite und explizite Typkonvertierung
Was passiert, wenn bei einer Zuweisung die Formel einen anderen Typ hat, als die Variable, der der Wert
zugewiesen werden soll? Beispiel:
long a,e;
float b;
e=a/b;
Das Ergebnis der Formel a/b ist vom Typ float, weil b vom Typ float ist. Aber e ist eine long-Variable. Daher
muß der Ergebnistyp dem Variablentyp von e angepaßt werden. Solche Anpassungen können implizit
vorgenommen werden, wie in diesem Beispiel, oder sie können auch explizit vorgegeben werden.
Bei geeignetem Programmzusammenhang erfolgt die Typumwandlung automatisch (implizit). Zusätzlich kann
der Programmierer explizite Typ-Konvertierungen (engl. type casting) vornehmen. Beispiele:
int i,j;
bool a,b;
char z;
a
i
j
b
=
=
=
=
true;
int(a);
7;
bool(j);
j
i
b
z
i
=
=
=
=
=
0;
a;
j;
’Ç’;
z;
D:\75807564.doc vom 13.10.99
//
//
//
//
//
//
//
//
//
//
//
keine Konvertierung
explizit: int(true) ist 1, daher ist i gleich 1
keine Konvertierung
explizit: bool(7) ist true, daher ist b gleich true
Jede Ganzzahl <> 0 ergibt als bool-Wert true
keine Konvertierung
implizit: i wird auf 1 gesetzt
implizit: b wird auf 0 gesetzt
keine Konvertierung; Zeichen mit ASCII-Wert 128
implizit: Aufpassen: i auf -128 gesetzt, da i ein
signed int ist
3-9
BBS III Mainz
OOP für IT-Berufe mit C++
3.1.8 Inkrement- und Dekrementoperatoren
C++ erlaubt spezielle Schreibweisen für Operationen, die sich auf eine einzige Variable beziehen. Soll der Wert
einer Variablen erhöht oder vermindert werden, kann man folgende Schreibweisen anwenden:
int i=3,a=6;
i+=15;
// i
i-=17*a; // i
++i;
// i
i++;
// i
--i;
// i
i--;
// i
i*=17;
// i
i/=2*a; // i
i%=a;
// i
wird
wird
wird
wird
wird
wird
wird
wird
wird
um 15 erhöht
um 17*a vermindert
um 1 erhöht. Inkrement-Operator (Pre-Inkrement)
um 1 erhöht. Inkrement-Operator (Post-Inkrement)
um 1 vermindert. Dekrement-Operator (Pre-Dekrement)
um 1 vermindert. Dekrement-Operator (Post-Dekrement)
mit 17 multipliziert
durch 2*a dividiert
der Divisionsrest von i/a zugewiesen
Der Unterschied, ob man die Operatoren ++ bzw. – vor oder nach der Variablen plaziert, ist erst wichtig, wenn
die Operatoren innerhalb von Ausdrücken verwendet werden:
++i: Zuerst wird i inkrementiert, dann wird mit diesem i im Ausdruck weitergerechnet (analog für --i)
i++: Zuerst wird der Ausdruck mit dem momentanen Wert von i berechnet, danach wird i inkrementiert
(analog für i--)
Beispiele:
int i=10, j=5, k;
k = i*j++;
// jetzt hat k hat den Wert 50, j hat den Wert 6
int i=10, j=5, k;
k = i*++j;
// jetzt hat k den Wert 60, j hat den Wert 6.
D:\75807564.doc vom 13.10.99
3-10
BBS III Mainz
OOP für IT-Berufe mit C++
Testen Sie dazu das folgende Programmbeispiel KurzOp.cpp.
// PROGRAMM KurzOp.cpp
// Inkrement-, Dekrement- und Zuweisungsoperationen testen.
#include <iostream.h>
void main()
{
int i, j;
cout << "Ganze Zahl eingeben: ";
cin >> j;
i = 1000 * ++j;
cout << "Inkrement-Operator vor der Variablen ergibt: " << i << endl;
j--; // Erhöhung zurücknehmen
i = 1000 * j++;
cout << "Inkrement-Operator nach der Variablen ergibt: " << i << endl;
j = 5;
i += j;
cout << "Addition um 5 ergibt: " << i << endl;
i -= j;
cout << "Subtraktion von 5 ergibt: " << i << endl;
i *= j;
cout << "Multiplikation mit 5 ergibt: " << i << endl;
}
Ganze Zahl eingeben: 3
Inkrement-Operator vor der Variablen ergibt: 4000
Inkrement-Operator nach der Variablen ergibt: 3000
Addition um 5 ergibt: 3005
Subtraktion von 5 ergibt: 3000
Multiplikation mit 5 ergibt: 15000
D:\75807564.doc vom 13.10.99
3-11
BBS III Mainz
D:\75807564.doc vom 13.10.99
OOP für IT-Berufe mit C++
3-12
BBS III Mainz
D:\75807564.doc vom 13.10.99
OOP für IT-Berufe mit C++
3-13
BBS III Mainz
D:\75807564.doc vom 13.10.99
OOP für IT-Berufe mit C++
3-14
BBS III Mainz
D:\75807564.doc vom 13.10.99
OOP für IT-Berufe mit C++
3-15
BBS III Mainz
OOP für IT-Berufe mit C++
3.2 Folge (Sequenz)
Sollen für die Lösung einer Aufgabe einfach nur mehrere Anweisungen hintereinander ausgeführt werden,
können sie im Algorithmus in der gleichen Reihenfolge beschrieben werden.
Das Beispielprogramm Erstprog.cpp entspricht folgendem (ausführlichem) Struktogramm.
Kommt es einem nur auf das Wesentliche an, kann man dieses Struktogramm auch wie folgt verkürzen.
Die Verkürzungen, die hier möglich waren, beziehen sich auf das Thema „benutzerfreundliche
Programmierung“. Wenn im zweiten Struktogramm steht „Eingabe a,b“, ist damit die benutzerfreundliche
Eingabe von a und b gemeint, die die entsprechenden Ausgaben von Eingabeaufforderungen beinhalten. Durch
diese Verkürzungen lenken im Struktogramm die benutzerfreundlichen Teile nicht von den wesentlichen Teilen
der Programmierung ab.
D:\75807564.doc vom 13.10.99
3-16
BBS III Mainz
OOP für IT-Berufe mit C++
3.2.1 Grundlegende Übungsaufgaben
1. (ErstProg2.cpp) Verändern Sie das erste Beispielprogramm, so daß es die Differenz der beiden eingegebenen
Zahlen ausgibt.
2. (Benzinv.cpp) Schreiben Sie ein Programm, das nach Eingabe der gefahrenen Kilometer und der Menge des
verbrauchten Kraftstoffes den Durchschnittsverbrauch auf 100km berechnet und ausgibt.
Berechnung des Durchschnittsverbrauchs
verbrauchter Kraftstoff in l: 32.3
gefahrene Strecke in km: 423.1
Der Durchschnittsverbrauch ist 7.63 l/100km
Programmende.
3. (Ganzteil.cpp) Das ganzzahlige Ergebnis und der Rest der Division von zwei einzugebenden ganzen Zahlen
sollen mit einem Programm berechnet werden.
Ganzzahlige Division mit Rest
Dividend: 23
Divisor: 4
Das Ergebnis der Division 23:4 ist 5 Rest 3
Programmende
4. (ParallelR.cpp) Der Gesamtwiderstand zweier parallelgeschalteter Widerstände soll nach der Eingabe der
beiden Widerstandswerte berechnet werden.
Rg 
R1  R2
R1  R2
Widerstands-Parallelschaltung
R1 in Ohm: 12000
R2 in Ohm: 10000
Der Widerstand der Gesamtschaltung ist 5454.545 Ohm
Programmende
D:\75807564.doc vom 13.10.99
3-17
BBS III Mainz
OOP für IT-Berufe mit C++
3.2.2 Anfangsprobleme und deren Lösungen
Bei den ersten Übungen entstehen normalerweise Probleme, von denen hier eine Auswahl mitsamt Lösungen
angegeben sind.
3.2.2.1 Vergessene Strichpunkte (Syntaxfehler)
Einzelne Anweisungen werden stets durch Strichpunkt getrennt. Wurde dieser hinter einer Anweisung
vergessen, wird beim Übersetzen meist ein Fehler bei der folgenden Anweisung angemahnt. Beispiele (die 
zeigt die echte Fehlerstelle, die  die vom Compiler angezeigte Fehlerstelle an):
„Statement missing ;“ ist eine sehr klare Fehlermeldung in diesem Fall. Der Fehler wird aber leider hinter der
Fehlerstelle angezeigt.
cout << "Hallo"
cin >> a;
„Declaration syntax error“ kann seine Ursache auch in einem fehlenden Strichpunkt nach einer
Variablendeklaration haben.
Int a,b
cout << "Hallo";
3.2.2.2 Falsch geschriebene Worte (Syntaxfehler)
„Undefined Symbol ‘XXX’“ bedeutet, daß der Compiler mit dem Wort XXX nichts anfangen kann. Eine
mögliche Ursache ist ein Schreibfehler, wie im folgenden Fall mit XXX=Cout.
Cout << "Hallo";
Eine weitere Ursache ist eine falsche Variablendeklaration, wie im folgenden Fall mit XXX=Summe.
Float summe;
cout << Summe;
3.2.2.3 Ausgabe verschwindet sofort nach Programmende
Entweder die Entwicklungsumgebung bietet eine Möglichkeit, den Ausgabebildschirm noch einmal zu zeigen,
oder man hält durch das Setzen einer Unterbrechung (Breakpoint) nach dem letzten Programmbefehl das
Programm an dieser Stelle an.
Es gibt noch eine weitere Möglichkeit, indem man als letzten Befehl das Programm auf eine Eingabe warten
läßt. Dazu ist der Befehl getch(); aus der Bibliothek conio geeignet.
#include <iostream.h>
#include <conio.h>
// Variablendeklarationen ...
void main();
{
// Ein sinnvolles Hauptprogramm ...
getch();
}
3.2.2.4 Merkwürdiges Zahlenformat bei der Ausgabe
Fließkommazahlen werden manchmal mit einer unerwünscht hohen Genauigkeit oder in wissenschaftlicher
Notation (+1E+20 für 1020) angegeben. Auch die genaue Ausgabebreite liegt nicht fest, was bei Tabellen
wichtig ist. In C++ kann man mit folgenden Anweisungen die Anzahl der Vor- und Nachkommastellen genau
festlegen.
D:\75807564.doc vom 13.10.99
3-18
BBS III Mainz
cout.setf(ios::fixed); //
cout.width(20);
//
cout.precision(3);
//
cout << f1 << f2;
//
nämlich für f1, f2 wird
OOP für IT-Berufe mit C++
muß einmal am Anfang des Programmes stehen
die Gesamtbreite der Ausgabe wird bestimmt
Anzahl der Nachkommastellen
Vorsicht! Die Formatierung mit width() gilt genau einmal,
wieder mit variabler Gesamtbreite ausgegeben.
Die Anweisung cout.setf(ios::fixed); legt fest, daß das Programm bei Ausgaben auf den Bildschirm
nicht mit Fließkommadarstellung, sondern mit Festkommadarstellung arbeiten will. Deswegen legt dann die
Anweisung cout.precision(3); fest, mit wievielen festen Nachkommastellen gearbeitet werden soll. Diese beiden
Einstellungen haben ständige Gültigkeit, man kann damit am Anfang eines Programmes eine gewisse
Ausgabegenauigkeit festlegen.
Soll auch die Anzahl der Stellen vor dem Komma festliegen, muß mit cout.width(V+N+1); die
Ausgabebreite der gesamten Zahl gesetzt werden. V ist die Anzahl der gewünschten Vorkommastellen, N die
der Nachkommastellen. Leider muß die Ausgabebreite vor der Ausgabe jeder Variablen neu eingestellt werden.
3.2.2.5 Semantische Fehler
Im Gegensatz zu syntaktischen Fehlern können semantische Fehler nicht vom Compiler festgestellt werden, da
semantische Fehler durch einen falschen Sinn der Anweisungen entstehen, nicht durch feststellbar falsche
Schreibung.
summe=a-b;
// Eigentlich soll nicht die Differenz berechnet werden
cout << summe;
Dieses Programm wird übersetzt und ausgeführt, liefert aber wegen eines falschen Rechenzeichens ein falsches
Ergebnis. Es ist syntaktisch korrekt, aber semantisch falsch.
D:\75807564.doc vom 13.10.99
3-19
BBS III Mainz
OOP für IT-Berufe mit C++
3.3 Auswahl (Selektion)
In Abhängigkeit von einer Bedingung werden Anweisungen ausgeführt oder weggelassen. Diese Bedingungen
sind in der Regel an Operatoren wie > (größer als), < (kleiner als), == (gleich) und != (ungleich) geknüpft.
3.3.1 Einseitige Auswahl
Bei der einseitigen Auswahl wird eine Anweisung oder eine Gruppe von Anweisungen nur dann ausgeführt,
wenn eine bestimmte Bedingung erfüllt ist.
Die Syntax lautet in C++:
if (Bedingung) Anweisung;
Beispiel:
if (SparkontoGuthaben >= AbbuchungsBetrag)
SparkontoGuthaben -= AbbuchungsBetrag;
Sollen mehrere Anweisungen in Abhängigkeit der Bedingung ausgeführt werden, muß diese Anweisungsgruppe
mit {...} geklammert werden.
if (SparkontoGuthaben>=UmbuchungsBetrag)
{
SparkontoGuthaben -= UmbuchungsBetrag;
GirokontoGuthaben += UmbuchungsBetrag;
};
D:\75807564.doc vom 13.10.99
3-20
BBS III Mainz
OOP für IT-Berufe mit C++
Die angegebene Bedingung ist entweder wahr oder falsch, sie nimmt also die booleschen Werte true oder
false an. Boolesche Werte können mit den Operatoren „&&“ für UND bzw. „||“ für ODER verknüpft
werden.
a
b
a && b
false
false
false
false
true
false
true
false
false
true
true
true
a
b
a || b
false
false
false
false
true
true
true
false
true
true
true
true
a&&b ist also nur dann wahr, wenn sowohl a als auch b wahr sind. Bei a||b genügt es, wenn mindestens eines
der beiden a oder b wahr ist.
Vergleichsoperationen werden vorrangig vor boolschen Operationen behandelt. Welchen Datentyp hat im
folgenden Beispiel die Variable GuterKunde?
if (GirokontoGuthaben >= AbbuchungsBetrag ||
GirokontoGuthaben+Dispositionskredit >= Abbuchungsbetrag || GuterKunde)
GirokontoGuthaben -= AbbuchungsBetrag;
D:\75807564.doc vom 13.10.99
3-21
BBS III Mainz
OOP für IT-Berufe mit C++
Grundlegende Übungsaufgaben:
1. (Bestell.cpp) Eine Firma liefert bei einem Bestellwert ab 200,-DM porto- und verpackungsfrei. Für Aufträge
unter 200.-DM beträgt die Versandpauschale 5,50DM. Ein Programm soll den Rechnungsbetrag in
Abhängigkeit vom Bestellwert ausgeben.
Berechnung des Rechnungsbetrages
Bestellwert in DM: 150
Rechnungsbetrag: 155.5 DM
Programmende.
Berechnung des Rechnungsbetrages
Bestellwert in DM: 250
Rechnungsbetrag: 250 DM
Programmende.
2. (Dreieck.cpp) Ein Dreieck läßt sich aus den Seiten a, b und c konstruieren, wenn die Dreiecksungleichungen
a+b>c, a+c>b, b+c>a gelten. Schreiben Sie ein Programm, das bei einzugebenden Seiten überprüft, ob sich
das Dreieck konstruieren läßt.
Konstruierbarkeit eines Dreiecks pruefen
Seite a: 12.5
Seite b: 12.5
Seite c: 12.5
Das Dreieck ist konstruierbar.
Programmende.
Konstruierbarkeit eines Dreiecks pruefen
Seite a: 12.5
Seite b: 21
Seite c: 7.5
Das Dreieck ist nicht konstruierbar.
Programmende.
Zusätzliche Aufgaben:
1. (LinGl.cpp) Die Gleichung ax+b=0 soll für einzulesende Werte a und b vom Computer gelöst werden.
Denken Sie daran, daß sowohl a als auch b den Wert 0 annehmen können.
D:\75807564.doc vom 13.10.99
3-22
BBS III Mainz
OOP für IT-Berufe mit C++
3.3.2 Probleme und deren Lösung
In diesem Abschnitt sollen Sie auf ein paar typische Fehler im Zusammenhang mit der einfachen Auswahl
aufmerksam werden.
3.3.2.1 Strichpunkt nach if (semantischer Fehler)
Die Auswirkung dieses Fehlers ist meist, daß Programmteile immer ausgeführt werden, obwohl sie nur unter
bestimmten Bedingungen ausgeführt werden sollen. Der „bedingte Teil“ des untenstehenden
Programmfragmentes wird immer ausgeführt, weil für den Compiler die bedingte Anweisung die leere
Anweisung vor dem Strichpunkt ist. Der Block in {} wird daher ohne Bedingung ausgeführt.
if (D<0);
{
D=-D; // Vorzeichen umdrehen
cout << "Vorzeichen umgedreht." << endl;
}
3.3.2.2 Vergessene geschweifte Klammern (semantischer Fehler)
Die Auswirkung dieses Fehlers ist meist, daß Programmteile immer ausgeführt werden, obwohl sie nur unter
bestimmten Bedingungen ausgeführt werden sollen. Der „bedingte Teil“ des untenstehenden
Programmfragmentes soll der eingerückte Teil sein. Da der Compiler Einrückungen aber nicht beachtet, wird
nur die erste Anweisung nach dem if() bedingt ausgeführt, die Ausgabeanweisung dagegen immer.
if (D<0)
D=-D; // Vorzeichen umdrehen
cout << "Vorzeichen umgedreht." << endl;

Richtig wäre:
if (D<0)
{
D=-D; // Vorzeichen umdrehen
cout << "Vorzeichen umgedreht." << endl;
}
3.3.2.3 Zuweisung statt Vergleich (semantischer Fehler)
Verwechselt man den Vergleichsoperator == mit dem Zuweisungsoperator =, erhält man vom Compiler eine
Warnung: „Possibly incorrect assignment.“ In der folgenden Zeile ist der Bedingungsausdruck niemals wahr,
obwohl nach Ausführen der Anweisung garantiert D=0 ist; die Ausgabe wird nie getätigt.
if (D=0) cout << "D ist 0!" << endl;
D=0 ist eine Zuweisung an D, wonach D den Wert 0 erhält. Der Wert des gesamten Ausdrucks ist auch 0, was
logisch „falsch“ entspricht. Damit wird die Ausgabeanweisung nie ausgeführt. richtig wäre hier:
if (D==0) cout << "D ist 0!" << endl;
D:\75807564.doc vom 13.10.99
3-23
BBS III Mainz
OOP für IT-Berufe mit C++
3.3.3 Zweiseitige Auswahl
Bei der zweiseitigen Auswahl wird in Abhängigkeit davon, ob eine Bedingung erfüllt ist oder nicht, jeweils eine
bestimmte Anweisung oder Anweisungsgruppe ausgeführt.
Die Syntax lautet in C++:
if (Bedingung) Anweisung1; else Anweisung2;
Man kann die zweiseitige Auswahl also als eine erweiterung der einseitigen Auswahl um eine Alternative
betrachten. Beachten Sie: die else-Anweisung bezieht sich immer auf das letzte, nicht durch else abgeschlossene
if im gleichen Block.
Beispiel:
if (SparkontoGuthaben >= AbbuchungsBetrag)
SparkontoGuthaben -= AbbuchungsBetrag;
else
AbbuchungsBetrag=0;
Sollen mehrere Anweisungen in Abhängigkeit der Bedingung ausgeführt werden, muß diese Anweisungsgruppe
wieder mit {...} geklammert werden.
if (SparkontoGuthaben >= UmbuchungsBetrag)
{
SparkontoGuthaben -= UmbuchungsBetrag;
GirokontoGuthaben += UmbuchungsBetrag;
}
else
{
UmbuchungsBetrag=SparkontoGuthaben;
SparkontoGuthaben=0;
GirokontoGuthaben+=UmbuchungsBetrag;
}
D:\75807564.doc vom 13.10.99
3-24
BBS III Mainz
OOP für IT-Berufe mit C++
Zweiseite Auswahl mit Bedingungsoperator:
Falls es nur darum geht, eine bedingte Zuweisung zu treffen, kann man eine ganz rasche zweiseite Auswahl mit
dem Bedingungsoperator „?“ treffen:
Ergebnis = Bedingung ? AusdruckBeiWahr : AusdruckBeiFalsch;
Zuerst wird die Bedingung ausgewertet. Ist sie wahr, dann ist das Ergebnis gleich AusdruckBeiWahr, ansonsten
AusdruckBeiFalsch.
Beispiel:
int Max;
Max = a>b ? a:b; // if (a>b) Max = a; else Max = b;
Im Struktogramm muß man dies ausführlich darstellen.
D:\75807564.doc vom 13.10.99
3-25
BBS III Mainz
OOP für IT-Berufe mit C++
Grundlegende Übungsaufgaben
1. (BenzinV2.cpp) Schreiben Sie ein Programm, das nach Eingabe der gefahrenen Kilometer und der Menge
des verbrauchten Kraftstoffes den Durchschnittsverbrauch auf 100km berechnet und ausgibt. Die
Berechnung darf aber nur erfolgen, wenn die gefahrenen Kilometer ungleich null sind, ansonsten soll eine
(sinnvolle) Fehlermeldung ausgegeben werden.
Berechnung des Durchschnittsverbrauchs
verbrauchter Kraftstoff in l: 32.3
gefahrene Strecke in km: 0
Ungueltige Strecke eingegeben.
Programmende.
2. (Bestell2.cpp) Eine Firma liefert bei einem Bestellwert ab 200,-DM porto- und verpackungsfrei. Für
Aufträge unter 200.-DM beträgt die Versandpauschale 5,50DM. Ein Programm soll den Rechnungsbetrag in
Abhängigkeit vom Bestellwert ausgeben.
Berechnung des Rechnungsbetrages
Bestellwert in DM: 150
Rechnungsbetrag: 155.5 DM
Programmende.
Berechnung des Rechnungsbetrages
Bestellwert in DM: 250
Rechnungsbetrag: 250 DM
Programmende.
3. (Bestell3.cpp) Die Elektronikfirma Kleinkram erhebt für Bestellungen unter 100,-DM einen Porto- und
Verpackungsanteil von 5,50DM, von 100,-DM bis 200,-DM einen Betrag von 3,-DM, ab 200,-DM werden
keine Spesen berechnet. Ein Programm soll bei gegebener Auftragssumme den Rechnungsbetrag ausgeben.
Berechnung des Rechnungsbetrages
Auftragssumme in DM: 150
Rechnungsbetrag: 153.00 DM
Programmende.
4. (Widerstand.cpp) Wahlweise soll der Gesamtwiderstand zweier parallel oder in Reihe geschalteter
Widerstände nach der Eingabe der beiden Widerstandswerte berechnet werden.
Bei Parallelschaltung: Rg 
R1  R2
R1  R2
Widerstands-Schaltung
(P)arallel- oder (R)eihenschaltung? P
R1 in Ohm: 12000
R2 in Ohm: 10000
Der Widerstand der Parallelschaltung ist 5454.545 Ohm
Programmende
D:\75807564.doc vom 13.10.99
3-26
BBS III Mainz
OOP für IT-Berufe mit C++
5. (KinderG.cpp) Eine Familie in Kautschukistan erhält nach folgender einkommensabhängiger Tabelle
Kindergeld. Ein Programm ist gesucht, das nach Eingabe der Kinderzahl und des Einkommens das zu
zahlende Kindergeld berechnet.
Einkommen
< 45.000,- Talente
ab 45.000,- Talente
für das 1. Kind
70 Talente
70 Talente
für das 2. Kind
130 Talente
70 Talente
für das 3. Kind
220 Talente
140 Talente
ab dem 4. Kind
240 Talente
140 Talente
Zusätzliche Aufgaben
1. (Geraden.cpp) Der Schnittpunkt von 2 Geraden ist gesucht. Gerade 1 wird durch die Punkte A(ax,ay) und
B(bx,by), Gerade 2 durch die Punkte C(cx,cy) und D(dx,dy) angegeben. Denken Sie daran, daß die Geraden
auch parallel sein können, dies muß besonders beachtet werden.
Es gilt:
(bx - ax) y = (by - ay) x + (ay bx - ax by) und (dx - cx) y = (dy - cy) x + (cy dx - cx dy)
Die Geraden sind parallel, wenn gilt: (by - ay) (dx - cx) - (dy - cy) (bx - ax) = 0
D:\75807564.doc vom 13.10.99
3-27
BBS III Mainz
OOP für IT-Berufe mit C++
3.3.4 Mehrseitige Auswahl
Es gibt zwei Formen der mehrseitigen Auswahl, die sich darin unterscheiden, wie man sie programmieren kann.
Die erste Art ist sehr speziell und beruht darauf, daß die einzelnen Fälle sich durch feste Einzelwerte bestimmen
lassen.
Die mehrseitige Auswahl stützt ihre Wahl auf einen abzählbaren Datentyp, den Selektor, der mehr als zwei
Werte annehmen kann, z.B. int oder char. In Abhängigkeit des aktuellen Wertes wird dann eine von vielen
Möglichkeiten ausgewählt und ausgeführt.
Die Syntax lautet in C++:
switch(c)
{
case m1:
case m2:
case m3:
case m4:
case m5:
case m6:
default:
}
Anweisung1; break;
Anweisung2; break;
Anweisung34; break;
Anweisung56; break;
AnweisungS;
Der default-Teil kann auch weggelassen werden.
Beispiel:
switch (wochentag)
{
case Montag: case Dienstag:
case Mittwoch: case Freitag:
case Donnerstag:
default:
}
D:\75807564.doc vom 13.10.99
cout << "Werktag";
break;
cout << "Langer Werktag"; break;
cout << "Wochenende";
3-28
BBS III Mainz
OOP für IT-Berufe mit C++
Auch mehrere Anweisungen in Abhängigkeit der Bedingung sind möglich, dabei kommt es aber auf das
Beenden der Anweisungsfolge mit break an, die Anweisungen müssen nicht geklammert werden.
switch (wochentag)
{
case Montag: case Dienstag:
case Mittwoch: case Freitag:
case Donnerstag:
default:
cout << "Werktag";
Werktag=true;
break;
cout << "Langer Werktag"
Werktag=true;
break;
cout << "Wochenende";
Werktag=false;
}
D:\75807564.doc vom 13.10.99
3-29
BBS III Mainz
OOP für IT-Berufe mit C++
Falls der Selektor kein abzählbarer Datentyp ist oder die Bedingungen für die einzelnen Fälle komplizierter
sind, muß auf eine verschachtelte zweiseitige Auswahl zurückgegriffen werden. Dabei taucht die breakAnweisung aber nicht mehr auf, sondern mehrere Anweisungen werden wie bei der zweiseitigen Auswahl mit
geschweiften Klammern geklammert.
// a ist vom Typ float und damit nicht abzaehlbar
if
(a<1.7) b=1;
else if (a<2.5) { b=2; cout << "Fall 2" << endl; }
else if (a<4.0) b=3;
else
b=4;
// komplexe Bedingungen
if
(a<5 && c>7) b=1;
else if (a<5 && c<=7) b=2;
else if (a>=5 && c>7) { b=3; cout << "Fall 3" << endl; }
else
b=4;
Merke: Bei verschachtelter zweiseitiger Auswahl gehört das letzte else immer zum direkt vohergehenden if.
if (Bedingung1) Anweis1; else if (Bedingung2) Anweis2; else Anweis3;
// ist das gleiche wie:
if (Bedingung1) Anweis1; else { if (Bedingung2) Anweis2; else Anweis3; }
D:\75807564.doc vom 13.10.99
3-30
BBS III Mainz
OOP für IT-Berufe mit C++
Grundlegende Übungsaufgaben
I.
(MonName.cpp) Schreiben Sie ein Programm, das die Monatsnamen „Januar“ bis „Dezember“ ausgibt,
wenn eine der Zahlen von 1 bis 12 eingegeben wird.
Ausgabe des Monatsnamens
Nummer des Monats (1..12): 4
Der 4. Monat heisst April.
Programmende.
Ausgabe des Monatsnamens
Nummer des Monats (1..12): 15
Es gibt nur 12 Monate!
Programmende.
2. (Brief.cpp) Briefe werden nach ihrem Gewicht frankiert. Es gelten folgende Portobeträge:
bis (g)
20
50
100
250
500
1000
Talente
1,-
1,70
2,40
3,20
4,00
4,80
Sendungen
schwerer als 1kg werden als Päckchen oder Paket verschickt. Schreiben Sie ein Programm, das nach Eingabe
des Gewichtes das Porto ausgibt.
Portoberechnung
Gewicht des Briefes in g: 75.6
Kosten: 2.40 Talente
Programmende
Portoberechnung
Gewicht des Briefes in g: 1500
Das wird ein Paket!
Programmende
Zusätzliche Aufgaben
1. (QuadGl.cpp) Die quadratische Gleichung ax2 + bx + c = 0 ist zu lösen. Denken Sie daran, daß die Lösungen
imaginär sein können. Hinweis: Wurzeln werden in C++ mit der Funktion sqrt(x) berechnet, die in der
Headerdatei math.h deklariert wird.
2. (Volumen.cpp) Es sollen die Volumina und Oberflächen von Zylinder, Würfel, Quader, Kreiskegel oder
Kugel berechnet werden. Erstellen Sie ein Programm, das die Eingabe der Körperform und der notwendigen
Maße erlaubt und danach die Werte berechnet. Hinweis: Die Kreiszahl Pi ist in C++ in der Headerdatei
math.h als Konstante M_PI vereinbart.
D:\75807564.doc vom 13.10.99
3-31
BBS III Mainz
OOP für IT-Berufe mit C++
I.
Arbeiten Sie die Programmstrukturen hinsichtlich ihrer
Darstellungsmöglichkeiten (=Struktogramm DIN 66 261 oder
Programmablaufplan DIN 66 001)
anhand des Handouts PROGRAMMSTRUKTUREN durch.
II.
Erstellen Sie für jede Programmstruktur (A bis F) 3 Beispielaufgaben:
A. 3 Beispiele für eine Folgestruktur
B. 3 Beispiele für einseitige Auswahlstruktur
C. 3 Beispiele für zweiseitige Auswahlstruktur
D. 3 Beispiele für Mehrfachauswahl (=verschachtelte zweiseitige
Auswahlstruktur)
Datentyp muß nicht abzählbar sein
E. 3 Beispiele für Fallabfrage (=mehrseitige Auswahl)
Datentyp muß abzählbar sein
F. 3 Beispiele für jede Wiederholungsstruktur (Schleifen)
3 Beispiele für Repetition (kopfgesteuerte Schleife = WHILE .......)
3 Beispiele für Schleife mit Endabfrage
(fußgesteuerte Schleife = DO ......WHILE)
3 Beispiele für Zählschleife (zählergesteuerte Schleife = FOR ......)
D:\75807564.doc vom 13.10.99
3-32
BBS III Mainz
OOP für IT-Berufe mit C++
Leichte Aufgaben der Programmstrukturen A bis E
Erstellen Sie für jede Aufgabe ein Struktogramm und ermitteln Sie die Programmstruktur.
Codieren Sie die Programme .
1.
Erstellen Sie ein Programm, das 10% vom Umsatz eines Vertreters berechnet.
(A)
2.
Ein Vertreter erhält nur dann 10 % Provision von seinem Umsatz, wenn dieser größer als 10000 DM ist
(B)
3.
Ein Vertreter erhält bei einem Umsatz bis einschließlich 10000 DM 5 % Provision vom Umsatz. Ist der
Umsatz häher, erhält er 10 %
(C)
4.
4 Zahlen sollen eingegeben werden. Das Programm soll ermitteln, ob alle Zahlen gleich, mindestens zwei
Zahlen gleich bzw. alle vier Zahlen verschieden sind.
(B)
5.
Einem Auskunftsprogramm namens Rabatt liegen folgende Konditionen zur Gewährung von
Mengenrabatt zugrunde: 2% für Mengen unter 100 Stück, 10% bei Abnahme zwischen 100 und 200 Stück
und 12% bei mehr als 200 Stück.
(D)
6.
Wie 5. aber zur Unterscheidung der drei Fälle soll eine Fallabfrage erfolgen.
(D+E)
7.
Bei der Eingabe der Noten 1 bis 6 sollen die Kommentare „prima“ (bei den Noten 1 und 2), „Akzeptable
Leistung“ ( bei Note 3), „Leider nicht so gut (bei den Noten 4 und 5) sowie „Schweigen ist Gold!“ (bei
Note 6) ausgegeben werden.
(E)
8.
Auf die Frage „Der wievielte Wochentag ist heute?“ soll der zugrhörige Wochentag (z.B. „Dienstag“)
ausgegeben werden. Die Woche soll mit dem Montag = 1 beginnen.
(E)
9.
Zwei Spieler geben unabhängig voneinander gleichzeitig je eine nicht negative ganze Zahl an (etwa durch
Ausstrecken von Fingern auf Kommando oder durch verdecktes Aufschreiben). Nennen beide Spieler die
gleiche Zahl, so endet das Spiel unentschieden; andernfalls gewinnt, falls die Summe der genannten
Zahlen gerade ist, der Spieler, der die kleinere Zahl genannt hat, und sonst (falls also die Summe ungerade
ist) derjenige, der die größere Zahl genannt hat.
(D)
D:\75807564.doc vom 13.10.99
3-33
BBS III Mainz
10.
OOP für IT-Berufe mit C++
Beim Verkauf von Waren muß der Endverbraucher 16 % bzw. 7 % Umsatzsteuer tragen. Schreiben Sie
ein Programm, das es ermöglicht, die Umsatzsteuer und den Bruttopreis je nach Steuersatz (16 bzw. 7 %)
zu errechnen und auszugeben, wenn man den Nettobetrag eingibt und die jeweilige Umsatzsteuerhöhe
angibt. Es soll gelten:
Umsatzsteuerziffer 1 bedeutet 7 %
Nur Eingabetaste bedeutet 16 %
(D)
11.
12.
Die Mitarbeiter eines Betriebes erhalten einen Stundenlohn von 18,50 DM. Werden im Monat mehr als
170 Stunden gearbeitet, wird für die Mehrarbeitszeit ein Überstundenzuschlag von 20 % gezahlt.
(C)
Es sollen Noteneingaben (IHK-Prüfungen) nach untenstehendem Punkteschlüssel übertragen werden. Wie
lautet das Programm mit CASE, wenn folgende Punktetabelle zugrundeliegt:
Punkte
0 - 29
30 - 49
50 - 66
67 - 82
83 - 92
93 - 100
Note
ungenügend
mangelhaft
ausreichend
befriedigend
gut
sehr gut
Bildschirmausgabe:
Geben Sie die Punkte ein: ______
Die Prüfung wurde mit der Note ________
abgeschlossen.
13.
(E)
In einer Autovermietung werden – unter anderem – die Mietkosten anhand der gefahrenen km
berechnet. Dabei wird wie folgt vorgegangen:
1. die ersten 200 km werden nicht berechnet,
2. für die nächsten 800 km werden 0,65 DM je km berechnet,
3. Darüber hinausgehende kmm werden mit 0,40 DM je km berechnet
Schreiben Sie ein Programm, dass den km-Stand vor Abfahrt und den nach Rückkehr einliest und aus der
Differenz nach den obigen Vorgaben die km-Kosten berechnet
(D)
14.
RATESPIEL
Analysieren Sie das komplexe Struktogramm RATESPIEL hinsichtlich seiner Strukturen.
D:\75807564.doc vom 13.10.99
3-34
BBS III Mainz
OOP für IT-Berufe mit C++
3.3.5 Probleme und deren Lösung
Bei Verwendung der switch-Anweisung werden einige spezielle Fehler gerne gemacht. Auch bei
verschachtelten if()-else()-Konstruktionen wird oft unbeabsichtigtes programmiert.
3.3.5.1 Selektor nicht abzählbar (Syntaxfehler)
Tritt folgende Fehlermeldung beim Übersetzen auf: „Switch selection expression must be of integral type.“,
wurde versucht, in der Switch-Anweisung einen nicht abzählbaren Datentypen zu verwenden, wie z.B. float.
float i;
cin >> i;
switch (i)
{
case 1:...
case 2:...
...
}
Verwenden Sie als Typen in der switch-Anweisung nur char, short, int oder long (unsigned oder signed) oder
selbstdefinierte enum-Typen.
Abhilfe 1 (Datentyp ändern):
int i;
cin >>
switch
{
case
case
...
}
i;
(i)
1:...
2:...
Abhilfe 2 (kein switch verwenden):
float i;
cin >> i;
if
(i>0.5 && i<=1.5) {...}
else if (i>1.5 && i<=2.5) {...}
else ...
3.3.5.2 Vergessene break-Anweisung (semantischer Fehler)
Dieser Fehler äußert sich darin, daß plötzlich die Aktionen mehrerer switch-Fälle nacheinander ausgeführt
werden.
int i;
cin >> i;
switch (i)
{
case 1: cout
case 2: cout
case 3: cout
default: cout
}
cout << endl;
<<
<<
<<
<<
"Eins ";
"Zwei ";
"Drei ";
"was anderes";
Bei der Eingabe von 4, 5, 6 usw. wird korrekt „was anderes“ ausgegeben. Bei der Eingabe 1 dagegen wird „Eins
Zwei Drei was anderes“ ausgegeben, weil hinter der Ausgabeanweisung cout << "Eins "; kein
abschließendes break; folgt, ebenso hinter den folgenden Ausgabeanweisungen. Abhilfe:
D:\75807564.doc vom 13.10.99
3-35
BBS III Mainz
int i;
cin >> i;
switch (i)
{
case 1: cout
case 2: cout
case 3: cout
default: cout
}
cout << endl;
OOP für IT-Berufe mit C++
<<
<<
<<
<<
"Eins "; break;
"Zwei "; break;
"Drei "; break;
"was anderes";
3.3.5.3 Falsche if()-else()-Konstruktionen (semantischer Fehler)
Diese äußern sich in wirren, unbeabsichtigten Reaktionen des Programmes, genauer lassen sich die Folgen
dieses Fehlers leider nicht beschreiben. Beachten Sie im folgenden Programmfragment die Einrückungen, die
symbolisieren, was der Programmierer programmieren will.
if (D>0)
if (E>0) cout << "D und E sind beide positiv.";
else
if (E<=0) cout << "D und E sind beide nicht positiv.";
Was der Programmierer tatsächlich programmiert hat, ist aber folgendes:
if (D>0)
if (E>0) cout << "D und E sind beide positiv.";
else
if (E<=0) cout << "D und E sind beide nicht positiv.";
Begründung: Eine else-Anweisung bezieht sich immer auf das zuletzt noch nicht durch else abgeschlossene if.
Abhilfe:
if (D>0)
{
if (E>0) cout << "D und E sind beide positiv.";
}
else
{ // diese geschweifte Klammer ist nicht notwendig, aber konsequent
if (E<=0) cout << "D und E sind beide nicht positiv.";
} // diese geschweifte Klammer ist nicht notwendig, aber konsequent
Dadurch muß die else-Anweisung sich auf die im Block vorhergehende if-Anweisung beziehen.
D:\75807564.doc vom 13.10.99
3-36
BBS III Mainz
OOP für IT-Berufe mit C++
3.4 Wiederholung (Repetition)
In Abhängigkeit einer Bedingung werden Anweisungen wiederholt. Dabei ist zu unterscheiden zwischen
Bedingungen, die bereits am Schleifenanfang abgefragt werden und solchen, die erst beim Beenden der Schleife
geprüft werden.
3.4.1 Schleife mit Anfangsabfrage
Bei der Schleife mit Anfangsabfrage, auch kopfgesteuerte Schleife genannt, kann es vorkommen, daß der
Schleifenkörper nie durchlaufen wird, weil die Schleifenbedingung schon zu Anfang nicht zutrifft. Solange die
Bedingung wahr ist, wird die Schleife durchlaufen.
Die Syntax in C++ lautet:
while (Bedingung) Anweisung;
Beispiel:
cout << "Ja oder Nein? ";
cin >> c;
while (c!='j' && c!='n')
{
cout << (char)7; // (char)7=ASCII-Zeichen 7=BELL; erzeugt Warnton
cin >>c;
}
D:\75807564.doc vom 13.10.99
3-37
BBS III Mainz
OOP für IT-Berufe mit C++
Grundlegende Übungsaufgaben
1. (Primzahl.cpp) Lassen Sie prüfen, ob eine long-Zahl Primzahl ist. Dividieren Sie die zu prüfende Zahl durch
i=2, 3, usw. bis i2>x ist und prüfen Sie, ob die Division x/i einen Rest läßt.
2. Lassen Sie den Rechner die Summe der folgenden Zahlen bis zu einem Grenzwert g berechnen und geben
Sie die Anzahl der benötigten Glieder aus.
a) (SumA.cpp) 1 + 2 + 3 + 4 + ...+ n +...
b) (SumB.cpp) 1 - 2 + 3 - 4 + 5 ... + (2n+1) - 2n ...
c) (SumC.cpp) 1 + ½ + 1/3 + ...+ 1/n +...
d) (SumD.cpp) 1 - ½ + 1/3 ... + 1/(2n+1) - 1/(2n) ...
e) (SumE.cpp) 1 + 4 + 9 + 16 + 25 +...+ n2 +...
f) (SumF.cpp) 1 + 2 + 4 + 7 + 11 + 16 + ... + xn + (xn+n) + ...
Berechnung der Summe 1+2+3+4+... bis zu einem Grenzwert
Grenzwert: 16
Nach 6 Gliedern ist 16 erreicht. Die Summe ist 21.
Berechnung der Summe 1+2+3+4+... bis zu einem Grenzwert
Grenzwert: 28
Nach 7 Gliedern ist 28 erreicht. Die Summe ist 28.
Zusätzliche Übungsaufgaben
1. (ProdAdd.cpp) Das Produkt zweier ganzer Zahlen ist mit Hilfe der Addition zu berechnen. Beachten Sie, daß
die Zahlen auch 0 oder negativ sein können.
Berechnung des Produktes ueber Addition
Multiplikator: 3
Multiplikand : 5
Das Produkt ist 15.
Berechnung des Produktes ueber Addition
Multiplikator 1: 0
Multiplikator 2: 5
Das Produkt ist 0.
2. (QuotSub.cpp) Der ganzzahlige Quotient a/b zweier ganzer Zahlen a und b und der entstehende Rest sollen
über die Subtraktion berechnet werden. Überlegen Sie, was es hier für Fehlerquellen geben kann und
berücksichtigen Sie diese im Programm. Beispiel: 25 / 8 = 3 Rest 1
D:\75807564.doc vom 13.10.99
3-38
BBS III Mainz
OOP für IT-Berufe mit C++
3.4.2 Probleme und deren Lösung
Die hier geschilderten Probleme ähneln den bei der einfachen Auswahl aufgezeigten.
3.4.2.1 Strichpunkt nach while (semantischer Fehler)
Auswirkungen dieses Fehlers sind meist, daß
 der Schleifenkörper nur einmal ausgeführt wird, obwohl er öfter ausgeführt werden sollte.
 eine Endlosschleife entsteht, so daß es den Eindruck macht, das Programm „hänge“.
Der „Schleifenkörper“ des untenstehenden Programmfragmentes wird immer einmal ausgeführt, wenn ein
negatives e eingegeben wird, ansonsten entsteht eine Endlosschleife. Für den Compiler ist der Schleifenkörper
die leere Anweisung vor dem Strichpunkt. Der Block in {} wird daher nicht als Schleife, sondern als Sequenz
nach der Schleife ausgeführt. Bei negativem e wird dann die Schleife nie ausgeführt, weil sofort i>e ist. Bei
nichtnegativem e ist i<=e sofort erfüllt, und da sich weder i noch e in der leeren Schleife ändern, entsteht eine
Endlosschleife.
cin >> e;
i=0; s=0;
while (i<=e);
{
s+=i; // Summieren
cout << i++ << " " << endl;
}
3.4.2.2 Vergessene geschweifte Klammern (semantischer Fehler)
Auswirkungen dieses Fehlers sind meist, daß
 der Schleifenkörper nur einmal ausgeführt wird, obwohl er öfter ausgeführt werden sollte.
 eine Endlosschleife entsteht, so daß es den Eindruck macht, das Programm „hänge“.
Die Ausgabe des untenstehenden Programmfragmentes wird immer einmal ausgeführt, wenn ein negatives e
eingegeben wird, ansonsten entsteht eine Endlosschleife. Für den Compiler ist der Schleifenkörper die
Anweisung s+=i. Die Ausgabe wird daher nicht in der Schleife, sondern als Sequenz hinter der Schleife
ausgeführt. Bei negativem e wird dann die Schleife nie ausgeführt und stattdessen sofort die Ausgabe, weil
sofort i>e ist. Bei nichtnegativem e ist i<=e sofort erfüllt, und da sich weder i noch e in der Schleife ändern,
entsteht eine Endlosschleife.
cin >> e;
i=0; s=0;
while (i<=e);
s+=i; // Summieren
cout << i++ << " " << endl;

D:\75807564.doc vom 13.10.99
3-39
BBS III Mainz
OOP für IT-Berufe mit C++
3.4.3 Schleife mit Endabfrage
Die Schleife mit Endabfrage wird mindestens einmal durchlaufen, sie wird auch als fußgesteuerte Schleife
bezeichnet. Die Schleife wird solange wierholt, wie die Bedingung wahr ist.
Die Syntax in C++ lautet:
do Anweisung while Bedingung;
Beispiel:
cout << "Ja oder Nein? ";
do
{
cin >> c;
if (c!='J' && c!='j' && c!='N' && c!='n') cout << (char)7;
} while (c!='J' && c!='j' && c!='N' && c!='n');
D:\75807564.doc vom 13.10.99
3-40
BBS III Mainz
OOP für IT-Berufe mit C++
Grundlegende Übungsaufgaben
1. (Teiler.cpp) Lassen Sie alle Teiler einer einzulesenden ganzen Zahl ausgeben. Benutzen Sie auch hier eine
Division mit ganzzahligem Ergebnis. Beispiel: 8 hat die Teiler 1, 2, 4, 8.
2. (Bremsen.cpp) Bei einem Bremsvorgang wird ein Auto mit 4,3m/sec2 gebremst. Ein Programm soll die
Bremswege bis zu einem einzugebenden maximalen Bremsweg in Tabellenform ausgeben, wobei die
Geschwindigkeit von 5km/h beginnend in Schritten von 5km/h zu steigern ist. Gleichzeitig soll die
Bremszeit berechnet und ausgegeben werden.
(s = v2 / (2 a); t = v / a)
Zusätzliche Übungsaufgaben
1. (Suppe.cpp) Eine Schüssel Suppe kühlt pro Minute um 19% der Differenz aus Suppentemperatur und
Umgebungstemperatur ab. Es soll die Temperatur der Suppe nach jeder Minute angegeben werden, wenn
man als Anfangstemperatur 90oC und als Umgebungstemperatur 20oC annimmt. Schreiben Sie das
Programm so, daß Suppen- und Umgebungstemperatur eingegeben werden können.
2. (Zins.cpp) Ein Kapital k wird mit Zinseszins verzinst. Lassen Sie ein Tabelle ausgeben, die den Betrag
ausgibt, der am Ende jeden Jahres auf dem Konto steht. Der Zinsfuß p, das Kapital k und der zu erreichende
Kontostand bmax sollen einzugeben sein.
(b = k ( 1 + p / 100)n)
D:\75807564.doc vom 13.10.99
3-41
BBS III Mainz
OOP für IT-Berufe mit C++
3.4.4 Zählschleife
Die Zählschleife ist eine Struktur, bei der von Anfang an feststeht, wie viele Wiederholungen ausgeführt
werden. Dabei wird noch ein Zähler (genannt Laufvariable) mitgeführt. Im Beispiel ändert sich die Laufvariable
von Anfangswert bis Endwert.
Die Syntax in C++ lautet:
for (Laufvariable=Anfangswert; Laufvariable<=Endwert; Laufvariable++) Anweisung;
Beispiele:
// gibt 20 Sterne in einer Zeile aus
for (i=1;i<=20;++i) cout << '*'; cout << endl;
// addiert 5 einzugebende Werte auf
cout << "5 Werte eingeben." << endl;
summe=0;
for (z=1; z<=5; z++)
{
cout << z << "-ter Wert: ";
cin >> x;
summe+=x;
}
Jede Zählschleife kann durch eine Schleife mit Anfangsabfrage ersetzt werden:
for (Laufvariable=Anfangswert; Laufvariable<=Endwert; Laufvariable++)
Anweisung;
// ist das gleiche wie:
Laufvariable=Anfangswert;
while (Laufvariable<=Endwert)
{
Anweisung; Laufvariable++;
}
D:\75807564.doc vom 13.10.99
3-42
BBS III Mainz
OOP für IT-Berufe mit C++
3.4.5 Zählschleife mit variabler Schrittweite
Ersetzt man die Inkrementierung der Laufvariablen Laufvariable++ in den obigen Ausdrücken durch einen
anderen Inkrementierungsausdruck (z.B. Laufvariable+=5), dann ergibt sich eine Zählschleife mit variabler
Schrittweite.
Laufvariable=Anfangswert;
while (Laufvariable<=Endwert)
{
Anweisung; Laufvariable+=Weite;
}
// ist das gleiche wie:
for (Laufvariable=Anfangswert; Laufvariable<=Endwert; Laufvariable+=Weite)
Anweisung;
Die folgende Anweisung bildet z.B. eine Schleife mit der Schrittweite 3 und setzt die Laufvariable i
nacheinander auf die Werte 1, 4, 7, 10:
for (i=1; i<=10; i+=3)
cout << "Wert der Laufvariable: " << i << endl;
D:\75807564.doc vom 13.10.99
3-43
BBS III Mainz
OOP für IT-Berufe mit C++
Grundlegende Übungsaufgaben
1. (ProdAdd2.cpp) Das Produkt zweier ganzer Zahlen ist mit Hilfe der Addition zu berechnen. Beachten Sie,
daß die Zahlen auch 0 oder negativ sein können.
2. (Ungerade.cpp) Lassen Sie den Computer die ungerade Zahlen zwischen zwei einzugebenden Werten
ausdrucken.
3. (Rechnen.cpp) Es sollen die Quadratzahlen, die Kubikzahlen und die Kehrwerte der Zahlen zwischen zwei
einzugebenden Grenzen ausgegeben werden.
4. Schreiben Sie Programme, die nach Eingabe von n die folgenden Summen berechnen:
a) (SumNa.cpp) 12 + 22 + 32 + 42 + ... + n2 b) (SumNb.cpp) 12 + 32 + 52 + ... + (2 n - 1)2
c) (SumNc.cpp) 22 + 42 + 62 + ... + (2 n)2 d) (SumNd.cpp) 1 + 1 / 2 + 1 / 3 + ... + 1 / n
5. (Primzahl2.cpp) Verändern Sie Primzahl.cpp, so daß es alle Primzahlen ab 2 bis zu einer einzugebenden
Obergrenze berechnet.
Zusätzliche Übungsaufgaben
1. (N3und5.cpp) Der Rechner soll alle natürlichen Zahlen zwischen einzugebenden Grenzen ausgeben, die
sowohl durch 3 als auch durch 5 teilbar sind.
2. (N3oder5.cpp) Der Rechner soll alle natürlichen Zahlen zwischen einzugebenden Grenzen ausgeben, die
durch 3 oder durch 5 teilbar sind.
3. (N4und7.cpp) Der Rechner soll alle natürlichen Zahlen zwischen einzugebenden Grenzen ausgeben, die
sowohl durch 4 als auch durch 7 teilbar sind.
4. (N4oder7.cpp) Der Rechner soll alle natürlichen Zahlen zwischen einzugebenden Grenzen ausgeben, die
durch 4 oder durch 7 teilbar sind.
5. (Kapital.cpp) Ein Kapital k wird n Jahre lang mit Zinseszins verzinst. Lassen Sie ein Tabelle ausgeben, die
den Betrag ausgibt, der am Ende jeden Jahres auf dem Konto steht. Der Zinsfuß p, das Kapital k und die
Anzahl der Jahre n sollen einzugeben sein. (b = k ( 1 + p / 100)n)
6. (Reis.cpp) Der indische König Schehram verlangte, daß Sessa, der Erfinder des Schachspiels, sich eine
Belohnung wählen solle. Dieser erbat sich die Summe Weizenkörner, die sich ergibt, wenn für das 1. Feld
des Schachbrettes 1 Korn, für das 2. Feld 2 Körner für das 3. Feld 4 Körner usw. gerechnet werden.
Schreiben Sie ein Programm, das für alle 64 Felder die Gesamtsumme der Körner berechnet. Geben Sie auch
das Gewicht der Körner an, wenn 200 Körner 1g wiegen.
Wieviele Eisenbahnwaggons bräuchte man zum Transport des Reises und wie lange wäre der Zug, wenn ein
Eisenbahnwaggon 30t faßt und 15m lang ist?
D:\75807564.doc vom 13.10.99
3-44
BBS III Mainz
OOP für IT-Berufe mit C++
3.4.6 Anweisungen für Schleifenabbruch und -wiederholung
Zwei zusätzliche Anweisungen können in einigen Fällen die Übersicht bei der Programmierung von Schleifen
erhöhen, aber auch stark beeinträchtigen. Die Anweisungen sollten daher sparsam und nach reiflicher
Überlegung eingesetzt werden; bei Vertretern der „reinen Lehre“ der strukturierten Programmierung gelten
derartige Anweisungen als „unstrukturiert“.
 break;
Die Schleife wird sofort verlassen (beendet).
 continue;
Die Schleifenbedingung wird sofort erneut abgefragt.
Die Anweisungen break und continue können bei allen Schleifenformen eingesetzt werden. Für break existiert
eine eigene Darstellung im Struktogramm:
// Verwendung von break:
do
{
cout << "Ja oder Nein? ";
cin >> c;
if (c==’J’ || c==’j’ || c==’N’ || c==’n’) { break; }
cout << "Ungültige Eingabe!" << endl;
}
while (true);
D:\75807564.doc vom 13.10.99
3-45
BBS III Mainz
OOP für IT-Berufe mit C++
// Verwendung von continue:
bool ok;
do
{
cout << "Ja oder Nein? ";
cin >> c;
ok = (c!='J' && c!='j' && c!='N' && c!='n');
if (!ok) continue; // Dann nochmal Ja oder Nein abfragen
// Hier würde nun ein längerer Verarbeitungsteil folgen,
// der nur abgearbeitet wird, wenn Ja oder Nein eingegeben wird.
}
while (false);
Für die Verwendung der continue-Anweisung gibt es keine gesonderte Struktogrammdarstellung. Man kann
jedoch die folgende Ersetzung verwenden:
// Schleife mit continue
// entspricht Schleife mit Auswahl
while (Bedingung1) do
{
Anweisung1;
if (Bedingung2) continue;
Anweisung2;
Anweisung3; // usw.
}
while (Bedingung1) do
{
Anweisung1;
if (!Bedingung2)
{
Anweisung2;
Anweisung3; // usw.
}
}
Grundlegende Übungsaufgaben
Entwerfen Sie diese Programme jeweils ohne Verwendung von break oder continue und dann verändern Sie sie,
so daß break oder continue zum Einsatz kommen.
1. (AddBel.cpp) Schreiben Sie ein Programm, das eine beliebige Anzahl von einzugebenden Zahlen addiert, bis
die Zahl 0 eingegeben wird.
Addition von Zahlen
Nach der letzten Zahl 0 eingeben
1. Zahl: 15.2
2. Zahl: 17.1
3. Zahl: 20
4. Zahl: 0
Die Summe der 3 eingegebenen Zahlen ist 52.3.
Programmende.
2. (DivBel.cpp) Schreiben Sie ein Programm, das eine beliebige Anzahl von einzugebenden Zahlenpaaren
dividiert, bis als erste Zahl 0 eingegeben wird. Wird als zweite Zahl 0 eingegeben, soll die Eingabe
wiederholt werden.
Division von Zahlen
Nach der letzten Zahl 0 eingeben
1. Zahl: 15.2
2. Zahl: 17.1
15.2/17.1 ist 0.8888888888889.
1. Zahl: 20
2. Zahl: 0
Durch 0 kann nicht dividiert werden.
1. Zahl: 0
Programmende.
D:\75807564.doc vom 13.10.99
3-46
BBS III Mainz
OOP für IT-Berufe mit C++
3.5 Modul (Unterprogramm)
Wenn die zu bearbeitenden Algorithmen so groß werden, daß man sie durch ein
übersichtliches Struktogramm nicht mehr darstellen kann, muß man die
Aufgabenstellung modularisieren. Das bedeutet, ein Problem wird in Teilprobleme
zerlegt, die einzeln für sich gelöst werden.
Jede
strukturierte
Programmiersprache
besitzt
Sprachelemente
zur
Modularisierung. In C++ heißen die Module Funktionen. Bislang enthielten unsere
C++-Programme schon ein einziges Modul, nämlich das Hauptprogramm main().
Bereits an void main() erkennen wir, daß eine Funktion einen Typ hat, einen
Bezeichner und einen Anweisungsblock, der zwischen geschweiften Klammern
„{“ und „}“ eingeschlossen wird. Allgemein wird eine Funktion deklariert als
Rückgabetyp Funktionsname (Parameterliste)
Also z.B.
void
bool
int
float
Ausgabe(float f)
IstPrimzahl(int n)
Quadrat(int n)
Kegelvolumen(float r,h)
Der Rückgabetyp der Funktion main() war bislang void, das heißt, die Funktion main() gibt nach ihrem Ablauf
keinen Wert an das Betriebssystem zurück.
Als Rückgabetyp von Funktionen können beliebige Datentypen eingesetzt werden. Möchte man eine Funktion
ohne Rückgabetyp verwenden, benutzt man dafür das Schlüsselwort void:
void main() { /* Anweisungen */ } // Kein Rückgabewert an das
// Betriebssystem.
Diesen Spezialfall der typlosen Funktion bezeichnet man in anderen Programmiersprachen, z.B. Pascal, als
Prozedur. Wir werden in den nächsten Kapiteln zunächst nur auf Funktionen ohne Rückgabewert eingehen,
danach aber besprechen, wie Funktionen mit Rückgabewert programmmiert werden.
D:\75807564.doc vom 13.10.99
3-47
BBS III Mainz
OOP für IT-Berufe mit C++
3.5.1 Funktionen ohne Parameter
Module bieten den Vorteil, daß sie mehrmals in Programmen ausgeführt werden können, obwohl man sie nur
einmal deklarieren und definieren muß. Am folgenden Programmbeispiel MAXIMUM1.CPP wird dies
demonstriert. Die Funktion Max wird zweimal aufgerufen, um jeweils das Maximum von zwei Zahlen
anzugeben.
/* PROGRAMM Maximum1.cpp
Maximum von zwei Zahlen. Typlose Funktion ohne Parameter */
#include <iostream>
int a,b,x,y;
void Max() // Deklaration der Funktion
{
if (a > b) cout << "Maximum = " << a;
else cout << "Maximum = " << b;
cout << endl;
}
// Definition der Funktion
void main()
{
cout << "Zwei Zahlen: ";
cin >> a >> b;
Max();
// Erster Funktionsaufruf
cout << "Zwei andere Zahlen: ";
cin >> x >> y;
a = x; b = y;
Max();
// Zweiter Funktionsaufruf
cout << "Programmende.";
}
Zwei Zahlen: 1000 2000
Maximum = 2000
Zwei andere Zahlen: 5 9
Maximum = 9
Programmende.
Grundlegende Übungsaufgabe
Schreiben Sie das früher bereits behandelte Programm BENZINV.CPP zu BENZIN2.CPP um. Im
Hauptprogramm sollen nacheinander drei typlose Funktionen aufgerufen werden:
main() { /* ... */ Eingabe(); Verarbeitung(); Ausgabe(); /* ... */ }
D:\75807564.doc vom 13.10.99
3-48
BBS III Mainz
OOP für IT-Berufe mit C++
3.5.2 Funktionen mit Werteparametern
Das Programm MAXIMUM1 arbeitet an einer Stelle sehr umständlich: Da innerhalb der Funktionsdefinition
von Max() die Variablen a und b verglichen werden, müssen vor dem zweiten Funktionsaufruf die neuen
Variablen x und y explizit „von Hand“ auf a und b zugewiesen werden.
Hier wünscht man sich eine implizite Variablenzuweisung, die so arbeitet, daß in der Funktionsdefinition
„Platzhaltervariablen“ angegeben werden können, in die dann beim Funktionsaufruf automatisch die tatsächlich
benötigten Variablen eingesetzt werden.
Das folgende Programm MAXIMUM2 erledigt diese „automatische“ Wertübergabe mittels Werteparametern.
Die Parameter x und y werden innerhalb der Funktion Max() als „Platzhalter“ verarbeitet. Sie sind formale
Parameter. Beim Funktionsaufruf werden an die Stelle der formalen Parameter x und y die aktuellen Parameter
a und b bzw. c und d eingesetzt. Statt dem Begriff Parameter benutzt man auch die Bezeichung Argument.
/* PROGRAMM Maximum2.cpp
Maximum von zwei Zahlen. Typlose Funktion mit Werteparametern */
#include <iostream>
int a,b,c,d;
void Max(int x, int y) // Funktion mit formalen Werteparametern x und y
{
if (x > y) cout << "Maximum = " << x;
else cout << "Maximum = " << y;
cout << endl;
}
void main()
{
cout << "Zwei Zahlen: ";
cin >> a >> b;
Max(a,b);
// Erster Funktionsaufruf
cout << "Zwei andere Zahlen: ";
cin >> c >> d;
Max(c,d);
// Zweiter Funktionsaufruf
cout << "Programmende.";
}
Die Bildschirmausgabe von MAXIMUM2 ist identisch mit der von Maximum1.
Globale und lokale Variablen
Die Variablen a, b, c und d sind im globalen Programmteil als globale Variablen deklariert worden. Globale
Variablen sind in allen Modulen bekannt, die ein Programm verwendet. Die Eingabeparameter x und y jedoch
sind als lokale Variablen nur innerhalb der Funktion Max() gültig und bekannt.
D:\75807564.doc vom 13.10.99
3-49
BBS III Mainz
OOP für IT-Berufe mit C++
Werteparameter
Ein Werteparameter kann innerhalb einer Funktion zwar verändert werden, der geänderte Wert kann jedoch
nicht an den aktuellen Parameter des Hauptprogrammes zurückgegeben werden. Deshalb nennt man
Werteparameter auch Eingabeparameter. Sie werden in das Modul nur hineingegeben, das Modul kann sie
nicht (geändert) wieder ausgeben.
Grundlegende Übungsaufgabe
Schreiben Sie das Programm BENZINV.CPP um zu BENZIN3.CPP. Bei gleicher Bildschirmausgabe soll die
folgende Funktion aufgerufen werden - innerhalb der Funktion sollen keinerlei Tastatureingaben oder
Bildschirmausgaben getätigt werden:
void DVerbrauch(float Verbrauch, float Strecke);
D:\75807564.doc vom 13.10.99
3-50
BBS III Mainz
OOP für IT-Berufe mit C++
3.5.3 Funktionen mit Variablenparametern
In den bisherigen Programmbeispielen war innerhalb des Moduls Max() eine Bildschirmausgabe enthalten. Das
ist unschön, denn Programmodule sollen möglichst universell verwendbar sein. So wünscht man sich eine
Funktion Max(), die den Maximalwert nur an das aufrufende Programm zurückgibt, anstatt ihn sofort auf dem
Bildschirm auszugeben. Das aufrufende Programm kann dann den in der Funktion ermittelten Wert beliebig
verwerten - ihn z.B. in arithmetischen Ausdrücken weiter verrechnen oder mit einem speziellen Format selbst
auf dem Bildschirm ausgeben, usw.
Das Zurückgeben von Werten, die in einem Modul geändert wurden, an das rufende Programm kann man mit
Variablenparametern erreichen, auch Referenzparameter oder Ein-Ausgabeparameter genannt:
/* PROGRAMM Maximum3.cpp
Maximum von zwei Zahlen.
Typlose Funktion mit Wert- und Variablenparameter */
#include <iostream>
#include <stdlib>
int a,b,Maximum;
void Max(int x, int y, int& erg)
// Funktion mit Wertparametern x und y und Referenzparameter erg
{ erg = x > y ? x:y ; } // Kurzform von { if (x > y) erg = x; else erg = y; }
void main()
{
int i;
for (i=0;i<2;i++)
{
if (!i) cout << "Zwei Zahlen: ";
else cout << "Zwei andere Zahlen: ";
cin >> a >> b;
Max(a,b,Maximum);
cout << "Maximum = " << Maximum << endl;
}
cout << "Programmende.";
}
Man erkennt, daß ein Variablenparameter durch das Symbol & direkt hinter der Typbezeichnung
gekennzeichnet wird. Ein formaler Variablenparameter in einer Funktion zeigt immer auf die gleiche
Speicheradresse wie der aktuelle Parameter im rufenden Programm. Daher verändert sich der aktuelle
Parameter nach Ablauf der Funktion.
D:\75807564.doc vom 13.10.99
3-51
BBS III Mainz
OOP für IT-Berufe mit C++
Grundlegende Übungsaufgaben
1. Das folgende Programm Erhoehen.cpp ruft zweimal nacheinander eine Funktion namens Erhoehe auf.
Innerhalb der Funktion wird der übergebene Variablenparameter um 10 erhöht und das Ergebnis wird an das
Hauptprogramm zurückgegeben. Lassen Sie das Programm ablaufen und studieren Sie seine Wirkungsweise.
/* PROGRAMM Erhoehen.cpp
Erhoehen einer Zahl um 10.
Typlose Funktion mit Variablenparameter */
#include <iostream>
float a;
void Erhoehe(float& par)
{ par += 10; }
void main()
{
int i;
for (i=0;i<2;i++)
{
if (!i) cout << "Eine Zahl eingeben: ";
else cout << "Noch eine Zahl eingeben: ";
cin >> a;
Erhoehe(a);
cout << "Die um 10 erhoehte Zahl lautet " << a << endl;
}
cout << "Programmende.";
}
Eine Zahl eingeben: 3.4
Die um 10 erhöhte Zahl lautet 13.4
Noch eine Zahl eingeben: 5.6
Die um 10 erhöhte Zahl lautet 15.6
Programmende.
2. Machen Sie den Variablenparameter par in Erhoehen.cpp zum Werteparameter, lassen Sie das Programm
ablaufen und studieren Sie die Wirkung. Begründen Sie das Programmverhalten.
3. Schreiben Sie ein Programme TAUSCHE.CPP, in dem zwei Zahlen a und b getauscht werden. Verwenden
Sie darin die folgende Funktion. Auch hier sollen innerhalb der Funktion weder Tastatureingaben noch
Bildschirmausgaben stehen.
void Tausch(/* Zwei Argumente */);
D:\75807564.doc vom 13.10.99
3-52
BBS III Mainz
OOP für IT-Berufe mit C++
3.5.4 Funktionen mit Rückgabetyp
Eine Funktion in C++ kann beliebig viele Wertparameter und/oder Variablenparameter mit sich tragen. Sehr
oft hat einen Funktion jedoch den Zweck, genau einen einzigen, ganz bestimmten Wert zu berechnen. Dann gibt
man der Funktion einen Rückgabetyp, dadurch wird der zu berechnende Wert im Funktionsnamen
zurückgegeben. Diese Verwendung einer Funktion ist der Normalfall in C++:
/* PROGRAMM Maximum4.cpp
Maximum von zwei Zahlen mit Integerfunktion */
#include <iostream>
int a,b,Erg; // Erg kann auch eingespart werden, siehe unten
int Max(int x, int y) // Kein Variablenparameter mehr erforderlich
{ if (x > y) return x; else return y; }
void main()
{
for (int i=0;i<2;i++) // i gleichzeit deklariert und verwendet!
{
if (!i) cout << "Zwei Zahlen: ";
else cout << "Zwei andere Zahlen: ";
cin >> a >> b;
Erg = Max(a,b); cout << "Maximum = " << Erg << endl;
// noch kürzer ohne Erg statt der oberen Zeile:
// cout << "Maximum = " << Max(a,b) << endl;
}
cout << "Programmende.";
}
Man beachte die elegante Schreibweise, die mit typisierten Funktionen möglich ist: Da der Name der Funktion
sowohl das Unterprogramm als auch die zurückgegebene Variable repräsentiert, kann der Funktionsaufruf
syntaktisch genauso verwendet werden wie eine Variable. Die Anweisung return bewirkt Zweierlei:
 Der Rückgabewert der Funktion wird mit dem angegebenen Wert initialisiert.
 Die Funktion wird danach (sofort, ohne Ausführung eventuell in der Funktion folgender Befehle!) verlassen.
Wenn man Funktionen benötigt, die mehr als einen Rückgabewert an das rufende Programm erfordern, muß
man zusätzlich noch Variablenparameter verwenden.
D:\75807564.doc vom 13.10.99
3-53
BBS III Mainz
OOP für IT-Berufe mit C++
Grundlegende Übungsaufgabe
(PotFunk.cpp) Schreiben Sie ein Programm mit einer C++-Funktion, die die folgende mathematische Funktion
berechnet, wobei das Funktionsergebnis vom Typ float ist. Der Wert von x soll als Parameter an die Funktion
übergeben werden.
f(x) = 3x3 + x2 + 5x - 22
Eine Zahl eingeben: 1
Das Funktionsergebnis lautet -13
Noch eine Zahl eingeben: 2
Das Funktionsergebnis lautet 16
Programmende.
Verschachtelter Funktionsaufruf
Das nachfolgende Programm MAXIMUM5 demonstriert, wie man typisierte Funktionen geschachtelt aufruft.
Damit können komplexe Aufgabenstellungen mit kompaktem Programmcode erledigt werden:
/* PROGRAMM Maximum5.cpp
Geschachtelte Aufrufe einer Integerfunktion */
#include <iostream>
int a,b,c;
int Max(int x, int y)
{ if (x > y) return x; else return y; }
void main()
{ cout << "Drei Zahlen: "; cin >> a >> b >> c;
cout << "Maximum = " << Max(Max(a,b),c) << endl;
cout << "Programmende.";
}
Drei Zahlen: 4 8 6
Maximum = 8
Programmende.
D:\75807564.doc vom 13.10.99
3-54
BBS III Mainz
OOP für IT-Berufe mit C++
3.5.5 Sprachspezifische Besonderheiten bei Funktionen
Die im folgenden erläuterten Eigenschaften sind typisch für die Sprache C++. Bei anderen höheren
Programmiersprachen sind ähnliche Konzepte definiert.
3.5.5.1 Gültigkeitsbereich und Sichtbarkeit von Bezeichnern
An der Lösung von TAUSCHE.CPP ist zu ersehen, daß es außer globalen Variablen und (formalen) Parametern
auch noch echte lokale Variablen innerhalb von Funktionen gibt:
void Tausch( /* ... */)
{ int platz; // echte lokale Variable, ist nur in Funktion Tausch gültig
/* ... */
}
Das wirft die Frage auf, in welchen Programmteilen Variablen und andere Bezeichner gültig oder sichtbar sind.
Allgemein gilt:
 Beliebige Namen sind nur nach der Deklaration und innerhalb des Blocks (eingeschlossen durch { und } )
gültig, in dem sie deklariert wurden.
 Bezeichner im aktuellen Block verdecken Bezeichner in übergeordneten Blöcken gleichen Namens - diese
sind dann im aktuellen Block unsichtbar. In diesem Fall kann man globale Variablen trotzdem ansprechen,
indem man einen doppelten Doppelpunkt :: voranstellt
Beispiel:
/* Programm test.cpp */
include<iostream>
int n;
float a, b, i; // a und b sind global - überall im Programm gültig
double testfunc(int x, double& y)
{
char buchst;
/* x, y, buchst sind nur in diesem Block gültig */
y = a; // das geht!
c = x; // ergibt Compilerfehler, da c hier ungültig
}
void main()
{
int c; // nur innerhalb des main()-Blocks gültig
for (int i=0;i<5;i++) // i wird hier deklariert !!
{
// i ist nur innerhalb dieses Blockes gültig !!
// Das globale i wird hier verdeckt.
cout << ::i; // Damit ist das globale i trotzdem sichtbar
}
testfunc(n,b);
}
D:\75807564.doc vom 13.10.99
3-55
BBS III Mainz
OOP für IT-Berufe mit C++
3.5.5.2 Funktionsdefinition und Funktionsdeklaration
Wir unterscheiden in C++ Funktionsdeklaration und Funktionsdefinition. Eine Funktionsdeklaration erfordert
lediglich den Funktionsnamen, den Rückgabetyp sowie Typ und Anzahl der übergebenen Argumente - die
Namen der Argumente müssen bei einer Deklaration nicht angegeben werden! Eine Funktionsdeklaration wird
auch als Funktionsprototyp bezeichnet. Demgegenüber muß eine Funktionsdefinition zusätzlich noch die
Parameternamen und alle Anweisungen des Funktionsrumpfes enthalten.
Beispiel:
a) Deklaration (Prototyp):
void AddiereWas(int, int);
b) Definition:
void AddiereWas(int p, int q) { p += 10; q +=20; }
Zusätzliche Funktionsdeklarationen werden dann benötigt, wenn die Funktionsdefinition in einer anderen
Quelldatei steht als der Funktionsaufruf. Unter Umständen kann der eigentliche Funktionsrumpf bereits
compiliert in einer Bibliothek vorliegen. Dann macht man in der Quelldatei, die den Funktionsaufruf enthält,
dem Compiler die Funktion lediglich durch Deklaration bekannt.
extern void AddiereWas(int,int);
// Funktion in anderer Quelldatei definiert
Bei größeren Programmprojekten ist es üblich, alle benötigten Funktionsdeklarationen in sog. Header-Dateien
(*.h) aufzulisten. Die Header.Dateien werden dann in allen benötigen cpp-Quelldateien mit der Direktive
#include eingebunden.
3.5.5.3 Inline-Funktionen
Eine Funktion kann als inline definiert werden, z.B. mit
inline void AddiereWas(int p, int q);
In diesem Fall versucht der Compiler, den gesamten Objektcode der Funktion bei jedem Funktionsaufruf immer
wieder in die ablauffähige Datei einzusetzen. Das Zielprogramm wird dadurch größer, der Programmablauf
wird jedoch schneller.
3.5.5.4 Initialisierung von lokalen Variablen, statische lokale Variablen
Eine lokale Variable wird initialisiert (mit einem Anfangswert belegt), wenn der Programmablauf die
Variablendefinition erreicht. Standardmäßig passiert das bei jedem Aufruf der Funktion. Jeder Funktionsaufruf
verwaltet seine eigene Kopien von Wertparametern und lokalen Variablen. Demgegenüber ist zu beachten:
Lokale, nichtstatische Variablen und dynamische Datenstrukturen (auf dem sog. Heap, werden in späteren
Kapiteln noch besprochen) werden nicht mit Nullwert initialisiert, wenn sie nicht definiert werden - sie bleiben
dann undefiniert.
Man kann eine lokale Variable jedoch auch als static deklarieren. Dann wird im gesamten Programm bei allen
Funktionsaufrufen ein einziges statisches Speicherobjekt für diese Variable benutzt. In diesem Fall wird die
lokale Variable nur beim erstmaligen Erreichen der Variablendefinition initialisiert. Eine statische lokale
Variable erzeugt für eine Funktion ein „Gedächtnis“, ohne daß man eine globale Variable verwenden muß, die
von anderen Funktionen benutzt und verfälscht werden könnte.
/* Programm StatBsp.cpp
Demonstration von statischen lokalen Variablen */
#include <iostream>
void staticfkt(int a)
{
while (a--)
{ static int n = 0; // Variable einmal mit 0 initialisert
int x = 0;
// bei jedem Funktionseintritt initialisiert
cout << " n == " << n++ << ", x == " << x++ << endl;
D:\75807564.doc vom 13.10.99
3-56
BBS III Mainz
}
}
OOP für IT-Berufe mit C++
int main()
{ staticfkt(3);
}
n == 0, x == 0
n == 1, x == 0
n == 2, x == 0
3.5.5.5 Konstante Wert- und Referenzparameter
Es kommt öfter vor, daß bei Funktionen Parameter übergeben werden, bei denen der Ersteller der Funktion
schon von vornherein weiß, daß die Parameterwerte innerhalb der Funktion nicht geändert werden dürfen. In
solchen Fällen werden die Parameter als konstant deklariert. Wenn die Werte von konstanten Parametern
innerhalb von Funktionen geändert werden, kann das Programm nicht kompiliert werden - der Compiler liefert
eine entsprechende Fehlermeldung.
char Grossbuchstabe(const char buchst); // Konstanter Wertparameter
{ /* ... Hier darf die Variable buchst nicht geändert werden */ }
char Grossbuchstabe(const char& buchst); // Konstanter Variablenparameter
{ /* .. Hier gilt das Gleiche. Damit stellt man sicher, daß sowohl der formale Parameter
buchst als auch der jeweilige aktuelle Parameter beim Funktionsaufruf ihren Wert
behalten. }
Auf den ersten Blick erscheint es sinnlos, konstante Variablenparameter zu definieren. Normalerweise ist es ja
gerade die Aufgabe eines Variablenparameters, Werte geändert aus einer Funktion zurückzugeben. Man muß
jedoch bedenken, daß beim Funktionsaufruf für Wertparameter grundsätzlich eine lokale Kopie der
Parametervariablen erzeugt wird. Das kostet Speicherplatz und Rechenzeit. Bei Variablenparametern wird der
Funktion lediglich ein Verweis auf den aktuellen Parameter übergeben (auch Zeiger oder Adresse geannt - siehe
spätere Kapitel), das ist ressourcenschonender und schneller. Konstane Variablenparameter verwendet man
also, wenn man Ressourcen und Rechenzeit sparen und trotzdem sicherstellen will, daß sich ein Parameter nicht
ändern kann.
D:\75807564.doc vom 13.10.99
3-57
BBS III Mainz
OOP für IT-Berufe mit C++
Grundlegende Übungsaufgabe:
Kopieren Sie Progamm Maximum3.cpp nach Maximum6.cpp. Machen Sie in der Funktion Max von
Maximum6.cpp den Variablenparameter erg konstant. Kompilieren Sie das Programm und beobachten Sie die
Reaktion des Compilers. Kompilieren Sie nochmals, nachdem Sie den Wertparameter x ebenfalls konstant
gemacht haben und ihn innerhalb von Max inkrementiert (d.h. um 1 erhöht) haben.
D:\75807564.doc vom 13.10.99
3-58
BBS III Mainz
OOP für IT-Berufe mit C++
3.5.5.6 Referenz auf den Funktionsnamen
Möchte man zusätzlich noch vermeiden, daß für den Rückgabewert des Funktionsnamens zusätzlicher
Speicherplatz reserviert wird, kann man auch diesen Funktionsnamen als Referenz definieren:
/* Programm Minvar1.cpp
liefert das Minimum von drei Werten,
Demonstration einer Referenz auf den Funktionsnamen */
#include <iostream>
int& Min(int& x, int& y, int& z) {
if (x<y) { if (x<z) return x; }
else if (y<z) return y;
return z;
}
int main() {
int a=5, b=3, c=2;
cout << "Minimum von a = " << a << ", b = " << b << ", c = " << c
<< " : " << Min(a,b,c);
}
Im Beispiel Minvar1.cpp wird für den Rückgabewert im Funktionsnamen Min keine zusätzlicher Speicherplatz
reserviert. Stattdessen wird als Min ein Verweis auf die Variable zurückgeliefert, die nach dem Schlüsselwort
return aufgelistet ist - je nach dem Funktionsergebnis ist das einer der aktuellen Variablenparameter a, b oder c.
Wenn man (wie im obigen Beispiel) genau weiß, daß innerhalb der Funktion kein Variablenwert (hier x, y oder
z) geändert wird, kann man auch vollständig konstante Variablenparameter verwenden. Der Compiler meldet
dann einen Fehler, wenn der Programmierer trotzdem unabsichtlich diese als konstant definierten Werte
verändern will:
/* Programm Minvar2.cpp
liefert das Minimum von drei Werten,
Demonstration einer konstanten Referenz auf den Funktionsnamen */
#include <iostream>
const int& Min(const int& x, const int& y, const int& z) {
if (x<y) { if (x<z) return x; }
else if (y<z) return y;
return z;
}
int main() {
int a=5, b=3, c=2;
cout << "Minimum von a = " << a << ", b = " << b << ", c = " << c
<< " : " << Min(a,b,c);
}
D:\75807564.doc vom 13.10.99
3-59
BBS III Mainz
OOP für IT-Berufe mit C++
3.5.5.7 Überladene Funktionsnamen
Manchmal schreibt man Funktionen, die die gleiche oder ähnliche Aufgaben für verschiedene Typen ausführen
sollen. Dann kann man die Funktion mehrmals mit gleichem Namen definieren.
int Max(int x, int y) // Kein Variablenparameter mehr erforderlich
{ cout << "Das ist die int-Version: " << endl;
if (x > y) return x; else return y;
}
float Max(float x, float y) // Kein Variablenparameter mehr erforderlich
{ cout << "Das ist die int-Version: " << endl;
if (x > y) return x; else return y;
}
Die Funktionen können bei jeder einzelnen Definition eine vollkommen unterschiedliche Anzahl von
Argumenten und auch unterschiedliche Typen von Argumenten und Rückgabewerten besitzen. In Wirklichkeit
existieren unterschiedliche Funktionen, die lediglich den gleichen Namen tragen. Welche der einzelnen
Funktionen beim Aufruf tatsächlich zur Ausführung gelangt, wird aufgrund der Typen der Aktualparameter
entschieden. Bei dieser Auswahl spielt der Typ des Rückgabewertes einer Funktion keine Rolle.
Grundlegende Übungsaufgabe:
Kopieren Sie das Programm Maximum4.cpp nach Maximum7.cpp und fügen Sie in Maximum7.cpp zwei
zusätzliche globale float-Variablen c und d ein sowie eine zweite Funktion Max wie oben beschrieben. Lassen
Sie das Programm ablaufen, geben Sie ganzzahlige und reelle Werte ein und beobachten Sie die Reaktion des
Programmes.
D:\75807564.doc vom 13.10.99
3-60
BBS III Mainz
OOP für IT-Berufe mit C++
3.5.5.8 Default-Argumente
Eine allgemein gehaltene Funktion benötigt oft mehr Parameter, als für alle Funktionsaufrufe nötig ist - einige
spezielle Parameter werden nur bei ganz bestimmten Funktionsaufrufen benötigt. Möchte man nicht auf das
(relativ aufwendige) Konzept des Überladens zurückgreifen, kann man für Funktionen stattdessen DefaultArgumente angeben. Ein Default-Argument wird bei der Funktionsdeklaration typgeprüft und mit dem DefaultWert initialisiert, wenn kein Aktualparameter vorhanden ist.
Beispiel:
/* PROGRAMM Maximum8.cpp
Maximum von zwei Zahlen, Funktion mit Default-Parameter */
#include <iostream>
int a,b;
int Max(int x, int y =5) // Vergleich mit Default-Wert 5
{ if (x > y) return x; else return y; }
void main()
{
cout << "Zwei Zahlen: ";
cin >> a >> b;
cout << "Maximum = " << Max(a,b) << endl;
cout << "Nur eine Zahl (Vergleich mit 5): ";
cin >> a;
cout << "Maximum = " << Max(a) << endl; // Zweiter Aktualparameter fehlt!
cout << "Programmende.";
}
Zwei Zahlen: 3 8
Maximum = 8
Nur eine Zahl (Vergleich mit 5): 4
Maximum = 5
Programmende.
Dieses Konzept bietet einen weiteren Vorteil bei der Programmwartung. Stellt es sich heraus, daß in einer
bereits entworfenen und vielfältig eingesetzten Funktion manchmal weitere Parameter notwendig sind, fügt man
diese neuen Parameter mit Default-Argumenten hinten in der Parameterliste an, so daß alle bisher getätigten
Funktionsaufrufe gültig bleiben und funktionieren.
D:\75807564.doc vom 13.10.99
3-61
BBS III Mainz
OOP für IT-Berufe mit C++
3.5.5.9 Schablonen (Templates) für Funktionen
Die dritte (und wohl eleganteste) Möglichkeit, Funktionen mit verschiedenen Typen aufzurufen, besteht darin,
eine Schablone (engl. Template) zu definieren. Eine Schablone ist eine Definition, bei der die Datentypen noch
nicht festgelegt werden. Es wird ein Platzhalter eingefügt, der später durch den tatsächlich benötigten Datentyp
ersetzt wird. Die Syntax zur Deklaration einer Schablone lautet:
template <class TypBezeichner> Funktionsdefinition
Beispiel:
/* PROGRAM Maximum9.cpp
Maximum von zwei Zahlen mit Funktionsschablone */
#include <iostream>
char
float
a,b;
c,d;
template<class TIrgendwas>
TIrgendwas Max(TIrgendwas x, TIrgendwas y)
{ if (x > y) return x; else return y; }
void main()
{
cout << "Zwei Buchstaben: ";
cin >> a >> b;
cout << "Maximum = " << Max(a,b) << endl;
cout << endl << "Zwei Dezimalzahlen: ";
cin >> c >> d;
cout << "Maximum = " << Max(c,d) << endl;
cout << "Programmende.";
}
Innerhalb von main() stellt der Compiler anhand des Funktionsaufrufs fest, für welchen Datentyp die Funktion
benötigt wird und bildet die Definition mit Hilfe der Schablone. Für jeden bei Aufrufen benötigten Datentyp
wird vom Compiler aus der Schablone eine Funktion erzeugt.
D:\75807564.doc vom 13.10.99
3-62
BBS III Mainz
OOP für IT-Berufe mit C++
3.6 Verbunde und Objekte: Strukturen und Klassen
Die Datentypen, welche bislang behandelt wurden (int, float, enum, usw.), nennt man auch einfache
Datentypen. Nun werden komplexere Datenstrukturen behandelt.
3.6.1 Strukturen
Sehr oft benötigt man in der Datenverarbeitung Variablen unterschiedlichen Typs, die im Rahmen eines
Verbundes zusammengefaßt und unter einem Namen angesprochen werden können. (z.B. Person als
Zusammenfassung von Personalnummer, Name, Vorname, Postleitzahl, usw.). In C++ wird ein solcher
Verbund als Struktur bezeichnet. Eine Struktur wird folgendermaßen definiert:
struct Typname Felddefinition(en) Variable(nliste);
Beispiel:
Ein metereologisches Institut startet dreimal am Tag einen Wetterballon, der Temperatur, Luftdruck, Relative
Feuchte und Windstärke mißt sowie mittels eines Sensors anzeigt, ob Regen fällt oder nicht. Die Meßwerte
sollen per Programm ausgewertet werden. Dazu wird eine Struktur Tballon definiert.
// Zunächst einige Typdefinitionen für die Strukturelemente von Wetterballon
typedef enum { morgens=1, mittags, abends } Tzeit; // Beobachtungstermin
typedef short unsigned int Tfeuchte; // Für rel. Feuchte (0-100 %)
struct Tballon {
int Temperatur;
float Luftdruck;
Tfeuchte Feuchte;
bool Regen;
Tzeit Tageszeit;
};
Tballon Ballon; // deklariert die Variablen Ballon mit dem Typ Tballon
Die einzelnen Elemente des Verbundes werden über den Punktoperator . in der Form StrukturVariablenname.Elementname angesprochen. Diese Art des Elementzugriffs nennt man Qualifizierung:
Ballon.Temperatur = 20;
Auf die Elementvariablen können alle Operationen angewandt werden, die auch mit gewöhnlichen Variablen
möglich sind, z.B. Vergleiche mit = = oder < =, usw.
D:\75807564.doc vom 13.10.99
3-63
BBS III Mainz
OOP für IT-Berufe mit C++
Beispiel:
Im folgenden Programm Wetter1.cpp werden alle Elemente der Struktur mit Eingabewerten gefüllt, die über die
Tastatur eingegeben werden. Danach werden alle Strukturelemente auf dem Bildschirm ausgegeben. Die
Elemente Regen und Tageszeit können nicht direkt ein- oder ausgegeben werden, das ist über cin und cout
sinnvoll nur mit den Typen Zahlen oder Zeichen bzw. Zeichenketten möglich. Daher wird eine boolsche
Eingabefunktion für „Ja“ oder „Nein“ definiert und der Typ Tageszeit wird nach int konvertiert.
D:\75807564.doc vom 13.10.99
3-64
BBS III Mainz
/* Programm Wetter1.cpp
Demonstration einer Struktur */
#include <iostream.h>
#include <ctype.h> // wegen toupper
OOP für IT-Berufe mit C++
typedef int Tzeit; // Beobachtungs-Tageszeit
typedef short unsigned int Tfeuchte; // Für rel. Feuchte (0-100 %)
struct Tballon {
int Temperatur;
float Luftdruck;
Tfeuchte Feuchte;
bool Regen;
Tzeit Tageszeit;
};
Tballon Ballon;
bool JNEingabe() {
char antw;
do {
cin >> antw;
antw = toupper(antw);
} while (antw != 'J' && antw != 'N');
return antw=='J';
}
void main() {
int Tageszeitzahl;
cout
cout
cout
cout
cout
cout
<<
<<
<<
<<
<<
<<
"Eingabe eines Datensatzes von Messwerten:\n\n";
"Temperatur in Grad Celsius: "; cin >> Ballon.Temperatur;
"Luftdruck in bar:
"; cin >> Ballon.Luftdruck;
"Relative Feuchte in %:
"; cin >> Ballon.Feuchte;
"Regen (J/N):
"; Ballon.Regen = JNEingabe();
"Tageszeit (1-3):
"; cin >> Tageszeitzahl;
Ballon.Tageszeit = Tageszeitzahl;
cout << "\nAusgabe des Datensatzes:\n\n";
cout << "Temperatur:
" << Ballon.Temperatur << " Grad Celsius"<<endl;
cout << "Luftdruck:
" << Ballon.Luftdruck << " bar" << endl;
cout << "Relative Feuchte: " << Ballon.Feuchte << " %" << endl;
cout << "Regen:
";
if (Ballon.Regen) cout << "Ja"; else cout << "Nein"; cout << endl;
cout << "Tageszeit:
" << Ballon.Tageszeit << endl;
cout << "\nProgrammende.";
}
D:\75807564.doc vom 13.10.99
3-65
BBS III Mainz
Eingabe eines Datensatzes von Messwerten:
Temperatur in Grad Celsius:
Luftdruck in bar:
Relative Feuchte in %:
Regen (J/N):
Tageszeit (1-3):
OOP für IT-Berufe mit C++
20
1.04
40
j
2
Ausgabe des Datensatzes:
Temperatur:
Luftdruck:
Relative Feuchte:
Regen:
Tageszeit:
20 Grad Celsius
1.04 bar
40 %
Ja
2
Programmende.
Strukturvariablen können auch direkt bei der Typdefinition definiert werden. Gleichzeitig können die
Strukturelemente initialisiert werden:
struct Tballon {
int Temperatur;
float Luftdruck;
Tfeuchte Feuchte;
bool Regen;
Tzeit Tageszeit;
} Ballon
// Definition der Variablen
= {20,1.04,70,true,morgens}; // Initialisierung der Elemente
3.6.2 Funktionen zum Zugriff auf Strukturelemente
Da sich die Eingabe- und Ausgabeanweisungen im obigen Programmbeispiel direkt auf die Struktur beziehen,
bietet sich eine Modularisierung mittels geeigneter Funktionen an. Dabei kann man den Funktionen die
Strukturvariablen als Variablenparameter übergeben und auf diese Weise den langen Strukturbezeichner (hier:
Ballon) abgekürzt verwenden (hier: b). Zusätzlich wurde im nachstehenden Beispiel Wetter2.cpp eine
aussagekräftigere Ausgaberoutine für die Anzeige der Tageszeit eingebaut.
/* Programm Wetter2.cpp
Demonstration einer Struktur
mit Ein- Ausgabezugriff über Funktionen */
#include <iostream.h>
#include <ctype.h>
typedef int Tzeit;
typedef short unsigned int Tfeuchte;
struct Tballon {
int Temperatur;
float Luftdruck;
Tfeuchte Feuchte;
bool Regen;
Tzeit Tageszeit;
};
Tballon Ballon;
bool JNEingabe() {
char antw;
do {
cin >> antw;
antw = toupper(antw);
} while (antw != 'J' && antw != 'N');
return antw=='J';
}
void Eingabe(Tballon& b) {
int Tageszeitzahl;
D:\75807564.doc vom 13.10.99
3-66
BBS III Mainz
OOP für IT-Berufe mit C++
cout << "Temperatur in Grad Celsius:
cout << "Luftdruck in bar:
cout << "Relative Feuchte in %:
cout << "Regen (J/N):
cout << "Tageszeit (1-3):
b.Tageszeit = Tageszeitzahl;
";
";
";
";
";
cin >> b.Temperatur;
cin >> b.Luftdruck;
cin >> b.Feuchte;
b.Regen = JNEingabe();
cin >> Tageszeitzahl;
}
void AusgabeTageszeit(int z) {
switch (z) {
case 1 : cout << "morgens"; break;
case 2 : cout << "mittags"; break;
case 3 : cout << "abends"; break;
}
}
void Ausgabe(Tballon& b) {
cout << "Temperatur:
" <<
cout << "Luftdruck:
" <<
cout << "Relative Feuchte: " <<
cout << "Regen:
";
if (b.Regen) cout << "Ja"; else
cout << "Berichtsjzeit:
";
AusgabeTageszeit(b.Tageszeit);
cout << endl;
}
b.Temperatur << " Grad Celsius" << endl;
b.Luftdruck << " bar" << endl;
b.Feuchte << " %" << endl;
cout << "Nein"; cout << endl;
void main() {
cout << "Eingabe eines Datensatzes von Messwerten:\n\n";
Eingabe(Ballon);
cout << "\nAusgabe des Datensatzes:\n\n";
Ausgabe(Ballon);
cout << "\nProgrammende.";
}
D:\75807564.doc vom 13.10.99
3-67
BBS III Mainz
OOP für IT-Berufe mit C++
3.6.3 Kapselung von Struktur und Zugriffsfunktionen: Objekt
Im letzten Programmbeispiel ist offensichtlich, daß die Module Eingabe(Tballon&) und Ausgabe (Tballon&)
nur mit der Struktur Tballon zusammen sinnvoll eingesetzt werden können. Ebenso kann die Struktur Tballon
alleine nicht sinnvoll verwendet werden, wenn keine Zugriffsfunktionen für sie existieren. Bestimmte
Datentypen erfordern immer dazu passende Algorithmen und umgekehrt.
Daher bietet es sich an, die Struktur Tballon zusammen mit ihren Zugriffsfunktionen vollständig zu kapseln.
Auch in größeren Programmen weiß man dann stets, welche Strukturen zu welchen Zugriffsfunktionen gehören.
Damit wird die Struktur Ballon zum Objekt Ballon, welches sowohl Elementdaten enthält, auch Eigenschaften
genannt, als auch zugehörige Funktionsmodule. In ein Objekt eingekapselte Funktionen nennt man Methoden
(auch Elementfunktionen genannt):
Ein Objekt besteht aus Eigenschaften und Methoden
(auch Elementdaten und Elementfunktionen genannt)
/* Programm Wetter3.cpp
Demonstration einer Struktur mit eingekapselten Methoden (Objekt) */
#include <iostream>
#include <ctype>
struct Tballon {
int Temperatur;
// Eigenschaften von Tballon
float Luftdruck;
short unsigned int Feuchte;
bool Regen;
int Tageszeit;
// Ende der Eigenschaften
bool
void
void
void
JNEingabe();
Eingabe();
AusgabeTageszeit();
Ausgabe();
// Methoden von Tballon (Deklarationen)
// Ende der Methoden-Deklarationen
};
bool Tballon::JNEingabe() {
// Beginn der Methoden-Definitionen von Tballon
char antw;
// :: ist der Zugriffsbereichsoperator!
do {
cin >> antw;
antw = toupper(antw);
} while (antw != 'J' && antw != 'N');
return antw=='J';
}
void Tballon::Eingabe() {
int Tageszeitzahl;
cout << "Temperatur in Grad Celsius:
cout << "Luftdruck in bar:
cout << "Relative Feuchte in %:
cout << "Regen (J/N):
cout << "Tageszeit (1-3):
Tageszeit = Tageszeitzahl;
";
";
";
";
";
cin >> Temperatur;
cin >> Luftdruck;
cin >> Feuchte;
Regen = JNEingabe();
cin >> Tageszeitzahl;
}
void Tballon::AusgabeTageszeit() {
switch (Tageszeit) {
case morgens : cout << "morgens"; break;
case mittags : cout << "mittags"; break;
D:\75807564.doc vom 13.10.99
3-68
BBS III Mainz
case abends
}
}
: cout << "abends"; break;
OOP für IT-Berufe mit C++
void Tballon::Ausgabe() {
cout << "Temperatur:
" << Temperatur << " Grad Celsius" << endl;
cout << "Luftdruck:
" << Luftdruck << " bar" << endl;
cout << "Relative Feuchte: " << Feuchte << " %" << endl;
cout << "Regen:
";
if (Regen) cout << "Ja"; else cout << "Nein"; cout << endl;
cout << "Berichtsjzeit:
";
AusgabeTageszeit();
cout << endl;
}
// Ende der Methoden-Definitionen
main() {
Tballon Ballon;
cout << "Eingabe eines Datensatzes von Messwerten:\n\n";
Ballon.Eingabe(); // Ausführung einer Methode durch Qualifizierung mit .
cout << "\nAusgabe des Datensatzes:\n\n";
Ballon.Ausgabe();
cout << "\nProgrammende.";
}
Das Konzept der Objektorientierung ist eine Fortentwicklung der strukturierten Programmiertechnik mit
prozeduralen Programmiersprachen wie C oder Pascal. Wenn man Datentypen (Eigenschaften) und
Elementfunktionen (Methoden) grundsätzlich zusammenfaßt, ist das resultierende Objekt viel leichter wartbar,
speziell dann, wenn man - wie bei professionellen Programmierprojekten üblich - mit vielen Programmierern an
einem einzigen Projekt arbeitet.
D:\75807564.doc vom 13.10.99
3-69
BBS III Mainz
OOP für IT-Berufe mit C++
3.6.4 Objektklasse und Objektinstanz
An der obigen Programmlösung ist noch unschön, daß die Datenelemente des Objekts Ballon durch
Algorithmen außerhalb des Objekts geändert werden können. Variieren Sie z.B. Wetter3.cpp wie folgt:
main() {
Tballon Ballon;
cout << "Eingabe eines Datensatzes von Messwerten:\n\n";
Ballon.Eingabe();
Ballon.Temperatur := -50; // Absichtlich verändern!!
cout << "\nAusgabe des Datensatzes:\n\n";
Ballon.Ausgabe();
cout << "\nProgrammende.";
}
Nun wird das Programm immer eine Temperatur von -50 °C anzeigen, unabhängig davon, welchen Wert Sie
mit der Objektmethode Tballon.Eingabe() eingegeben haben. Eine Beeinflussung von Objektdaten durch äußere
Algorithmen ist in der Regel unerwünscht. Unter vollständiger Kapselung versteht man, daß die Eigenschaften
eines Objektes nur über die zugeordneten Objektmethoden geändert werden können.
Betrachtet man sich die Struktur Tballon genau, dann erscheint sinnvoll, daß alle Strukturelemente Temperatur
bis Tageszeit sowie die Elementfunktionen Tballon::JNEingabe() und Tballon::AusgabeTageszeit() nur
innerhalb des Objektes bekannt sind, denn außerhalb werden sie nicht benötigt. Wenn sie auch außerhalb des
Objektes gültig sind, können sie auch von Programmierern verwendet werden, die das Objekt Tballon nicht
selbst erstellt haben. Wenn nun der „Erfinder“ von Tballon solche „öffentlich zugänglichen“ Objektmethoden
irgendwann einmal ändert, hat das mit Sicherheit ungünstige Auswirkungen auf fremde Programmteile.
Lediglich die Elementfunktionen Tballon:Eingabe und Tballon::Ausgabe sollen öffentlich sein. Man erreicht
das gewünschte Verhalten, indem man die Schlüsselwörter class (statt struct) und public verwendet:
/* Programm Wetter4.cpp
Demonstration eines vollständig gekapselten Objekts */
/* ... include-Direktiven wie bei Wetter3.cpp */
class Tballon {
// Deklaration einer Objektklasse Tballon
int Temperatur;
// Alle Felder einer class sind standardmäßig private
float Luftdruck;
short unsigned int Feuchte;
bool Regen;
int Tageszeit;
bool JNEingabe();
// Zwei private-Methoden
void AusgabeTageszeit();
public:
void Eingabe();
// Zwei öffentliche Zugriffsmethoden
void Ausgabe();
};
/* ... Hier folgen die Methodendefinitionen wie bei Wetter3.cpp ... */
Tballon Ballon; // Deklaration einer Objektinstanz Ballon.
Nun kann man vom Hauptprogramm aus Ballon.Temperatur nicht mehr verändern, denn diese Variable ist
außerhalb der Objektklasse Tballon unbekannt, genauso wie alle übrigen Objekteigenschaften. Auch die
privaten Elementfunktionen JNEingabe() und AusgabeTageszeit() kann man von außerhalb des Objektes nicht
mehr ausführen - probieren Sie es aus, indem Sie das Programm Wetter4.cpp entsprechend variieren.
Merken Sie sich auch die folgende Ausdrucksweisen: Wenn man den Typ eine Objektes deklariert (mit class typ
{ ...), dann spricht man von einer Objektklasse. Sobald man eine Variable mit dem Typ dieser Klasse deklariert
(mit Tballon Ballon), redet man von einer Objektinstanz.
Üblicherweise verwendet man das Schlüsselwort class, wenn man Objektklassen definiert. Bei einer class sind
standardmäßig alle Elemente private, das heißt, sie sind außerhalb der Klasse ungültig. Bei einer struct ist es
genau umgekehrt: Alle Elemente sind standardmäßig public, d.h. sie sind überall im Programm bekannt und
können global verwendet werden.
D:\75807564.doc vom 13.10.99
3-70
BBS III Mainz
OOP für IT-Berufe mit C++
Die folgenden Definitionen von class und struct haben die gleiche Bedeutung:
class Tetwas {
/* Eigenschaften, Methoden */
public:
/* Eigenschaften, Methoden */
}
D:\75807564.doc vom 13.10.99
struct Tetwas {
private:
/* Eigenschaften, Methoden */
public:
/* Eigenschaften, Methoden */
3-71
BBS III Mainz
OOP für IT-Berufe mit C++
4 Objektorientierte Programmierung
4.1 Prinzipien der objektorientierten Programmierung
Moderne Betriebssysteme und Anwenderprogramme sind grafikorientiert und ereignisgesteuert: Auf dem
Bildschirm werden ständig eine Menge von “Objekten” zur Auswahl angeboten. Mit der Tastatur angewählt
oder mit der Maus angeklickt, erwacht ein solches Objekt zum “Leben” und führt bestimmte Aktionen aus danach wartet das System auf weitere Maus- oder Tastaturereignisse.
Die objektorientierte Programmierung (OOP) ist aus der modernen Softwareentwicklung nicht mehr
wegzudenken. Die Betrachtungsweise der OOP ist optimal an die Funktionsweise moderner Programme und
Betriebssysteme angepaßt.
Bereits im Kapitel “Verbunde und Objekte: Strukturen und Klassen” haben wir gesehen, wie man Daten und
Funktionen (Eigenschaften und Methoden) zu einer Objektklasse zusammenfaßt. In diesem Sinne versteht man
unter einem Objekt eine Zusammenfassung von Datenstrukturen (auch Elementdaten oder Eigenschaften
genannt) mit zugehörigen Programmodulen (auch Methoden oder Elementfunktionen genannt.).
Die OOP verfährt nach drei Basisprinzipien:
 Kapselung
Alle Daten und Methoden eines Objektes gehören fest zur jeweiligen Objektklasse. Daten und Methoden
sind außerhalb von Klassen nicht definiert, sie können von außen nur über fest definierte Schnittstellen
angesprochen werden. Daten und zugehöriger Programmcode einer Objektklasse sind gekapselt.
 Vererbung
Eine Objektklasse ist in der Lage, eine Reihe bestimmter Programmanforderungen zu erfüllen. Kommen
zusätzliche Anforderungen hinzu, wird eine neue Klasse als Nachkomme der alten Objektklasse erzeugt. Die
neue Klasse erbt zunächst alle Eigenschaften und Methoden des Vorfahren. Sie kann erweitert und
abgewandelt werden, indem neue Eigenschaften und Methoden hinzugefügt bzw. alte Elementdaten und
Elementfunktionen überladen werden.
 Polymorphie
Eine einmal definierte Methode ist für alle Nachkommen einer Objektklasse gültig. Methoden mit gleichem
Namen können jedoch so implementiert werden, daß sie in verschiedenen Nachfolgeklassen unterschiedliche
Aktionen auslösen. Diese Eigenschaft der Vielgestaltigkeit von Objektklassen bezeichnet man mit dem
(griechischen) Ausdruck Polymorphie.
Die Realisierung der drei Basisprinzipien der OOP in der Programmiersprache C++ wird nun näher untersucht.
D:\75807564.doc vom 13.10.99
4-72
BBS III Mainz
OOP für IT-Berufe mit C++
4.2 Das OOP-Prinzip Kapselung
4.2.1 Klassen
Jedes Objekt, auch in der realen Welt, ist mit bestimmten Eigenschaften und Verhaltensweisen versehen.
Einzelne Objekte können zu (Objekt-)klassen zusammengefaßt werden. Diese wiederum können in
Klassenhierarchien zueinander stehen und Eigenschaften voneinander übernehmen.
Ein Beispiel: Das Urtier hatte eine Länge, Höhe und Breite und konnte fressen und laufen. Die von ihm
abstammende Klasse der Flugsaurier konnte zusätzlich noch fliegen, während die Klasse der Schwimmsaurier
schwimmen konnte, aber ansonsten die Eigenschaften und Fähigkeiten der Klasse Urtier vererbt bekam.
Eine Klasse definiert eine Kategorie von Objekten, hat aber keinen Repräsentanten in der Realität. Jedes real
existierende Objekt ist eine Instanz einer Klasse und hat daher die gleiche Beziehung zu einer Klasse wie eine
Variable zu einem Datentyp.
In C++ bezeichnet man die Eigenschaften als die Datenelemente der Klasse und die Fähigkeiten als
Elementfunktionen oder Methoden der Klasse. Methoden können den Zustand eines Objekts durch
Manipulation seiner Datenelemente verändern.
// Programm Klasse1.cpp
#include <iostream.h>
#include <conio.h>
class miniobjekte
{
private:
int daten;
public:
void setzedaten(int d)
{ daten = d; }
void zeigedaten()
{ cout << "\nDie Daten sind " << daten;}
};
void main()
{
miniobjekte s1, s2;
s1.setzedaten(1066);
s2.setzedaten(1776);
s1.zeigedaten();
s2.zeigedaten();
getch();
}
Die Klasse Miniobjekte dieses Programms enthält ein Datenelement und zwei Methoden. Diese Funktionen
bieten die einzige Möglichkeit, auf die Daten des Objekts zuzugreifen. Die Definition einer Klasse beginnt mit
dem Schlüsselwort class, gefolgt vom jeweiligen Klassennamen. Wie bei einer Struktur ist der Rumpf einer
Klasse von geschweiften Klammern eingeschlossen und wird mit einem Semikolon beendet.
Der Rumpf der Klasse enthält zwei bisher unbekannte Schlüsselworte, private und public. Diese definieren
Schutzmechnismen für die Daten einer Klasse. Auf private Daten oder Funktionen kann nur innerhalb der
Klasse zugegriffen werden. Auf durch public gekennzeichnete Daten oder Funktionen kann man auch von
außerhalb der Klasse zugreifen. Normalerweise sind Daten in einer Klasse private und Funktionen public, damit
die Daten geschützt sind und die einmal definierten Funktionen mehrfach verwendet werden können.
Wie man sieht, erfolgt der Zugriff auf Methoden analog zum Zugriff auf Elemente einer Struktur.
Hier ein weiteres Beispiel mit mehr Datenelementen und Methoden, die auf die Datenelemente zugreifen.
//Programm Klasse2.cpp
#include <iostream.h>
D:\75807564.doc vom 13.10.99
4-73
BBS III Mainz
#include <conio.h>
class teile
{
private:
int modellnr;
int teilnr;
float kosten;
OOP für IT-Berufe mit C++
public:
void setzeteile(int mn, int tn, float k)
{
modellnr = mn;
teilnr = tn;
kosten = k;
}
void kostenaendern()
{
cout <<"\nDer Preis hat sich veraendert; geben Sie den neuen Preis ein: ";
cin >> kosten;
}
void zeigeteile()
{
cout << "\nModell " << modellnr;
cout << ", Teil " << teilnr;
cout << ", Kosten " << kosten << " DM" ;
}
};
void main()
{
teile teil1;
teil1.setzeteile(4711, 13,245.34);
teil1.zeigeteile();
teil1.kostenaendern();
teil1.zeigeteile();
getch();
}
4.2.2 Methoden
Eine Methode (oder auch Elementfunktion genannt) ist eine Funktion, die in eine Klassendefinition eingebettet
ist. Sie wird im im Kontext einer Klasse aufgerufen und kann auf die Daten ihrer Klasse zugreifen.
//Programm Englobj.cpp
//Feet and inches
#include <iostream.h>
#include <conio.h>
class laenge
{
private:
int feet;
float inches;
public:
void setzelaenge(int ft, float in)
{ feet = ft; inches = in; }
void holelaenge()
{
cout << "\nBitte Feet eingeben: "; cin >> feet;
cout << "\nBitte Inches eingeben: ";cin >> inches;
}
void zeigelaenge()
//Länge ausgeben
{
cout << feet << "\'-" << inches << '\"'; }
};
void main()
{
D:\75807564.doc vom 13.10.99
4-74
BBS III Mainz
laenge l1,l2;
l1.setzelaenge(14, 8.25);
OOP für IT-Berufe mit C++
l2.holelaenge();
cout << "\nLaenge1 = "; l1.zeigelaenge();
cout << "\nLaenge2 = "; l2.zeigelaenge();
getch();
}
Die Methoden setzelaenge(int ft, float in), holelaenge() und zeigelaenge() sind alle in der Klasse laenge
deklariert und greifen auf die Datenelemente feet und inches zu.
4.2.3 Der Bereichsoperator ::
In dem obigen Beispielprogramm ENGLOBJ.CPP sind alle Funktionen innnerhalb der Klasse definiert worden.
Man kann jedoch auch Funktionen innerhalb der Klasse deklarieren und ausserhalb definieren.
//Programm Englobj2.cpp
//Feet and inches
#include <iostream.h>
#include <conio.h>
class laenge
{
private:
int feet;
float inches;
public:
void setzelaenge(int ft, float in)
{ feet = ft; inches = in; }
void holelaenge();
void zeigelaenge()
//Länge ausgeben
{
cout << feet << "\'-" << inches << '\"'; }
};
void laenge::holelaenge()
{
cout << "\nBitte Feet eingeben: "; cin >> feet;
cout << "\nBitte Inches eingeben: ";cin >> inches;
}
void main()
{
laenge l1,l2;
l1.setzelaenge(14, 8.25);
l2.holelaenge();
cout << "\nLaenge1 = "; l1.zeigelaenge();
cout << "\nLaenge2 = "; l2.zeigelaenge();
getch();
}
Der Funktion holelaenge() geht hier der Klassenname laenge voraus und das neue Symbol "::". Dieser
sogenannte Bereichsoperator gibt an, zu welcher Klasse ein Funktion gehört. In diesem Beispiel bedeutet
laenge::holelaenge(), dass die Funktion holelaenge() aus der Klasse laenge definiert werden soll. Diese Art der
getrennten Deklaration und Definition verwendet man, wenn die Definition der Funktion umfangreich ist und
dadurch die Übersichtlichkeit der Deklaration der Klasse leiden würde.
D:\75807564.doc vom 13.10.99
4-75
BBS III Mainz
OOP für IT-Berufe mit C++
4.2.4 Konstruktoren
4.2.4.1 Standardkonstruktor
Konstruktoren werden zur Initialisierung von Objekten benötigt. Wenn kein Konstruktor angegeben ist, wird
das Objekt automatisch initialisiert, wie in den Beispielen Klasse1.cpp und Klasse2.cpp.
In Klasse1.cpp wird durch miniobjekte s1, s2; erreicht, dass zwei Objekte des Typs miniobjekte erstellt werden.
Dies geschieht durch den automatischen Aufruf des Konstruktors miniobjekte().
Ein weiteres Beispiel für Konstruktoren liefert Zaehler1.cpp:
//Programm Zaehler1.cpp
#include <iostream.h>
#include <conio.h>
class Zaehler
{
private:
unsigned int count;
public:
Zaehler()
{ count = 0; }
void inc_count() { count++; }
int get_count()
{ return count; }
};
void main()
{
Zaehler c1,c2;
cout << "\nc1=" << c1.get_count();
cout << "\nc2=" << c2.get_count();
c1.inc_count();
c2.inc_count();
c2.inc_count();
cout << "\nc1=" << c1.get_count();
cout << "\nc2=" << c2.get_count();
getch();
}
Hier ist die Funktion Zaehler() zum einen dafür verantwortlich, dass die beiden Objekte c1 und c2 initialisiert
werden, zum anderen werden diese beiden auch gleich auf Null gesetzt.
Die allgemeine Syntax für Konstruktoren lautet:
class className
{
public:
className(); //Standardkonstruktor
className(<Parameterliste>); //anderer Konstruktor
className(const className& c); //Kopierkonstruktor
};
Einige Regeln für Konstruktoren:
1. Sie haben denselben Namen wie die Klasse, in der sie als Methode definiert sind.
2. Es gibt für Konstruktoren keine Rückgabewerte (auch nicht void), da sie automatisch vom System
aufgerufen werden.
3. Eine Klasse kann eine beliebige Anzahl von Konstruktoren (also auch keinen) haben.
4. Standardkonstruktor ist derjenige Konstruktor, der entweder über keine Parameter verfügt oder in dessen
Parameterliste für alle Parameter Vorgabeargumente verwendet werden.
D:\75807564.doc vom 13.10.99
4-76
BBS III Mainz
OOP für IT-Berufe mit C++
Ein Beispiel für 4):
class punkt
{
private:
double x,y;
public:
punkt(double xwert = 0, double ywert=0);
};
4.2.4.2 Überladener Konstruktor
Wir betrachten das obige Beispiel Englobj.cpp. Es wäre schön, wenn wir einer Länge bereits beim
Programmstart einen Wert zuordnen könnten, z.B. laenge weite(4, 3.25). Hier wird ein Objekt weite definiert
und gleichzeitig seine Werte auf 4 für feet und 3.25 für inches gesetzt. Dies geschieht mit einem Konstruktor
wie
laenge(int ft, float in)
{ feet = ft; inches = in;}
//Programm Englcon.cpp
//Feet and inches
#include <iostream.h>
#include <conio.h>
class laenge
{
private:
int feet;
float inches;
public:
laenge() {}
//Konstruktor ohne Argument
laenge(int ft, float in) //Konstruktor mit 2 Argumenten
{ feet = ft; inches = in; }
void holelaenge()
//Benutzer gibt Daten ein
{
cout << "\nBitte Feet eingeben: "; cin >> feet;
cout << "\nBitte Inches eingeben: ";cin >> inches;
}
void zeigelaenge()
//Länge ausgeben
{
cout << feet << "\'-" << inches << '\"'; }
void add_laenge( laenge, laenge ); //Deklaration der Methode
};
void laenge::add_laenge( laenge l2, laenge l3 ) //Definition
{
inches = l2.inches + l3.inches;
feet = 0;
if (inches >= 12.0)
{
inches-= 12.0;
feet++;
}
feet += l2.feet + l3.feet;
}
void main()
{
laenge l1,l3;
laenge l2(11, 6.25);
l1.holelaenge();
D:\75807564.doc vom 13.10.99
4-77
BBS III Mainz
l3.add_laenge(l1,l2);
OOP für IT-Berufe mit C++
cout << "\nLaenge1 = "; l1.zeigelaenge();
cout << "\nLaenge2 = "; l2.zeigelaenge();
cout << "\nLaenge3 = "; l3.zeigelaenge();
getch();
}
In diesem Programm werden verschiedene Elemente implementiert: Zum einen mehrere Konstruktoren, der
Bereichsoperator und Objekte als Funktionsparameter. Die Längen l1 und l3 sind mit dem Konstruktor ohne
Argumente erstellt worden, der sie auch nicht initialisiert. Die Länge l2 wiederum ist mit dem 2-ArgumentKonstruktor erstellt worden und wird mit den Werten initialisiert, die als Argumente übergeben werden.
Mit l3.add_laenge(l1,l2); werden die beiden Längen addiert, wobei l1 und l2 Objekte sind. Die Syntax dafür ist
dieselbe wie bei anderen einfachen Datentypen wie Integer. Da add_laenge eine Methode von laenge ist, kann
sie auf die privaten Daten aller Objekte der Klasse laenge zugreifen.
Da es jetzt mehr als einen Konstruktor in einer Klasse gibt, nennt man dies auch Überladung von
Konstruktoren. Man kann beliebig viele überladenene Konstruktoren in einer Klasse deklarieren. Wenn man
einen überladenen Konstruktor deklariert, muß man den ansonsten vom System bereitgestellten
Standardkonstruktor ebenfalls deklarieren, z.B. laenge() {}. (Ohne die leeren geschweiften Klammern wäre dies
eine Deklaration und die Definition müßte nachgeholt werden.)
Wenn man mehrere Konstruktoren in einer Klasse deklariert hat, entscheidet das Programm zur Laufzeit anhand
der übergebenen Argumente, welcher Konstruktor verwendet wird.
4.2.4.3 Kopierkonstruktor
Man kann ein Objekt definieren und ihm zugleich die Werte eines anderen Objekts zuweisen:
alpha a3(a2);
alpha a3 = a2;
Beide Schreibweisen rufen einen Kopierkonstruktor auf, d.h. einen Konstruktor, der seine Argumente in das
neue Objekt kopiert. Der Standard-Kopierkonstruktor, den das System bereitstellt, kopiert alle Datenelemente in
das neue Objekt.
4.2.5 Destruktoren
Ähnlich wie Konstruktoren werden Destruktoren automatisch aufgerufen, wenn ein Objekt verschwinden soll.
Ein Destruktor hat denselben Namen wie der Konstruktor (wie auch die Klasse), aber mit vorangestelltem
Tilde-Zeichen.
Bsp.:
Class Abc
{
private:
int daten;
public:
Abc() { daten = 0; }
~Abc() {}
}
Wie die Konstruktoren haben auch die Destruktoren keinen Rückgabewert. Außerdem ist ihre Parameterliste
leer. Das Haupteinsatzgebiet von Destruktoren ist die Freigabe von Speicher, der für Objekte durch den
Konstruktor beim Anlegen des Objektes oder andere Methoden während der Laufzeit reserviert wurde. Dazu
sind jedoch Vorkenntnisse über Zeiger auf Objekte und dynamische Variablen notwendig.
D:\75807564.doc vom 13.10.99
4-78
BBS III Mainz
OOP für IT-Berufe mit C++
4.2.6 Objekte als Rückgabewert von Funktionen
//Programm Englret.cpp
//Feet and inches
#include <iostream.h>
#include <conio.h>
class laenge
{
private:
int feet;
float inches;
public:
laenge() {}
//Konstruktor ohne Argument
laenge(int ft, float in) //Konstruktor mit 2 Argumenten
{ feet = ft; inches = in; }
void holelaenge()
//Benutzer gibt Daten ein
{
cout << "\nBitte Feet eingeben: "; cin >> feet;
cout << "\nBitte Inches eingeben: ";cin >> inches;
}
void zeigelaenge()
//Länge ausgeben
{
cout << feet << "\'-" << inches << '\"'; }
laenge add_laenge( laenge ); //Deklaration der Methode
};
laenge laenge::add_laenge( laenge l2 ) //Definition der Methode
{
laenge temp;
temp.inches = inches + l2.inches;
if (temp.inches >= 12.0)
{
temp.inches-= 12.0;
temp.feet=1;
}
temp.feet += feet + l2.feet;
return temp;
}
void main()
{
laenge l1,l3;
laenge l2(13, 4.25);
l1.holelaenge();
l3 = l1.add_laenge(l2);
cout << "\nLaenge1 = "; l1.zeigelaenge();
cout << "\nLaenge2 = "; l2.zeigelaenge();
cout << "\nLaenge3 = "; l3.zeigelaenge();
getch();
}
Die Längen l1 und l3 werden mit dem Konstruktor ohne Argumente erstellt und daher nicht initialisiert. l2
wiederum ist mit dem Konstruktor mit 2 Argumenten erstellt worden und wird daher mit Werten initialisiert.
Mit l3 = l1.add_laenge(l2); werden l1 und l2 addiert und das Ergebnis l3 zugewiesen. Der Objektname l2 wird
der Funktion add_laenge als Argument übergeben. Das Resultat der Addition wird im Objekt temp gespeichert
und dann an das Hauptprogramm mittels return temp übergeben.
D:\75807564.doc vom 13.10.99
4-79
BBS III Mainz
OOP für IT-Berufe mit C++
4.2.7 Klassen, Objekte und Speicher
Bisher konnte man den Eindruck gewinnen, dass jedes Objekt, das aus einer Klasse entstanden ist, eigene
Kopien der Daten der Klasse und der Methoden besitzt. Die Analogie dazu ist ein Modell eines Autos und die
realen Autos, die vom Band rollen. Dies ist jedoch nicht der Fall: Es ist richtig, dass jedes Objekt seine eigenen
Daten beinhaltet, jedoch benutzen alle Objekte einer Klasse dieselben Methoden, da diese für jedes Objekt
gleich sind, während die Daten verschieden sind. Die Daten werden daher mehrfach in den Speicher geladen,
die Methoden nur einmal.
Ausnahme: Wenn ein Datenelement einer Klasse als static definiert wurde, wird es für die gesamte Klasse nur
einmal kreiert, egal, wieviele Instanzen der Klasse es gibt. Ein statisches Datenelement ist sinnnvoll, wenn alle
Instanzen einer Klasse ein gemeinsames Element besitzen sollen. Ein Beispiel dafür ist ein Autorennen, bei dem
ein Fahrer eines Autos wissen möchte, wieviele weitere Fahrer sich noch im Rennen befinden. In diesem Fall
könnte man eine Variable zaehler einführen, auf die alle Objekte der Klasse zugreifen könnten und die für alle
denselben Wert liefert.
4.2.8 Operator-Überladung
Dies ist ein Feature, das dem C++-Programmierer das Leben erleichtern kann: Statt d3.addobj(d1,d2) kann man
auch d3 = d1+d2 schreiben, wenn der Operator "+" entsprechend definiert ist. Stellen Sie sich vor, bei d1 und
d2 handelt es sich um Matrizen, bei denen man “normalerweise” mit verschachtelten Schleifen arbeiten muss.
Die allgemeine Syntax ist:
ReturnDatentyp operator (Argumentliste) {Funktionscode}
 steht für eines der möglichen C++-Operatorsymbole. Ein selbstdefinierte Operator ist eine Funktion in einer
syntaktisch anderen Verkleidung. Die richtige Bedeutung des Operators ermittelt der Compiler aus den
Datentypen der Operanden. Der Funktionsname einer Operatorfunktion besteht aus dem Schlüsselwort operator
und dem angehängten Operatorzeichen.
Der Inkrement-Operator ++ kann sowohl in der Präfix- als auch in der Postfix-Notation auftreten. Der ++Operator in der Postfix-Notation (count++(int)) hätte ein zusätzliches Argument, das normalerweise mit 0
belegt wird und nicht verwendet wird.
Wir verändern das Beispiel des Zählerprogramms so, dass nicht mehr c1.inc_count() aufgerufen wird, sondern
man einfach ++c1 schreiben kann.
//Programm zaehler2.cpp
//Operator-Überladung
#include <iostream.h>
#include <conio.h>
class Zaehler
{
private:
unsigned int count;
public:
Zaehler()
{ count = 0; }
int get_count()
{ return count; }
void operator ++ () { ++count; }
};
void main()
{
Zaehler c1,c2;
cout << "\nc1=" << c1.get_count();
cout << "\nc2=" << c2.get_count();
++c1;
++c2;
D:\75807564.doc vom 13.10.99
4-80
BBS III Mainz
++c2;
OOP für IT-Berufe mit C++
cout << "\nc1=" << c1.get_count();
cout << "\nc2=" << c2.get_count();
getch();
}
Das Überladen des ++-Operators hat jedoch auch Nachteile, zum Beispiel geht c2=++c1; nicht, da die
Operator-Funktion ++ keinen Rückgabewert hat. Überladen von Operatoren hat man bei Java übrigens nicht
implementiert.
Nun folgt ein längeres Beispiel über die Verwendung von Klassen und Methoden, das Ihnen zum einen einen
Einblick über solche Porgramme geben soll, zum anderen auch als Anschauungsmaterial für die folgenden
Übungen dienen soll.
Mit dem Programm Airport1.cpp können Sie Befehle an drei Flugzeuge richten. Airplane.h enthält die HeaderDatei und danach folgt die Programmdatei.
//Header-Datei airplane.h fuer das Airport-Programm
//--------------------------------------------------------------------------#ifndef airplaneH
#define airplaneH
//--------------------------------------------------------------------------#define AIRLINER
0
#define COMMUTER
1
#define PRIVATE
2
#define TAKINGOFF
0
#define CRUISING
1
#define LANDING
2
#define ONRAMP
3
#define MSG_CHANGE
0
#define MSG_TAKEOFF 1
#define MSG_LAND
2
#define MSG_REPORT
3
class Airplane {
public:
Airplane(const char* _name, int _type = AIRLINER);
~Airplane();
virtual int GetStatus(char* statusString);
int GetStatus()
{
return status;
}
int Speed()
{
return speed;
}
int Heading()
{
return heading;
}
int Altitude()
{
return altitude;
}
void ReportStatus();
bool SendMessage(int msg, char* response,
int spd = -1, int dir = -1, int alt = -1);
char* name;
protected:
virtual void TakeOff(int dir);
virtual void Land();
private:
int speed;
int altitude;
int heading;
D:\75807564.doc vom 13.10.99
4-81
BBS III Mainz
int status;
int type;
int ceiling;
};
#endif
OOP für IT-Berufe mit C++
//Programm Airport1.cpp
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
#include "airplane.h"
#pragma hdrstop
// Constructor performs initialization
//
Airplane::Airplane(const char* _name, int _type) :
type(_type),
status(ONRAMP),
speed(0),
altitude(0),
heading(0)
{
switch (type) {
case AIRLINER : ceiling = 35000; break;
case COMMUTER : ceiling = 20000; break;
case PRIVATE : ceiling = 8000;
}
name = new char[50];
strcpy(name, _name);
}
//
// Destructor performs cleanup.
//
Airplane::~Airplane()
{
delete[] name;
}
//
// Gets a message from the user.
//
bool Airplane::SendMessage(int msg, char* response, int spd, int dir, int alt)
{
//
// Check for bad commands.
//
if (spd > 500) {
strcpy(response, "Speed cannot be more than 500.");
return false;
}
if (dir > 360) {
strcpy(response, "Heading cannot be over 360 degrees.");
return false;
}
if (alt < 100 && alt != -1) {
strcpy(response, "I'd crash, bonehead!");
return false;
}
if (alt > ceiling) {
strcpy(response, "I can't go that high.");
return false;
}
//
// Do something base on which command was sent.
//
switch (msg) {
case MSG_TAKEOFF : {
D:\75807564.doc vom 13.10.99
4-82
BBS III Mainz
OOP für IT-Berufe mit C++
// Can't take off if already in the air!
if (status != ONRAMP) {
strcpy(response, "I'm already in the air!");
return false;
}
TakeOff(dir);
break;
}
case MSG_CHANGE : {
// Can't change anything if on the ground.
if (status == ONRAMP) {
strcpy(response, "I'm on the ground");
return false;
}
// Only change if a non-negative value was passed.
if (spd != -1) speed = spd;
if (dir != -1) heading = dir;
if (alt != -1) altitude = alt;
status == CRUISING;
break;
}
case MSG_LAND : {
if (status == ONRAMP) {
strcpy(response, "I'm already on the ground.");
return false;
}
Land();
break;
}
case MSG_REPORT : ReportStatus();
}
//
// Standard reponse if all went well.
//
strcpy(response, "Roger.");
return true;
}
//
// Perform takeoff.
//
void
Airplane::TakeOff(int dir)
{
heading = dir;
status = TAKINGOFF;
}
//
// Perform landing.
//
void
Airplane::Land()
{
speed = heading = altitude = 0;
status == ONRAMP;
}
//
// Build a string to report the airplane's status.
//
int
Airplane::GetStatus(char* statusString)
{
sprintf(statusString, "%s, Altitude: %d, Heading: %d, "
"Speed: %d\n", name, altitude, heading, speed);
return status;
}
//
// Get the status string and output it to the screen.
//
void
Airplane::ReportStatus()
D:\75807564.doc vom 13.10.99
4-83
BBS III Mainz
{
char buff[100];
GetStatus(buff);
cout << endl << buff << endl;
}
OOP für IT-Berufe mit C++
int getInput(int max);
void getItems(int& speed, int& dir, int& alt);
void main()
{
char returnMsg[100];
//
// Set up an array of Airplanes and create
// three Airplane objects.
//
Airplane* planes[3];
planes[0] = new Airplane("TWA 1040");
planes[1] = new Airplane("United Express 749", COMMUTER);
planes[2] = new Airplane("Cessna 3238T", PRIVATE);
//
// Start the loop.
//
do {
int plane, message, speed, altitude, direction;
speed = altitude = direction = -1;
//
// Get a plane to whom a message will be sent.
// List all of the planes and let the user pick one.
//
cout << endl << "Who do you want to send a message to?";
cout << endl << endl << "0. Quit" << endl;
for (int i=0;i<3;i++)
cout << (i + 1) << ". " << planes[i]->name << endl;
//
// Call the getInput() function to get the plane number.
//
plane = getInput(4);
//
// If the user chose item 0 then break out of the loop.
//
if (plane == -1) break;
//
// The plane acknowledges.
//
cout << endl << planes[plane]->name << ", roger.";
cout << endl << endl;
//
// Allow the user to choose a message to send.
//
cout << "What message do you want to send?" << endl;
cout << endl << "0. Quit" << endl;;
cout << "1. State Change" << endl;
cout << "2. Take Off" << endl;
cout << "3. Land" << endl;
cout << "4. Report Status" << endl;
message = getInput(5);
//
// Break out of the loop if the user chose 0.
//
if (message == -1) break;
//
// If the user chose item 1 then we need to get input
// for the new speed, direction, and altitude. Call
// the getItems() function to do that.
//
if (message == 0)
getItems(speed, direction, altitude);
//
// Send the plane the message.
D:\75807564.doc vom 13.10.99
4-84
BBS III Mainz
OOP für IT-Berufe mit C++
//
bool goodMsg = planes[plane]->SendMessage(
message, returnMsg, speed, direction, altitude);
//
// Something was wrong with the message
//
if (!goodMsg) cout << endl << "Unable to comply.";
//
// Display the plane's response.
//
cout << endl << returnMsg << endl;
} while (1);
//
// Delete the Airplaine objects.
//
for (int i=0;i<3;i++) delete planes[i];
}
int getInput(int max)
{
int choice;
do {
choice = getch();
choice -= 49;
} while (choice < -1 || choice > max);
return choice;
}
void getItems(int& speed, int& dir, int& alt)
{
cout << endl << "Enter new speed: ";
getch();
cin >> speed;
cout << "Enter new heading: ";
cin >> dir;
cout << "Enter new altitude: ";
cin >> alt;
cout << endl;
}
//---------------------------------------------------------------------------
Im Header finden Sie am Anfang der Datei #define-Anweisungen, mit denen Text (nach Konvention in
Grossbuchstaben) durch Ziffern ersetzt wird. Danach erfolgt die Deklaration der Airplane-Klasse. In dieser gibt
es eine überladene Funktion, GetStatus(); wird sie mit einem Zeiger auf ein char-Array als Argument
aufgerufen, liefert sie eine Statusmeldung und den Wert des Datenelements status zurück, wird sie ohne
Argument aufgerufen, wird nur das Datenelement status zurückgeliefert.
Das Programm Airport1.cpp beginnt mit der Definition der Klasse Airplane. Der Konstruktor führt
Initialisierungen durch und reserviert Speicherplatz für das char-Array, das den Namen des Flugzeugs enthält.
Dieser Speicherplatz wird später vom Destruktor wieder freigegeben. In der Funktion SendMessage() überprüft
eine switch-Anweisung die eingegangene Meldung und sorgt für eine entsprechende Reaktion. Im Hauptteil des
Programms richtet das Programm ein Array von Airplane-Zeigern ein und bildet drei Instanzen der AirplaneKlasse. Danach beginnt eine Schleife, in der Sie den einzelnen Flugzeugen Nachrichten schicken können. Die
while-Schleife wird endlos ausgeführt, kann jedoch bei Eingabe einer "0" mit break beendet werden.
4.2.9 Übungsaufgaben:
1. (Intclass.cpp) Erstellen Sie eine Klasse, die einen Teil der Funktionalität des Datentyps Integer imitiert und
nennen Sie diese Int. Das einzigen Datenelement dieser Klasse ist eine Integervariable. Desweiteren sollen
Elementfunktionen enthalten sein, die ein Objekt mit 0 initialisieren, die ein Objekt mit einem int-Wert
initialisieren und die die Variable ausgeben. Außerdem sollen 2 Int-Objekte addiert werden. Schreiben Sie
ein Programm Intclass.cpp, das diese Klasse ausführt, indem Sie zwei initialisierte und ein nichtinitialisiertes
D:\75807564.doc vom 13.10.99
4-85
BBS III Mainz
OOP für IT-Berufe mit C++
Int-Objekt erstellen, die beiden initialisierten addieren, das Ergebnis dem nichtinitialiserten zuweisen und
dann ausgeben.
2. (Maut.cpp) Stellen Sie sich eine Mautstelle an einer Brücke vor. Jedes Fahrzeug kostet einen Euro, jedoch
bezahlen nicht alle Fahrer. Eine elektronische Kasse zählt sowohl die Fahrzeuge als auch die eingenommene
Geldmenge. Simulieren Sie diese Mautstelle mit einer Klasse Mautkasse. Die beiden Datenfelder sind vom
Typ unsigned int für die Anzahl der Fahrzeuge und vom Typ double für die Geldmenge. Ein Konstruktor soll
beide Datenfelder mit 0 initialiseren. Eine Methode Bezfahrzeug() erhöht die Anzahl der Fahrzeuge und
addiert einen Euro zur Gesamteinnahme. Eine weitere Methode, Nbezfahrzeug() erhöht zwar die Anzahl der
Fahrzeuge, nicht jedoch die Gesamteinnahme. Schließlich soll eine Methode Anzeige() die beiden Resultate
ausgeben. Für das Hauptprogramm Maut.cpp lassen Sie den Benutzer eine Taste drücken für ein zahlendes
Fahrzeug und eine andere für ein nichtbezahlendes Fahrzeug. Mit ESC gibt das Programm die
Fahrzeuganzahl und die Gesamteinnahme aus und terminiert.
D:\75807564.doc vom 13.10.99
4-86
BBS III Mainz
OOP für IT-Berufe mit C++
4.3 Vererbung
4.3.1 Das Prinzip der Vererbung
Vererbung ist neben den Klassen das herausragende Merkmal der Objekt-orientierten
Programmierung. Vererbung ist der Prozess der Entstehung neuer Klassen (abgeleiteter
Klassen) aus bereits bestehenden Klassen (Oberklassen).
Oberklasse
Eigenschaft 1
Eigenschaft 2
Fähigkeit 1
Fähigkeit 2
Abgeleitete Klasse
Eigenschaft 1
Eigenschaft 2
Eigenschaft 3
Fähigkeit 1
Fähigkeit 2
Fähigkeit 3
Der Pfeil bedeutet "ist abgeleitet von". Die abgeleitete Klasse erbt die Eigenschaften und Fähigkeiten
der Oberklasse. Beachten Sie, daß der Pfeil entgegen der Vererbungsrichtung und damit entgegen der
zeitlichen Entstehungsgeschichte der Klassen weist.
Die oberste Klasse wird auch Basisklasse genannt.
D:\75807564.doc vom 13.10.99
4-87
BBS III Mainz
OOP für IT-Berufe mit C++
Beispiel: Publikationen haben die Eigenschaften Titel und Preis, die in der Methode holedaten() und
zeigedaten() eingelesen und ausgegeben werden. Die Klasse Publikationen vererbt diese
Eigenschaften weiter, wenn das Zugriffsrecht public gesetzt wird. Nachkommen der Klasse
Publikationen könnten die abgeleiteten Klassen (=Nachkommen) Buch und CD sein. Buch
und CD erben zunächst alle Eigenschaften und Methoden von Publikationen, wenn das
Zugriffsrecht public gesetzt wird. Auch können Buch und CD erweitert und abgewandelt
werden (bei Buch werden die Anzahl Seiten zusätzlich abgefragt/ bei CD wird die Spielzeit
abgefragt). Buch erhält z.B. die neue Eigenschaft seitenzahl und CD die neue Eigenschaft
spielzeit. Beide Eigenschaften werden in der gleichnamigen Methode holedaten() und
zeigedaten() eingelesen und ausgegeben (=Erweiterung der gleichnamigen Methoden aus
der Basisklasse Publikationen).
Das Vererbungskonzept unterstützt die Wiederverwendbarkeit von Programmcode. Wenn eine
Basisklasse entworfen und getestet wurde, kann sie in verschiedenen Situationen benutzt werden,
ohne daß weitere Tests notwendig wären. Außerdem hilft das Vererbungskonzept beim Design
eines komplexen Programms. Ein Resultat der Vererbung ist die Verbreitung von
Klassenbibliotheken, die von anderen Programmierern oder Firmen erstellt wurden.
D:\75807564.doc vom 13.10.99
4-88
BBS III Mainz
OOP für IT-Berufe mit C++
// Programm Verlag1.cpp
//Vererbung von Klassen und Übernahme von Methoden aus der
//Basisklasse
//Fachjargon: Abgeleitete Klassen und Überschreiben von Methoden
#include <iostream.h>
#include <conio.h>
const int LEN = 80;
class Publikation
{
private:
char titel[LEN];
float preis;
public:
void holedaten()
{
cout << "\nBitte geben Sie den Titel ein: "; cin >> titel;
cout << "\nBitte geben Sie den Preis ein: "; cin >> preis;
}
void zeigedaten()
{
cout << "\nDer Titel ist: "; cout << titel;
cout << "\nDer Preis ist: "; cout << preis;
}
};
Die Klasse Buch
wird von der Klasse
Publikation abgeleitet
class Buch : public Publikation
{
private:
int seitenzahl;
public:
Definition der Methode „holedaten()“
void holedaten()
der abgeleiteten Klasse Buch
{
Publikation::holedaten();
Die Methode „holedaten“ der
Klasse Publikation wird
aufgerufen
Man sagt:
DIE METHODE „holedaten() von
Buch“
überschreibt die METHODE
„holedaten() von Publikation“.
cout << "\nBitte geben Sie die Anzahl der Buchseiten ein: ";
cin >> seitenzahl;
}
void zeigedaten()
{
Publikation::zeigedaten();
cout << "\nDie Anzahl der Seiten beträgt: " << seitenzahl;
}
};
D:\75807564.doc vom 13.10.99
4-89
BBS III Mainz
OOP für IT-Berufe mit C++
class CD : public Publikation
{
private:
float spielzeit;
1
public:
2
void holedaten()
{
Publikation::holedaten();
cout << "\nBitte geben Sie die Spielzeit der CD ein: ";
cin >> spielzeit;
}
void zeigedaten()
{
Publikation::zeigedaten();
3
cout << "\nDie Spielzeit der CD beträgt: " << spielzeit;
}
};
void main()
{
Buch buch1;
CD cd1;
buch1.holedaten();
cd1.holedaten();
buch1.zeigedaten();
cd1.zeigedaten();
Vererbung von Zugriffsrechten
class Buch : public Publikation
Zugriff auf Methoden und Daten öffnen
Public bedeutet, dass Funktionen und Daten der Oberklasse, die als
public gekennzeichnet sind, auch als Funktionen und Daten der
abgeleiteten Klasse zur Verfügung stehen und von ihren
Objektinstanzen benutzt werden können.
Zugriff auf Methoden und Daten sperren
getch();
}
Wenn anstelle von public private verwendet würde, können die
Objektinstanzen der abgeleiteten Klasse nicht auf public-Funktionen
und Daten der Basisklasse zugreifen.
Da Objektinstanzen niemals auf private und public Daten einer Klasse
zugreifen können, wäre das Ergebnis in diesem Fall, dass
Objektinstanzen von abgeleiteten Klassen niemals auf Elemente der
Basisklasse zugreifen könnten
D:\75807564.doc vom 13.10.99
4-90
BBS III Mainz
OOP für IT-Berufe mit C++
Merke:
Eine abgeleitete Klasse kann nur auf public- und protected-Teile
einer Oberklasse zugreifen. Die private-Teile der Oberklasse
sind für die abgeleitete Klasse im Prinzip unsichtbar
„Wäre holedaten() von Publikation mit dem Zugriffsrecht private
ausgestattet, dann könnten die abgeleiteten Klassen Buch und Cd
auf die Methode holedaten() nicht zugreifen“
Klassenhierarchie
Entwickeln Sie ein Programm, dass die Vererbung bereits beim Design einsetzt (siehe
VERLAG1). Es wird die Datenbank eines kleinen Unternehmens mit den drei Kategorien
Manager, Wissenschaftler und Arbeiter, gefordert. Sie enthält einen Namen und eine Nummer
für jeden Beschäftigten sowie bei Managern deren Titel und ihren jährlichen Golfklubbeitrag
und bei Wissenschaftlern ihre Publikationsanzahl.
Lösungsansätze
Klasse Beschaeftigte
Name
Nummer
Klasse Manager
Klasse Wissenschaftler Klasse Arbeiter
Titel
Publikationen
Golfklubbeitrag
Im Hauptprogramm werden vier Objekte von verschiedenen Klassen deklariert: zwei Manager,
ein Wissenschaftler und ein Arbeiter. Dann werden die jeweiligen Methoden (holedaten()- und
zeigedaten()) aufgerufen, um Daten ein- und auszugeben.
Hinweis
Welchen Zweck hat die Basisklasse Beschaeftigte, wenn sie keine
Objekte benötigt?
D:\75807564.doc vom 13.10.99
4-91
BBS III Mainz
OOP für IT-Berufe mit C++
Diese dient nur als Ausgangspunkt zur Erstellung weiterer
abgeleiteteter Klassen. Eine solche Klasse nennt man abstrakte
Klasse, in diesem Fall abstrakte Basisklasse.
Das Vererbungskonzept unterstützt die Wiederverwendbarkeit von Programmcode. Wenn eine Basisklasse
entworfen und getestet wurde, kann sie in verschiedenen Situationen benutzt werden, ohne daß weitere Tests
notwendig wären. Außerdem hilft das Vererbungskonzept beim Design eines komplexen Programms. Ein
Resultat der Vererbung ist die Verbreitung von Klassenbibliotheken, die von anderen Programmierern oder
Firmen erstellt wurden.
Wir betrachten als Beispiel die bei der Operator-Überladung schon behandelte Klasse Zaehler. Wir sind
zufrieden mit ihrer Funktionalität, möchten aber noch eine Möglichkeit einfügen, um den Zaehler zu verringern.
Angenommen, wir möchten die Kunden einer Bank zählen und erhöhen den Zähler, wenn ein Kunde die Bank
betritt und verringern ihn wieder, wenn er sie verläßt, so daß der Zähler immer den aktuellen Kundenstand in
der Bank zu einem beliebigen Zeitpunkt angibt.
//Zaehler3.cpp
//Vererbung mit Zähler-Klasse
#include <iostream.h>
#include <conio.h>
class Zaehler
{
protected:
unsigned int count;
public:
Zaehler()
{ count = 0; }
Zaehler(int c)
{ count = c; }
int hole_zaehler(){ return count; }//
Zaehler operator ++ () { return Zaehler(++count); }
};
class Zaehlerab : public Zaehler
{
public:
Zaehler operator --() {return Zaehler(--count); }
};
void main()
{
Zaehlerab c1;
cout << "\nc1=" << c1.hole_zaehler();
++c1; ++c1; ++c1;
cout << "\nc1=" << c1.hole_zaehler();
--c1; --c1; --c1;
cout << "\nc1=" << c1.hole_zaehler();
getch();
}
Nach der Klasse Zaehler wird die abgeleitete Klasse Zaehlerab spezifiziert. Die Syntax class Zaehlerab : public
Zaehler gibt an, dass die Klasse Zaehlerab von der Klasse Zaehler abgeleitet wurde. Die Bedeutung von public
vor Zaehler erfahren Sie im Abschnitt „Private- und Public-Vererbung“.
Die Klasse Zaehlerab enthält eine neue Funktion, operator--(), die den Zähler verringert. Das entscheidende bei
der Vererbung ist nun, dass die abgeleitete Klasse alle Daten und Methoden der Basisklasse enthält, so daß
diese nicht mehr aufgeführt werden, aber trotzdem zur Verfügung stehen. Der einfache Doppelpunkt beschreibt
die Beziehung zwischen den Klassen.
D:\75807564.doc vom 13.10.99
4-92
BBS III Mainz
OOP für IT-Berufe mit C++
Ein wichtiger Punkt bei der Vererbung ist es, den Zugriff auf Methoden der Basisklasse genau zu kennen. Im
Hauptprogramm des letzten Zaehler3-Beispiels wurde durch Zaehlerab c1 ein Objekt der Klasse Zaehlerab
erstellt und mit 0 initialisiert. Wie war dies möglich, ohne dass ein Konstruktor in dieser Klasse definiert ist, der
die Initialisierung hätte ausführen können? Unter bestimmten Umständen wird diese Aufgabe dann vom
Konstruktor ohne Argument der Basisklasse übernommen. Für die Ausnahmen siehe „Konstruktoren in
abgeleiteten Klassen“. Das Objekt c1 der Zaehlerab-Klasse benutzt auch den Operator operator++() und die
Funktion hole_zaehler() aus der Klasse zaehler. Da der Compiler die Funktionen nicht in der Klasse von c1
findet, benutzt er diejenigen der Basisklasse.
Vererbung funktioniert nicht in die Gegenrichtung; Oberklassen und deren Instanzen wissen nichts von den
abgeleiteten Klassen und deren Objekten
4.3.2 Protected
In dem Beispiel Zaehler3.cpp finden wir in der Zaehler-Klasse zum ersten Mal die Kennzeichnung protected
vor dem Datenelement count, während bisher nur public oder private verwendet wurden.
Bei der Betrachtung ohne Vererbung galt: Auf Klassenelemente, seien es Daten oder Methoden, kann immer
von der eigenen Klasse zugegriffen werden, unabhängig davon, ob die Elemente als public oder private
gekennzeichnet sind. Jedoch können Objekte einer Klasse, die außerhalb definiert wurde, auf die Elemente der
betreffenden Klasse nur zugreifen, wenn diese als public gekennzeichnet sind.
Die Frage im Fall der Vererbung ist, ob Methoden der abgeleiteten Klasse auf Daten der Oberklasse zugreifen
können? Ja, wenn diese Daten public oder protected sind; nein, wenn sie private sind. Im obigen Beispiel sollte
count nicht public sein, da sonst jede Funktion darauf Zugriff hat, was nicht erwünscht ist. Beachten Sie, dass
zwar von Methoden der eigenen Klasse und von abgeleiteten Klassen auf ein mit protected gekennzeichnetes
Datenfeld zugegriffen werden kann, nicht jedoch von außerhalb definierten Funktionen wie main().
Bezeichner
Zugriff von der
eigenen Klasse
Zugriff von der
abgeleiteten Klasse
Zugriff von Objekten
ausserhalb der
Klassen
Public
Ja
Ja
Ja
Protected
Ja
Ja
Nein
Private
Ja
Nein
Nein
4.3.3 Konstruktoren in abgeleiteten Klassen
Betrachten Sie das Zaehler3-Programm: Wie kann man ein Zaehlerab-Objekt mit einem Wert initialisieren?
Kann man dies mit dem Konstruktor mit einem Argument aus Zaehler tun? Nein, da nur Konstruktoren ohne
Argument vererbt werden. Daher muß man für die abgeleitete Klasse neue Konstruktoren definieren:
//Zaehler4.cpp
//Konstruktoren in der abgeleiteten Klasse
#include <iostream.h>
#include <conio.h>
class Zaehler
{
D:\75807564.doc vom 13.10.99
4-93
BBS III Mainz
protected:
unsigned int count;
public:
Zaehler()
{
Zaehler(int c)
{
int hole_zaehler(){
Zaehler operator ++
};
OOP für IT-Berufe mit C++
count = 0; }
count = c; }
return count; }//
() { return Zaehler(++count); }
class Zaehlerab : public Zaehler
{
public:
Zaehlerab() : Zaehler() {}
Zaehlerab(int c) : Zaehler(c) {}
Zaehlerab operator --() {return Zaehlerab(--count); }
};
void main()
{
Zaehlerab c1;
Zaehlerab c2(200);
cout << "\nc1=" << c1.hole_zaehler();
cout << "\nc2=" << c2.hole_zaehler();
++c1; ++c1; ++c1;
cout << "\nc1=" << c1.hole_zaehler();
--c2; --c2;
cout << "\nc2=" << c2.hole_zaehler();
Zaehlerab c3 = --c2;
cout << "\nc3=" << c3.hole_zaehler();
getch();
}
Um einen Konstruktor mit einem Argument in der abgeleiteten Klasse zu definieren, bedient man sich der
folgenden Schreibweise:
Zaehlerab(int c) : Zaehler(c) { }
Der Doppelpunkt bedeutet, dass beim Aufruf des Ein-Argument-Konstruktors (z.B. durch Zaehlerab c2(200) )
der Konstruktor der Oberklasse mit dem Argument 200 aufgerufen wird.
4.3.4 Überschreiben von Methoden
Dazu betrachten wir das folgende Beispiel, in dem ein Array als Datenfeld einer Klasse definiert ist.
//stackar.cpp
//Array als Datenfeld einer Klasse
#include <iostream.h>
#include <conio.h>
const int MAX = 50;
class Stack
{
private:
int st[MAX];
int top;
public:
Stack()
{ top = 0; }
void push(int var)
{ st[++top] = var; }
int pop()
D:\75807564.doc vom 13.10.99
4-94
BBS III Mainz
{ return st[top--]; }
OOP für IT-Berufe mit C++
};
void main()
{
Stack s1;
s1.push(111);
s1.push(222);
cout << "1: " << s1.pop() << endl;
cout << "2: " << s1.pop() << endl;
s1.push(333);
s1.push(444);
s1.push(555);
s1.push(666);
cout
cout
cout
cout
<<
<<
<<
<<
"3:
"4:
"5:
"6:
"
"
"
"
<<
<<
<<
<<
s1.pop()
s1.pop()
s1.pop()
s1.pop()
<<
<<
<<
<<
endl;
endl;
endl;
endl;
getch();
}
Der Stack selbst besteht aus dem Feld st. Die Integer-Variable top kennzeichnet den Index des zuletzt auf dem
Stack abgelegten Elements. Elemente werden mit push() abgelegt und mit pop() vom Stack geholt. Das obige
Hauptprogramm legt zwei Datenelemente auf dem Stack ab, nimmt sie wieder herunter und gibt sie aus. Dann
erfolgt das Gleiche mit 4 Datenelementen. Die Ausgabe ist:
1:
2:
3:
4:
5:
6:
222
111
666
555
444
333
Wie man sieht, werden die Datenelemente in der umgekehrten Reihenfolge ausgegeben. Dies nennt man auch
LIFO: last in, first out.
Durch st[++top] = var; wird in der Funktion push() zuerst top erhöht, so dass es auf das nächste freie
Feldelement zeigt und danach erst der Wert der Variablen var zugewiesen. Der Befehl return st[top--]; gibt
zuerst den Wert der Stackspitze an die aufrufende Funktion (hier main()) zurück und verringert dann top so,
dass es auf das vorangegangene Element zeigt.
Das Programm hat jedoch zwei Schwächen:
 wenn zu viele Items auf den Stack geschrieben werden, führt dies zum Programmabbruch.
 wenn zu viele Items gelesen werden, würde dies zu einem unsinnigen Resultat führen, da man Daten aus
Speicherbereichen ausserhalb des Arrays lesen würde.
Um diese Mängel zu beheben, wird eine neue Klasse Stack2 erstellt, die von Stack abgeleitet ist, aber in o.g.
Fällen Warnungen ausgibt.
//stack2.cpp
//Überschreiben von Methoden
#include <iostream.h>
#include <process.h> //Für den Befehl exit()
#include <conio.h>
D:\75807564.doc vom 13.10.99
4-95
BBS III Mainz
OOP für IT-Berufe mit C++
const int MAX = 50;
class Stack
{
protected:
int st[MAX];
int top;
public:
Stack()
{ top = 0; }
void push(int var)
{ st[++top] = var; }
int pop()
{ return st[top--]; }
};
class Stack2 : public Stack
{
public:
void push(int var)
{
if (top >=MAX)
{ cout << "\nFehler: Stack ist voll"; exit(1); }
Stack::push(var);
}
int pop()
{
if (top <= 0)
{ cout << "\nFehler: Stack ist leer"; exit(1); }
return Stack::pop();
}
};
void main()
{
Stack2 s1;
s1.push(111);
s1.push(222);
s1.push(333);
cout << "1: " << s1.pop() << endl;
cout << "2: " << s1.pop() << endl;
cout << "3: " << s1.pop() << endl;
getch();
cout << "4: " << s1.pop() << endl; //einmal zuviel
}
Die abgeleitete Klasse Stack2 enthält zwei Funktionen, push() und pop(). Diese Funktionen haben denselben
Namen, dieselben Argumente und dieselben Rückgabewerte wie die Funktionen in der Oberklasse Stack. Wenn
man eine solche Funktion in main() aufruft (z.B. s1.push()), stellt sich die Frage, woher der Compiler weiß,
welche der beiden push-Funktionen verwendet werden soll?
Die Regel lautet, dass in einem solchen Fall die Funktion der abgeleiteten Klasse ausgeführt wird. Man sagt,
dass die abgeleitete Methode die Methode der Oberklasse überschreibt.
In den obigen Beispiel wird also die push-Funktion von Stack2 ausgeführt, nicht die von Stack. Diese Funktion
überprüft, ob der Stack voll ist, gibt gegebenenfalls eine Fehlermeldung aus und beendet das Programm. Falls
noch Platz auf dem Stack ist, wird die push-Funktion von Stack aufgerufen. Analog funktioniert pop().
D:\75807564.doc vom 13.10.99
4-96
BBS III Mainz
OOP für IT-Berufe mit C++
Auffällig ist noch die Kennzeichnung der Datenelemente von Stack mit protected, da bei Kennzeichnung als
private Methoden von Stack2 nicht darauf zugreifen könnten.
4.3.5 Klassenhierarchie
Im folgenden Beispiel wird Vererbung bereits beim Design des Programms eingesetzt. Es wird die Datenbank
eines kleinen Unternehmens mit den drei Kategorien Manager, Wissenschaftler und Arbeiter, abgebildet. Sie
enthält einen Namen und eine Nummer für jeden Beschäftigten sowie bei Managern deren Titel und ihren
jährlichen Golfklubbeitrag und bei Wissenschaftlern ihre Publikationsanzahl.
D:\75807564.doc vom 13.10.99
4-97
BBS III Mainz
OOP für IT-Berufe mit C++
Klasse Beschaeftigte
Name
Nummer
Klasse Manager
Titel
Klasse Wissenschaftler Klasse Arbeiter
Publikationen
Golfklubbeitrag
//Programm besch.cpp
//Vererbung anhand eines einfachen Firmenmodells
#include <iostream.h>
#include <conio.h>
class Beschaeftigte
{
private:
char name[80];
unsigned long nummer;
public:
void holedaten()
{
cout << "\n Bitte den Nachnamen eingeben: ";
cin >> name;
cout << "\n Bitte die Beschaeftigtennummer eingeben: ";
cin >> nummer;
}
void zeigedaten()
{
cout << "\n Name: " <<name;
cout << "\n Nummer: " << nummer;
}
};
class Manager : public Beschaeftigte
{
private:
char titel[80];
double gbeitrag;
public:
void holedaten()
{
Beschaeftigte::holedaten();
cout << "\n Bitte den Titel eingeben: ";
cin >> titel;
cout << "\n Bitte den Golfbeitrag eingeben: ";
cin >> gbeitrag;
}
void zeigedaten()
{
Beschaeftigte::zeigedaten();
cout << "\n Titel: " <<titel;
cout << "\n Golfbeitrag: " << gbeitrag;
}
};
class Wissenschaftler : public Beschaeftigte
{
private:
D:\75807564.doc vom 13.10.99
4-98
BBS III Mainz
int publik;
OOP für IT-Berufe mit C++
public:
void holedaten()
{
Beschaeftigte::holedaten();
cout << "\n Bitte die Anzahl der Publikationen eingeben: ";
cin >> publik;
}
void zeigedaten()
{
Beschaeftigte::zeigedaten();
cout << "\n Anzahl der Publikationen: " << publik;
}
};
class Arbeiter : public Beschaeftigte
{
};
void main()
{
Manager mana1, mana2;
Wissenschaftler wiss1;
Arbeiter arb1;
cout << endl;
cout << "\nBitte Daten fuer Manager 1 eingeben";
mana1.holedaten();
cout << "\nBitte Daten fuer Manager 2 eingeben";
mana2.holedaten();
cout <<endl;
cout << "\nBitte Daten für Wissenschaftler 1 eingeben";
wiss1.holedaten();
cout <<endl;
cout << "\nBitte Daten für Arbeiter 1 eingeben";
arb1.holedaten();
cout << "\n Daten von
mana1.zeigedaten();
cout <<endl;
cout << "\n Daten von
mana2.zeigedaten();
cout <<endl;
cout << "\n Daten von
wiss1.zeigedaten();
cout <<endl;
cout << "\n Daten von
arb1.zeigedaten();
Manager 1";
Manager 2";
Wissenschaftler 1";
Arbeiter 1";
getch();
}
Im Hauptprogramm werden vier Objekte von verschiedenen Klassen deklariert: zwei Manager, ein
Wissenschaftler und ein Arbeiter. Dann werden die jeweiligen holedaten()- und zeigedaten()-Funktionen
aufgerufen, um Daten ein- und auszugeben.
Eine Verbesserung des Programms wäre z.B. die Speicherung der Daten in einem Feld oder einer Datei.
Auffällig ist, dass es keine Objekte der Basisklasse Beschaeftigte gibt. Diese dient nur als Ausgangspunkt zur
Erstellung weiterer abgeleiteteter Klassen. Eine solche Klasse nennt man abstrakte Klasse, in diesem Fall
abstrakte Basisklasse.
D:\75807564.doc vom 13.10.99
4-99
BBS III Mainz
OOP für IT-Berufe mit C++
4.3.6 Vererbung von Zugriffsrechten
Ein weiterer Mechanismus des Zugriffsschutzes auf Klassen ist die Art ihrer Vererbung., z.B. class Manager :
public Beschaeftigte wie im Beispiel Besch.cpp. Welche Bedeutung hat das Schlüsselwort public in diesem
Zusammenhang? Es bedeutet, dass Funktionen und Daten der Oberklasse, die als public gekennzeichnet sind,
auch als Funktionen und Daten der abgeleiteten Klasse zur Verfügung stehen und von ihren Objektinstanzen
benutzt werden können. Wenn anstelle von public private verwendet würde, können die Objektinstanzen der
abgeleiteten Klasse nicht auf public-Funktionen der Basisklasse zugreifen.. Da Objektinstanzen niemals auf
private oder protected Daten einer Klasse zugreifen könen, wäre das Ergebnis in diesem Fall, dass
Objektinstanzen von abgeleiteten Klassen niemals auf Elemente der Basisklasse zugreifen könnten.
//Programm privpubl.cpp
//Private und public Vererbung
#include <iostream.h>
class A
{
private:
int privatdatenA;
protected:
int protdatenA;
public:
int pubdatenA;
}
class B : public A
{
public:
void funktion()
{
int vari;
vari = privatdatenA;
vari = protdatenA;
vari = pubdatenA;
}
};
class C : private A
{
public:
void funktion()
{
int vari;
vari = privatdatenA;
vari = protdatenA;
vari = pubdatenA;
}
//Fehler: kein Zugriff möglich
// o.k.
// o.k.
//Fehler: kein Zugriff möglich
// o.k.
// o.k.
void main()
{
int vari;
B objB;
vari = objB.privatdatenA;
vari = objB.protdatenA;
vari = objB.pubdatenA;
C objC;
vari = objC.privatdatenA;
vari = objC.protdatenA;
vari = objC.pubdatenA;
//Fehler: kein Zugriff möglich
//Fehler: kein Zugriff möglich
// o.k. (B ist public vererbt)
//Fehler: kein Zugriff möglich
//Fehler: kein Zugriff möglich
//Fehler: kein Zugriff möglich
// (B ist privat vererbt)
}
D:\75807564.doc vom 13.10.99
4-100
BBS III Mainz
OOP für IT-Berufe mit C++
In der folgenden Tabelle erkennt man, welche Zugriffsattribute in der abgeleiteten Klasse entstehen, je nachdem
welche Ableitungsart gewählt und welches Attribut in der Oberklasse verwendet wurde:
Oberklasse
Attribut bei Ableitung
private
protected
public
private
private
(kein Zugriff)
private
(kein Zugriff)
private
(kein Zugriff)
protected
private
protected
protected
public
private
protected
public
Eine abgeleitete Klasse kann somit nur auf die public- und protected-Teile einer Oberklasse zugreifen. Die
private-Teile der Oberklasse sind für die abgeleitete Klasse im Prinzip unsichtbar.
Public-Vererbung ist dann sinnvoll, wenn in der abgeleiteten Klasse die gleichen public-Deklarationen
vorkommen sollen, während private-Vererbung dann eingesetzt werden sollte, wenn keine Teile der Oberklasse
zur Schnittstelle (der public-Deklaration) der abgeleiteten Klasse gehören soll.
Wenn kein Schlüsselwort bei der Vererbung verwendet wird, wird eine private-Vererbung angenommen.
4.3.7 Mehrere Stufen der Vererbung
Klassen können von Klassen abgeleitet werden, die wiederum von anderen Klassen abgeleitet wurden.
Bsp.:
class A {};
class B : public A
{};
class C : public B
{};
In diesem Fall ist B abgeleitet von A und C wiederum von B. Diesen Prozeß kann man bis ins Unendliche
weiterführen.
Als konkretes Beispiel soll eine Erweiterung des Besch.cpp-Programms dienen: Angenommen, wir leiten von
der Gruppe Arbeiter die Gruppe Meister ab. Diese werden nach der Qualität ihrer Ergebnisse (100% ist das
Maximum) bewertet.
//Programm besch2.cpp
//Mehrere Ebenen der Vererbung
#include <iostream.h>
#include <conio.h>
class Beschaeftigte
{
private:
char name[80];
unsigned long nummer;
public:
void holedaten()
{
cout << "\n Bitte den Nachnamen eingeben: ";
cin >> name;
cout << "\n Bitte die Beschaeftigtennummer eingeben: ";
cin >> nummer;
}
void zeigedaten()
{
cout << "\n Name: " <<name;
cout << "\n Nummer: " << nummer;
}
};
D:\75807564.doc vom 13.10.99
4-101
BBS III Mainz
OOP für IT-Berufe mit C++
class Manager : public Beschaeftigte
{
private:
char titel[80];
double gbeitrag;
public:
void holedaten()
{
Beschaeftigte::holedaten();
cout << "\n Bitte den Titel eingeben: ";
cin >> titel;
cout << "\n Bitte den Golfbeitrag eingeben: ";
cin >> gbeitrag;
}
void zeigedaten()
{
Beschaeftigte::zeigedaten();
cout << "\n Titel: " <<titel;
cout << "\n Golfbeitrag: " << gbeitrag;
}
};
class Wissenschaftler : public Beschaeftigte
{
private:
int publik;
public:
void holedaten()
{
Beschaeftigte::holedaten();
cout << "\n Bitte die Anzahl der Publikationen eingeben: ";
cin >> publik;
}
void zeigedaten()
{
Beschaeftigte::zeigedaten();
cout << "\n Anzahl der Publikationen: " << publik;
}
};
class Arbeiter : public Beschaeftigte
{
};
class Meister : public Arbeiter
{
private:
float quote;
public:
void holedaten()
{
Arbeiter::holedaten();
cout << "\n Bitte die Erfolgsquote in Prozent eingeben: ";
cin >> quote;
}
void zeigedaten()
{
Arbeiter::zeigedaten();
cout << "\n Erfolgsquote: " << quote;
}
};
void main()
{
Manager mana1, mana2;
Wissenschaftler wiss1;
D:\75807564.doc vom 13.10.99
4-102
BBS III Mainz
Arbeiter arb1;
Meister meis1;
OOP für IT-Berufe mit C++
cout << endl;
cout << "\nBitte Daten fuer Manager 1 eingeben";
mana1.holedaten();
cout << "\nBitte Daten fuer Manager 2 eingeben";
mana2.holedaten();
cout <<endl;
cout << "\nBitte Daten für Wissenschaftler 1 eingeben";
wiss1.holedaten();
cout <<endl;
cout << "\nBitte Daten für Arbeiter 1 eingeben";
arb1.holedaten();
cout <<endl;
cout << "\nBitte Daten für Meister 1 eingeben";
meis1.holedaten();
cout << "\n Daten von
mana1.zeigedaten();
cout <<endl;
cout << "\n Daten von
mana2.zeigedaten();
cout <<endl;
cout << "\n Daten von
wiss1.zeigedaten();
cout <<endl;
cout << "\n Daten von
arb1.zeigedaten();
cout <<endl;
cout << "\n Daten von
meis1.zeigedaten();
getch();
}
Manager 1";
Manager 2";
Wissenschaftler 1";
Arbeiter 1";
Meister 1";
Auffällig ist, dass eine Klassenhierarchie nicht unbedingt einem Organigramm entsprechen muß. Eine
Klassenhierarchie entsteht durch Verallgemeinerung gemeinsamer Charakteristika. Je allgemeiner die Klasse,
umso höher ist sie in der Hierarchie, z.B. steht Arbeiter über Meister, der ein spezielles Beispiel für einen
Arbeiter ist.
4.3.8 Mehrfachvererbung
Eine Klasse kann von mehr als einer Basisklasse abgeleitet werden. Dies nennt man Mehrfachvererbung. Die
Syntax ist analog der bei der "normalen" Vererbung:
class A
{}; //Basisklasse A
class B
{};
//Basisklasse B
class C : public A, public B {};
// C ist von A und B abgeleitet
Angenommen, wir möchten in Besch.cpp die schulische Vorbildung der Beschäftigten integrieren. Weiterhin
angenommen, wir hätten schon eine Klasse Student entwickelt, die die Vorbildung von Studenten enthält.
Anstatt nun die Beschäftigten-Klasse zu modifizieren, damit sie die Vorbildungsdaten enthält, lösen wir das
Problem mittels Mehrfachvererbung von der Klasse Student. Diese enthält den Namen der Schule oder
Universität und den erreichten Abschluss. Zwei Funktionen getedu() und putedu() erwarten eine Eingabe und
stellen sie am Bildschirm dar. Die Vorbildung soll nur bei Managern und Wissenschaftlern eine Rolle spielen.
Angestellte
D:\75807564.doc vom 13.10.99
Student
4-103
BBS III Mainz
Arbeiter
Wissenschaftler
Manager
OOP für IT-Berufe mit C++
Die Pfeile drücken wieder die Relation "ist abgeleitet von" aus.
//Programm erbmehrf.cpp
//Mehrfachvererbung
#include <iostream.h>
#include <conio.h>
class Student
{
private:
char schule[80];
char abschluss[80];
public:
void getedu()
{
cout << "\nBitte geben Sie den Namen der Schule oder Universitaet ein: ";
cin >> schule;
cout << "\nBitte geben Sie Ihren hoechsten Abschluss ein: ";
cin >> abschluss;
}
void putedu()
{
cout <<"\n Schule oder Uni: " <<schule;
cout <<"\n Hoechster Abschluss: "<< abschluss;
}
};
class Beschaeftigte
{
private:
char name[80];
unsigned long nummer;
public:
void holedaten()
{
cout << "\n Bitte den Nachnamen eingeben: ";
cin >> name;
cout << "\n Bitte die Beschaeftigtennummer eingeben: ";
cin >> nummer;
}
void zeigedaten()
{
cout << "\n Name: " <<name;
cout << "\n Nummer: " << nummer;
}
};
class Manager : private Beschaeftigte, private Student
{
private:
char titel[80];
double gbeitrag;
public:
void holedaten()
{
Beschaeftigte::holedaten();
cout << "\nBitte den Titel eingeben: ";
cin >> titel;
cout << "\nBitte den Golfbeitrag eingeben: ";
cin >> gbeitrag;
Student::getedu();
}
void zeigedaten()
{
Beschaeftigte::zeigedaten();
cout << "\nTitel: " <<titel;
cout << "\nGolfbeitrag: " << gbeitrag;
D:\75807564.doc vom 13.10.99
4-104
BBS III Mainz
Student::putedu();
}
};
OOP für IT-Berufe mit C++
class Wissenschaftler : private Beschaeftigte, private Student
{
private:
int publik;
public:
void holedaten()
{
Beschaeftigte::holedaten();
cout << "\nBitte die Anzahl der Publikationen eingeben: ";
cin >> publik;
Student::getedu();
}
void zeigedaten()
{
Beschaeftigte::zeigedaten();
cout << "\nAnzahl der Publikationen: " << publik;
Student::putedu();
}
};
class Arbeiter : public Beschaeftigte
{
};
void main()
{
Manager mana1;
Wissenschaftler wiss1, wiss2;
Arbeiter arb1;
cout << endl;
cout << "\nBitte Daten fuer Manager 1 eingeben";
mana1.holedaten();
cout << "\nBitte Daten für Wissenschaftler 1 eingeben";
wiss1.holedaten();
cout <<endl;
cout << "\nBitte Daten für Wissenschaftler 2 eingeben";
wiss2.holedaten();
cout <<endl;
cout << "\nBitte Daten für Arbeiter 1 eingeben";
arb1.holedaten();
cout << "\nDaten von
mana1.zeigedaten();
cout <<endl;
cout << "\nDaten von
wiss1.zeigedaten();
cout <<endl;
cout << "\nDaten von
wiss1.zeigedaten();
cout <<endl;
cout << "\nDaten von
arb1.zeigedaten();
Manager 1";
Wissenschaftler 1";
Wissenschaftler 2";
Arbeiter 1";
getch();
}
D:\75807564.doc vom 13.10.99
4-105
BBS III Mainz
OOP für IT-Berufe mit C++
4.3.9 Übungsaufgaben
1. (Verlag1.cpp) Stellen Sie sich einen Verlag vor, der sowohl Bücher als auch CDs vermarktet. Erstellen Sie
eine Klasse Publikation, die den Titel (ein String) und den Preis (Typ float) einer Publikation beinhaltet. Von
dieser Klasse werden zwei Klassen abgeleitet: Buch, die zusätzlich eine Seitenzahl (Typ int) enthält und CD,
die eine Spielzeit in Minuten (Typ float) enthält. Alle diese Klassen sollen eine holedaten()-Funktion
enthalten, die den Benutzer zur Dateneingabe auffordert und eine zeigedaten()-Funktion zur Anzeige der
Daten. Schreiben Sie ein Programm Verlag.cpp zum Test dieser Klassen und lassen Sie Daten ein- und
ausgeben.
2. (Verlag2.cpp) Fügen Sie zu obigem Beispiel eine Klasse Verkauf hinzu, die ein Feld mit drei float-Zahlen
enthält, in dem die Verkäufe einer beliebigen Publikation in den letzten drei Monaten gespeichert werden.
Die holedaten()-Funktion soll die Verkaufszahlen vom Benutzer erfragen und die zeigedaten()-Funktion
diese anzeigen. Ändern Sie die Buch- und CD-Klassen so, dass sie sowohl von Publikation als auch von
Verkauf abgeleitet werden. Eine Instanz der Klassen Buch oder CD sollte die Verkaufszahlen zusammen mit
den anderen Daten ausgeben.
D:\75807564.doc vom 13.10.99
4-106
BBS III Mainz
OOP für IT-Berufe mit C++
4.4 Polymorphie
Unter Polymorphismus („Vielgestaltigkeit“) versteht man, dass mit der „gleichen“ Funktion für verschiedene
Objekte unterschiedliche Resultate erzielt werden können. Stellen Sie sich vor, Sie haben die Klasse Fahrzeug
entwickelt, wovon Sie die Klassen Auto und Motorrad abgeleitet haben. Jede dieser Klassen besitzt die
Funktion fahren (gleicher Funktionsname, gleiche Parameterliste), die jedoch in allen Klassen unterschiedlich
implementiert wird. Erst zur Laufzeit des Programms wird entschieden, welche Funktion ausgeführt werden
soll. Dies wird durch sogenannte virtuelle Funktionen erreicht.
4.4.1 Virtuelle Methoden/Funktionen
4.4.1.1 Funktionen ohne Zeigerzugriff
Betrachten Sie das folgende nicht-virtuelle Beispiel:
//Programm nvirtoz.cpp
#include <iostream.h>
#include <conio.h>
class X
{
public:
double A(double x)
{
return x*x;
}
double B(double x)
{
return A(x)/2;
}
};
class Y : public X
{
public:
double A(double x)
{
return x*x*x;
}
};
void main()
{
Y y;
cout << y.B(3) << endl;
getch();
}
Die Klasse X umfaßt die Funktionen A und B, wobei die Funktion B die Funktion A aufruft. Die Klasse Y, die
von der Klasse X abgeleitet ist, erbt die Funktion B, aber überschreibt die Funktion A. Die Ausgabe des
Programms ist jedoch 4.5=3*3/2 und nicht, wie beabsichtigt, 13.5=3*3*3/2! Der Compiler benutzt zur
Auflösung des Ausdrucks y.B(3) die geerbte Funktion X::B, die wiederum X::A aufruft. Um zu erreichen, dass
zur Berechnung in Y.B auch Y.A aufgerufen wird, muß man das Schlüsselwort virtual verwenden, um klar zu
machen, dass für die Funktion A Polymorphie erwünscht ist.
//virtoz.cpp
//Virtuelle Funktionen ohne Zeiger
#include <iostream.h>
#include <conio.h>
class X
D:\75807564.doc vom 13.10.99
4-107
BBS III Mainz
{
public:
virtual double A(double x)
{
return x*x;
}
double B(double x)
{
return A(x)/2;
}
};
OOP für IT-Berufe mit C++
class Y : public X
{
public:
virtual double A(double x)
{
return x*x*x;
}
};
void main()
{
Y y;
cout << y.B(3) << endl;
getch();
}
Dieses Beispiel gibt nun 13.5 aus, da zur Laufzeit bei der Berechnung von B die richtige Funktion A, nämlich
Y::A zugeordnet wird.
Wenn man eine Funktion als virtual deklariert hat, kann man sie nur mit Funktionen in den abgeleiteten Klassen
überschreiben, die die gleiche Parameterliste haben. Virtuelle Funktionen können nicht-virtuelle Funktionen in
Basisklassen überschreiben. Eine Funktion, die in einer bestimmten Klasse als virtuell deklariert wurde, wird in
allen abgeleiteten Klassen ebenfalls als virtuell angesehen, gleichgültig, ob das Schlüsselwort virtual verwendet
wurde oder nicht. Daher gilt die Regel für die Deklaration virtueller Funktionen: „einmal virtuell, immer
virtuell”. Wenn in der abgeleiteten Klasse nicht die gleiche Parameterliste verwendet wird, kann die abgeleitete
Klasse (und keine von ihr abgeleitete Klasse) nicht auf die virtuelle Funktion der Basisklasse zugreifen.
Man kann in C++ nicht-virtuelle, überladene Funktionen, die den gleichen Namen wie die virtuelle Funktion,
aber eine andere Parameterliste, haben, deklarieren. Außerdem werden nicht-virtuelle Funktionen, die den
gleichen Namen tragen wie virtuelle Funktionen, nicht vererbt.
//Programm virtmi.cpp
//Virtuelle und nicht-virtuelle Funktionen
#include <iostream.h>
#include <conio.h>
class A
{
public:
A() {}
virtual void foo(char c)
{
cout << "virtual A::foo() returns " << c << endl;
}
};
class B : public A
{
public:
B() {}
void foo(const char* s)
{
cout << "B::foo() returns " << s << endl;
}
D:\75807564.doc vom 13.10.99
4-108
BBS III Mainz
OOP für IT-Berufe mit C++
void foo(int i)
{
cout << "B::foo() returns " << i << endl;
}
virtual void foo(char c)
{
cout << "virtual B::foo() returns " << c << endl;
}
};
class C : public B
{
public:
C() {}
void foo(const char* s)
{
cout << "C::foo() returns " << s << endl;
}
void foo(double x)
{
cout << "C::foo() returns " << x << endl;
}
virtual void foo(char c)
{
cout << "virtual C::foo() returns " << c << endl;
}
};
void
{
A
B
C
main()
Aobj;
Bobj;
Cobj;
Aobj.foo('A');
Bobj.foo('B');
Bobj.foo(10);
Bobj.foo("Bobj");
Cobj.foo('C');
Cobj.foo(255.345);
Cobj.foo("Cobj");
cin.peek();
}
Die drei Klassen A, B und C bilden eine lineare Hierarchie. In A wird die Funktion foo(char) als virtuell
deklariert, B deklariert ihre eigene Version davon und die nicht-virtuellen, überladenen Funktionen foo(const
char*) und foo(int). Die von B abgeleitet Klasse C ist ähnlich definiert.
Zu beachten ist, dass C die Funktion foo(const char*) deklarieren muß, wenn sie die Funktion benötigt, da sie
die Funktion B::foo(const char*) nicht erben kann.
4.4.1.2 Zeiger auf Objekte
Virtuelle Funktionen werden sehr häufig in Kombination mit Zeigern verwendet. Dies wird im folgenden
verdeutlicht.
Zur Erinnerung:
element* bezeichnet den Zeiger auf ein Element.
*elementzeiger dereferenziert einen solchen Zeiger und ist selbst wieder vom Typ element.
elementzeiger = &element Elementzeiger zeigt auf die Adresse von element.
D:\75807564.doc vom 13.10.99
4-109
BBS III Mainz
OOP für IT-Berufe mit C++
Der new-Operator wird oft in Konstruktoren verwendet. Wir benutzen ihn in einem Beispiel mit einer
selbstdefinierten String-Klasse.
//Programm newstr.cpp
//Der Befehl new zur Reservierung von Speicher
#include <iostream.h>
#include <conio.h>
#include <string.h>
class String
{
private:
char* str;
public:
String(char* s)
{
int laenge = strlen(s);
str = new char[laenge+1];
strcpy(str,s);
}
~String()
{
delete str;
}
void anzeige()
{
cout << str;
}
};
void main()
{
String s1 = "Who knows nothing doubts nothing.";
cout << endl << "s1=";
s1.anzeige();
getch();
}
Die Klasse String enthält nur ein Datenelement, einen Zeiger str auf char. Dieser zeigt auf einen String, der im
String-Objekt selbst nicht enthalten ist. Der Konstruktor wird durch String s1 = "Who ..." aufgerufen. Eine
andere Schreibweise wäre String s1("Who ....");. Der Konstruktor in diesem Beispiel hat einen String als
Argument und reserviert mittels new Speicherplatz, um eine Kopie dieses Strings zu erstellen. Str zeigt auf
diesen Speicherplatz.
Mit Hilfe des Destruktors ~String() wird der reservierte Speicherplatz am Programmende wieder freigegeben.
Objekte werden normalerweise "zerstört", wenn die Funktion, in der sie definiert sind, beendet wird. Der
Destruktor indiesem Beispiel stellt sicher, dass der von einem String-Objekt benutzte Speicher dem System
zurückgegeben wird.
Wenn man zu Beginn eines Programms noch nicht weiß, wieviele Objekte erstellt werden sollen, benutzt man
new, um Objekte zur Laufzeit zu erstellen. Im folgenden Beispiel werden beide Arten der Objekterstellung
benutzt:
//Programm Englptr.cpp
//Feet und inches mit Pointern
#include <iostream.h>
#include <conio.h>
class laenge
{
private:
int feet;
float inches;
D:\75807564.doc vom 13.10.99
4-110
BBS III Mainz
OOP für IT-Berufe mit C++
public:
void holelaenge()
//Benutzer gibt Daten ein
{
cout << "\nBitte Feet eingeben: "; cin >> feet;
cout << "\nBitte Inches eingeben: ";cin >> inches;
}
void zeigelaenge()
//Länge ausgeben
{
cout << feet << "\'-" << inches << '\"'; }
};
void main()
{
laenge l1;
l1.holelaenge();
l1.zeigelaenge();
laenge* laengenzeiger;
laengenzeiger = new laenge;
laengenzeiger->holelaenge();
laengenzeiger->zeigelaenge();
getch();
}
In dem obigen Beispiel wird auf die Methoden des Objekts laenge mittels "->" - Operator zugegriffen, analog
wie bei Zeigern auf Strukturen. Der "->"- Operator hat bei Zeigern auf Objekte dieselbe Funktion wie der "."Operator bei Objektinstanzen.
4.4.1.3 Funktionen mit Zeigern
Im nächsten Beispiel werden Funktionen verwendet, die sowohl in der Basisklasse als auch in der abgeleiteten
Klasse denselben Namen haben.
//Programm Nichtvir.cpp
//Funktionen mit Zeigerzugriff
#include <iostream.h>
class Basis
{
public:
void zeige()
{ cout << "\nBasis"; }
};
class Abgel1 : public Basis
{
public:
void zeige()
{ cout << "\nAbgeleitete Klasse 1"; }
};
class Abgel2 : public Basis
{
public:
void zeige()
{ cout << "\nAbgeleitete Klasse 2"; }
};
void main()
{
D:\75807564.doc vom 13.10.99
4-111
BBS III Mainz
Abgel1 ab1;
Abgel2 ab2;
Basis* zeiger;
OOP für IT-Berufe mit C++
zeiger = &ab1;
zeiger->zeige();
zeiger = &ab2;
zeiger->zeige();
}
Die Klassen Abgel1 und Abgel2 sind von der Klasse Basis abgeleitet. Alle drei beinhalten eine Funktion zeige().
Im Hauptprogramm erstellen wir Funktionen der Klassen Abgel1 und Abgel2 und einen Zeiger aus Basis. Dann
setzen wir den Zeiger zeiger auf die Adresse eines abgeleiteten Objekts. Dies funktioniert, da Zeiger auf
Objekte einer abgeleiteten Klasse typkompatibel sind zu Zeigern auf Objekte der Oberklasse (jedoch nicht
umgekehrt). Die Frage ist, welche zeige()-Funktion ausgeführt wird? Ist es Basis::zeige() oder ab1::zeige() bzw.
ab2::zeige() ? Die Programmausgabe löst das Rätsel:
Basis
Basis
Wie man sieht, wird die Funktion der Basisklasse zweimal ausgeführt. Der Compiler ignoriert den Typ des
Inhalts des Zeigers zeiger und wählt diejenige Funktion aus, die mit dem Zeigertyp übereinstimmt. Manchmal
ergibt dies einen Sinn, löst jedoch in diesem Fall nicht das Problem.
Eine kleine Änderung, das Schlüsselwort "virtual" vor der zeige()-Funktion in der Basisklasse, löst das
Problem.
//Programm Nichtvir.cpp
//Funktionen mit Zeigerzugriff
#include <iostream.h>
#include <conio.h>
class Basis
{
public:
virtual void zeige()
{ cout << "\nBasis"; }
};
class Abgel1 : public Basis
{
public:
virtual void zeige()
{ cout << "\nAbgeleitete Klasse 1"; }
};
class Abgel2 : public Basis
{
public:
virtual void zeige()
{ cout << "\nAbgeleitete Klasse 2"; }
};
void main()
{
Abgel1 ab1;
Abgel2 ab2;
Basis* zeiger;
zeiger = &ab1;
zeiger->zeige();
zeiger = &ab2;
D:\75807564.doc vom 13.10.99
4-112
BBS III Mainz
zeiger->zeige();
OOP für IT-Berufe mit C++
getch();
}
Die Ausgabe des Programms ist nun:
Abgeleitete Klasse 1
Abgeleitete Klasse 2
Das Programm entscheidet nun erst zur Laufzeit, welche zeige()-Funktion aufgerufen wird, je nachdem, auf
welches Objekt zeiger zeigt. Dies nennt man “späte Bindung” oder “dynamische Bindung”.
4.4.2 Virtuelle Basisklassen
Eltern
Kind1
Kind2
Enkel
Dieses Beispiel ist im Sinne der Vererbung in der OOP zu verstehen. Es entstehen Probleme, wenn eine
Funktion aus einem Enkel-Objekt Daten oder Funktionen aus der Eltern-Klasse benutzen möchte.
//Programm basis1.cpp
//Mehrdeutigkeit beim Zugriff auf Basisklassen
#include <iostream.h>
#include <conio.h>
class Eltern
{
protected:
int basisdaten;
};
class Kind1 : public Eltern
{ };
class Kind2 : public Eltern
{ };
class Enkel : public Kind1, Kind2
{
public:
int holedaten()
{
return basisdaten; //Fehler: Mehrdeutigkeit
}
};
Es gibt einen Compiler-Fehler, wenn die holedaten()-Funktion in Enkel auf basisdaten in der Elternklasse
zugreifen möchte. Da Kind1 und Kind2 von Eltern abgeleitet sind, erbt jedes eine Kopie der Daten und
Funktionen des Elternobjekts, darunter auch basisdaten. Wenn das Enkel-Objekt nun auf basisdaten zugreift,
weiß es nicht, auf welche Kopie es zugreifen soll. Durch das Schlüsselwort virtual vor Kind1 und Kind2
erreicht man, dass beide Klassen sich eine Kopie der Daten und Methoden von basisdaten teilen.
//Programm basisvi.cpp
//Virtuelle Basisklassen
#include <iostream.h>
D:\75807564.doc vom 13.10.99
4-113
BBS III Mainz
#include <conio.h>
OOP für IT-Berufe mit C++
class Eltern
{
protected:
int basisdaten;
};
class Kind1 : virtual public Eltern
{ };
class Kind2 : virtual public Eltern
{ };
class Enkel : public Kind1, Kind2
{
public:
int holedaten()
{
return basisdaten; //Funktioniert jetzt
}
};
4.4.3 Friend-Funktionen
Das Konzept der Kapselung und des Schutzes der Daten eines Objekts gegen unbefugten Zugriff von außen
durch Schlüsselworte wie private oder protected kann sich manchmal nachteilig auswirken. Stellen Sie sich vor,
Sie möchten eine Funktion schreiben, die auf Objekten zweier verschiedener Klassen funktioniert und als
Argumente zwei Klassen hat, die in keinerlei Beziehung zueinander stehen. Wären sie von derselben Klasse
abgeleitet, könnte man die Funktion in die Basisklasse schreiben. Ist dies aber nicht so, dienen FriendFunktionen als Brücke zwischen zwei Klassen. Mit solchen Funktionen kann man auf alle Datenelemente einer
befreundeten Klasse zugreifen.
//Porgramm friend.cpp
//Friend-Funktionen
#include <iostream.h>
#include <conio.h>
class beta;
class alpha
{
private:
int data;
public:
alpha()
{ data = 5; }
friend int frifunc(alpha, beta);
};
class beta
{
private:
int data;
public:
beta()
{ data = 8; }
friend int frifunc(alpha, beta);
};
int frifunc(alpha a, beta b)
{
return( a.data + b.data );
}
void main()
{
alpha aa;
D:\75807564.doc vom 13.10.99
4-114
BBS III Mainz
beta bb;
cout << frifunc(aa,bb);
getch();
}
OOP für IT-Berufe mit C++
In diesem Beispiel gibt es zwei Klassen alpha und beta, deren Konstruktoren die jeweiligen Datenelemente mit
5 und 8 initialisieren. Damit die Funktion frifunc() nun auf die Datenelemente beider Klassen zugreifen kann,
muß sie mit dem Schlüsselwort friend zu einer Friend-funktion gemacht werden. Eine solche Funkton kann an
einer beliebigen Stelle in der Klasse definiert werden.
Friend-Funktionen sollten sehr vorsichtig eingesetzt werden, da sie dem eigentlichen Konzept der OOP (nur
Funktionen der eigenen Klasse können auf die jeweiligen Daten zugreifen) zuwiderlaufen und zu
unübersichtlichen Programmen führen können.
4.4.4 This-Zeiger
Alle Klassen verfügen über ein verborgenes Datenelement, den sogenannten this-Zeiger, der auf die Instanz der
Klasse im Speicher verweist. Wie oben bereits beschrieben, erhält jede Instanz einer Klasse ihre eigene Kopie
der Datenelement, aber alle Klassen-Instanzen teilen sich dieselben Funktionen. Da alle Methoden über einen
verborgenen this-Parameter verfügen, weiß der Compiler, welche Instanz zu welchem Funktionsaufruf gehört.
Der This-Zeiger spielt bei der Arbeit mit der VCL (Visual Component Library) des C++-Builders eine Rolle
und arbeitet bei der “normalen” C++-Programmierung im Hintergrund.
4.4.5 Übungsaufgaben
1. (Rechteck.cpp) Schreiben Sie ein Programm rechteck.cpp, das eine Klasse Quadrat und eine davon
abgeleitete Klasse Rechteck besitzt. Die Klasse Quadrat hat das Datenelement laenge und die Methoden
Laenge, Breite und Flaeche, wobei Flaeche die Methoden Laenge und Breite aufruft. Die Klasse Rechteck
hat neben dem Konstruktor nur noch eine Methode Breite. Die Seitenlaengen eines Quadrats bzw. eines
Rechtecks können zu Programmbeginn festgelegt werden. Dann sollen die jeweiligen Flächen berechnet und
zusammen mit den Seitenlängen ausgegeben werden.
D:\75807564.doc vom 13.10.99
4-115
BBS III Mainz
OOP für IT-Berufe mit C++
Lösungen zu den Übungsaufgaben:
6.2.9
// Programm Intclass.cpp
#include <iostream.h>
#include <conio.h>
class Int
{
private:
int i;
public:
Int() { i = 0; }
Int(int j) { i = j; }
void add(Int i1, Int i2) { i = i1.i + i2.i; }
void display()
{ cout << i; }
};
void main()
{
Int Int1(9);
Int Int2(14);
Int Int3;
Int3.add(Int1,Int2);
cout << "\nInt3 = "; Int3.display();
getch();
}
// Programm Maut.cpp
#include <iostream.h>
#include <conio.h>
const char ESC = 27;
const double Kosten = 0.5;
class Mautkasse
{
private:
unsigned int anzfz;
double geldmenge;
public:
Mautkasse() { anzfz = 0; geldmenge = 0.0; }
void Bezfahrzeug() {anzfz++; geldmenge +=Kosten; }
void Nbezfahrzeug() {anzfz++; }
void Anzeige()
{ cout << "\nFahrzeuge = " << anzfz
<< ", Einnahmen = " << geldmenge << "
Euro"; }
};
void main()
{
Mautkasse Mkasse1;
char ch;
cout << "\nGeben Sie 0 ein für jedes nichtbezahlende Fahrzeug,"
"\n
1 ein für jedes bezahlende Fahrzeug,"
"\n
ESC ein zum Beenden des Programms.\n" << flush;
do
{
ch = getche();
if ( ch == '0' ) Mkasse1.Nbezfahrzeug();
if ( ch == '1' ) Mkasse1.Bezfahrzeug();
}
D:\75807564.doc vom 13.10.99
4-116
BBS III Mainz
while ( ch != ESC );
Mkasse1.Anzeige();
OOP für IT-Berufe mit C++
getch();
}
6.3.9
// Programm Verlag1.cpp
#include <iostream.h>
#include <conio.h>
const int LEN = 80;
class Publikation
{
private:
char titel[LEN];
float preis;
public:
void holedaten()
{
cout << "\nBitte geben Sie den Titel
cout << "\nBitte geben Sie den Preis
}
void zeigedaten()
{
cout << "\nDer Titel ist: "; cout <<
cout << "\nDer Preis ist: "; cout <<
}
};
ein: "; cin >> titel;
ein: "; cin >> preis;
titel;
preis;
class Buch : private Publikation
{
private:
int seitenzahl;
public:
void holedaten()
{
Publikation::holedaten();
cout << "\nBitte geben Sie die Anzahl der Buchseiten ein: ";
cin >> seitenzahl;
}
void zeigedaten()
{
Publikation::zeigedaten();
cout << "\nDie Anzahl der Seiten beträgt: " << seitenzahl;
}
};
class CD : private Publikation
{
private:
float spielzeit;
public:
void holedaten()
{
Publikation::holedaten();
cout << "\nBitte geben Sie die Spielzeit der CD ein: ";
cin >> spielzeit;
}
void zeigedaten()
{
Publikation::zeigedaten();
cout << "\nDie Spielzeit der CD beträgt: " << spielzeit;
}
};
D:\75807564.doc vom 13.10.99
4-117
BBS III Mainz
OOP für IT-Berufe mit C++
void main()
{
Buch buch1;
CD cd1;
buch1.holedaten();
cd1.holedaten();
buch1.zeigedaten();
cd1.zeigedaten();
getch();
}
// Programm Verlag2.cpp
#include <iostream.h>
#include <conio.h>
const int LEN = 80;
const int Monate = 3;
class Publikation
{
private:
char titel[LEN];
float preis;
public:
void holedaten()
{
cout << "\nBitte geben Sie den Titel
cout << "\nBitte geben Sie den Preis
}
void zeigedaten()
{
cout << "\nDer Titel ist: "; cout <<
cout << "\nDer Preis ist: "; cout <<
}
};
ein: "; cin >> titel;
ein: "; cin >> preis;
titel;
preis;
class Verkauf
{
private:
char vzahlen[Monate];
public:
void holedaten()
{
int i;
cout << "Bitte die Verkaufsergebnisse der letzten" <<
"3 Monate eingeben:\n"<<endl;
for(i=0; i<Monate; i++)
{
cout << "
Monat " << (i+1) <<": ";
cin >> vzahlen[i];
}
}
void zeigedaten()
{
int i;
for(i=0; i<Monate; i++)
{
cout << "\n Verkäufe für Monat " << i+1 << ": " << endl;;
cout << vzahlen[i];
}
D:\75807564.doc vom 13.10.99
4-118
BBS III Mainz
OOP für IT-Berufe mit C++
}
};
class Buch : private Publikation, private Verkauf
{
private:
int seitenzahl;
public:
void holedaten()
{
Publikation::holedaten();
cout << "\nBitte geben Sie die Anzahl der Buchseiten ein: ";
cin >> seitenzahl;
Verkauf::holedaten();
}
void zeigedaten()
{
Publikation::zeigedaten();
cout << "\nDie Anzahl der Seiten beträgt: " << seitenzahl;
Verkauf::zeigedaten();
}
};
class CD : private Publikation, private Verkauf
{
private:
float spielzeit;
public:
void holedaten()
{
Publikation::holedaten();
cout << "\nBitte geben Sie die Spielzeit der CD ein: ";
cin >> spielzeit;
Verkauf::holedaten();
}
void zeigedaten()
{
Publikation::zeigedaten();
cout << "\nDie Spielzeit der CD beträgt: " << spielzeit;
Verkauf::zeigedaten();
}
};
void main()
{
Buch buch1;
CD cd1;
buch1.holedaten();
cd1.holedaten();
buch1.zeigedaten();
cd1.zeigedaten();
getch();
}
6.4.5
// Programm Rechteck.cpp
#include <iostream.h>
#include <conio.h>
class Quadrat
{
protected:
double laenge;
D:\75807564.doc vom 13.10.99
4-119
BBS III Mainz
OOP für IT-Berufe mit C++
public:
Quadrat(double len)
{ laenge = len; }
double Laenge()
{ return laenge; }
virtual double Breite() { return laenge; }
double Flaeche()
{ return Laenge() * Breite(); }
};
class Rechteck : public Quadrat
{
protected:
double breite;
public:
Rechteck(double len, double wide)
{laenge = len;
breite= wide; }
virtual double Breite() { return breite; }
};
void main()
{
Quadrat quad(10);
Rechteck recht(10, 12);
cout << "Das Quadrat hat eine Seitenlaenge von " << quad.Laenge() << endl
<< "
und eine Flaeche von " << quad.Flaeche() << endl;
cout << "Das Rechteck hat eine Laenge von "
<< recht.Laenge() << endl
<< "
und eine Breite von "
<< recht.Breite() << endl
<< "
und eine Flaeche von "
<< recht.Flaeche() << endl;
getch();
}
D:\75807564.doc vom 13.10.99
4-120
Herunterladen