Verlässliches Programmieren in C/C++

Werbung
Verlässliches Programmieren in C/C++
Teil B: Einführung in OOP mit C++
Prof. Dr. F. Belli
Universität Paderborn,
Fakultät EIM, Fachbereich Elektrotechnik und Informationstechnik
Angewandte Datentechnik (Softwaretechnik)
Universität Paderborn, ADT
Gliederung
1. Einleitung
2. Programmgestalt und Sprachkonstrukte
3. Variablen und Datenstrukturen
4. Steuerstrukturen
5. Funktionen
6. Klassen
7. Vererbung
8. Templates
9. Ausnahmebehandlung
10. Kommandozeilen und Dateiverarbeitung
11. Zusammenfassung
Universität Paderborn, ADT
Verlässliches Programmieren in C/C++
1. Einleitung
1. Einleitung
Universität Paderborn, ADT
1.1
Verlässliches Programmieren in C/C++
1. Einleitung
Historie von C++
– Die Programmiersprache C wurde 1970 von B. Kernighan und
D. Ritchie zur Implementation des
Betriebssystems UNIX entworfen
(rein prozedural)
– C ist seit 1989 normiert (ANSI
Standard X3.159-1989, ISO/IEC
Standard 9899:1990 )
– C++ ist von B. Stroustrup 1980
bei den Bell Labs (Bell AT&T) als
Erweiterung von C entworfen worden
– Wichtigstes neues Sprachkonzept:
Klassen (ursprünglich hieß die
Sprache C with Classes)
– C++ ist im Wesentlichen Obermenge von C, ANSI-C-Programme
können oft mit einem C++Compiler übersetzt werden
– ISO–C++–Standard
14882) seit 1998
Universität Paderborn, ADT
(ISO/IEC
1.2
Verlässliches Programmieren in C/C++
1. Einleitung
Programmierparadigmen
– Programmiersprachen sind Ergebnis von Denkweisen und Erfahrungen über den Programmierprozeß
– Durch Generalisierung entstehen
aus Denkprozessen Modellmethoden für den Entwurf und die Implementierung von Programmen (Paradigma)
– Paradigmen beziehen sich auf Programmentwurf, auf Verwendung
von Abstraktion und Strukturierung
Universität Paderborn, ADT
– Programmiersprache unterstützt
ein Paradigma, wenn ihre Leistungsmerkmale die Anwendung
der Methode erleichtern
– Gemeinsamkeit von Programmierparadigmen: auf Abstraktion beruhender Entwurf, der mit mit den
Elementen des Programmierproblems korrespondiert
1.3
Verlässliches Programmieren in C/C++
1. Einleitung
Prozedurale Programmierung
Programm
Problem
Funktionen
– Weitverbreitetes Modell
– Programm wird aus einer Reihe
von Funktionen zusammengestellt
– Entwurf, Strukturierung und Implementierung erfolgt nach funktionaler Zerlegung
Universität Paderborn, ADT
f1(x, y)
f2(y,z)
f3(z,w)
Methode
1. Identifizierung von Funktionen zur
Lösung des Programmierproblems
(abstrakte Operatoren)
2. Aufteilung von Funktionen in Moduln (Strukturierung)
1.4
Verlässliches Programmieren in C/C++
1. Einleitung
Datenabstraktion
Problem
Datenstrukturen
Programm
d1{op1,op2}
d2{op3,op4}
d3{op5,op6}
– Daten, nicht Funktionen stehen im
Mittelpunkt des Interesses
– Datenstruktur ist definiert durch
die darauf ausführbaren Operationen, nicht durch die Struktur der
Implementierung
– Umgebung einer Datenstruktur
durch einen abstrakten Datentyp
(Datenkapselung)
Universität Paderborn, ADT
– Zugriff auf die Datenstruktur
durch Operationen (Methoden),
die Teil des Datentyps sind
– Datenabstraktion
ergänzt
die
Sichtweise prozeduraler Programmierung
1.5
Verlässliches Programmieren in C/C++
1. Einleitung
Objektorientierte Programmierung (OOP) (a)
Programm
Problem
Objekte
O1
Methode1
Methode2
O3
Methode3
Methode4
– Modell der Programmerstellung
als Reihe abstrakter Datentypinstanzen
– Typen, die im Programmierproblem die Objekte darstellen, stehen im Mittelpunkt der Betrachtung
Universität Paderborn, ADT
O2
Methode1
Methode2
O4
Methode3
Methode4
– Operationen in den Objekttypen
sind abstrakte Operatoren zur
Lösung des Problems
– Objekttyp dient als wiederverwendbares Modul zur Lösung gleicher und ähnlicher Probleme
1.6
Verlässliches Programmieren in C/C++
1. Einleitung
Objektorientierte Programmierung (OOP) (b)
– Starke Koppelung von Daten und
Operationen (Verhalten)
– Erste objektorientierte Programmiersprache war Simulationsprache (SIMULA67, 1967)
– Berechnung wird als Simulation
von Verhalten betrachtet
– Gemeinsamkeit aller objektorientierten Programmiersprachen
– Simuliert werden Objekte durch
Abstraktion
• Datenkapselung
• Polymorphie
• Vererbung
Universität Paderborn, ADT
1.7
Verlässliches Programmieren in C/C++
1. Einleitung
Datenkapselung
Beispiel Kartenspiel
– Dies sind Typen, Teilaspekt des
Kartenspiels
– Repräsentation soll vor der Außenwelt (Benutzer) verborgen werden
und die Benutzung nicht beeinflussen
– Repräsentation
im
z. B. durch Zahlen
– Eigenschaften von Daten
Operationen eines Objekts
– Spielfarben: ♣, ♥, ♦, ♠
♣ = 0
♥ = 1
♦ = 2
♠ = 3
Universität Paderborn, ADT
Programm
und
• gekapselt (private): interne Repräsentation
• offen (public): Schnittstelle
(Interface)
zu
gekapselten
Daten und Operationen
1.8
Verlässliches Programmieren in C/C++
1. Einleitung
Polymorphie
Polymorphie (griechisch): Vielgestaltigkeit, Verschiedengestaltigkeit
– Beispiel Graphiksystem
– Erlaubt Konstruktion allgemeiner
Schnittstellen und Operatoren
– Lokalisiert die Verantwortlichkeit
für das Verhalten (Bindung der
Operation an einen Datentyp)
– Beispiel arithmetische Operationen: ganze, reelle, komplexe
Zahlen
Universität Paderborn, ADT
• Darstellung verschiedener geometrischer Objekte auf dem
Bildschirm
• Zeichnen der Objekte kann intern verschieden sein (Kreise,
Polygone, etc.)
• Allgemeine Operation (draw)
kann objektunabhängig benutzt
werden
1.9
Verlässliches Programmieren in C/C++
1. Einleitung
Vererbung
– Ermöglicht hierarchische Klassifizierung von Objekten
– Methode zur
Komplexität
Reduzierung
der
– Fördert Wiederverwendung von
Programm-Code: aus genereller
Beschreibung
(Klasse)
durch
Hierarchisierung zu Spezialisierung (Objekt) für das aktuelle
Programmierproblem
– Beispiel Obst
– Spezialisierung von Objekten: Vererbung aller Eigenschaften der Eltern, Definition von Daten und
Operationen, die zu den Eltern abgrenzen
• Ein Apfel ist eine Frucht
• Eine Frucht ist Nahrung
• Früchte besitzen alle Eigenschaften von Nahrung
• Äpfel besitzen alle Eigenschaften von Früchten
Universität Paderborn, ADT
1.10
Verlässliches Programmieren in C/C++
1. Einleitung
Vorteile von OOP
– Gut anwendbar auf sehr große Programmierprojekte
– Datenkapselung erlaubt gute Aufteilbarkeit auf mehrere Entwickler und
Programmierer
– Polymorphie erlaubt leichte intuitive Benutzung von Operationen (Methoden) und reduziert die Komplexität für Entwickler und Programmierer
– Vererbung erlaubt gute Strukturierung des Programmierproblems durch
Hierarchisierung von Typen und Objekten
– Objektorientierte Programme sind oft robuster und besser zu warten und
zu ändern als prozedurale Programme
Universität Paderborn, ADT
1.11
Verlässliches Programmieren in C/C++
2. Programmgestalt und Sprachkonstrukte
2. Programmgestalt und
Sprachkonstrukte
Universität Paderborn, ADT
2.1
Verlässliches Programmieren in C/C++
2. Programmgestalt und Sprachkonstrukte
Ein einfaches Programmierproblem
Wurzelberechnung
Newton-Verfahren
– Iterationsverfahren
f (x)
T1 = f (x0)(x − x1 )
T2 = f (x1)(x − x2 )
f (x0)
1. Starte mit Schätzwert x0
2. Berechne
f (xn )
xn+1 := xn − f (xn)
bis |xn+1 − xn| ≤ ε, wobei ε Genauigkeit ist
f (x1)
0
0
x2x1
x0
Schnittpunkt der Tangente T1 mit der
x-Achse
f (x0)
f (x0) =
x0 − x1
f (x0)
⇐⇒ x1 = x0 − f (x0)
Universität Paderborn, ADT
– Spezialfall f (x) = x2 − c,
√
Berechnung von c
−c
x2
xn+1 = xn − n
2xn
2.2
Verlässliches Programmieren in C/C++
2. Programmgestalt und Sprachkonstrukte
Ein einfaches C++-Programm
/* Wurzelberechnung
* Eingabe: positive reelle Zahl x
* Ausgabe: Wurzel aus x
*/
#include <iostream>
// Konstanten und Variablen
const float genauigkeit = 1e-4;
int main ()
{
using namespace std;
float wert, wurzel, altwurzel, fehler;
// Eingabe
cout << "Bitte eine positive Zahl eingeben: ";
cin >> wert;
if (wert <= 0) {
cout << wert << " ist nicht positiv!";
cout << endl;
return 1;
}
Universität Paderborn, ADT
// Algorithmus nach Newton
wurzel = wert; // Erster Schaetzwert
do {
altwurzel = wurzel;
wurzel = wurzel
- (wurzel * wurzel - wert) / (2 * wurzel);
if (altwurzel <= wurzel) {
fehler = wurzel - altwurzel;
}
else {
fehler = altwurzel - wurzel;
}
} while (fehler >= genauigkeit);
// Ausgabe
cout << "Wurzel aus " << wert;
cout << " = " << wurzel << endl;
// Rueckgabewert
return 0;
}
2.3
Verlässliches Programmieren in C/C++
2. Programmgestalt und Sprachkonstrukte
Elemente eines C++-Programms
Ein einfaches Programm,
generelle Struktur
1. Kommentar
2. Präprozessoranweisungen
3. Konstantendeklarationen
4. Variablendeklarationen
5. Eingabeanweisungen
6. Programmanweisungen
7. Ausgabeanweisungen
8. Rückgabewert
Universität Paderborn, ADT
/*
...
*/
#include <iostream>
const float genauigkeit = 1e-4;
int main ()
{
...
cin >> ...;
...
wurzel = wert; // Erster Schaetzwert
do {
...
} while (fehler >= genauigkeit);
...
cout << ...;
return 0;
}
2.4
Verlässliches Programmieren in C/C++
2. Programmgestalt und Sprachkonstrukte
Kommentare
– Kommentare erläutern, was ein
Programm oder Programmteil bewirkt
– Programmanweisungen beschreiben, wie etwas bewirkt wird
– Kommentarblock, mehrzeilig
Benutzung zu längeren Erläuterungen, insbesondere zu Beginn
des Programms
– Einzeiliger Kommentar
Benutzung zu kurzen Erläuterungen, z. B. zur Beschreibung von
Variablen
Universität Paderborn, ADT
– Kommentarblock beginnt mit “/*”
und endet mit “*/”
/* Wurzelberechnung
* Eingabe: positive reelle Zahl x
* Ausgabe: Wurzel aus x
*/
– Einzeiliger Kommentar beginnt
mit “//” und endet mit dem Zeilenende
// Konstanten und Variablen
// Eingabe
// Algorithmus nach Newton
wurzel = wert; // Erster Schaetzwert
// Ausgabe
// Rueckgabewert
2.5
Verlässliches Programmieren in C/C++
2. Programmgestalt und Sprachkonstrukte
Präprozessoranweisungen
– Präprozessor ist
vorgeschaltet
dem
Compiler
– Führt Ergänzungen und Veränderungen des Quellprogramms durch
– Präprozessoranweisungen
nen mit “#”
begin-
– Zunächst wichtig: Einfügen von
Programmtext (include)
#include Datei
– Eingebunden werden meist Header -Dateien, sie enthalten Datenund Funktions- bzw. Klassendeklarationen
Universität Paderborn, ADT
– Zur Benutzung von Ein- und
Ausgabeoperatoren ist die Datei
iostream in das Quellprogramm
einzubinden
#include <iostream>
– Ein Dateiname in spitzen Klammern (<>) bedeutet eine Systemdatei (Suche in den Systemverzeichnissen)
– Ein Dateiname in Anführungszeichen ("") wird im aktuellen Verzeichnis gesucht
2.6
Verlässliches Programmieren in C/C++
2. Programmgestalt und Sprachkonstrukte
Datendeklaration
– Konstanten und Variablen müssen
vor Ihrer Benutzung deklariert
werden
– Konstante, eine Fließkommazahl
mit Wert
– gekennzeichnet durch Namen und
Typ
const float genauigkeit = 1e-4;
– Datendeklarationen werden wie
alle
Programmkonstrukte
mit
Ausnahme von Kommentaren und
Präprozessoranweisungen
durch
ein Semikolon “;” abgeschlossen
Universität Paderborn, ADT
0.0001 = 1 · 10−4
– Variablen, vier Fließkommazahlen
float wert, wurzel,
altwurzel, fehler;
2.7
Verlässliches Programmieren in C/C++
2. Programmgestalt und Sprachkonstrukte
main ()-Funktion
– Ein C++-Programm besteht aus
mindestens einer Funktion
– Eine Funktion besteht aus
• Funktionskopf mit Namen und
Argumentliste, eingeschlossen
in Klammern (())
• Funktionsrumpf mit in geschweiften Klammern ({}) eingeschlossenen Programmkonstrukten
– Eine Funktion wird beendet mit
der Anweisung
return wert;
– Jedes C++-Programm muß die
Funktion
main ()
enthalten
– int main ()
{
// Eingabe
...
// Algorithmus nach Newton
...
// Ausgabe
...
// Rueckgabewert
return 0;
}
Rückgabewert ist wert
Universität Paderborn, ADT
2.8
Verlässliches Programmieren in C/C++
2. Programmgestalt und Sprachkonstrukte
Eingabeanweisung
– Stream-orientierte
C++
Eingabe
in
– Mehrere Eingaben:
– Benutzt wird die Klasse cin mit
dem Operator >>
– Die Deklarationen zu Streamorientierter Ein- und Ausgabe sind
in <iostream> enthalten.
– Zur Benutzung
#include <iostream>
cin >> wert1 >> wert2 >> wert3;
– “Leerzeichen” (Blank, Tabulator,
Return) werden als Ende der Eingabe interpretiert!
– Eingabe
20 30 40
– cin >> wert;
– Die Variable wert erhält den Wert,
der über die Tastatur eingegeben
wird
Universität Paderborn, ADT
wert1=20; wert2=30; wert3=40;
2.9
Verlässliches Programmieren in C/C++
2. Programmgestalt und Sprachkonstrukte
Ausgabeanweisung
– cout << "Bitte ... eingeben: ";
– Analog zu Eingabe in C++
– Benutzt wird die Klasse cout mit
dem Operator <<
– Mehrere Ausgaben
cout << "Wurzel aus " << wert;
cout << " = " << wurzel << endl;
– Zur Benutzung
#include <iostream>
– Wert des Ausdrucks wird auf dem
Bildschirm ausgegeben
– Strings werden durch Anführungszeichen ("") eingeschlossen
– Spezielle Konstante für Zeilenumbruch
endl
Universität Paderborn, ADT
2.10
Verlässliches Programmieren in C/C++
2. Programmgestalt und Sprachkonstrukte
Ausdrücke und Zuweisungen
– Zuweisung geschieht
Operator =
– Zunächst arithmetische Ausdrücke
– wurzel
- (wurzel * wurzel - wert)
/ (2 * wurzel)
wurzel2 − wert
wurzel −
2 ∗ wurzel
dem
Wert
von
– variable = ausdruck
– variable erhält
ausdruck
–
– entspricht
mit
den
wurzel = wurzel
- (wurzel * wurzel - wert)
/ (2 * wurzel)
– Zuweisung wird oft mit Vergleich
(==) verwechselt!
Universität Paderborn, ADT
2.11
Verlässliches Programmieren in C/C++
2. Programmgestalt und Sprachkonstrukte
Verzweigung
– if (wert <= 0) {
cout << wert << " ist nicht positiv!";
cout << endl;
return (1);
– Bedingte Anweisungen
– Programmkonstrukt
1. if (ausdruck) {
anweisungen
}
2. if (ausdruck) {
anweisungen
}
else {
anweisungen
}
– ausdruck kann die Werte wahr
oder falsch annehmen
}
– if (altwurzel <= wurzel) {
fehler = wurzel - altwurzel;
}
else {
fehler = altwurzel - wurzel;
}
implementiert
wurzel = xn+1
|xn+1 − xn | =
mit
altwurzel
xn − xn+1
xn+1 − xn
=
xn ,
falls xn+1 ≤ xn ;
andernfalls.
– Arithmetischer Vergleich
(wert <= 0) entspricht wert ≤ 0
Universität Paderborn, ADT
2.12
Verlässliches Programmieren in C/C++
2. Programmgestalt und Sprachkonstrukte
Schleife
– Bedingte Wiederholung
– Programmkonstrukt nicht abweisende Schleife
do {
anweisungen
} while (ausdruck);
– anweisungen werden solange ausgeführt, wie ausdruck wahr ist
Universität Paderborn, ADT
– do {
altwurzel = wurzel;
wurzel = wurzel
- (wurzel * wurzel - wert)
/ (2 * wurzel);
if (altwurzel <= wurzel) {
fehler = wurzel - altwurzel;
}
else {
fehler = altwurzel - wurzel;
}
} while (fehler >= genauigkeit);
2.13
Verlässliches Programmieren in C/C++
2. Programmgestalt und Sprachkonstrukte
Rückgabewert
– if (wert <= 0) {
– Für die Funktion main () gibt
der Rückgabewert den Status der
Programmausführung an das Betriebssystem zurück
– Unter UNIX:
• Wert 0 bedeutet, daß das Programm ordnungsgemäß beendet ist
• Wert ungleich 0 bedeutet, daß
das Programm nicht ordnungsgemäß beendet ist
Universität Paderborn, ADT
cout << wert << " ist nicht positiv!";
cout << endl;
return 1;
}
– Nicht ordnungsgemäß, falsche Eingabe
– int main () {
...
return 0;
}
– Ordnungsgemäß,
durchgeführt
Berechnung
2.14
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
3. Variablen und Datenstrukturen
Universität Paderborn, ADT
3.1
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Variablendeklaration
typ variablenname [, variablenname]*;
– Eine Variable muß deklariert werden, bevor sie benutzt wird
– Ziele der Deklaration
• Definition des Namens
• Angabe des Typs
• Beschreibung für
ser/Programmierer
den
Le-
– typ ist ein gültiger C++-Datentyp
– variablenname beginnt mit einem
Buchstaben oder einem Unterstrich (_), gefolgt von einer Anzahl
von Buchstaben, Ziffern oder Unterstrichen
int studentenanzahl;
char initial; // Anfangsbuchstabe des Namens
float mittelwert;
int i, j; // Zeilen und Spaltenindex der Matrix
Universität Paderborn, ADT
3.2
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Initialisierung
1. typ variablenname(wert);
2. typ variablenname = wert;
– Variablen haben nach ihrer Deklaration einen undefinierten Wert
(einen Zufallswert)
– Zwei Methoden
1. in C++-Syntax
2. in alter C-Syntax
– Initialisierung, d. h. Zuweisung eines Anfangswerts, kann bei der
Deklaration erfolgen
– Auch mehrere Variablendeklarationen mit Initialisierung
int studentanzahl(40); // Anfangswert 40
int studentenanzahl = 40; // Anfangswert 40
int i, j = 5, k = 4; // i nicht initialisiert
Universität Paderborn, ADT
3.3
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Konstanten und Referenzen
1. const typ konstantenname = wert;
2. typ &variablenname1 = variablenname2;
1. Ein im Programm unveränderlicher Wert heißt Konstante
2. Variablen können referenziert werden
• variablenname1 ist ein Alias-Name
für variablenname2
– Guter Stil:
• Variablennamen in Kleinbuchstaben
• Konstantennamen
buchstaben
in
Groß-
const float PI = 3.1415926; // Die Kreiskonstante
int zaehler; // Anzahl von Gegenstaenden
int &akt_zaehler = zaehler; // Alias fuer zaehler
zaehler = 33; // akt_zaehler = 33
akt_zaehler = 44; // zaehler = 44
Universität Paderborn, ADT
3.4
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Vordefinierte Datentypen
– Grundlegende Datentypen: ein boolescher Typ, Zeichentypen, ganzzahlige
Typen und Gleitkommatypen
– Aufzählungstyp enum, Typ void (tut die Abwesenheit von Informationen
kund)
– Zeiger-, Feld- und Referenztypen, Datenstrukturen und Klassen
– Es gibt eine Ordnung auf den Zeichen (ASCII-Anordnung)
Bezeichner
char
int
float
double
void
Universität Paderborn, ADT
Standard-Datentypen
Erklärung
Wertebereich
von
bis
Zeichen
−128
127
ganze Zahlen
−32.768
32.767
Fließkommazahl 3, 4 × 10−38
3, 4 × 10+38
Fließkommazahl 1, 7 × 10−308 1, 7 × 10+308
kein Wert
Beispiel
’w’
10
2.345E+10
2.345E+10
3.5
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Modifiers
– Wertebereichsmodifikation der Standard-Datentypen
– signed, unsigned, long, short
Typ
char
unsigned char
signed char
int
unsigned int
signed int
short int
unsigned short int
signed short int
long int
signed long int
unsigned long int
float
double
long double
Universität Paderborn, ADT
Modifiers
Bit-Länge
Wertebereich
von
bis
8 −128
127
8 0
255
8 −128
127
16 −32.768
32.767
16 0
65.535
16 −32.768
32.767
16 −32.768
32.767
16 0
65.535
16 −32.768
32.767
32 −2.147.483.648 2.147.483.647
32 −2.147.483.648 2.147.483.647
32 0
4.294.967.295
−38
32 3, 4 × 10
3, 4 × 10+38
64 1, 7 × 10−308
1, 7 × 10+308
80 3, 4 × 10−4932
1, 1 × 10+4932
3.6
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Datentyp bool
– In C existiert ein boolescher Datentyp erst seit dem C99-Standard
– Jeder Ausdruck ist logisch interpretierbar – auch Zeiger (ein Nullzeiger wird als wahr, jeder andere
als falsch interpretiert)
– Logische Operatoren liefern immer
0 oder 1, d.h. !(!n) kann sich von
n unterscheiden!
Universität Paderborn, ADT
– In C++ gibt es den Datentyp bool,
der die Werte true oder false annehmen kann
– true hat per Definition den Wert
1, false den Wert 0
3.7
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Datentyp char
Bezeichner
\b
\f
\n
\t
\"
\’
\0
\\
\v
\a
\?
\N
\xN
Universität Paderborn, ADT
Sonderzeichen in char
Bedeutung
Backspace
Formfeed
Newline
horizontaler tab
Anführungszeichen (Double-quote)
einfaches Anführungszeichen (single-quote)
Null
Backslash
vertikaler Tabulator
alert
Fragezeichen
Oktalzahl N
Hexadezimalzahl N
3.8
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Felder (Array)
typ variablenname[größe][[größe]]*;
– Ein Feld (Array ) ist eine endliche Sequenz von Daten desselben
Typs
– Elementanzahl wird bei der Deklaration festgelegt: größe (muss ein
konstanter Ausdruck sein!)
– Indizierung
größe − 1
mit
Indizes
0
– Auch mehrdimensionale Felder
bis
– Initialisierung
int feld[5] = {1, 2, 3, 4, 5};
int feld[] = {1, 2, 3, 4, 5};
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int feld[20]; // 20 Zahlen
int matrix[5][10]; // 5x10-Matrix
Universität Paderborn, ADT
3.9
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Zeichenketten (String) in C
– Es gibt in C keinen Datentyp für
Zeichenketten
– Zeichenketten sind Felder vom
Typ char
– Vereinfachte Syntax: Zeichenkette
wird in Anführungszeichen (") eingeschlossen
– Besonderheit: letztes Zeichen ist
das Null-Zeichen (\0), wird automatisch angehängt
– Für eine Zeichenkette string der
Länge n muß ein Feld der
Länge n + 1 deklariert werden
(char string[n + 1])
"Hallo!" entspricht
H
a
l
l
o !
"%$#^*"
entspricht
%
$
#
^
* \0
""
entspricht
\0
Universität Paderborn, ADT
\0
3.10
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
String-Funktionen (C-Stil)
#include <cstring>
Funktion
Beschreibung
strlen(string)
gibt die Länge von string zurück, dabei
wird das abschließende Null-Zeichen \0
nicht mitgezählt.
strcmp(string1 , string2 ) vergleicht string1 und string2 ; der Rückgabewert ist 0, falls sie gleich sind. Ist
string1 lexikographisch größer als string2 ,
wird eine positive Zahl zurückgegeben, ist
string1 lexikographisch kleiner als string2 ,
eine negative Zahl.
strcat(string1 , string2 ) hängt string2 an das Ende von string1 an,
string2 bleibt unverändert.
strcpy(string1 , string2 ) kopiert den Inhalt von string2 in string1 .
Universität Paderborn, ADT
3.11
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Datentypdeklaration
typedef typ typbezeichner ;
– Mit typedef kann ein bereits bestehender Datentyp über einen neuen Namen angesprochen werden
– Sinnvoll z. B. bei Feldern
– Portabilität: Maschinenabhängige Datentypen müssen unter Benutzung
von typedef bei einer Portierung nur einmal geändert werden
– Erhöhung der Verständlichkeit des Quellcodes (?)
typedef char string[80]; // Neuer Datentyp string mit 80 Zeichen
string zeile; // Variable vom Typ String
Universität Paderborn, ADT
3.12
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Zeichenketten in C
/**************************************************************
* Zusammenfassung von Vor- und Nachnamen
*
**************************************************************/
#include <cstring>
#include <iostream>
typedef char string[80]; // Neuer Datentyp string mit 80 Zeichen
int main()
{
string vorname, nachname; // Vor- und Nachname einer Person
string name; // vollstaendiger Name einer Person
std::cout << "Gib Vor- und Nachnamen ein: ";
std::cin >> vorname >> nachname; // Lese Namen ein
strcpy(name, nachname);
strcat(name, ", "); // Komma zwischen Nach- und Vornamen
strcat(name, vorname);
std::cout << name << std::endl; // Namen ausgeben
return 0;
}
Universität Paderborn, ADT
3.13
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Zeichenketten (String) in C++
– Konkatenation mittels des Operators +
– In C++ stellt die Standardbibliothek den string-Typ zur
Verfügung
– C-Strings sollten nach Möglichkeit
nicht benutzt werden
– Anhängen von Strings durch +=
– viele weitere Operationen zur Manipulation von Strings werden bereitgestellt
– Beispiel:
– Anmerkung: Der Typ string ist eine sogenannte Klasse
Universität Paderborn, ADT
string s1 = "Hello, ";
string s2 = "World!";
string s3 = s1 + s2;
3.14
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Zeichenketten in C++
/**************************************************************
* Zusammenfassung von Vor- und Nachnamen
*
**************************************************************/
#include <iostream>
int main()
{
using namespace std;
string vorname, nachname; // Vor- und Nachname einer Person
string name; // vollstaendiger Name einer Person
cout << "Gib Vor- und Nachnamen ein: ";
cin >> vorname >> nachname; // Lese Namen ein
name = nachname;
name += ", ";
name += vorname;
cout << name << endl; // Namen ausgeben
return 0;
}
Universität Paderborn, ADT
3.15
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Aufzählungtypen
enum typbezeichner {
element1 [= Zahlenwert],
element2 [= Zahlenwert],
...
elementN [= Zahlenwert],
};
– Die Elemente erhalten eine Numerierung gemäß ihrer Aufzählungsreihenfolge, beginnend bei 0
– Es können auch explizite Zahlenwerte zugewiesen werden
Universität Paderborn, ADT
– enum wochentag {
Sonntag,
//
Montag,
//
Dienstag,
//
Mittwoch,
//
Donnerstag, //
Freitag,
//
Samstag
//
};
=
=
=
=
=
=
=
0
1
2
3
4
5
6
– enum alter {
Hugo = 25,
Berta, // = 26
Helmut = Hugo + 10 // = 35
};
3.16
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Aggregattypen (Strukturen)
struct strukturbezeichner {
typ1 bezeichner1 ;
typ2 bezeichner2 ;
...
typN bezeichnerN;
};
– struct datum {
wochentag w_tag;
int tag;
char monat[10];
int jahr;
};
– Strukturen sind Zusammenfassungen von Daten verschiedenen Typs
– Die einzelnen Daten heißen Komponenten
– Zugriff auf Komponenten
Punkt-Operator (.)
Universität Paderborn, ADT
mit
– datum geburtstag,
hugo_geb_tag = { // Initialisierung
Montag, 12, "Januar", 1970
};
geburtstag.w_tag = Donnerstag;
geburtstag.tag = 15;
strcpy(geburtstag.monat, "Januar");
geburtstag.jahr = 1970;
3.17
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Vereinigungstypen
union [typbezeichner ] {
typ1 bezeichner1 ;
typ2 bezeichner2 ;
...
typN bezeichnerN;
};
– union zahl_oder_zeichen {
– Ein Vereinigungstyp (union) ist
eine Speicherstelle, die von Daten verschiedenen Datentyps belegt wird
int i;
char c;
};
zahl_oder_zeichen zz;
zz.i = 55;
cout << zz.i << endl;
zz.c = ’a’;
cout << zz.c << endl;
– Eine union kann bezeichnet oder
anonym sein
– Anonyme unions
Fehlerquellen
Universität Paderborn, ADT
sind
typische
3.18
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Typkonvertierung
typ(ausdruck) // C++-Stil
(typ)ausdruck // C-Stil
– C++ nimmt automatisch Typkonvertierungen vor
– int gewonnen, verloren;
float ratio; // Verhaeltnis gewonnen/verloren
gewonnen = 5;
verloren = 3;
ratio = gewonnen / verloren; // ratio = 1.0!
// entspricht float(gewonnen / verloren)
– ratio = float(gewonnen) / float(verloren); // ratio = 1.66667
ratio = (float)gewonnen / (float)verloren; // C-Stil
Universität Paderborn, ADT
3.19
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Zeiger (Pointer) (a)
Adresse (Zeiger)
typ *variablenbezeichner ;
Speicherstelle 0x100
Inhalt
22
Wert
– Ein Zeiger (Pointer ) ist eine Variable, die eine Speicheradresse enthält
– Die Speicheradresse ist gewöhnlich die Stelle einer anderen Variablen
– Wenn x die Adresse von y enthält, dann zeigt x auf y
– int *p; // Zeiger auf int
float *q; // Zeiger auf float
Universität Paderborn, ADT
3.20
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Zeiger (Pointer) (b)
– Es gibt zwei Operatoren zur Benutzung von Zeigern, die sich komplementär verhalten
– Der Referenzoperator & liefert die Adresse des Operanden
int erg, *erg_ptr;
erg_ptr = &erg; // erg_ptr zeigt auf erg
– Der Dereferenzierungsoperator * liefert den Wert der Variablen, auf die
der Operand zeigt
int erg, *erg_ptr;
erg = *erg_ptr; // erg erhaelt den Wert auf den erg_ptr zeigt
Universität Paderborn, ADT
3.21
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Zeigerindizierung
– Zeiger und Felder können (fast)
synonym verwendet werden
– char str[80];
char *p;
float *f, feld[80];
p = str;
f = feld;
– Zuweisung von Zeigern zu Feldern:
Zeiger zeigt auf das erste Element
des Feldes
Universität Paderborn, ADT
– Zeigerarithmetik:
– str[4] und *(p+4) ergeben das
fünfte Element von str[]
– feld[3] und *(f+3) ergeben das
vierte Element von feld[]
– Bei der Addition/Subtraktion von
ganzen Zahlen auf einen Zeiger
wird automatisch die Größe des
Datentyps berücksichtigt
3.22
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Beispiel: Tokenizer (C-Strings)
/* Extrahiere Worte aus einem Satz */
#include <iostream>
char str[80], token[80], *p, *q;
int main()
{
using namespace std;
cout << "Gib einen Satz ein: ";
cin.getline(str, sizeof(str)); // Lesen eines Strings mit White-spaces
p=str;
// Lies ein Token (Wort) aus dem Satz
while (*p) { // p zeigt nicht auf das Null-Zeichen \0
q = token; // q zeigt auf den Anfang von token
// Lese Zeichen bis entweder ein Leerzeichen oder das Null-Zeichen erscheint
while (*p != ’ ’ && *p) { // && ist logisches UND, != ist "ungleich"
*q = *p;
q++; // q = q + 1
p++; // p = p + 1
}
if (*p) p++; // uebergehe das Leerzeichen
*q = ’\0’; // beende das Token (Wort)
cout << token << endl;
}
return 0;
}
Universität Paderborn, ADT
3.23
Verlässliches Programmieren in C/C++
3. Variablen und Datenstrukturen
Dynamische Speicherverwaltung
– Zur dynamischen Speicher-Allokation gibt es in C++ das Konstrukt new
int *zahl1, *zahl2, *feld; // Zeiger auf int
zahl1 = new int; // neuer Speicher fuer Zahl1
zahl2 = new int(9); // neuer Speicher fuer zahl2, initialisiert mit 9
feld = new int[9]; // neuer Speicher fuer 9er Feld von int-Zahlen
– Zur dynamischen Speicherfreigabe gibt es in C++ das Konstrukt delete
– Soll ein Array freigegeben werden, so muß delete [] verwendet werden
delete zahl1; // Speicher fuer Zahl1 freigegeben
delete [] feld; // Speicher fuer gesamtes feld freigegeben
– Synonym bei Zugriff auf dereferenzierte Struktur (struct, class):
zeiger ->komponente für (*zeiger ).komponente
Universität Paderborn, ADT
3.24
Verlässliches Programmieren in C/C++
4. Steuerstrukturen
4. Steuerstrukturen
Universität Paderborn, ADT
4.1
Verlässliches Programmieren in C/C++
4. Steuerstrukturen
Bedingungen
– In C++ gibt es den Datentyp
bool, der die Wahrheitswerte true
und false annehmen kann
– Verzweigung: if, switch
– Schleife: for, while, do-while
– Sprung: break, continue, return,
goto
– Verzweigungen und Schleifen werden durch eine Bedingung mit den
Werten wahr oder falsch gesteuert
Universität Paderborn, ADT
– Es ist aber auch jeder andere
Grundtyp logisch interpretierbar
– Beispiel:
int a;
a = ...
if (a)
{
...
}
4.2
Verlässliches Programmieren in C/C++
4. Steuerstrukturen
Operatoren
–
Vergleich, Logik
Relational
kleiner
größer
kleiner gleich
größer gleich
Gleichheit
gleich
ungleich
Logik
Negation
UND
ODER
<
>
<=
>=
==
!=
!
&&
||
– Die Operatoren liefern int-Werte
0 oder 1, verkürzte Auswertung
– Vorsicht bei Verwechslung von ==
und =, z. B. ist if (i = 1) immer
wahr !
Universität Paderborn, ADT
– Logische Negation kann auf jeden Ausdruck angewendet werden,
z. B. (i !< 5)
– Vorsicht: ! ist keine Negation (¬)
im mathematischen Sinn
– Mathematik: ¬(¬x) = x für beliebigen Ausdruck x
– C++: !(!5) = 1, weil !5 = 0 und
!0 = 1
– Bedingungs-Operator
term1 ? term2 : term3
// x = max {y, z}
x = (y < z) ? y : z;
4.3
Verlässliches Programmieren in C/C++
4. Steuerstrukturen
if-Konstrukt
– if (ausdruck) {
anweisungen
}
if (i) {
if (j) {
anweisung1;
– if (ausdruck) {
}
anweisungen
if (k) {
}
anweisung2;
else {
}
anweisungen
else {
}
anweisung3; // zu if (k)
– Bei Schachtelungen bezieht sich }
das else auf das nächste vorher- else anweisung4; // zu if(i)
gehende if im selben Block ({})
Universität Paderborn, ADT
4.4
Verlässliches Programmieren in C/C++
4. Steuerstrukturen
Beispiel 1 if-Konstrukt
/* Berechnung des Quotienten aus zwei Zahlen
* Eingabe: Zwei ganze Zahlen
* Quotient aus erster und zweiter Zahl
*/
#include <iostream>
int main()
{
using namespace std;
int a, b;
cout << "Gib zwei ganze Zahlen ein: ";
cin >> a >> b;
if (b) { // b ist ungleich 0
cout << a / b << endl;
return 0;
}
else {
cerr << "Division durch 0" << endl; // cerr: Standard-Fehlerkanal
return 1;
}
}
Universität Paderborn, ADT
4.5
Verlässliches Programmieren in C/C++
4. Steuerstrukturen
Beispiel 2 if-Konstrukt
// Simulation von case (if ladder)
#include <iostream>
int main()
{
using namespace std;
int x;
for (x = 0; x < 6; x++) {
if (x == 1) { cout << "x ist eins" << endl; }
else if(x == 2) { cout << "x ist zwei" << endl; }
else if(x == 3) { cout << "x ist drei" << endl; }
else if(x == 4) { cout << "x ist vier" << endl; }
else { cout << "x ist nicht zwischen eins und vier" << endl; }
}
return 0;
}
Universität Paderborn, ADT
4.6
Verlässliches Programmieren in C/C++
4. Steuerstrukturen
Beispiel 3 if-Konstrukt
/* Lösung einer quadratischen Gleichung
* a * x * x + b * x +c = 0
*/
else if (discr < 0.0)
cout << "Lösung ist
return 1;
}
else {
wurzel1 = wurzel2 =
}
cout << wurzel1 << ",
return 0;
#include <iostream>
#include <cmath>
int main()
{
using namespace std;
float a, b, c; // Koeffizienten
float wurzel1, wurzel2; // Loesungen
float discr, sq_disc; // Diskiminante
cout << "Koeffizienten a, b, c?
cin >> a >> b >> c;
discr = b * b - 4 * a * c;
if ((discr > 0.0)
&& (sq_disc = sqrt(discr)))
wurzel1 = (-b + sq_disc) / (2
wurzel2 = (-b - sq_disc) / (2
}
Universität Paderborn, ADT
";
{
* a);
* a);
{
komplex" << endl;
-b / (2 * a);
" << wurzel2 << endl;
}
– Das Programm nutzt die verkürzte Auswertung von && aus
– Zuweisung als
Ausdrucks!
zweiter
Teil
des
&&-
4.7
Verlässliches Programmieren in C/C++
4. Steuerstrukturen
switch-Konstrukt
– ausdruck muß vom Datentyp int
oder char sein (oder enum)
switch (ausdruck) {
case konstante1 :
anweisungen
[break;]
...
case konstanteN :
anweisungen
[break;]
[default :]
anweisungen
}
Universität Paderborn, ADT
– ausdruck wird sukzessiv gegen die
Konstanten der case-Zeilen getestet
– Sobald eine Konstante gleich
ausdruck ist, werden die folgenden anweisungen bis zum nächsten
break ausgeführt (auch über weitere cases hinweg!)
– Die auf die default-Zeile folgenden anweisungen werden ausgeführt, falls keine Konstante
gleich ausdruck ist
4.8
Verlässliches Programmieren in C/C++
4. Steuerstrukturen
Beispiel switch-Konstrukt
Ausgabe
switch ohne break und default
#include <iostream>
int main()
{
using namespace std;
for (int i = 0; i < 6; i++) {
switch (i) {
case 0: cout << "kleiner 1\n";
case 1: cout << "kleiner 2\n";
case 2: cout << "kleiner 3\n";
case 3: cout << "kleiner 4\n";
case 4: cout << "kleiner 5\n";
}
cout << endl;
}
kleiner
kleiner
kleiner
kleiner
kleiner
1
2
3
4
5
kleiner
kleiner
kleiner
kleiner
2
3
4
5
kleiner 3
kleiner 4
kleiner 5
kleiner 4
kleiner 5
return 0;
}
Universität Paderborn, ADT
kleiner 5
4.9
Verlässliches Programmieren in C/C++
4. Steuerstrukturen
for-Schleife
for (initialisierung; ausdruck; inkrement) {
anweisungen }
// Summierung von 5 Zahlen
– initialisierung ist eine Zuweisung,
die den Anfangswert der Schleifen- #include <iostream>
Steuervariable setzt, es können int main() {
auch Schleifenvariablen definiert
using namespace std;
werden
int summe = 0;
// Summe aller Zahlen
– ausdruck ist eine Bedingung, die
zur Terminierung der Schleife
führt
– inkrement definiert den Wert, um
den bei jedem Schleifendurchlauf die Schleifen-Steuervariable
verändert wird
}
Universität Paderborn, ADT
int aktuell; // Einzugebender Wert
int zaehler; // Schleifen-Steuervariable
for (zaehler = 0; zaehler < 5; ++zaehler) {
cout << "Zahl? ";
cin >> aktuell;
summe += aktuell;
// summe = summe + aktuell
}
cout << "Summe: " << summe << endl;
return 0;
4.10
Verlässliches Programmieren in C/C++
4. Steuerstrukturen
Beispiel for-Schleife
– Teile des Schleifenkopfs können leer sein
– /* Eingabe einer Zahl bis diese gleich 123 ist
* Eingabe: Ganze Zahlen
* Ausgabe: keine
*/
#include <iostream>
int main()
{
for (int x = 0; x != 123;) {
std::cout << "Gib eine Zahl ein: ";
std::cin >> x;
}
return 0;
}
Universität Paderborn, ADT
4.11
Verlässliches Programmieren in C/C++
4. Steuerstrukturen
Besonderheiten for-Schleife
– Beliebiges Inkrement
// Abwaertszaehlen in Fuenfer-Schritten
for (i = 100; >= -100; i -= 5)
– Mehrfach-Initialisierungen, Mehrfach-Inkremente
for ( x = 0, y = 0; x <= 10; ++x, --y) {
cout << x << ", " << y << endl;
– Endlos-Schleife
for (;;) {
// Irgendwas
}
Universität Paderborn, ADT
4.12
Verlässliches Programmieren in C/C++
4. Steuerstrukturen
while und do-while-Schleife
while (ausdruck) {
anweisungen
}
– Abweisende Schleife
– anweisungen werden so lange ausgeführt, wie ausdruck wahr (ungleich 0) ist
– Rumpf der Schleife kann leer sein
/* Erzeuge so lange
* Zufallszahlen,
* bis die Zahl gleich 100 ist
*/
while (rand() != 100);
Universität Paderborn, ADT
do {
anweisungen
} while (ausdruck);
– Nichtabweisende Schleife
– anweisungen werden einmal ausgeführt und dann so lange, wie
ausdruck wahr ist
– Eingabeschleife
do {
cout << "Positive Zahl? ";
cin >> zahl;
} while (zahl <= 0);
4.13
Verlässliches Programmieren in C/C++
4. Steuerstrukturen
Beispiel do-while-Schleife
#include <iostream>
#include <cstdlib>
int main()
{
using namespace std;
int magic; // zu ratende Zahl
int rat; // geratene Zahl
else {
cout << "...Pech gehabt.";
if (rat > magic) {
cout << " zu gross\n";
}
else {
cout << " zu klein\n";
}
}
} while (rat != magic);
magic = rand(); // Zufallszahl
do {
cout << "Gib einen Tip ein: ";
cin >> rat;
return 0;
if (rat == magic) {
}
cout << "**Treffer** ";
cout << magic << " ist richtig!\n";
}
Universität Paderborn, ADT
4.14
Verlässliches Programmieren in C/C++
4. Steuerstrukturen
Sprungkonstrukte break, continue
– break: Terminierung einer Schleife (for, while, do-while) und Verzweigung
(switch)
– break bewirkt einen Sprung hinter das Ende der Schleife bzw. Verzweigung
– continue: Wiederholung der Schleife (for, while, do-while)
– continue bewirkt einen Sprung zur Schleifenbedingung
Universität Paderborn, ADT
4.15
Verlässliches Programmieren in C/C++
4. Steuerstrukturen
Beispiel break, continue
#include <iostream>
int main()
{
using namespace std;
int summe(0), summand;
if (summand < 0) {
continue; // Neue Eingabe
}
summe += summand;
}
while (true) {
cout << "Summe: " << summe << endl;
cout << "Betrag? (0 fuer ende) ";
return 0;
cin >> summand;
}
if (summand == 0) {
break; // beendet die Schleife
}
Universität Paderborn, ADT
4.16
Verlässliches Programmieren in C/C++
4. Steuerstrukturen
Sprungkonstrukt goto
goto marke;
marke: anweisung;
...
for (x = x; x < X_LIMIT; ++x) {
for (y = 0; y < Y_LIMIT; ++y) {
– goto bewirkt einen Sprung zu einer
if (data[x][y] == 0) {
Marke
goto gefunden;
}
– goto sollte möglichst nicht verwen}
}
det werden!
cout << "Nicht gefunden"
<< endl;
– Für jedes Programm, daß goto- ...
Konstrukte beinhaltet, gibt es ein
gefunden:
wirkungsgleiches Programm ohne
cout << "Gefunden bei "
cout << "(" << x << ", "
goto-Konstrukt
<< y << ")" << endl;
...
Universität Paderborn, ADT
4.17
Verlässliches Programmieren in C/C++
5. Funktionen
5. Funktionen
Universität Paderborn, ADT
5.1
Verlässliches Programmieren in C/C++
5. Funktionen
Funktionsdefinition
[typ] funktionsbezeichner (parameterdeklarationen)
{
anweisungen
}
– typ spezifiziert den Datentyp des
Rückgabewertes (Ergebnistyp)
– Soll eine Funktion keinen Rückgabewert haben, so ist typ mit void
anzugeben
– parameterdeklarationen geben Anzahl, Position und Datentyp der
Funktionsargumente an
– Beendet wird eine Funktion durch
die Anweisung
return [ausdruck];
ausdruck ist Rückgabewert
– Bei
void-Funktionen
ausdruck
Universität Paderborn, ADT
entfällt
5.2
Verlässliches Programmieren in C/C++
5. Funktionen
Prototypen
[typ] funktionsbezeichner (parameterdeklarationen);
– Eine Funktion kann deklariert werden, bevor sie definiert wird (Funktionsprototyp)
– Damit kann eine Funktion im Programm benutzt werden, ohne daß sie
vorher definiert ist
– Funktionsprototypen werden zur besseren Strukturierung eines Programms
eingesetzt
Universität Paderborn, ADT
5.3
Verlässliches Programmieren in C/C++
5. Funktionen
Beispiel Funktion
#include <iostream>
// Funktionsprototyp
int min(int x, int y);
int main()
{
int j, k, m;
std::cout << "Zwei ganze Zahlen? ";
std::cin >> j >> k;
m = min(j,k);
std::cout << m << " = min("
<< j << ", " << k << ")\n";
// Funktionsdefinition
int min(int x, int y)
{
if(x < y) {
return x;
}
else {
return y;
}
}
return 0;
}
Universität Paderborn, ADT
5.4
Verlässliches Programmieren in C/C++
5. Funktionen
Default-Argumente
– Bei der Funktionsdefinition und der Funktionsdeklaration können vordefinierte Argumente angegeben werden (Default-Argumente)
– Vordefinition durch Zuweisung in den parameterdeklarationen
typ bezeichner = ausdruck
– Beim Aufruf wird dann, falls das entsprechende Argument nicht angegeben
wird, der vordefinierte Wert verwendet
– Andernfalls wird der Wert des Aufruf-Arguments verwendet
– Sinnvoll, wenn ein Argument oder mehrere Argumente einer Funktion
häufig mit den selben Werten belegt werden
Universität Paderborn, ADT
5.5
Verlässliches Programmieren in C/C++
5. Funktionen
Beispiel Default-Argumente
#include <iostream>
// Zweites Arg k ist als 2 vordef.
int power(int n, int k = 2);
int main()
{
using namespace std;
int n;
cout << "Ganze Zahl? ";
cin >> n;
// Aufruf mit Default Arg (2)
cout << power(n) << " = "
<< n << "^2\n";
// Aufruf mit explititem Arg (3)
cout << power(n, 3) << " = "
<< n << "^3\n";
// k = 2 ist default
int power(int n, int k)
{
if(k == 2) {
return (n * n);
}
else { // Rekursion
return (power(n, k - 1) * n);
}
}
return 0;
}
Universität Paderborn, ADT
5.6
Verlässliches Programmieren in C/C++
5. Funktionen
Overloading
– Funktionen können in einem Programm mehrere Bedeutungen haben (Polymorphie)
– in C++ heißt dies Overloading bzw. Überladen
– Beispiel Mittelwert von Zahlenfolgen
double mean(const int a[], int size)
{
int sum = 0;
for(int i = 0; i < size; ++i) {
sum += a[i]; // int Arithmetik
}
// Typ-Konversion int->double
return(double(sum)/double(size));
}
Universität Paderborn, ADT
double mean(const double a[], int size)
{
double sum = 0.0;
for(int i = 0; i < size; ++i) {
sum += a[i]; // double Arithmetik
}
return(sum/size);
}
5.7
Verlässliches Programmieren in C/C++
5. Funktionen
Gültigkeitsbereiche (a)
– Gültigkeitsbereiche von Bezeichnern sind in C++ durch Blöcke definiert
und durch { und } begrenzt
– Alle Bezeichner (z. B. von Variablen), die im Rumpf einer Funktion deklariert sind, sind außerhalb der Funktion nicht gültig (bekannt)
– Dasselbe gilt für die Bezeichner der parameterdeklarationen des Funktionskopfs
{
int a=2;
cout << a << ’\n’
{
int a=7;
cout << a << ’\n’
}
cout << ++a << ’\n’
// aeusserer Block a
// druckt 2
// innerer Block a
// druckt 7
// druckt 3
}
Universität Paderborn, ADT
5.8
Verlässliches Programmieren in C/C++
5. Funktionen
Gültigkeitsbereiche (b)
– In C++ können Variablendeklarationen an jeder Stelle eines Blocks erscheinen
– Gültigkeitsbereich ist vom Anfang der Deklaration bis zum Ende des innersten einschließenden Blocks
int max(int* pArr, int size)
{
cout << "Feldgroesse ist " << size << ’\n’;
int comp = pArr[0];
// Deklaration von comp
for(int i = 1; i < size; ++i)
// Deklaration von i
if(pArr[i]>comp) {
comp=pArr[i];
}
return comp;
}
Universität Paderborn, ADT
5.9
Verlässliches Programmieren in C/C++
5. Funktionen
Argumentübergabe
– Call-by-value: Wert des Arguments wird kopiert, mit dieser Kopie wird
gearbeitet
– Call-by-reference: Es wird mit der Variablen selbst gearbeitet
Argumenttypen
Typ
Deklaration
Value
function(int var)
Constant-value
function(const int var)
Reference
function(int &var)
Constant-reference function(const int &var)
Array
function(int array[])
Address
function(int *var)
Universität Paderborn, ADT
5.10
Verlässliches Programmieren in C/C++
5. Funktionen
Beispiel 1 Argumentübergabe
– Call-by-reference durch Call-by-address (üblich in C)
– Umständlich, schwer verständlich
int main()
{
int i = 7, j = 3;
void ordne(int *p, int *q);
void ordne(int *p, int *q)
{
int temp;
if(*p > *q) {
temp = *p;
*p = *q;
*q = temp;
}
cout << i << ’\t’ << j << ’\n’;
ordne(&i, &j);
cout << i << ’\t’ << j << ’\n’;
return 0;
}
Universität Paderborn, ADT
}
5.11
Verlässliches Programmieren in C/C++
5. Funktionen
Beispiel 2 Argumentübergabe
– Call-by-reference direkt (in C++ üblich)
– Leicht verständlich, leicht benutzbar
int main()
{
int i = 7, j = 3;
void ordne(int &p, int &q);
void ordne(int &p, int &q)
{
int temp;
if (p > q) {
temp = p;
p = q;
q = temp;
}
cout << i << ’\t’ << j << ’\n’;
ordne(i, j);
cout << i << ’\t’ << j << ’\n’;
return 0;
}
Universität Paderborn, ADT
}
5.12
Verlässliches Programmieren in C/C++
5. Funktionen
Call-by-value vs. Call-by-reference
Falsch:
Richtig:
void eingabe(int i)
{
cout << "Zahl? ";
cin >> i;
}
void eingabe(int &i)
{
cout << "Zahl? ";
cin >> i;
}
int main()
{
int x = 0;
eingabe(x);
cout << x << ’\n’;
int main()
{
int x = 0;
eingabe(x);
cout << x << ’\n’;
return 0;
return 0;
}
}
Eingabe und Ausgabe:
Eingabe und Ausgabe:
Zahl? 2
0
Zahl? 2
2
Universität Paderborn, ADT
5.13
Verlässliches Programmieren in C/C++
5. Funktionen
Operatoren
typ operator op(parameterdeklarationen) {
anweisungen
}
– In C++ können auch Operatoren
Funktionsbezeichner sein.
– Operatorfunktionen werden für
Overloading von Zuweisungsoperatoren, relationalen und arithmetischen Operatoren etc. verwendet.
Universität Paderborn, ADT
– So können für einen neu definierten Datentyp entsprechende
Operatoren definiert werden. Das
erhöht die Lesbarkeit des Programmtextes.
– Alle C++-Operatoren können so
für entsprechende Datentypen neu
definiert werden.
5.14
Verlässliches Programmieren in C/C++
5. Funktionen
Beispiel Operatoren
// Datentyp complex
struct complex {
float re;
float im;
};
// Operatorfunktion +, Addition komplexer Zahlen
complex operator +(complex &zahl1, complex &zahl2)
{
complex erg;
erg.re = zahl1.re + zahl2.re;
erg.im = zahl1.im + zahl2.im;
return erg;
}
Universität Paderborn, ADT
5.15
Verlässliches Programmieren in C/C++
5. Funktionen
Beispiel Operatoren (Forts.)
#include <iostream>
#include "complex.h"
int main ()
{
using namespace std;
complex z1, z2, z3;
cout << "Komplexe Zahl z1? ";
cin >> z1.re >> z1.im;
cout << "Komplexe Zahl z2? ";
cin >> z2.re >> z2.im;
z3 =
cout
z3 =
cout
z3 =
cout
z1
<<
z1
<<
z1
<<
+ z2;
"z1 + z2 = " << z3.re << " + i" << z3.im << ’\n’;
* z2;
"z1 * z2 = " << z3.re << " + i" << z3.im << ’\n’;
/ z2;
"z1 / z2 = " << z3.re << " + i" << z3.im << ’\n’;
return 0;
}
Universität Paderborn, ADT
5.16
Verlässliches Programmieren in C/C++
6. Klassen
6. Klassen
Universität Paderborn, ADT
6.1
Verlässliches Programmieren in C/C++
6. Klassen
Einleitung
– Eine Klasse faßt Datenstrukturen und zugehörige Funktionen zusammen
– Sie
kann
Datenelemente
(Member-Variablen)
und
Funktionselemente
(MemberFunktionen, Methoden) enthalten
– Member-Variablen
repräsentieren einen Typ und MemberFunktionen repräsentieren Operationen, die auf dem Datentyp
ausführbar sind
Universität Paderborn, ADT
– Member-Variablen und MemberFunktionen stellen die Schnittstelle einer Klasse dar
– Teile der Klasse können entweder
verborgen (gekapselt) oder explizit verfügbar gemacht werden; so
kann eine Klasse ihre Datenstruktur vollständig von der Umgebung
isolieren
– Eine Instanz einer Klasse heißt ein
Objekt
6.2
Verlässliches Programmieren in C/C++
6. Klassen
Klassendefinition
class klassenbezeichner {
[private:]
private daten und funktionen
[public:]
öffentliche daten und funktionen
};
– Eine Klasse definiert einen Datentyp, der zur Erzeugung von Objekten
dient
– private daten und funktionen sind nur innerhalb der Klasse, d. h. für Funktionen innerhalb der Klasse, zugänglich
– öffentliche daten und funktionen sind auch von Funktionen außerhalb der
Klasse zugänglich
– Wird keines der Schlüsselworte private oder public angegeben, so werden
alle Daten und Funktionen als privat angenommen
Universität Paderborn, ADT
6.3
Verlässliches Programmieren in C/C++
6. Klassen
Definition/Deklaration
– Funktionen (Member-Funktionen) einer Klasse können bei der Klassendefinition
• vollständig implementiert sein
• als Prototypen deklariert sein
– Bei Implementierung der als Prototypen angegebenen Funktionen ist als
Qualifikator der Klassenbezeichner voranzustellen
Klassendefinition
Implementierung
class klasse {
...
int func(. . .); // Prototyp
...
};
int klasse::func(. . .)
{
...
}
Universität Paderborn, ADT
6.4
Verlässliches Programmieren in C/C++
6. Klassen
Beispiel Schlange q.h
/* FIFO Datenorganisation
* Datentyp int
* Dateneingabe: put(element)
* Datenausgabe: get()
*/
const unsigned int QLEN = 5; // maximale Laenge
class queue {
private:
unsigned int laenge; // aktuelle Laenge
int data[QLEN]; // Elemente der Schlange
int erster, letzter;
public:
queue(); // Konstruktor, Initialisierung
void put(int element);
int get(void);
};
Universität Paderborn, ADT
6.5
Verlässliches Programmieren in C/C++
6. Klassen
Beispiel Schlange q.cc
#include <iostream>
#include "q.h"
queue::queue()
{
erster = letzter = laenge = 0;
cerr << "Schlange initialisiert"
<< endl;
}
void queue::put(int element)
{
if (laenge == QLEN) {
cerr << "Schlange ist voll" << endl;
}
else {
data[letzter] = element;
letzter = (letzter + 1) % QLEN;
++laenge;
}
return;
}
Universität Paderborn, ADT
int queue::get(void)
{
int element;
if (! laenge) {
cerr << "Schlange ist leer" << endl;
return(0);
}
else {
element = data[erster];
erster = (erster + 1) % QLEN;
--laenge;
return(element);
}
}
6.6
Verlässliches Programmieren in C/C++
6. Klassen
Beispiel Schlange qmain.cc
#include <iostream>
#include "q.h"
int main()
{
queue a; // Erzeugung einer Instanz
int element;
char aktion;
// p = put, g = get, x = ende
do {
cout << "Aktion? ";
cout << "(p = put, g = get, ";
cout << "x = ende) ";
cin >> aktion;
switch (aktion) {
case ’x’:
break;
case ’p’:
cin >> element;
a.put(element);
break;
Universität Paderborn, ADT
case ’g’:
cout << a.get() << endl;
break;
default:
continue; // Neue Eingabe
} // end switch
} while (aktion != ’x’);
return 0;
}
6.7
Verlässliches Programmieren in C/C++
6. Klassen
Spezielle Member-Funktionen
– Jede Klasse enthält mindestens vier Member-Funktionen
1. Default constructor. Dient der Initialisierung
2. Copy constructor. Dient der Duplizierung von Klasseninstanzen (Variablen)
3. Destructor. Dient der Zerstörung von Klasseninstanzen
4. Assignment operator. Dient der Zuweisung von Klasseninstanzen (Variablen)
– Falls diese Member-Funktionen nicht im Programm implementiert sind,
werden sie vom Compiler automatisch generiert
Universität Paderborn, ADT
6.8
Verlässliches Programmieren in C/C++
6. Klassen
Konstruktoren
– klasse::klasse()
Default constructor. Wird ausgeführt, wenn eine Instanz (Variable) von
klasse deklariert wird (Achtung: werden Member-Variablen nicht initialisiert, so ist ihr Wert undefiniert!)
– klasse::klasse(const klasse &org klasse)
Copy constructor. Wird bei Call-by-value Parameterübergabe, Initialisierung und Funktionsrückgabe ausgeführt; kopiert alle Member-VariablenWerte von org klasse in die neue Instanz; kann auch zur Duplizierung von
Instanzen verwendet werden:
klasse var1;
...
klasse var2(var1); // var2 ist eine Kopie von var1
– Konstruktoren haben keine Typangabe (als Ergebniswert)
Universität Paderborn, ADT
6.9
Verlässliches Programmieren in C/C++
6. Klassen
Destruktor und Zuweisung
– klasse::~klasse()
Destructor. Wird ausgeführt, wenn eine Instanz zerstört wird; erfolgt, wenn
eine Instanz ihren Gültigkeitsbereich verläßt
– klasse klasse::operator =(const klasse &org klasse)
Zuweisungsoperator. Zielobjekt existiert bereits. Dient der Zuweisung von
Instanzen von klasse; kopiert alle Member-Variablen von org klasse in die
neue Instanz
klasse var1, var2;
var1 = var2; // ‘operator =’ wird aufgerufen
klasse var3 = var2; //Initialisierung => Aufruf copy constructor!
– Destruktoren haben keine Typangabe (als Ergebniswert)
Universität Paderborn, ADT
6.10
Verlässliches Programmieren in C/C++
6. Klassen
Konstruktor mit Parameter
– Es kann mehrere Konstruktoren für eine Klasse geben
– Typischer Einsatz bei parametrisierten Objekten
class queue {
private:
// aktuelle Laenge
unsigned int laenge;
// Elemente der Schlange
int data[QLEN];
int erster, letzter;
int name; // Kennung der Schlange
public:
queue(); // Schlange der Laenge QLEN
// Numerierung der Instanzen
queue(int id);
void put(int element);
int get(void);
};
Universität Paderborn, ADT
// Konstruktor mit Parameter
queue::queue(int id)
{
erster = letzter = laenge = 0;
name = id;
cerr << "Schlange " << name
<< " initialisiert\n";
}
6.11
Verlässliches Programmieren in C/C++
6. Klassen
Instanzen einer Klasse erzeugen
Objekte können auf zwei Arten erzeugt werden (statisch durch Variablendefinition oder durch Zeigerdefinition mit Anforderung von dynamischen Speicher):
Default
constructor
Copy constructor
Destructor
Assignment
operator
Klasse var1;
Aufruf durch Variablendefinition
impliziter Aufruf bei Funktionsaufrufen mit Call-by-value,
Initialisierungen und Funktionsrückgaben
impliziter Aufruf durch Blockbzw. Programmende
Klasse var2; var2 = var1;
Universität Paderborn, ADT
Klasse *pVar1 = new Klasse;
expliziter
Aufruf
durch
new Klasse;
–
expliziter
Aufruf
delete pVar1;
–
durch
6.12
Verlässliches Programmieren in C/C++
6. Klassen
Dynamische mehrdimensionale Felder (a)
– Erzeugung eines Feldes von Zeigern auf ein Feld, z. B. Basistyp
double **elem
– Eleganter Zugriff auf Feldelemente durch Überschreiben des Operators ()
– Im Konstruktor
– double &operator()(int i, int j)
{
// Matrix-Index von
// 1 bis zeilen bzw. spalten,
// Feld von 0 bis zeilen - 1
// bzw. spalten - 1
return(elem[i - 1][j - 1]);
}
double **elem;
elem = new double*[zeilen];
for (i = 0; i < zeilen; ++i) {
elem[i] = new double[spalten];
}
– Im Destruktor
for (i = 0; i < zeilen; ++i) {
delete [] elem[i];
}
delete [] elem;
Universität Paderborn, ADT
– Überschreiben ermöglicht Zugriff
auf Feldelemente mit Ausdruck
m(3, 4), z. B.
cout << m(3, 4); // m[2][3]
6.13
Verlässliches Programmieren in C/C++
6. Klassen
Dynamische mehrdimensionale Felder (b)
const int DIM = 3;
class matrix
{
private:
const int zeilen, spalten;
double **elem;
public:
matrix();
matrix(int dim);
matrix(int zeilen, int spalten);
~matrix();
matrix(const matrix &old_matrix);
matrix &operator=(const matrix &m);
double &operator()(int i, int j);
void input();
void print();
};
Universität Paderborn, ADT
matrix::matrix(int z, int s) :
zeilen(z), spalten(s)
{
int i;
elem = new double*[zeilen];
for (i = 0; i < zeilen; ++i) {
elem[i] = new double[spalten];
}
init();
}
matrix::~matrix()
{
int i;
for (i = 0; i < zeilen; ++i) {
delete [] elem[i];
}
delete [] elem;
}
double &matrix::operator()(int i, int j)
{
return(elem[i - 1][j - 1]);
}
6.14
Verlässliches Programmieren in C/C++
6. Klassen
Statische Member-Variablen
– Eine Member-Variable gilt für Instanzen einer Klasse, d. h., jede Instanz besitzt ihre eigene Variable
– Eine statische Member-Variable
gilt für die Klasse, d. h., es gibt
nur eine solche Variable für alle Instanzen
class queue {
private:
// ...
public:
// Anzahl von Schlangen
static int anzahl;
queue() // Implementierung in Def.
{
++anzahl; // Neue Schlange
// ...
}
~queue() // Implementierung in Def.
{
--anzahl; // Eine Schlange weniger
}
};
– statische
Member-Variablen
können beispielsweise zum Zählen
von
Instanzen
einer
Klasse int queue::anzahl = 0; // Initialisierung
queue q1, q2; // queue::anzahl nun 2
verwendet werden
// Zugriff
cout << "Anzahl von Schlangen: "
<< queue::anzahl;
Universität Paderborn, ADT
6.15
Verlässliches Programmieren in C/C++
6. Klassen
Statische Member-Funktionen
– Sind statische Member-Variablen
privat, so kann vom außen nicht
auf sie zugegriffen werden
– Zum Zugriff auf private statische Member-Variablen von außerhalb einer Klasse gibt es statische
Member-Funktionen
class queue {
private:
// Aktuelle Anzahl von Schlangen
static int anzahl;
// ...
public:
static int schlangen_anzahl(void)
{
return(anzahl);
}
// ...
};
// Zugriff
cout << "Aktive Schlangen:
<< queue::schlangen_anzahl();
Universität Paderborn, ADT
6.16
Verlässliches Programmieren in C/C++
6. Klassen
Bedeutung von static
static-Deklaration
Benutzung
Bedeutung
Variable
außerhalb Gültigkeitsbereich der Variable ist die Datei, in der
Funktion
sie steht
Variable innerhalb einer Variable ist permanent, sie wird nur einmal initialiFunktion
siert und nur einmal erzeugt, auch wenn die Funktion mehrfach bzw. rekursiv aufgerufen wird
Funktion
Gültigkeitsbereich der Funktion ist die Datei, in
der sie steht
Member-Variable
Nur eine Variable pro Klasse (nicht eine pro Instanz)
Member-Funktion
Funktion kann nur auf statische MemberVariablen der Klasse zugreifen
Universität Paderborn, ADT
6.17
Verlässliches Programmieren in C/C++
6. Klassen
Initialisierung von Member-Variablen (a)
– Member-Variablen können in Konstruktoren statt durch Zuweisungen direkt initialisiert werden
– class drei_d {
private:
int x, y, z; // Koordinaten
public:
drei_d(int a, int b, int c) : x(a), y(b), z(c) {}
};
entspricht
class drei_d {
private:
int x, y, z; // Koordinaten
public:
drei_d(int a, int b, int c) {x = a; y = b; z = c; }
};
Universität Paderborn, ADT
6.18
Verlässliches Programmieren in C/C++
6. Klassen
Initialisierung von Member-Variablen (b)
– Bei konstanten Member-Variablen kann eine Zuweisung nicht erfolgen
– Die Initialisierung mit dem “:”-Konstrukt ist dann erforderlich
– Initialisierung geschieht in Reihenfolge der Deklaration!
Richtig:
Falsch:
class vektor {
private:
const int streckfaktor;
// <int x, y, z; // Koordinaten // <public:
vektor(int s) : streckfaktor(s),
x(0), y(0), z(0) {}
vektor(int s, int a, int b, int c) :
streckfaktor(s),
x(a*streckfaktor),
y(b*streckfaktor),
z(c*streckfaktor)
{}
};
class vektor {
private:
int x, y, z; // Koordinaten // <const int streckfaktor;
// <public:
vektor(int s) : streckfaktor(s),
x(0), y(0), z(0) {}
vektor(int s, int a, int b, int c) :
streckfaktor(s),
x(a*streckfaktor),
y(b*streckfaktor),
z(c*streckfaktor)
{}
};
Universität Paderborn, ADT
6.19
Verlässliches Programmieren in C/C++
6. Klassen
Operator-Overloading und Selbstreferenz
– Für jede Klasse können Operatoren neu definiert werden
– Die Neudefinition geschieht analog zu der Weise, wie sie bei normalen
Datentypen erfolgt
– Bei jedem Aufruf einer Member-Funktion wird automatisch ein Zeiger, der
auf das aktuelle Objekt zeigt, übergeben; der Zeiger heißt this
– Der this-Zeiger ist ein impliziter Parameter jeder Member-Funktion
– Der this-Zeiger wird als Selbstreferenz bezeichnet
Universität Paderborn, ADT
6.20
Verlässliches Programmieren in C/C++
6. Klassen
Dynamische Speicherverwaltung (a)
– Oft wird bei Klassenkonstruktoren mit new eine Datenstruktur erzeugt
– Beispiel: Matrix
class matrix {
public:
float **data; // Matrix von Zahlen
matrix() { data = new float[3][3]; } // Standard 3x3
matrix(int groesse) { data = new float[groesse][groesse]; }
matrix(int zeilen, int spalten) { data = new float[zeilen][spalten]; }
~matrix() { delete [] data; }
// Operatoren, Zuweisung, etc.
};
int main()
{
matrix a; // eine 3x3-Matrix
matrix b(5); // eine 5x5-Matrix
matrix c(3, 4); // eine 3x4-Matrix
...
}
Universität Paderborn, ADT
6.21
Verlässliches Programmieren in C/C++
6. Klassen
Dynamische Speicherverwaltung (b)
– Bei dynamischer Speicherverwaltung sind die automatisch erzeugten CopyKonstruktoren, Destruktoren und Zuweisungsoperatoren meist unbrauchbar
– Bei dynamischer Speicherverwaltung sind die relevanten Member-Variablen
Zeiger
– Der Copy-Konstruktor erzeugt einen neuen Zeiger, der auf die Daten der zu
kopierenden Instanz zeigt! Änderungen in der Kopie bewirken Änderungen
im Original
– Bei Feldern gibt der Destruktor nur den Speicher für das erste Element
frei
– Der Zuweisungsoperator weist nur die Zeiger zu
Universität Paderborn, ADT
6.22
Verlässliches Programmieren in C/C++
6. Klassen
Dynamische Speicherverwaltung (c)
class matrix {
public:
const int zeilen, spalten;
float **data;
// Konstruktoren
// ...
// Copy-Konstruktor
matrix(const matrix &org_matrix) :
zeilen(org_matrix.zeilen),
spalten(org_matrix.spalten)
{
data = new float[zeilen][spalten];
for (int i = 0; i < zeilen; ++i) {
for (int j = 0; j < spalten; ++j) {
data[i][j] = org_matrix.data[i][j];
}
}
}
// ...
Universität Paderborn, ADT
matrix operator =(const matrix &org_matrix)
{
if ((zeilen != org_matrix.zeilen)
|| (spalten != org_matrix.spalten)) {
cerr << "Matrizen haben ";
cerr << "verschiedenes Format. ";
cerr << "Keine Zuweisung";
cerr << endl;
return(*this);
}
for (int i = 0; i < zeilen; ++i) {
for (int j = 0; j < spalten; ++j) {
data[i][j] = org_matrix.data[i][j];
}
}
return(*this);
}
// ...
};
6.23
Verlässliches Programmieren in C/C++
6. Klassen
Beispiel Raumkoordinaten dreid.h
class drei_d {
– Ganzzahlige Raumkoordinaten als private:
int x, y, z; // Koordinaten
Klasse
public:
drei_d() // Konstruktor
– Neben Konstruktor und Ausgabe{
x = y = z = 0;
funktion sind Zuweisungsoperator
};
(=) und die Inkrementierungsoperatoren (++) als Overloading realisiert
– Inkrementierungsoperatoren
sowohl als präfix als auch suffix
– Inkrementierungsoperatoren
benutzen den impliziten Parameter
this
Universität Paderborn, ADT
drei_d(int i, int j, int k) // Konstruktor
{
x = i;
y = j;
z = k;
};
drei_d operator =(const drei_d op2);
drei_d operator ++(void); // praefix
drei_d operator ++(int nil); // suffix
void show(void);
};
6.24
Verlässliches Programmieren in C/C++
6. Klassen
Beispiel Raumkoordinaten dreid.cc
#include <iostream>
#include "dreid.h"
// Overload =
drei_d drei_d::operator =(const drei_d op2)
{
x=op2.x;
y=op2.y;
z=op2.z;
return *this;
// Overload suffix ++
// Parameter int nil wird nicht benutzt!
// Bewirkt suffix
drei_d drei_d::operator ++(int nil)
{
drei_d erg=*this;
++x; // ++ fuer int
++y; // ++ fuer int
++z; // ++ fuer int
}
return erg;
// Overload praefix ++
drei_d drei_d::operator ++(void)
{
++x; // ++ fuer int
++y; // ++ fuer int
++z; // ++ fuer int
return *this;
}
// Ausgabe einer 3D-Koordinate
void drei_d::show(void)
{
cout << x << ", " << y << ", "
<< z << endl;
}
}
Universität Paderborn, ADT
6.25
Verlässliches Programmieren in C/C++
6. Klassen
Beispiel Raumkoordinaten dreidmain.cc
#include "dreid.h"
int main()
{
drei_d x(1,2,3), y;
x.show();
y.show();
++y;
y.show();
y++;
y.show();
y=x++;
y.show();
x.show();
Ausgabe
1,
0,
1,
2,
1,
2,
2,
0,
1,
2,
2,
3,
3
0
1
2
3
4
}
Universität Paderborn, ADT
6.26
Verlässliches Programmieren in C/C++
6. Klassen
Friend-Funktion (a)
friend datentyp funktionsbezeichner (parameterdeklarationen);
– Nicht-Member-Funktionen können auf private Daten einer Klasse nicht
zugreifen
– Der Zugriff auf private Daten kann in der Klassendefinition für bestimmte
Nicht-Member-Funktionen zugelassen werden
– Durch Friend-Funktionen hat man für eine Klasse die Kontrolle darüber,
welche externen Funktionen Zugriff auf die Daten einer Klasse haben
Universität Paderborn, ADT
6.27
Verlässliches Programmieren in C/C++
6. Klassen
Friend-Funktion (b)
Eine gewöhnliche Deklaration einer Member-Funktion spezifiziert drei logisch
unterschiedliche Dinge:
1. Die Funktion kann auf private Teile der Klasse zugreifen
2. Die Funktion liegt im Sichtbarkeitsbereich der Klasse
3. Aufruf der Funktion erfolgt über ein Objekt
– statische Member-Funktionen besitzen nur die ersten beiden Eigenschaften
– für als friend deklarierte Funktionen gilt nur die erste Eigenschaft
Universität Paderborn, ADT
6.28
Verlässliches Programmieren in C/C++
6. Klassen
Beispiel Komplexe Zahlen complex.h
class complex {
private:
double re, im; // Real-, Imaginaerteil
public:
complex() // Konstruktor
{
re = 0.0;
im = 0.0;
};
void input();
void output();
friend complex operator +(complex z1, complex z2);
friend complex operator *(complex z1, complex z2);
friend complex operator /(complex z1, complex z2);
complex operator +=(complex z2);
complex operator =(complex z2);
};
Universität Paderborn, ADT
6.29
Verlässliches Programmieren in C/C++
6. Klassen
Beispiel Komplexe Zahlen complex.cc (a)
#include <iostream>
#include "complex.h"
// Eingabe von komplexen Zahlen
void complex::input()
{
cin >> re >> im;
}
// Ausgabe von komplexen Zahlen
void complex::output()
{
cout << re << " + i" << im;
}
Universität Paderborn, ADT
6.30
Verlässliches Programmieren in C/C++
6. Klassen
Beispiel Komplexe Zahlen complex.cc (b)
// Operatorfunktion +, Addition komplexer Zahlen, friend
complex operator +(complex z1, complex z2)
{
complex erg;
erg.re=z1.re+z2.re;
erg.im=z1.im+z2.im;
return erg;
}
// Operatorfunktion *, Multiplikation komplexer Zahlen, friend
complex operator *(complex z1, complex z2)
{
complex erg;
erg.re=z1.re*z2.re-z1.im*z2.im;
erg.im=z1.re*z2.im+z2.re*z1.im;
return erg;
}
Universität Paderborn, ADT
6.31
Verlässliches Programmieren in C/C++
6. Klassen
Beispiel Komplexe Zahlen complex.cc (c)
// Operatorfunktion +=
complex complex::operator +=(complex z2)
{
re=re+z2.re;
im=im+z2.im;
return *this;
}
// Operatorfunktion =, Zuweisung komplexer Zahlen
complex complex::operator =(complex z2)
{
re=z2.re;
im=z2.im;
return *this;
}
Universität Paderborn, ADT
6.32
Verlässliches Programmieren in C/C++
6. Klassen
Beispiel Komplexe Zahlen complexmain.cc
#include <iostream>
#include "complex.h"
int main()
{
complex a, b, c;
a.output();
cout << " + ";
b.output();
cout << " = ";
a+=b; // a hat jetzt den wert von a+b
a.output();
cout << ’\n’;
cout << "komplexe Zahl? ";
a.input();
cout << "komplexe Zahl? ";
b.input();
a.output();
cout << " / ";
b.output();
cout << " = ";
c=a/b;
c.output();
cout << ’\n’;
Universität Paderborn, ADT
return(0);
}
6.33
Verlässliches Programmieren in C/C++
7. Vererbung
7. Vererbung
Universität Paderborn, ADT
7.1
Verlässliches Programmieren in C/C++
7. Vererbung
Konzept (a)
– Datenabstraktion ist eine effektive Methode zur Erweiterung des vordefinierten Typsystems, wenn ein einziges, klar definiertes Konzept vorliegt,
z. B. komplexe Zahlen
– Oft steht aber nur ein abstrakter Datentyp, eine Klasse, zur Verfügung,
die eine bestimmte Situation nur teilweise bzw. annähernd beschreibt
– Es kann auch eine Zusammenstellung von Klassen verfügbar sein, die sich
in Bedeutung und Implementierung ähnlich sind, aber nicht identisch
– Hier ist Vererbung eine nützliche Methode zur Ergänzung der Datenabstraktion
Universität Paderborn, ADT
7.2
Verlässliches Programmieren in C/C++
7. Vererbung
Konzept (b)
– Vererbung bietet die Möglichkeit
hierarchischer Klassifizierung von
Datentypen (Klassen)
– Eine Klasse kann aus einer anderen
Klasse durch
• Erweiterung oder
• Spezialisierung
abgeleitet werden
Universität Paderborn, ADT
– Die Klasse, aus der abgeleitet
wird, heißt Basisklasse, sie vererbt ihre Members (Variablen und
Funktionen)
– Die Klasse, die ableitet, heißt abgeleitete Klasse
• sie erbt die Members ihrer Basisklasse
• sie kann Leistungsmerkmale an
bestimmte Anforderungen anpassen und neue Leistungsmerkmale hinzufügen
7.3
Verlässliches Programmieren in C/C++
7. Vererbung
Konzept (c)
Ableitung von Klassen ermöglicht
– die Implementierung von generischen Datentypen
– durch spezielle Anpassung (Ableitung) die Wiederverwendung bewährten
Programm-Codes
– Strukturierung von Datentypen
Universität Paderborn, ADT
7.4
Verlässliches Programmieren in C/C++
7. Vererbung
Definition
class klassenbezeichner : [public|protected|private] basisklasse {
[private:]
private daten und funktionen
[protected:]
geschützte daten und funktionen
[public:]
öffentliche daten und funktionen
};
– Im Kopf der Klassendefinition einer abgeleiteten Klasse kann ein Zugriffsmodus (public, protected oder private) auf die Basisklasse angegeben
werden
– Der Zugriffsmodus bestimmt die Möglichkeiten des Zugriffs auf MemberVariablen und Member-Funktionen innerhalb der Vererbungshierarchie
Universität Paderborn, ADT
7.5
Verlässliches Programmieren in C/C++
7. Vererbung
Zugriffsmodi
– private: geschützte und öffentliche Members der Basisklasse sind privat in
der abgeleiteten Klasse (default, wenn kein Modus angegeben)
– protected: geschützte und öffentliche Members der Basisklasse sind geschützt in der abgeleiteten Klasse
– public: geschützte Members der Basisklasse sind geschützt und öffentliche
Members der Basisklasse sind öffentlich in der abgeleiteten Klasse
– Private Members der Basisklasse sind in der abgeleiteten Klasse nicht zugreifbar
– Geschützte Members der Basisklasse sind nur in der Basisklasse und in
abgeleiteten Klassen zugreifbar
Universität Paderborn, ADT
7.6
Verlässliches Programmieren in C/C++
7. Vererbung
Beispiel: Vererbung
class base {
private:
int i;
protected:
int j;
public:
int k;
void seti(int a) { i = a; }
int geti() { return i; }
};
class derived: protected
public:
// j ist protected
void setj(int a) { j =
// k ist protected
void setk(int a) { k =
int getj() { return j;
int getk() { return k;
};
Universität Paderborn, ADT
int main()
{
derived ob;
// illegal, da protected in derived!
// ob.seti(10);
// illegal, da protected in derived!
// cout << ob.geti();
// illegal, da k protected!
// ob.k = 10;
base {
ob.setk(10);
cout << ob.getk() << ’ ’;
ob.setj(12);
cout << ob.getj() << ’ ’;
a; }
a; }
}
}
return 0;
}
7.7
Verlässliches Programmieren in C/C++
7. Vererbung
Wiederverwendung
– Klassenhierarchien können verwendet werden, um generische Klassen zu
bilden
– Generische Klassen geben nur eine bestimmte Implementierungsstruktur
vor, sie sind Basisklassen
– Eine konkrete Implementierung erfolgt in abgeleiteten Klassen für spezifische Ausprägungen der Basisklasse
– Generische Klassen bilden eine Schnittstelle zu bestimmtem ProgrammCode, der in abgeleiteten Klassen wiederverwendet wird
Universität Paderborn, ADT
7.8
Verlässliches Programmieren in C/C++
7. Vererbung
Beispiel Binärer Suchbaum (a)
– Ein binärer Suchbaum ist eine Datenstruktur, die Elemente gemäß einer
Sortierung speichert
– Jeder Knoten des Baumes besitzt zwei Nachfolger (left, right)
– Die Nachfolger sind ebenfalls binäre Suchbäume
– Im Nachfolger left befinden sich alle Elemente, die kleiner sind als das
Element im Knoten
– Im Nachfolger right befinden sich alle Elemente, die größer sind als das
Element im Knoten
– Jeder Knoten besitzt zwei Dateneinträge: (1) Element (data), (2) Vielfachheit (count) des Elements
Universität Paderborn, ADT
7.9
Verlässliches Programmieren in C/C++
7. Vererbung
Beispiel Binärer Suchbaum (b)
t
left
data
7
count
2
right
Universität Paderborn, ADT
t.insert(13);
4
15
1
3
2
11
19
1
1
1
8
13
4
1
7.10
Verlässliches Programmieren in C/C++
7. Vererbung
Beispiel Binärer Suchbaum (c) gentree.h
// Generische binaere Suchbaeume
// generischer Zeiger, verweist in
// abgeleiteter Klasse auf Daten
typedef void *p_gen;
class bnode { // Knoten
private:
friend class gen_tree;
p_gen data;
bnode *left;
bnode *right;
int count; // Vielfachheit eines Datums
// Konstruktor
bnode(p_gen d, bnode *l, bnode *r)
: data(d),
left(l),
right(r),
count(1)
{}
// Vergleich von Daten
friend int comp(p_gen a, p_gen b);
friend void print(bnode *n);
};
Universität Paderborn, ADT
class gen_tree { // Baum
protected:
bnode *root;
p_gen find(bnode *r, p_gen d);
void print(bnode *r);
public:
// Konstruktor
gen_tree()
{
root=0; // leerer Baum
}
void insert(p_gen d);
p_gen find(p_gen d)
{
return find(root, d);
}
void print()
{
print(root);
}
};
7.11
Verlässliches Programmieren in C/C++
7. Vererbung
Beispiel Binärer Suchbaum (d)
– Benutzung eines void-Zeigers (generischer Zeiger ) für die Elemente, damit
in abgeleiteten Klassen beliebige Typen deklariert werden können
– Eine Klasse bnode, die einen Knoten des Baums beschreibt
– Eine Klasse gen_tree, die den binären Suchbaum beschreibt (Member:
bnode, geschützt, damit nur in abgeleiteten Klassen darauf zugegriffen werden kann
– Alle Members in bnode sind privat, gen_tree ist friend, kann also auf alle
Daten von bnode zugreifen
– Konstruktor in bnode mit Initialisierungsliste
Universität Paderborn, ADT
7.12
Verlässliches Programmieren in C/C++
7. Vererbung
Beispiel Binärer Suchbaum (e)
– Member-Funktionen in gen_tree: Konstruktor (erzeugt leeren Baum), Einfügen (insert()), Suchen (find()), Ausgeben (print())
– Vergleichsoperation für Elemente (comp()) ist datentypunabhängig, wird
außerhalb der Klasse definiert, (friend von bnode)
– Gleiches gilt für die Ausgabefunktion print()
– Im geschützten Teil von gen_tree gibt es eine Hilfsfunktion
find(bnode *r, p_gen d) (sucht im Teilbaum r nach d)
– Im öffentlichen Teil gibt es die Funktion find(p_gen d) (sucht im gesamten
Baum nach d
– Gleiche Konstruktion für die Ausgabe (print())
Universität Paderborn, ADT
7.13
Verlässliches Programmieren in C/C++
7. Vererbung
Beispiel Binärer Suchbaum (f)
#include "gentree.h"
void gen_tree::insert(p_gen d)
{
bnode *temp = root;
bnode *old;
if (root == 0) {
root = new bnode(d, 0, 0);
return;
}
while (temp != 0) {
old = temp;
if (comp(temp->data, d) == 0) {
(temp->count)++;
return;
}
if (comp(temp->data, d) > 0)
temp = temp->left;
else
temp = temp->right;
}
if (comp(old->data, d) > 0)
old->left = new bnode(d, 0, 0);
else
old->right = new bnode(d, 0, 0);
}
Universität Paderborn, ADT
// In-order Durchlauf, binaere Suche
p_gen gen_tree::find(bnode *r, p_gen d)
{
if (r == 0)
return 0;
else if (comp(r->data, d) == 0)
return r->data;
else if (comp(r->data, d) > 0)
return find(r->left, d);
else
return find(r->right, d);
}
// Pre-order Durchlauf, sortierte Ausgabe
void gen_tree::print(bnode *r)
{
if (r != 0) {
print(r->left);
print(r); // Nicht-Member-Funktion!
print(r->right);
}
}
7.14
Verlässliches Programmieren in C/C++
7. Vererbung
Beispiel Binärer Suchbaum (g)
– Die generische Klasse ist nicht zur Erzeugung von Instanzen geeignet
– Es muß eine Klasse abgeleitet werden, in der der Datentyp der Einträge
definiert ist
– Die datentypabhängigen Funktionen für den Elementvergleich (comp()) und
die Ausgabe (print()) müssen definiert werden
– Alle Funktionen, die auf Elemente (generischer Datentyp void *p_gen) zugreifen, müssen mit einer Typkonversion arbeiten
– Die abgeleitete Klasse leitet mit dem Zugriffsmodus private von gen_tree
ab, denn es soll keine weitere Konkretisierung der Datentypen geben
Universität Paderborn, ADT
7.15
Verlässliches Programmieren in C/C++
7. Vererbung
Beispiel Binärer Suchbaum (h)
#include <iostream>
#include <cstring>
#include "gentree.h"
class s_tree: private gen_tree {
public:
s_tree() { cout << "Baum erzeugt\n"; }
void insert(char *d)
{ gen_tree::insert(p_gen(d)); }
char *find(char *d)
{ return (char*)
gen_tree::find(p_gen(d)); }
void print() { gen_tree::print(); }
};
int comp(p_gen i, p_gen j)
{
return strcmp((char*)i, (char*)j);
}
void print(bnode *n)
{
cout << (char*)n->data << " (";
cout << n->count << ")\t";
}
Universität Paderborn, ADT
int main()
{
s_tree daten; // Suchbaum von Zeichenketten
char datum[80]; // Zeichenkette
char *el; // Element des Suchbaums
cout << "Bitte Strings eingeben ";
cout << "(Ende mit Ctrl-D):\n";
// cin.good() prueft auf typgerechte Eingabe
while (cin >> datum && cin.good()) {
el = new char[strlen(datum) + 1];
strcpy(el, datum);
daten.insert(el);
}
daten.print();
cout << ’\n’;
return 0;
}
7.16
Verlässliches Programmieren in C/C++
7. Vererbung
Zeigerregel
– Abgeleitete Klassen sind bezüglich Zeiger kompatibel zur Basisklasse
– Ein Zeiger auf eine Basisklasse kann auf davon abgeleitete Klassen zeigen
( upcasting“)
”
Bsp.: basis *pB = new abgeleitet;
– Ein Zeiger auf eine abgeleitete Klasse kann nicht auf die Basisklasse oder
eine andere davon abgeleitete Klasse zeigen – eine explizite Typumwandlung mittels eines Casts ist jedoch möglich ( downcasting“):
”
Bsp.: abgeleitet *pA = (abgeleitet*) pB;
besser: abgeleitet *pA = dynamic_cast<abgeleitet*>(pB);
(bei einem fehlgeschlagenen Cast gibt der Operator dynamic_cast den Wert
0 zurück)
Universität Paderborn, ADT
7.17
Verlässliches Programmieren in C/C++
7. Vererbung
Polymorphie und virtuelle Funktionen
– Virtuelle Funktionen erlauben Polymorphie von Funktionen
– Eine virtuelle Funktion wird in der Basisklasse deklariert und in abgeleiteten
Klassen neu definiert
– Der Prototyp der neu definierten Funktion muß dieselbe Struktur wie die
virtuelle Funktion in der Basisklasse haben (gleiche Parameter, gleicher
Datentyp des Rückgabewerts)
– Beim Aufruf einer virtuellen Funktion über einen Zeiger auf die Basisklasse
wird die aktuelle Funktion durch den Typ des Objekts, auf den der Zeiger
zeigt, bestimmt und nicht durch die Basisklasse
– Dadurch wird zur Laufzeit die auszuführende Funktion bestimmt, und nicht
zur Compile-Zeit (Laufzeitbindung)
Universität Paderborn, ADT
7.18
Verlässliches Programmieren in C/C++
7. Vererbung
Virtuelle Funktionen
virtual datentyp funktionsbezeichner (parameterdeklarationen)
{
anweisungen
}
– Die Eigenschaft, virtuell zu sein, wird durch die Vererbungshierarchie weitergereicht
– Ist eine Funktion in der Basisklasse als virtuell deklariert, so ist sie in
allen (auch mehrstufig) abgeleiteten Klassen virtuell, ohne dort als virtuell
deklariert zu sein
Universität Paderborn, ADT
7.19
Verlässliches Programmieren in C/C++
7. Vererbung
Beispiel Virtuelle Funktion
#include <iostream>
class basis {
public:
int i;
virtual void print_i() { cout << i << " in basis\n"; }
};
class abgeleitet: public basis {
public:
void print_i() { cout << i << " in abgeleitet\n"; }
};
int main()
{
basis b;
basis *b_ptr=&b;
abgeleitet d;
}
d.i = 1 + (b.i = 1);
b_ptr->print_i();
// "1 in basis"
b_ptr = &d;
b_ptr->print_i();
// "2 in abgeleitet" (waere "2 in basis",
// falls print_i nicht virtuell)
Universität Paderborn, ADT
7.20
Verlässliches Programmieren in C/C++
7. Vererbung
Abstrakte Klassen
virtual datentyp funktionsbezeichner (parameterdeklarationen) = 0;
– Wenn eine virtuelle Funktion der Basisklasse, die nicht in der abgeleiteten
Klasse redefiniert ist, von einem Objekt der abgeleiteten Klasse aufgerufen wird, so wird die Funktion, so wie sie in der Basisklasse definiert ist,
ausgeführt
– Oft gibt es in der Basisklasse keine sinnvolle Definition der virtuellen Funktion (z. B. Flächenberechnung geometrischer Objekte)
– Eine rein virtuelle Funktion besitzt in der Basisklasse keine Definition
– Eine Klasse, die mindestens eine rein virtuelle Funktion enthält, heißt abstrakte Klasse – aus ihr können keine Instanzen erstellt werden
Universität Paderborn, ADT
7.21
Verlässliches Programmieren in C/C++
7. Vererbung
Beispiel Geometrische Figuren (a)
Abstrakte Klasse figur mit abgeleiteten Klassen dreieck, rechteck und kreis
figur
dim: x, y
flaeche()
umfang()
dreieck
rechteck
breite, hoehe
flaeche()
umfang()
Universität Paderborn, ADT
breite, hoehe
flaeche()
umfang()
kreis
durchmesser
flaeche()
umfang()
7.22
Verlässliches Programmieren in C/C++
7. Vererbung
Beispiel Geometrische Figuren (b) figur.h
#include <iostream>
const double PI = 3.14159;
class figur {
protected:
double x, y; // Dimensionen
public:
void set_dim(double i, double j = 0)
{ x = i; y = j; }
virtual double flaeche() = 0;
virtual double umfang() = 0;
};
class dreieck: public figur {
// x: Breite, y: Hoehe
public:
double flaeche()
{ return(x * 0.5 * y); }
double umfang()
{ return(sqrt((x * 0.5) * (x * 0.5)
+ y * y) * 2 + x); }
};
Universität Paderborn, ADT
class rechteck: public figur {
// x: Breite, y: Hoehe
public:
double flaeche()
{ return(x * y); }
double umfang()
{ return(2 * x + 2 * y); }
};
class kreis: public figur {
// x: Durchmesser, y nicht benutzt
public:
double flaeche()
{ return((x * x / 4) * PI); }
double umfang()
{ return(x * PI); }
};
7.23
Verlässliches Programmieren in C/C++
7. Vererbung
Beispiel Geometrische Figuren (c) figurmain.cc
#include <iostream>
#include "figur.h"
for (int i = 0; i < dim; ++i) {
cout << "Objekt " << i << ": ";
cout << fig[i]->flaeche() << endl;
tot_flaeche += fig[i]->flaeche();
}
cout << "Gesamtflaeche: ";
cout << tot_flaeche << endl;
int const dim = 3;
int main()
{
double tot_flaeche=0;
dreieck d;
rechteck r;
kreis k;
figur *fig[dim];
return 0;
}
Ausgabe
d.set_dim(3.78, 2.87);
r.set_dim(2.5, 3.77);
k.set_dim(6.23);
fig[0] = &d;
fig[1] = &r;
fig[2] = &k;
Universität Paderborn, ADT
Objekt 0: 5.4243
Objekt 1: 9.425
Objekt 2: 30.4836
Gesamtflaeche: 45.3329
7.24
Verlässliches Programmieren in C/C++
7. Vererbung
Virtuelle Klassen und Mehrfachvererbung
class klassenbezeichner : [zugriff ] basisklasse [, [zugriff ] basisklasse]* {
daten und funktionen
};
– Eine Klasse kann von mehreren Klassen abgeleitet sein (Mehrfachvererbung, Multiple-inheritance)
– Bei Verwendung von Mehrfachvererbung können Mehrdeutigkeiten auftreten (Vererbung von Funktionen gleichen Namens und verschiedener Bedeutung aus verschiedenen Klassen)
– Es kann zu Duplizierung von Membervariablen kommen
Universität Paderborn, ADT
7.25
Verlässliches Programmieren in C/C++
7. Vererbung
Beispiel Mehrdeutigkeit
class werkzeug {
public:
int kosten();
// ...
};
class teile {
public:
int kosten();
// ...
};
class arbeit {
public:
int kosten();
// ...
};
class rechnung : public werkzeug,
public teile, public arbeit {
public:
int gesamtkosten()
{
// Korrekt, eindeutig
return(werkzeug::kosten()
+ teile::kosten()
+ arbeit::kosten());
}
};
int fun()
{
int preis;
rechnung *ptr;
// Inkorrekt, nicht eindeutig
preis = ptr->kosten();
}
Universität Paderborn, ADT
7.26
Verlässliches Programmieren in C/C++
7. Vererbung
Beispiel Duplizierung
name
name
selbst
abhaeng
steuerbescheid
// name ist zweifach
steuerbescheid::selbst::name
steuerbescheid::abhaeng::name
Universität Paderborn, ADT
class name {
public:
char *name;
// ...
};
class selbst : public name {
public:
int gehalt;
// ...
};
class abhaeng : public name {
public:
int einnahmen;
// ...
};
class steuerbescheid : public selbst,
public abhaeng {
// name ist zweifach vorhanden!
};
7.27
Verlässliches Programmieren in C/C++
7. Vererbung
Virtuelle Vererbung
class klassenbezeichner : virtual [zugriff ] basisklasse {
daten und funktionen
};
– Bei Mehrfachvererbung können zwei Basisklassen ihrerseits von einer gemeinsamen Basisklasse abgeleitet sein
– Wird eine Klasse von diesen beiden Basisklassen abgeleitet, so hat die abgeleitete Klasse zwei Member-Objekte der gemeinsamen Basis-Basisklasse
– Eine solche Duplizierung kann durch virtuelle Vererbung vermieden werden
Universität Paderborn, ADT
7.28
Verlässliches Programmieren in C/C++
7. Vererbung
Beispiel Virtuelle Vererbung
class name {
public:
char *name;
// ...
};
name
selbst
abhaeng
steuerbescheid
class selbst : virtual public name {
public:
int gehalt;
// ...
};
class abhaeng : virtual public name {
public:
int einnahmen;
// ...
};
class steuerbescheid : public selbst,
public abhaeng {
// name ist nur einfach vorhanden
};
Universität Paderborn, ADT
7.29
Verlässliches Programmieren in C/C++
7. Vererbung
Funktionsmaskierung
class basis {
– Ist in einer abgeleiteten Klasse ei- public:
int fun(int i, int j)
ne angeforderte Funktion nicht de{ return(i * j); }
float fun(float f)
finiert, so wird in der Basisklasse
{ return(f * 2); }
gesucht
};
– Probleme treten bei Polymorphie class abgeleitet : public basis {
public:
auf
int fun(int i, int j)
{ return(i + j); }
– Sind in der Basisklasse zwei Funk- };
tionen gleichen Namens definiert
int main() {
und ist in der abgeleiteten Klasse
abgeleitet test;
int i;
nur eine dieser Funktionen redefifloat f;
niert, so ist die andere Funktion
i = test.fun(1, 3); // Legal, i = 4
der Basisklasse in der abgeleiteten
f = test.fun(4.0); // Illegal, in test
Klasse nicht verfügbar
// nicht verfügbar
}
Universität Paderborn, ADT
7.30
Verlässliches Programmieren in C/C++
7. Vererbung
Konstruktoren und Destruktoren
– Konstruktoren und Destruktoren verhalten sich in abgeleiteten Klassen
anders als normale Member-Funktionen
– Bei Deklaration einer Instanz einer abgeleiteten Klasse wird erst der Konstruktor der Basisklasse und danach der Konstruktor der abgeleiteten Klasse ausgeführt
– Bei Zerstörung einer Instanz einer abgeleiteten Klasse wird erst der Destruktor der abgeleiteten Klasse und danach der Destruktor der Basisklasse
ausgeführt
– Probleme gibt es bei Zeigern vom Typ der Basisklasse, die auf Objekte
einer abgeleiteten Klasse zeigen
Universität Paderborn, ADT
7.31
Verlässliches Programmieren in C/C++
7. Vererbung
Beispiel Konstruktoren und Destruktoren
#include <iostream>
class basis {
public:
basis()
{
cout << "Konstruktor basis\n";
}
~basis()
{
cout << "Destruktor basis\n";
}
};
int main()
{
abgeleitet *test_ptr = new abgeleitet;
delete test_ptr;
test_ptr = 0;
return 0;
}
class abgeleitet : public basis {
public:
abgeleitet()
{
cout << "Konstruktor abgeleitet\n";
}
~abgeleitet()
{
cout << "Destruktor abgeleitet\n";
}
};
Universität Paderborn, ADT
Ausgabe
Konstruktor basis
Konstruktor abgeleitet
Destruktor abgeleitet
Destruktor basis
7.32
Verlässliches Programmieren in C/C++
7. Vererbung
Virtuelle Destruktoren
// Legale Deklaration
basis *basis_ptr = new abgeleitet;
delete basis_ptr;
basis_ptr = 0;
Ausgabe
Konstruktor basis
Konstruktor abgeleitet
Destruktor basis
class basis {
public:
basis()
{
cout << "Konstruktor basis\n";
}
virtual ~basis()
{
cout << "Destruktor basis\n";
}
wird };
– Bei Zeigern auf Klassen
die Funktion des Zeigertyps aus- Ausgabe
geführt
– Ein virtueller Destruktor bewirkt
die Ausführung des aktuellen Typs
(Laufzeitbindung)
Universität Paderborn, ADT
Konstruktor basis
Konstruktor abgeleitet
Destruktor abgeleitet
Destruktor basis
7.33
Verlässliches Programmieren in C/C++
7. Vererbung
Initialisierungsregeln
– Basisklassen werden in der Reihenfolge ihrer Deklaration initialisiert
– Members werden in der Reihenfolge ihrer Deklaration initialisiert
– Virtuelle Basisklassen werden vor jeder nicht-virtuellen Basisklasse erzeugt
– Destruktoren werden in umgekehrter Reihenfolge der Konstruktoren aufgerufen
Universität Paderborn, ADT
7.34
Verlässliches Programmieren in C/C++
7. Vererbung
Konstruktionsschema
class abstrakt_basis {
private:
// meist leer,
// beschraenkt spaeteren Entwurf
protected:
// statt private,
// ermoeglicht Vererbung
// ...
public:
// Standard-Konstruktor
abstrakt_basis();
// Copy-Konstruktor
abstrakt_basis(const abstrakt_basis&);
// Destruktor
// braucht Laufzeittyp
virtual ~abstrakt_basis();
// Ausdruck, rein virtuell
virtual void print() = 0;
// ...
};
Universität Paderborn, ADT
class abgeleitet :
virtual public abstrakt_basis {
private:
// ...
protected:
// statt private fuer Vererbung
// ...
public:
// Schnittstelle
// Realisiert die Instanz
// Standard-Konstruktor
abgeleitet();
// Copy-Konstruktor
abgeleitet(const abgeleitet&);
// Zuweisung
abgeleitet &operator =(const abgeleitet&);
// Konkretes Ausdrucken
void print();
// ...
};
7.35
Verlässliches Programmieren in C/C++
7. Vererbung
Wichtige Eigenschaften
– Eine virtuelle Funktion und ihre abgeleiteten Instanzen müssen dieselbe
Parameterstruktur und denselben Datentyp des Rückgabewertes haben
– Nicht-virtuelle Member-Funktionen können dieselbe Parameterstruktur haben und verschiedenen Rückgabetyp
– Alle Member-Funktionen mit Ausnahme von Konstruktoren und redefinierten new und delete können virtuell sein
– Konstruktoren, Destruktoren, Zuweisungsoperator (=) und friends werden
nicht vererbt
– Zugriff auf Members kann bei Vererbung nur eingeschränkt, nicht erweitert
werden
Universität Paderborn, ADT
7.36
Verlässliches Programmieren in C/C++
7. Vererbung
Ein Puzzle
Aufgabe: Warum funktioniert das Programm nicht?
#include <iostream>
class liste {
private:
int zahl;
public:
virtual void ausnullen() = 0;
void naechstes()
{
++zahl;
}
liste()
{
zahl = 0;
}
virtual ~liste()
{
ausnullen(); // (**)
}
};
Universität Paderborn, ADT
class zahlenliste : public liste {
public:
int feld[100];
void ausnullen()
{
int i; // Feldindex
for (i = 0; i < 100; ++i) {
feld[i] = 0;
}
}
};
int main()
{
zahlenliste *listen_ptr = new zahlenliste;
delete listen_ptr; // Es kracht!
// an der Stelle (**)
listen_ptr = 0;
return 0;
}
7.37
Verlässliches Programmieren in C/C++
7. Vererbung
Ein Puzzle (Lösung)
– Erst wird der Destruktor von zahlenliste ausgeführt, diese Instanz ist
zerstört
– Danach wird der Destruktor von liste ausgeführt, dieser ruft die Funktion
ausnullen() auf
– ausnullen() ist rein virtuell, sie muß in der abgeleiteten Klasse gesucht
werden
– Die Instanz der abgeleiteten Klasse ist bereits zerstört, es gibt ausnullen()
nicht mehr!
– Es geschehen seltsame Dinge (je nach Compiler)
– Fazit: Niemals rein virtuelle Funktionen in einem Destruktor aufrufen!
Universität Paderborn, ADT
7.38
Verlässliches Programmieren in C/C++
8. Templates
8. Templates
Universität Paderborn, ADT
8.1
Verlässliches Programmieren in C/C++
8. Templates
Parametrische Polymorphie
– Die Benutzung des selben Programm-Codes für verschiedene Datentypen
heißt parametrische Polymorphie
– Der Datentyp ist ein Parameter des Programm-Codes
– Parametrische Polymorphie wird zur Definition von Container-Klassen eingesetzt
– Die Definition einer Funktion oder Klasse unter Verwendung parametrischer Polymorphie heißt Template
– Templates ermöglichen Wiederverwendung von Programm-Code in einer
“typsicheren” Weise
Universität Paderborn, ADT
8.2
Verlässliches Programmieren in C/C++
8. Templates
Template-Funktionen
template <class klassenbezeichner [, class klassenbezeichner ]*>
typbezeichner funktionsbezeichner (parameterdeklarationen)
{
anweisungen
}
– klassenbezeichner ist ein formaler Parameter für einen Datentyp, er wird
innerhalb der Template-Definition verwendet
– Template-Definitionen erzeugen keinen Programm-Code
(daher: keine Aufteilung in Implementierung und Header-Datei)
– Erst bei der Benutzung einer Template-Funktion wird Programm-Code
erzeugt
Universität Paderborn, ADT
8.3
Verlässliches Programmieren in C/C++
8. Templates
Beispiel Maximum-Funktion
– Typunabhängige
Funktion
– TYP ist Parameter
template <class TYP>
TYP max(TYP d1, TYP d2)
{
if (d1 > d2) {
return d1;
} else {
return d2;
}
}
Universität Paderborn, ADT
Maximum– Benutzung, als wäre eine Funktion
für den Datentyp, den die Parameter besitzen, definiert
– Generierung der konkreten Funktion
float f = max(4.8, 7.9); // generiert
int i = max(100, 500); // generiert
char c = max(’A’, ’X’); // generiert
int j = max(600, 800); // nicht
generiert
8.4
Verlässliches Programmieren in C/C++
8. Templates
Funktionsweise (a)
– Erst bei Benutzung einer Template-Funktion wird Programm-Code erzeugt oder referenziert
– Zunächst wird nachgeschaut, ob es eine konkrete Funktionsdefinition für
die Datentypen der Parameter gibt – ist sie vorhanden, so wird sie ausgeführt
– Ist eine solche konkrete Funktion nicht vorhanden, so wird ein Template gesucht, aus dem sie generiert werden kann; ist ein solches Template
vorhanden, so wird die konkrete Funktion generiert und dann ausgeführt
– Explizite oder implizite Instanziierung einer Template-Funktion möglich
float f = max<double>(4.8, 7.9); // Explizit
float f = max(4.8, 7.9); // Implizit (nur wenn eindeutig)
Universität Paderborn, ADT
8.5
Verlässliches Programmieren in C/C++
8. Templates
Funktionsweise (b)
Quell-Code
Generierter Code
template <class TYPE>
TYP max(TYP d1, TYP d2) {
if(d1>d2)
return(d1);
return(d2);
}
generiert
generiert
generiert
float max(float d1, float d2) {
if(d1>d2)
return(d1);
return(d2);
}
main()
{
float f=max(4.8,
int
i=max(100,
chat ch=max(’A",
int
j=max(600,
...
}
Universität Paderborn, ADT
7.9);
500);
’X’);
800);
int max(int d1, int d2) {
if(d1>d2)
return(d1);
return(d2);
}
char max(char d1, char d2) {
if(d1>d2)
return(d1);
return(d2);
}
main()
{
float f=max(4.8,
int
i=max(100,
chat ch=max(’A",
int
j=max(600,
...
}
7.9);
500);
’X’);
800);
8.6
Verlässliches Programmieren in C/C++
8. Templates
Funktionsspezialisierung
– Template-Funktionen realisieren implizit eine Art von Funktions-Overloading
– Template-Funktionen können folgerichtig auch überschrieben werden
– Das explizite Überschreiben von Template-Funktionen heißt FunktionsSpezialisierung
– Aus der Funktionsweise von Template-Funktionen ergibt sich, daß bei Vorhandensein einer expliziten Funktionsdefinition das Template nicht benutzt
wird
Universität Paderborn, ADT
8.7
Verlässliches Programmieren in C/C++
8. Templates
Beispiel: Maximum von C-Strings
– Die Template-Funktion max() ist nur brauchbar für Datentypen, auf denen
der Vergleichoperator > definiert ist
– Für C-Strings muß eine andere Funktionsdefinition gegeben werden
– Funktions-Spezialisierung für C-Strings
char *max(char *d1, char *d2)
{
if (strcmp(d1, d2) < 0 ) {
return d1;
} else {
return d2;
}
}
Universität Paderborn, ADT
8.8
Verlässliches Programmieren in C/C++
8. Templates
Beispiel: Maximum-Funktion mit Spezialisierung
int main()
{
using std::cout; using std::endl;
#include <iostream>
#include <cstring>
cout << "max(1, 2) = " << max(1, 2);
//erzeugt aus Template
cout << endl;
template <class TYP>
TYP max(TYP d1, TYP d2) {
if(d1>d2)
return d1;
return d2;
}
cout << "max(2, 1) = " << max(2, 1);
//nicht erzeugt
cout << endl;
cout <<
cout <<
//nicht
cout <<
// Spezialisierung fuer C-Strings
char *max(char *d1, char *d2) {
if(strcmp(d1, d2)<0)
return d1;
return d2;
}
"max(\"berta\", \"armin\")= ";
max((char*)"berta", (char*)"armin");
erzeugt (Spezialisierung)
endl;
cout << "max(\"armin\", \"berta\")= ";
cout << max("armin", "berta") << endl;
//erzeugt aus Template (Typ: const char *)
return 0;
}
Universität Paderborn, ADT
8.9
Verlässliches Programmieren in C/C++
8. Templates
Klassen-Template
template <class klassenbezeichner [, class klassenbezeichner ]*>
class klassenbezeichner {
klasssendefinition
}
– Die Benutzung eines Klassen-Templates erfordert die Angabe eines Datentyps für die formalen class klassenbezeichner (formale Parameter)
– Klassendefinitionen mit formalen Parametern heißen Container-Klassen
Universität Paderborn, ADT
8.10
Verlässliches Programmieren in C/C++
8. Templates
Beispiel Stack
top_of()
stack
push()
pop()
– Ein
Stack
ist
Datenstruktur
eine
LIFO-
– Hauptfunktionen push() (Datum
einfügen) und pop() (Datum entfernen)
max_len
– Die Funktionen hängen logisch
nicht von dem Datentyp der Daten ab
– Implementierung als
Klasse stack<TYPE>
Universität Paderborn, ADT
Container-
8.11
Verlässliches Programmieren in C/C++
8. Templates
Beispiel Stack stack.h
// Template stack
template <class TYPE>
class stack {
private:
enum { EMPTY = -1};
TYPE *s;
int max_len;
int top;
public:
stack(): max_len(1000)
{ s = new TYPE[1000]; top = EMPTY; }
stack(int size): max_len(size)
{ s = new TYPE[size]; top = EMPTY; }
~stack() { delete []s; }
void reset() { top = EMPTY; }
void push(TYPE c) { s[++top] = c; }
TYPE pop() { return(s[top--]); }
TYPE top_of() { return(s[top]); }
bool empty() { return top == EMPTY; }
bool full() { return top == (max_len - 1); }
};
Universität Paderborn, ADT
8.12
Verlässliches Programmieren in C/C++
8. Templates
Beispiel Stack stackmain.cc
#include <iostream>
#include <cstring>
#include "stack.h"
// Umdrehen eines C-Strings
void reverse(char str[], int n)
{
stack<char> stk(n);
int i;
for (i = 0; i < n; ++i) {
stk.push(str[i]);
}
for (i = 0; i < n; ++i) {
str[i] = stk.pop();
}
};
Universität Paderborn, ADT
int main()
{
char str[80];
std::cout << "Zeichenkette? ";
std::cin >> str;
reverse(str, strlen(str));
std::cout << str << ’\n’;
return 0;
}
8.13
Verlässliches Programmieren in C/C++
8. Templates
Klassen-Spezialisierung
– Wie bei Template-Funktionen wird eine explizite Definition zuerst benutzt
bevor eine konkrete Definition aus einem Template erzeugt wird
– Für stack<int> werden die Funktionen stack<int>::push(),
stack<int>::pop(), etc. generiert
– Sollen C-Strings im Stack gespeichert werden (nicht Zeiger auf char), so
kann stack<char *>::push() überschrieben werden (Klassen-Spezialisierung)
// s speichert string, nicht Zeiger auf char
void stack<char *>::push(char *c)
{
s[++top] = strdup(c);
}
Universität Paderborn, ADT
8.14
Verlässliches Programmieren in C/C++
8. Templates
Besonderheiten (a)
– Es können Nicht-Typen als Parameter verwendet werden; diese
Parameter werden mit Konstanten
instanziert
– template<class T, int n>
class array_n {
private:
// n wird explizit angegeben
T items[n];
};
// w ist 1000er Feld von complex
array_n<complex, 1000> w;
Universität Paderborn, ADT
8.15
Verlässliches Programmieren in C/C++
8. Templates
Besonderheiten (b)
– Eine Friend-Funktion, die keine Template-Spezifikation benutzt, ist Friend
aller Instanzen der Klasse
– Eine Friend-Funktion, die eine Template-Spezifikation benutzt, ist Friend
nur der konkret generierten Klasse
template <class T>
class matrix {
private:
// universell
friend void foo_bar();
// speziell
friend vekt<T> produkt(vekt<T> v);
// ...
};
Universität Paderborn, ADT
8.16
Verlässliches Programmieren in C/C++
8. Templates
Besonderheiten (c)
– Static-Member-Variablen sind nicht universell für alle konkret generierten
Klassen, sondern immer nur speziell für einzelne konkret generierte Klassen
template <class T>
class foo {
public:
static int zaehler;
// ...
};
foo<int> a;
foo<double> b;
– Die static Variablen foo<int>::zaehler und foo<double>::zaehler sind verschieden
Universität Paderborn, ADT
8.17
Verlässliches Programmieren in C/C++
8. Templates
Beispiel Binärer Suchbaum (a)
– Die Klasse gen_tree kann als Klassen-Template eleganter implementiert
werden als mit generischen Zeigern und Vererbung
– Der generische Zeiger void *p_gen wird hier zu einem Template Parameter
T
– Die Funktion bnode<T>::print() ist hier nicht Friend-, sondern MemberFunktion
– Die Klasse gen_tree<T> ist nicht für Vererbung vorgesehen; alle Hilfsfunktionen sind privat
Universität Paderborn, ADT
8.18
Verlässliches Programmieren in C/C++
8. Templates
Beispiel Binärer Suchbaum (b)
– Die Vergleichsfunktion int comp() ist nicht als Friend-Funktion definiert
– Die Template-Funktion int comp() setzt voraus, daß die Vergleichsoperatoren == und < auf dem Datentyp T definiert sind
– Strings (Datentyp char*) werden durch Funktionsspezialisierung behandelt
– Das Klassen-Template gen_tree<T> erfordert nur Änderungen, die Objekte
der Klasse bnode betreffen; Ersetzung durch bnode<T>
Universität Paderborn, ADT
8.19
Verlässliches Programmieren in C/C++
8. Templates
Beispiel Binärer Suchbaum comp.cc
template <class
int comp(T i, T
{
if(i == j) //
return 0;
else
return((i <
}
T>
j) // allgemeiner Fall
setzt voraus, dass == fuer T definiert ist
j) ? -1 : 1);
int comp(char *i, char *j) // speziell fuer Strings
{
return strcmp(i, j);
}
Universität Paderborn, ADT
8.20
Verlässliches Programmieren in C/C++
8. Templates
Beispiel Binärer Suchbaum gentree.h
// Template-Version generischer binaerer Suchbaeume
#include <stream.h>
template <class T>
#include "comp.h"
class gen_tree {
private:
// forward-Deklaration
bnode<T> *root;
template <class T> class gen_tree;
T find(bnode<T> *r, T d);
void print(bnode<T> *r);
template <class T>
public:
class bnode {
gen_tree()
private:
{
friend class gen_tree<T>;
root = 0;
bnode<T> *left;
}
bnode<T> *right;
void insert(T d);
T data;
T find(T d)
int count;
{
bnode(T d, bnode<T> *l, bnode<T> *r)
return find(root, d);
: left(l), right(r),
}
data(d), count(1) {}
void print()
void print()
{
{
print(root);
cout << data << " (";
}
cout << count << ")\t";
};
}
};
Universität Paderborn, ADT
8.21
Verlässliches Programmieren in C/C++
8. Templates
Beispiel Binärer Suchbaum gentree.cc (a)
template <class T>
void gen_tree<T>::insert(T d)
{
bnode<T> *temp = root;
bnode<T> *old;
if (root == 0) {
root = new bnode<T>(d, 0, 0);
return;
}
while (temp != 0) {
old = temp;
if (comp(temp->data, d) == 0) {
(temp->count)++;
return;
}
Universität Paderborn, ADT
if(comp(temp->data, d) > 0)
temp = temp->left;
else
temp = temp->right;
}
if (comp(old->data, d) > 0)
old->left = new bnode<T>(d, 0, 0);
else
old->right = new bnode<T>(d, 0, 0);
}
8.22
Verlässliches Programmieren in C/C++
8. Templates
Beispiel Binärer Suchbaum gentree.cc (b)
template <class T>
T gen_tree<T>::find(bnode<T> *r, T d)
{
if (r == 0)
return 0;
else if (comp(r->data, d) == 0)
return r->data;
else if (comp(r->data, d) > 0)
return find(r->left, d);
else
return find(r->right, d);
}
Universität Paderborn, ADT
template <class T>
void gen_tree<T>::print(bnode<T> *r)
{
if (r != 0) {
print(r->left);
r->bnode<T>::print();
print r->right;
}
}
8.23
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
9. Ausnahmebehandlung
Universität Paderborn, ADT
9.1
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Laufzeitfehler
– Ein Programm, das in der Praxis eingesetzt wird, muß die Behandlung
möglicher Laufzeitfehler vorsehen
– Typische Laufzeitfehler treten auf, wenn Speicherplatz alloziert wird und
nicht genügend Speicher vorhanden ist, oder wenn eine Division durch 0
erfolgen soll
– C++ enthält einen speziellen Mechanismus zur Behandlung von Laufzeitfehlern (Exception-handling)
– Exception-handling ermöglicht dem Benutzer einer Klasse, beim Aufruf einer Member-Funktion, auftretende Fehler im Anwendungsprogramm sinnvoll abzufangen
Universität Paderborn, ADT
9.2
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
try, catch und throw (a)
– Die Ausnahmebehandlung in C++ beruht auf drei Konstrukten
– try: Programmteile (Program-statements), die vom Ausnahmemechanismus (Exception-handler) überwacht werden sollen, werden in einem try Block zusammengefaßt
– throw: Wenn ein Laufzeitfehler in einem try-Block auftritt, wird eine Ausnahme mittels throw ausgegeben
– catch: Die Ausnahme wird mittels catch aufgefangen und behandelt
Universität Paderborn, ADT
9.3
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
try, catch und throw (b)
try {
anweisungen
}
catch(typ1 parameter ) {
anweisungen
}
catch(typ2 parameter ) {
anweisungen
}
...
catch(typN parameter ) {
anweisungen
}
Universität Paderborn, ADT
– Es
können
mehrere
catchAnweisungen zu einem try-Block
gehören
– Der Typ der Ausnahme bestimmt, welche catch-Anweisung
ausgeführt wird
– Soll das gesamte Programm mittels Exception-handler überwacht
werden, muß die Funktion main()
innerhalb eines try-Blocks stehen
9.4
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Funktionsweise (a)
#include <iostream>
int main()
{
cout << "Start\n";
try { // Beginn des try-Blocks
cout << "Innerhalb eines try-Blocks\n";
throw 99; // Ausgabe einer Ausnahme
cout << "Dies wird nicht ausgefuehrt";
} // Ende des try-Blocks
catch(int i) { // Fange eine Ausnahme ab
cout << "Ausnahme, Wert=";
cout << i << ’\n’;
} // Ende catch
cout << "Ende\n";
return 0;
}
Ausgabe
Start
Innerhalb eines try-Blocks
Ausnahme, Wert=99
Ende
Universität Paderborn, ADT
– Die throw-Anweisung führt die
Programmsteuerung zum catchAnweisungsblock
– Der catch-Block wird nicht aufgerufen, sondern die Programmausführung wird dorthin übertragen
– Nach Ausführung des catchAnweisungsblocks
fährt
die
Programmausführung
mit
der
unmittelbar
auf
den
catchAnweisungsblock
folgenden
Anweisung fort
9.5
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Funktionsweise (b)
throw;
throw ausdruck;
– Der Datentyp von ausdruck muß mit dem Datentyp einer catch-Anweisung
übereinstimmen
– Eine throw-Anweisung ohne folgenden ausdruck reicht eine Ausnahme weiter (rethrow), wird benutzt, wenn ein weiterer Exception-handler zur Fehlerbehandlung eingesetzt werden soll
– Bei geschachtelten try-Blöcken wird der innerste try-Block, in dem eine
Ausnahme mittels throw gegeben wird, zur Auswahl des passenden catchAnweisungsblocks gewählt
Universität Paderborn, ADT
9.6
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Funktionsweise (c)
– Der durch throw gegebene ausdruck ist ein statisches temporäres Objekt,
das so lange existiert, bis der zugehörige Exception-handler (catch) verlassen wird
– Der Exception-handler kann diesen ausdruck benutzen
void foo()
{
int i;
// ...
throw i;
}
Universität Paderborn, ADT
int main()
{
try {
foo();
}
catch(int n) {...}
}
9.7
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Funktionsweise (d)
– Bei Ausnahmen in geschachtelten
Funktionen wird der Prozeß-Stack
soweit zurückgenommen, bis ein
Exception-handler gefunden wird
– Damit werden beim Verlassen
eines lokalen Prozesses (innere
Funktion) alle zugehörigen Objekte (Variablen, Instanzen) zerstört
Universität Paderborn, ADT
void foo()
{
int i, j;
// ...
throw i;
// ...
}
void call_foo()
{
int k;
// ...
foo();
// ...
}
int main()
{
try {
call_foo();
// foo() beendet, i, j zerstoert
}
catch(int n) {...}
}
9.8
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Weiterreichen von Ausnahmen
– Ausnahmen werden durch eine throw/catch-Sequenz behandelt
– Solche Ausnahmen können auch nach außerhalb einer throw/catch-Sequenz
weitergereicht werden
– Es können mehrfache Ausnahmebehandlungen vorgenommen werden (Verschiedene Exception-handler für verschiedene Aspekte einer Ausnahme)
– Weiterreichen von Ausnahmen kann nur innerhalb eines catch-Anweisungsblocks geschehen
Universität Paderborn, ADT
9.9
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Beispiel Weiterreichen von Ausnahmen
#include <iostream>
using namespace std;
int main()
{
cout << "Start\n";using namespace std;
try {
Xhandler();
}
catch(const char *s) {
cerr << "Ausnahme char * ";
cerr << "innerhalb von main. Wert: ";
cerr << s << ’\n’;
}
cout << "Ende\n";
void Xhandler()
{
try {
throw "Hallo";
}
catch(const char *s) {
cerr << "Ausnahme char * ";
cerr << "innerhalb von Xhandler. ";
cerr << "Wert: ";
cerr << s << ’\n’;
throw; // Weiterreichen von const char *
return 0;
}
}
}
Ausgabe
Start
Ausnahme char * innerhalb von Xhandler. Wert: Hallo
Ausnahme char * innerhalb von main. Wert: Hallo
Ende
Universität Paderborn, ADT
9.10
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Mehrfache Ausnahmen
– Es können mehrere catch-Anweisungsblöcke zu einem try-Block implementiert werden
– Die catch-Anweisungen müssen jedoch mit verschiedenen Datentypen verbunden sein
– Es gibt eine typunabhängige catch-Anweisung, mit der beliebige mit throw
gegebene Ausnahmen behandelt werden können (allgemeines catch)
catch(...) {
anweisungen
}
Universität Paderborn, ADT
9.11
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Beispiel Mehrfache Ausnahmen (a)
vektor::vektor(int n)
{
if (n < 1)
throw (n);
p = new int[n];
if (p == 0)
throw ("Kein Speicher mehr");
}
void g()
{
try {
vektor a(n), b(n);
// ...
}
catch (int n) {...} // Behandelt inkorrekte Groesse
catch (const char *error) {...} // Behandelt Speicherfehler
Universität Paderborn, ADT
9.12
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Programmabbruch
– Gewöhnlich wird ein catch-Anweisungsblock mit der C++ Standard-Bibliotheksfunktion exit() oder abort() beendet (Ausnahmebehandlung von katastrophalen Fehlern)
– void abort()
• bewirkt die sofortige Beendigung des Programms
• kein Rückgabewert an das Betriebssystem, schließt keine geöffneten
Dateien
– void exit(int status)
• schließt alle geöffneten Dateien
• beendet das Programm mit einem Rückgabewert an das Betriebssystem
Universität Paderborn, ADT
9.13
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Beispiel Mehrfache Ausnahmen (b)
Standardaktion für Mehrfachausnahmen
catch (const char *meldung)
{
cerr << meldung << endl;
exit(1);
}
catch(...) // Standardaktion
{
cerr << "Das war’s. Ich weiss nicht mehr weiter." << endl;
abort();
}
Universität Paderborn, ADT
9.14
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Beispiel Mehrfache Ausnahmen (c)
#include <iostream>
void Xhandler(int test)
{
try {
if(test==0)
throw "Der Wert ist null";
if(test==1 || test==3)
throw test;
if(test==2)
throw 22.89;
else
throw "Der Wert ist zu gross";
}
catch(int i) { // Fange int
cerr << "Fehler! Nr.: " << i << ’\n’;
}
catch(const char *str) { // Fange String
cerr << "Fehler! Text: ";
cerr << str << ’\n’;
}
catch(...) { // Fange andere Typen
cerr << "Fehler! Typ unbekannt\n";
}
}
Universität Paderborn, ADT
int main()
{
cout << "Start\n";
Xhandler(1);
Xhandler(2);
Xhandler(0);
Xhandler(3);
cout << "Ende\n";
return 0;
}
Ausgabe
Start
Fehler!
Fehler!
Fehler!
Fehler!
Ende
Nr.: 1
Typ unbekannt
Text: Der Wert ist null
Nr.: 3
9.15
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Ausnahmen als Klassen
– Für Ausnahmebehandlung von komplexen Fehlern sollten Fehlerklassen
implementiert werden
– Die entsprechenden Objekte enthalten dann Informationen über die Umgebung (Objektzustände, Variablenwerte, etc.) des Fehlerzustands
enum error { grenze, speicher, andere };
class vekt_error {
private:
error e_typ;
int og, index, groesse;
public:
vekt_error(error, int, int); // Grenzen
vekt_error(error, int); // out of memory
// ...
};
// ...
throw vekt_error(gr, i, og);
// ...
Universität Paderborn, ADT
9.16
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Matching von throw und catch
– Ein mit throw gegebener Ausdruck paßt auf einen Parameter von catch,
falls
• die Signatur genau übereinstimmt
• der catch-Parameter eine öffentliche Basisklasse des throw-Ausdrucks
ist
• der throw-Parameter ein Zeiger ist, der konvertierbar zu einem Zeigertyp des catch-Parameters ist
– Vorsicht bei der Reihenfolge der catch-Anweisungsblöcke
catch(void*) // auch char* passt
catch(char*) // wird nie benutzt
catch(Basistyp&) // paßt immer auf Abgeleitet
catch(Abgeleitet&) // wird nie benutzt
Universität Paderborn, ADT
9.17
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Beispiel Stack
– Ausnahmen eines Stack: Bereichsüberschreitungen
• Pop auf leeren Stack
• Push auf vollen Stack
– Klasse stack als Basisklasse für eine Klasse b_stack, die mit Ausnahmebehandlung Bereichsüberschreitungen abfängt
– Implementierungsprinzip:
• Standardmäßige Klasse stack (ohne Ausnahmebehandlung)
• Abgeleitete Klasse, die die relevanten Member-Funktionen (push() und
pop()) um Exception-handling erweitert
• Fehlerklasse bound_err
• Benutzung von Ausnahmeeinschränkung
Universität Paderborn, ADT
9.18
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Beispiel Stack Objektstruktur
stack
bound_err
protected:
// daten
public:
// Funkts
public:
// konstr
abgeleitet
public
benutzt
benutzt
bstack
main()
public:
// push
// pop
// Ausn mit
// bound_err
try {//...}
catch {//...}
Universität Paderborn, ADT
9.19
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Beispiel Stack err.hpp
#include <string>
class bound_err {
public:
std::string text; //Was war der Fehler
bound_err(std::string err) {
text = err;
}
};
Universität Paderborn, ADT
9.20
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Beispiel Stack stack.hpp bstack.hpp
– Klasse stack ohne Ausnahmebehandlung
#include "err.hpp"
class stack {
protected:
enum { EMPTY = -1};
int *s, max_len, top;
public:
stack();
stack(int size);
~stack();
void reset();
void push(int c);
int pop();
int top_of();
bool empty();
bool full();
};
Universität Paderborn, ADT
– Klasse
stack
b_stack
abgeleitet
von
– Ausnahmerelevante
MemberFunktionen push(), pop()
– Einschränkung der Ausnahmen auf
Fehlerklasse bound_err
#include "stack.hpp"
class b_stack: public stack {
public:
void push(int c);
int pop();
};
9.21
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Beispiel Stack bstack.cpp
#include "bstack.hpp"
void b_stack::push(int c)
{
if (full()) {
bound_err overflow("Push nicht moeglich, Stack ist voll");
throw overflow;
}
stack::push(c);
}
int b_stack::pop()
{
if (empty()) {
throw bound_err("Pop nicht moeglich, Stack ist leer");
}
return (stack::pop());
}
Universität Paderborn, ADT
9.22
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Beispiel Stack stack.cpp
#include "stack.hpp"
stack::stack()
: max_len(1000) {
s=new int[1000]; top=EMPTY;
}
stack::stack(int size)
: max_len(size) {
s=new int[size]; top=EMPTY;
}
stack::~stack() {
delete []s;
}
void stack::reset() {
top=EMPTY;
}
Universität Paderborn, ADT
void stack::push(int c) {
s[++top]=c;
}
int stack::pop() {
return (s[top--]);
}
int stack::top_of() {
return (s[top]);
}
bool stack::empty() {
return bool(top==EMPTY);
}
bool stack::full() {
return bool(top==max_len-1);
}
9.23
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Beispiel Stack main.cpp
int main()
{
b_stack *test_stack = new b_stack;
try {
push_viel(test_stack);
}
catch (bound_err err) {
cerr << "Fehler: ";
cerr << "Stack-Grenzen ueberschritten\n";
cerr << "Grund: " << err.text << ’\n’;
exit(1);
}
catch (...) {
cerr << "Fehler: ";
cerr << "unerwartete Ausnahmesituation\n";
exit(1);
}
return 0;
#include <iostream>
#include "bstack.hpp"
using namespace std;
void push_viel(b_stack *stack ) {
for(int i=0; i<5000; i++) {
stack->push(i);
cout << stack->top_of() << ’\n’;
}
}
}
Universität Paderborn, ADT
9.24
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Fehlertoleranz
– Exception-handler eignen sich gut zum implementieren fehlertoleranter
Programme
– Fehlertoleranz bedeutet, daß ein Programm auch bei Laufzeitfehlern weiterhin funktionstüchtig bleibt
– Eine wichtige Anwendung: Korrektur von illegalen Werten
– Vorteil gegenüber direkter Korrektur im entsprechenden Programm-Code:
Klare Unterscheidung zwischen einem Fehler und dessen Behandlung
Universität Paderborn, ADT
9.25
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Beispiel Fehlertoleranz
– Exception-handler ersetzt einen il- void g(int m)
legalen Wert durch einen stan- {
try {
dardmäßigen legalen Wert
vektor a(m);
// ...
– Dieses Vorgehen ist besonders
sinnvoll bei der Systementwicklung
und beim Integrationstest
vektor::vektor(int n)
{
if (n < 1)
throw (n);
p = new int[n];
if (p == 0)
throw ("Kein Speicher mehr");
}
Universität Paderborn, ADT
}
catch(int n)
{
cerr << "Groessenfehler " << n << endl;
g(10); // Versuch mit legalem Wert
}
catch(const char *error)
{
cerr << error << endl;
abort();
}
}
9.26
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Fehlertoleranz und Klassenkonstruktoren
– Allgemeines Schema zur Implementierung fehlertoleranter Klassen
– Der Objekt-Konstruktor enthält
Ausnahmen für illegale Zustände
– Es gibt eine hierarchische Struktur
von Fehlerklassen
– Der try-Block benutzt die Information zur Reparatur oder
zum Abbbruch des nicht-korrekten
Programm-Codes
Universität Paderborn, ADT
Objekt::Objekt(parameter )
{
if (illegalparameter1 )
throw ausdruck1 ;
if (illegalparameter2 )
throw ausdruck2 ;
...
// Konstruktion
...
}
try {
// Fehlertoleranter Programm-Code
}
catch(deklaration1 ) {/* Reparatur 1 */}
catch(deklaration2 ) {/* Reparatur 2 */}
// . . .
// Werte sind jetzt legal
9.27
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Fehlerklassen
class objektfehler {
public:
objektfehler(parameter );
// Members mit Daten des Fehlers
virtual void reparatur()
{ cerr << "Reparatur nicht moeglich"; endl; abort(); }
};
class objektfehler1 : public objektfehler {
public:
objektfehler1(parameter );
// Weitere Members mit Daten des Fehlers
void reparatur();
// Passende Reparatur
};
// Andere abgeleitete Fehlerklassen
Universität Paderborn, ADT
9.28
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Philosophie von Error-recovery
– Eine Methode, Fehlertoleranz zu erreichen ist Error-recovery
– Error-recovery bedeutet die Reparatur fehlerhafter Zustände im Programmlauf
– Erreichbar ist dies durch Eingriff in den Programmsteuerfluß (Ausnahmebehandlung)
– Zuviel Eingriff in den Steuerfluß führt zu Chaos
– Meist ist Reparatur nur bei Echtzeit-Programmen sinnvoll
– In den meisten Fällen ist es sinnvoll, bei Fehlern eine Diagnose auszugeben
und das Programm sicher zu beenden
Universität Paderborn, ADT
9.29
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Zusicherungen (a)
– Programmkorrektheit bedeutet, daß auf korrekte Eingabe eine korrekte
Ausgabe erfolgt
– Der aufrufende Teil hat die Verantwortung für eine korrekte Eingabe
– Der aufgerufene Teil hat die Verantwortung für eine korrekte Ausgabe
– Programming-by-contract: Regelung der Verantwortlichkeiten, typisch für
objektorientierte Programmierung
– In Memberfunktionen von Klassen sollte die Erfüllung der Pflichten geprüft
werden
Universität Paderborn, ADT
9.30
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Zusicherungen (b)
– Die Pflichten aus den Verantwortlichkeiten werden durch Zusicherungen
gegeben
– Eine Zusicherung (Assertion) ist
• Vorbedingung (Precondition), d. h. korrekte Eingabe
• Nachbedingung (Postcondition), d. h. korrekte Ausgabe
• Invariante (Invariant), d. h. das, was sich nicht ändern darf
– C++ bietet eine Möglichkeit, Zusicherungen zu implementieren und zu
prüfen
Universität Paderborn, ADT
9.31
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Zusicherungen (c)
– Die Standard-Bibliothek cassert
enthält ein Makro
void assert(int expression);
– Wirkungsweise:
• Wird expression zu falsch
ausgewertet, so wird die Programmausführung mit einer
Diagnose-Ausgabe
abgebrochen
• Wird expression zu wahr ausgewertet, so wird mit der Programmausführung fortgefahren
Universität Paderborn, ADT
– Direkt zu implementieren sind in
C++ nur Vorbedingungen und
Nachbedingungen
– vekt::vekt(int n)
{
assert(n > 0); //Vorbedingung
groesse = n;
p = new int[groesse];
assert (p != 0); //Nachbedingung
}
– Invarianten sollten gesondert implementiert werden
9.32
Verlässliches Programmieren in C/C++
9. Ausnahmebehandlung
Beispiel: Implementierung einer String-Klasse
#include <cstring>
#include <cassert>
class String{
char *Buffer;
unsigned int Buflen;
void Invariant();
public:
String(char *str);
~String();
String &operator=(char *str);
};
inline void String::Invariant() {
assert(strlen(Buffer) == Buflen);
};
String::String(char *str) {
assert(str != NULL);
Buflen = strlen(str) + 1;
Buffer = new char[Buflen];
assert(Buffer != NULL);
strcpy(Buffer, str);
Invariant();
}
Universität Paderborn, ADT
String::~String() {
Invariant();
delete [] Buffer;
}
String &String::operator=(char *str) {
Invariant();
assert(str != NULL);
if (strlen(str) >= Buflen) {
delete [] Buffer;
Buflen = strlen(str) + 1;
Buffer = new char[Buflen];
assert(Buffer != NULL);
}
strcpy(Buffer, str);
return(*this);
}
9.33
Verlässliches Programmieren in C/C++
10. Kommandozeilen und Dateiverarbeitung
10. Kommandozeilen und
Dateiverarbeitung
Universität Paderborn, ADT
10.1
Verlässliches Programmieren in C/C++
10. Kommandozeilen und Dateiverarbeitung
Parameter von main() (a)
– Beim Programmaufruf können auf der Kommandozeile Argumente übergeben werden
– Argumente der Kommandozeile werden als Parameter an die Funktion
main() übergeben
– Es stehen für main() zwei Parameter zur Verfügung
– Die Parameter enthalten
1. Anzahl der Kommandozeilen-Argumente (int argc)
2. Argumente als Feld von Zeichenketten (char *argv[],
bzw. char **argv)
Universität Paderborn, ADT
10.2
Verlässliches Programmieren in C/C++
10. Kommandozeilen und Dateiverarbeitung
Parameter von main() (b)
– int main(int argc, char *argv[])
{
// ...
}
– Kommandozeile (Programmname
args)
args dies ist ein test
– Parameter von main()
– Die Parameter müssen nicht argc
und argv heißen, Konvention: argc
= argument count, argv = argument value
– Es werden alle Zeichenketten der
Kommandozeile gezählt (argc)
Universität Paderborn, ADT
argc = 5
argv[0] =
argv[1] =
argv[2] =
argv[3] =
argv[4] =
"args"
"dies"
"ist"
"ein"
"test"
10.3
Verlässliches Programmieren in C/C++
10. Kommandozeilen und Dateiverarbeitung
UNIX-Konvention
– Schema von Kommandozeilen unter UNIX (gilt auch für viele andere Bereiche)
kommando [option]* [dateinamen]*
– option beginnt mit einem Strich (-) gewöhnlich gefolgt von einem Zeichen,
z. B. -v
– option kann zu ihrem Namen ein oder mehrere Argumente besitzen, z. B.
-oausgabedatei
– Beispiel Kommandozeilenformat (Programmdokumentation)
print_file [-v] [-l<length>] [-o<name>] [file1] [file2] ...
Universität Paderborn, ADT
10.4
Verlässliches Programmieren in C/C++
10. Kommandozeilen und Dateiverarbeitung
Kommandozeilenbearbeitung (a)
– Schleifenkonstruktion für Optionen
while ((argc > 1) && argv[1][0] == ’-’)) {
// Optionsbearbeitung
++argv; // naechstes Argument
--argc; // ein Argument abgearbeitet
}
// Argument ist Option
– Schleifenkonstruktion für Dateinamen
if (argc == 1) { // keine Datei angegeben
do_file("file.in"); // Standard-Datei
} else {
while (argc > 1) {
do_file(argv[1]);
++argv; // naechste Datei
--argc; // eine Datei bearbeitet
}
}
Universität Paderborn, ADT
10.5
Verlässliches Programmieren in C/C++
10. Kommandozeilen und Dateiverarbeitung
Kommandozeilenbearbeitung (b)
– Optionsbearbeitung
switch (argv[1][1]) {
case ’v’: verbose = true; break;
case ’o’: out_file = &argv[1][2]; break;
case ’l’: line_max = atoi(&argv[1][2]); break;
default:
cerr << "Bad option " << argv[1] << endl;
usage(); // Drucke Kommandozeilenformat
}
– -o<name>: argv[1][0] == ’-’, argv[1][1] == ’o’, argv[1][2] ==
Beginn von <name>
– -l<length>: Bibliotheksfunktion atoi() konvertiert eine Zeichenkette in eine
ganze Zahl (ascii to int); erfordert #include <cstdlib>
Universität Paderborn, ADT
10.6
Verlässliches Programmieren in C/C++
10. Kommandozeilen und Dateiverarbeitung
Beispiel (a)
#include <iostream>
#include <cstdlib>
bool verbose = false;
// verbose mode (default = false)
char *out_file = "print.out"; // output file name
char *program_name; // name of the program (for errors)
int line_max = 66; // number of lines per page
void do_file(char *name) // dummy routine to handle file
{
cout << "Verbose " << verbose << " Lines " << line_max <<
" Input " << name << " Output " << out_file << endl;
}
void usage(void)
{
cerr << "Usage is " << program_name << " [options] [file-list]\n";
cerr << "Options\n";
cerr << " -v
verbose\n";
cerr << " -l<number> Number of lines\n";
cerr << " -o<name> Set output file name\n";
exit (8);
}
Universität Paderborn, ADT
10.7
Verlässliches Programmieren in C/C++
10. Kommandozeilen und Dateiverarbeitung
Beispiel (b)
if (argc == 1) {
int main(int argc, char *argv[])
do_file("print.in");
{
} else {
program_name = argv[0];
while (argc > 1) {
while ((argc > 1) && (argv[1][0] == ’-’)) {
do_file(argv[1]);
switch (argv[1][1]) {
++argv;
case ’v’:
--argc;
verbose = true;
}
break;
}
case ’o’:
return 0;
out_file = &argv[1][2];
}
break;
case ’l’:
Programmlauf
line_max = atoi(&argv[1][2]);
print -v -oausgabe -l80 datei1 datei2
break;
Verbose 1 Lines 80 Input datei1 Output
default:
ausgabe
cerr << "Bad option " << argv[1];
Verbose 1 Lines 80 Input datei2 Output
cerr << endl;
ausgabe
usage();
}
print -oausgabe
++argv;
Verbose 0 Lines 66 Input print.in Output
--argc;
ausgabe
}
Universität Paderborn, ADT
10.8
Verlässliches Programmieren in C/C++
10. Kommandozeilen und Dateiverarbeitung
Ein- und Ausgaben
– C++ betrachtet Dateien (auch Tastatur und Bildschirm) als Strom von
Zeichen (Streams of bytes)
– In C++ gibt es drei Klassen zur Ein-Ausgabe-Bearbeitung (<iostream>)
• istream für Eingabe
• ostream für Ausgabe
• iostream für Ein-Ausgabe
– Bei Programmstart werden automatisch Variablen angelegt
Variable
cin
cout
cerr
clog
Benutzung
Standard Eingabe
Standard Ausgabe
Standard Fehler
Konsole Log
Universität Paderborn, ADT
10.9
Verlässliches Programmieren in C/C++
10. Kommandozeilen und Dateiverarbeitung
Dateien (a)
– Für Dateien gibt es in C++ abgeleitete Klassen von istream, ostream und
iostream (#include <fstream>)
• ifstream für Eingabedateien
• ofstream für Ausgabedateien
• fstream für Ein-Ausgabedateien
– Benutzung
ifstream eingabedatei; // Deklaration einer Eingabedatei
ofstream ausgabedatei; // Deklaration einer Ausgabedatei
eingabedatei.open("zahlen.dat"); // Oeffnen zum Lesen
ausgabedatei.open("ergebnis"); // Oeffnen zum Schreiben
eingabedatei.close(); // Schliessen
ausgabedatei.close(); // Schliessen
Universität Paderborn, ADT
10.10
Verlässliches Programmieren in C/C++
10. Kommandozeilen und Dateiverarbeitung
Dateien (b)
– Wie bei anderen Klassen, wird bei Deklaration von Ein/-Ausgabedateien
ein Konstruktor der betreffenden Klasse aufgerufen
– Für ifstream gibt es neben ifstream::ifstream() den Konstruktor
ifstream::ifstream(const char *name);
(ebenso für ofstream)
– Deklaration und öffnen einer Datei
ifstream eingabedatei("zahlen.dat");
– Benutzung wie cin oder cout
int var;
// ...
eingabedatei >> var;
ausgabedatei << var;
Universität Paderborn, ADT
10.11
Verlässliches Programmieren in C/C++
10. Kommandozeilen und Dateiverarbeitung
Beispiel Eingabedatei
/* Lese 100 Zahlen aus der
* Datei "numbers.dat"
* Warnung: Es gibt keine Ueberpruefung,
* ob die Datei auch 100 Zahlen enthaelt!
*/
for (i = 0; i < DATA_SIZE; ++i)
data_file >> data_array[i];
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
// Number of items in the data
const int DATA_SIZE = 100;
// The data
int data_array[DATA_SIZE];
// The input file
ifstream data_file("numbers.dat");
int i;
// Loop counter
int total; // Total of the numbers
total = 0;
for (i = 0; i < DATA_SIZE; ++i)
total += data_array[i];
cout << "Total of all the numbers is ";
cout << total << endl;
return 0;
}
if (!data_file.is_open()) { // Problem beim Oeffnen oder Lesen
cerr << "Error: Could not open numbers.dat" << endl;
exit (8);
}
Universität Paderborn, ADT
10.12
Verlässliches Programmieren in C/C++
11. Zusammenfassung
11. Zusammenfassung
Universität Paderborn, ADT
11.1
Verlässliches Programmieren in C/C++
11. Zusammenfassung
– Objektorientierte Programmierung mit C++
– Realisierung großer Projekte (mehr als 10.000 Programmzeilen)
– Optimierung unter Kriterien
• Korrektheit
• Robustheit
• Erweiterbarkeit
• Wiederverwendbarkeit
Universität Paderborn, ADT
11.2
Verlässliches Programmieren in C/C++
11. Zusammenfassung
– Konzepte von C++
– Erweiterung von ANSI-C
– Polymorphie (Funktionen, Operatoren, Klassen, Templates)
– Vereinfachte Parameter-Übergabeformen an Funktionen (gegenüber C)
– Klassenkonzept als erweiterte abstrakte Datentypen (Konstruktoren, Destruktoren, Standard-Operatoren)
– Vererbung (Zugriffsrechte, virtuelle Funktionen)
– Ausnahmebehandlung (throw, assert, etc.)
Universität Paderborn, ADT
11.3
Herunterladen