Bisher haben Sie mit cout
auf den Bildschirm geschrieben und mit cin
von der Tastatur
eingelesen, ohne eine genaue Vorstellung davon zu haben, was sich dahinter verbirgt.
Heute lernen Sie,
Wie man in C++ Daten auf dem Bildschirm ausgibt oder in eine Datei schreibt oder
wie Daten in ein Programm eingelesen werden, ist nicht in der Sprachspezifikation
festgelegt. Dies fällt vielmehr in den Bereich der Arbeit mit C++. Dafür enthält die
C++-Standardbibliothek die iostream
-Bibliothek, mit der die Ein- und Ausgabe (E/A)
erleichtert wird.
Der Vorteil, die Ein- und Ausgabe von der Sprache zu trennen und in Bibliotheken unterzubringen, liegt darin, daß die Sprache so leichter »plattformunabhängig« gemacht werden kann. Für Sie bedeutet dies, daß C++-Programme, die auf einem PC geschrieben wurden, nach erneuter Kompilierung auf einer Sun-Workstation ausgeführt werden können. Der Compiler-Hersteller stellt die entsprechende Bibliothek bereit und alles läuft problemlos. Soweit zumindest die Theorie.
Eine Bibliothek ist eine Sammlung von .obj-Dateien, die in Ihr Programm eingebunden werden können und ihm zusätzliche Funktionalität verleihen. Dies ist die einfachste Form der Wiederverwertung von einem Code. Es gibt sie, seit die Programmierer begonnen haben Einsen und Nullen in die Wände ihrer Höhlen zu meißeln.
Für die iostream
-Klassen ist der Datenfluß von Ihrem Programm zum Bildschirm ein
Strom (engl. stream) von Daten, bei dem ein Byte dem anderen folgt. Ist das Ziel des
Stream eine Datei oder der Bildschirm, ist die Quelle in der Regel Teil Ihres Programms.
Wird die Richtung des Stream umgekehrt, können die Daten von der Tastatur
oder einer Datei kommen und »ergießen« sich in Ihren Datenvariablen.
Eines der Hauptziele von Streams ist es, den Datenaustausch mit der Festplatte oder dem Bildschirm zu kapseln. Nachdem ein Stream erzeugt wurde, arbeitet das Programm nur noch mit diesem Stream, dem die Verantwortung für den korrekten Datentransfer obliegt. Den Grundgedanken dahinter entnehmen Sie Abbildung 16.1.
Abbildung 16.1: Kapselung durch Streams
Auf die Festplatte (und im geringeren Maße auch auf den Bildschirm) zu schreiben ist sehr »teuer«. Im Vergleich zu anderen Operationen dauert das Schreiben von Daten auf die Festplatte oder das Lesen von der Festplatte ziemlich lange, und die Programmausführung wird für diese Zeitdauer im allgemeinen angehalten. Um dieses Problem zu umgehen, werden Streams »gepuffert«. Die Daten werden in den Stream geschrieben, der jedoch noch nicht sofort auf die Platte zurückgeschrieben wird. Statt dessen füllt sich der Streampuffer stetig, und wenn er voll ist, schreibt er seinen Inhalt auf einmal auf die Platte.
Stellen Sie sich vor, daß Wasser oben in ein Becken läuft und dieses kontinuierlich füllt. Es läuft jedoch unten kein Wasser ab. Sehen Sie dazu Abbildung 16.2.
Abbildung 16.2: Den Puffer füllen
Wenn das Wasser (sprich: die Daten) den oberen Rand erreicht hat, öffnet sich unten das Ventil und das Wasser fließt in einem Rutsch ab. Abbildung 16.3 soll dieses illustrieren.
Abbildung 16.3: Den vollen Puffer leeren
Nachdem der Puffer geleert ist, wird das Ventil am Boden wieder geschlossen und neues Wasser fließt in das Wasserbecken. (Abbildung 16.4).
Abbildung 16.4: Den Puffer erneut füllen
Ab und zu müssen Sie das Wasser aus dem Becken ablassen, auch wenn es noch nicht voll ist. Dies nennt man auch »den Puffer leeren«. (Abbildung 16.5).
Abbildung 16.5: Den teilweise gefüllten Puffer leeren
Wie nicht anders zu erwarten, werden in C++ Streams und Puffer objektorientiert implementiert.
streambuf
verwaltet den Puffer, und ihre Elementfunktionen übernehmen
das Füllen, das vollständige oder teilweise Leeren sowie anderweitige Manipulationen
des Puffers.
ios
ist die Basisklasse für die Ein- und Ausgabe-Streamklassen. Die
ios
-Klasse verfügt über ein streambuf
-Objekt als Elementvariable.
istream-
und ostream
-Klassen leiten sich von ios
ab und sind für das Verhalten
von Eingabe- beziehungsweise Ausgabe-Streams spezialisiert.
iostream
leitet sich von istream
und von ostream
ab und stellt Ein- und
Ausgabe-Methoden zur Verfügung, um Daten auf dem Bildschirm auszugeben.
fstream
-Klassen unterstützen die Eingabe und Ausgabe von und in Dateien.
Wenn ein C++-Programm, das die iostream
-Klassen einbindet, ausgeführt wird, werden
vier Objekte erzeugt und initialisiert.
Die
iostream
-Klassenbibliothek wird vom Compiler automatisch in Ihr Programm mit eingebunden. Alles, was Sie machen müssen, ist, die entsprechendeinclude
-Anweisung an den Anfang Ihres Programmlistings zu stellen.
cin
ist verantwortlich für die Eingabe von dem Standardeingabegerät, der Tastatur.
cout
ist verantwortlich für die Ausgabe auf das Standardausgabegerät, den Bildschirm.
cerr
ist verantwortlich für die ungepufferte Ausgabe auf das Standardausgabegerät,
den Bildschirm. Da es sich hier um eine ungepufferte Ausgabe handelt, wird
alles, was an cerr
geschickt wird, sofort auf das Standardausgabegerät ausgegeben,
ohne darauf zu warten, daß das Becken gefüllt oder ein Entleerungsbefehl
empfangen wird.
clog
ist verantwortlich für gepufferte Fehlermeldungen; die Standardfehlerausgabe
erfolgt auf dem Bildschirm. Fehlermeldungen dieser Art werden in der Regel in
eine Protokolldatei »umgeleitet«, siehe folgenden Abschnitt.
Jedes der Standardgeräte, d.h. für Eingabe, Ausgabe und Fehler, kann auf ein anderes Gerät umgeleitet werden. Die Standardfehlerausgabe wird häufig in Dateien umgeleitet, die Standardein- und -ausgaben können mit Hilfe von Betriebssystembefehlen in Dateien gelenkt werden.
Umleiten bedeutet, die Ausgabe (oder Eingabe) nicht an das Standardziel zu schicken,
sondern zu einem anderen Ziel zu dirigieren. Die Umleitungsoperatoren für DOS und
UNIX lauten <
für das Umleiten der Eingabe und >
für das Umleiten der Ausgabe.
Beim Piping wird die Ausgabe eines Programms als Eingabe eines anderen verwendet.
DOS liefert rudimentäre Umleitungsbefehle wie (>
) und (<
). Unter UNIX sind die Umleitungsmöglichkeiten
wesentlich umfangreicher. Die Idee jedoch ist dieselbe: Man
nehme die Ausgabe, die für den Bildschirm bestimmt ist, und schreibe sie in eine Datei
oder lenke sie in ein anderes Programm. Entsprechend kann die Eingabe für ein Programm
von einer Datei extrahiert und nicht von der Tastatur eingelesen werden.
Die Umleitung ist eher eine Funktion des Betriebssystems als der iostream
-Bibliotheken.
In C++ haben Sie nur Zugriff auf die vier Standardgeräte. Es steht dem Anwender
allerdings frei, die Umleitung je nach Bedarf an ein beliebiges anderes Gerät vorzunehmen.
Das globale Objekt cin
ist verantwortlich für die Eingabe und steht Ihrem Programm
automatisch zur Verfügung, wenn Sie die Header-Datei iostream.h
einbinden. In den
bisherigen Beispielen haben Sie den überladenen Eingabe-Operator (>>
) verwendet,
um Daten in Ihren Programmvariablen aufzunehmen. Wie funktioniert das? Die Syntax,
wie Sie sich vielleicht erinnern, sieht folgendermaßen aus:
int eineVariable;
cout << "Geben Sie eine Zahl ein: ";
cin >> eineVariable;
Das globale Objekt cout
übergehen wir erst einmal. Es wird in diesem Kapitel weiter
hinten besprochen. Konzentrieren wir uns auf die dritte Zeile: cin >> eineVariable;
.
Was läßt sich über cin
sagen?
Offensichtlich handelt es sich um ein globales Objekt, da Sie es nicht in Ihrem Code
definiert haben. Aus früheren Erfahrungen mit Operatoren können Sie schließen, daß
cin
den Eingabe-Operator (>>
) überladen hat, mit der Folge, daß alles, was im Puffer
von cin
steht, in Ihre lokale Variable eineVariable
geschrieben wird.
Was vielleicht nicht so deutlich zu erkennen ist: cin
überlädt den Eingabe-Operator für
eine Vielzahl von Parametern, unter anderem int&
, short&
, long&
, double&
, float&
,
char&
, char*
und so weiter. In unserem Beispiel cin >> eineVariable;
wird der Typ
von eineVariable
vom Compiler geschätzt. Da es sich um einen int
-Wert handelt,
wird die folgende Funktion aufgerufen:
istream & operator>> (int &)
Beachten Sie, daß der Parameter als Referenz übergeben wird. Deshalb kann der Eingabe-Operator
auf der originalen Variablen operieren. Listing 16.1 demonstriert die
Verwendung von cin
.
Listing 16.1: cin arbeitet mit unterschiedlichen Datentypen
1: //Listing 16.1 - Zeichenstrings und cin
2:
3: #include <iostream.h>
4:
5: int main()
6: {
7: int myInt;
8: long myLong;
9: double myDouble;
10: float myFloat;
11: unsigned int myUnsigned;
12:
13: cout << "int: ";
14: cin >> myInt;
15: cout << "Long: ";
16: cin >> myLong;
17: cout << "Double: ";
18: cin >> myDouble;
19: cout << "Float: ";
20: cin >> myFloat;
21: cout << "Unsigned: ";
22: cin >> myUnsigned;
23:
24: cout << "\n\nInt:\t" << myInt << endl;
25: cout << "Long:\t" << myLong << endl;
26: cout << "Double:\t" << myDouble << endl;
27: cout << "Float:\t" << myFloat << endl;
28: cout << "Unsigned:\t" << myUnsigned << endl;
29: return 0;
30: }
int: 2
Long: 70000
Double: 987654321
Float: 3.33
Unsigned: 25
Int: 2
Long: 70000
Double: 9.87654e+08
Float: 3.33
Unsigned: 25
Die Zeilen 7 bis 11 deklarieren Variablen unterschiedlichen Typs. In den Zeilen 13 bis
22 wird der Anwender aufgefordert, Werte für diese Variablen einzugeben, und die
Ergebnisse werden (mit cout
) in den Zeilen 24 bis 28 ausgegeben.
Die Ausgabe zeigt, daß den Variablen der richtige Datentyp zugeordnet wurde und das Programm wie erwartet abläuft.
cin
verarbeitet auch Zeiger auf Zeichenketten (char*
) als Argument. Sie können also
einen Zeichenpuffer erzeugen und ihn mit cin
füllen. Sie könnten beispielsweise
schreiben:
char IhrName[50]
cout << "Bitte geben Sie Ihren Namen ein: ";
cin >> IhrName;
Wenn Sie Jesse eingeben, wird die Variable IhrName
mit den Zeichen J, e, s, s, e, \0
gefüllt. Das letzte Zeichen ist eine Null. cin
beendet den String automatisch mit einem
Nullzeichen. Ihr Puffer muß groß genug sein, um den gesamten String plus das Nullzeichen
zu fassen. Diese Null signalisiert den Standardbibliotheksfunktionen, die in Kapitel
21 diskutiert werden, »das Ende des String«.
Nachdem Sie gesehen haben, wie vielseitig cin
ist, werden Sie vielleicht überrascht
sein, wenn Sie versuchen, einen vollständigen Namen in einen String einzugeben. Für
cin
ist ein Leerzeichen gleich einem Trennzeichen. Stößt cin
auf ein Leerzeichen oder
auf ein Zeichen für den Zeilenumbruch, geht es davon aus, daß die Eingabe für diesen
Parameter abgeschlossen ist, und fügt im Falle von Strings direkt ein Nullzeichen an
das Ende. Listing 16.2 veranschaulicht das Problem.
Listing 16.2: Mehr als ein Wort mit cin einlesen
1: //Listing 16.2 - Zeichenstrings und cin
2:
3: #include <iostream.h>
4:
5: int main()
6: {
7: char YourName[50];
8: cout << "Ihr Vorname: ";
9: cin >> YourName;
10: cout << "Er lautet: " << YourName << endl;
11: cout << "Ihr voller Name: ";
12: cin >> YourName;
13: cout << "Er lautet: " << YourName << endl;
14: return 0;
15: }
Ihr Vorname: Jesse
Er lautet: Jesse
Ihr voller Name: Jesse Liberty
Er lautet: Jesse
Zeile 7 erzeugt einen Array, der die Eingabe des Anwenders aufnimmt. Zeile 8 fragt den Anwender nach einem Namen, der dann, wie die Ausgabe zeigt, ordnungsgemäß abgelegt wird.
Zeile 11 fordert den Anwender auf, diesmal den vollständigen Namen einzugeben. cin
liest die Eingabe, und wenn cin
auf das Leerzeichen zwischen den Namen trifft, setzt
es ein Nullzeichen hinter das erste Wort und beendet die Eingabe. Dies lag jedoch
nicht in Ihrer Absicht.
Um zu verstehen, warum das so ist, sollten Sie Listing 16.3 genau untersuchen, da dort die Eingabe für mehrere Felder gezeigt wird.
1: //Listing 16.3 - Zeichenstrings und cin
2:
3: #include <iostream.h>
4:
5: int main()
6: {
7: int myInt;
8: long myLong;
9: double myDouble;
10: float myFloat;
11: unsigned int myUnsigned;
12: char myWord[50];
13:
14: cout << "int: ";
15: cin >> myInt;
16: cout << "Long: ";
17: cin >> myLong;
18: cout << "Double: ";
19: cin >> myDouble;
20: cout << "Float: ";
21: cin >> myFloat;
22: cout << "Word: ";
23: cin >> myWord;
24: cout << "Unsigned: ";
25: cin >> myUnsigned;
26:
27: cout << "\n\nInt:\t" << myInt << endl;
28: cout << "Long:\t" << myLong << endl;
29: cout << "Double:\t" << myDouble << endl;
30: cout << "Float:\t" << myFloat << endl;
31: cout << "Word: \t" << myWord << endl;
32: cout << "Unsigned:\t" << myUnsigned << endl;
33:
34: cout << "\n\nInt, Long, Double, Float, Word, Unsigned: ";
35: cin >> myInt >> myLong >> myDouble;
36: cin >> myFloat >> myWord >> myUnsigned;
37: cout << "\n\nInt:\t" << myInt << endl;
38: cout << "Long:\t" << myLong << endl;
39: cout << "Double:\t" << myDouble << endl;
40: cout << "Float:\t" << myFloat << endl;
41: cout << "Word: \t" << myWord << endl;
42: cout << "Unsigned:\t" << myUnsigned << endl;
43:
44:
45: return 0;
46: }
Int: 2
Long: 30303
Double: 393939397834
Float: 3.33
Word: Hello
Unsigned: 85
Int: 2
Long: 30303
Double: 3.93939e+11
Float: 3.33
Word: Hello
Unsigned: 85
Int, Long, Double, Float, Word, Unsigned: 3 304938 393847473 6.66 bye -2
Int: 3
Long: 304938
Double: 3.93847e+08
Float: 6.66
Word: bye
Unsigned: 4294967294
Auch hier werden wieder mehrere Variablen eingerichtet, diesmal einschließlich eines
char
-Array. Der Anwender wird aufgefordert, eine umfangreichere Eingabe zu machen,
die danach wortgetreu ausgegeben wird.
Zeile 34 fordert den Anwender auf, alles auf einmal einzugeben. Jedes »Wort« der Eingabe
wird der entsprechenden Variablen zugeordnet. Zur Erleichterung dieser Art von
Mehrfachzuweisung muß cin
jedes Wort der Eingabe als vollständige Eingabe für eine
Variable betrachten. Würde cin
die gesamte Eingabe als Eingabe für eine Variable betrachten,
wäre diese Art der verketteten Eingabe nicht möglich.
Beachten Sie, daß in Zeile 42 das letzte angeforderte Objekt ein vorzeichenloser Integer
war, der Anwender hingegen -2 eingegeben hat. Da cin
davon ausgeht, daß es in
einen vorzeichenloses Integer schreibt, wird das Bitmuster von -2 als vorzeichenloser
Integer interpretiert und bei der Ausgabe mit cout
erscheint der Wert 4294967294.
Der vorzeichenlose Wert 4294967294 hat genau das gleiche Bitmuster wie der Wert
-2. Weiter hinten werden Sie lernen, wie man einen ganzen String in einen Puffer
schreibt, einschließlich mehrerer Worte. Jetzt beschäftigt uns aber erst einmal die Frage,
»Wie gelingt dem Eingabe-Operator der Trick mit der Verkettung?«.
Der Rückgabewert von cin
ist eine Referenz auf ein istream
-Objekt. Da cin
selbst ein
istream
-Objekt ist, kann der Rückgabewert einer Einleseoperation als Eingabe für die
nächste Einleseoperation verwendet werden.
int varEins, varZwei, varDrei;
cout << "Geben Sie drei Zahlen ein: "
cin >> varEins >> varZwei >> varDrei;
Wenn Sie cin >> varEins >> varZwei >> varDrei;
schreiben, wird zuerst (cin >>
VarEins)
eingelesen. Der Rückgabewert davon ist ebenfalls ein istream
-Objekt und
dessen Eingabe-Operator liest die Variable varZwei
ein. Genauso gut hätten Sie auch
((cin >> varEins) >> varZwei) >> varDrei;
schreiben können. Auf diese Technik gehen wir später im Zusammenhang mit cout
noch näher ein.
Zusätzlich zu dem überladenen >>
-Operator, verfügt cin
noch über eine Reihe weiterer
Elementfunktionen. Sie kommen zum Einsatz, wenn Sie eine genauere Kontrolle
über die Eingabe wünschen.
Ein >>
-Operator, der eine Zeichenreferenz übernimmt, kann dazu verwendet werden,
einzelne Zeichen aus der Standardeingabe einzulesen. Auch mit der Elementfunktion
get()
kann man einzelne Zeichen einlesen und dies sogar auf zwei Wegen: get()
gibt
es ohne Parameter, in welchem Falle der Rückgabewert verwendet wird, oder mit einer
Referenz auf ein Zeichen.
Die erste Form von get()
weist keine Parameter auf. Diese Version liefert den Wert
des gefundenen Zeichens zurück beziehungsweise EOF (End of file), wenn das Ende der
Datei erreicht wurde. Diese Form von get()
ohne Parameter kommt selten zur Anwendung.
Es ist hiermit nicht möglich, mehrere Eingaben zu verketten, da der Rückgabewert
kein iostream
-Objekt ist. Aus diesem Grund ist folgende Zeile nicht möglich:
cin.get() >>varEins >> varZwei; // ungueltig
Der Rückgabewert von cin.get() >> varEins
ist ein Integer und kein iostream
-Objekt.
Eine typische Anwendung für get()
ohne Parameter sehen Sie in Listing 16.4.
Listing 16.4: get() ohne Parameter
1: // Listing 16.4 - get ohne Parameter
2: #include <iostream.h>
3:
4: int main()
5: {
6: char ch;
7: while ( (ch = cin.get()) != EOF)
8: {
9: cout << "ch: " << ch << endl;
10: }
11: cout << "\nFertig!\n";
12: return 0;
13: }
Um dieses Programm zu beenden, müssen Sie von der Tastatur aus das Dateiende-Zeichen eingeben. Auf DOS-PCs lautet der Befehl Strg+Z und auf UNIX-PCs Strg+D.
Hello
ch: H
ch: e
ch: l
ch: l
ch: o
ch:
World
ch: W
ch: o
ch: r
ch: l
ch: d
ch:
(Strg-z)
Fertig!
Zeile 6 deklariert eine lokale Zeichenvariable. Die while
-Schleife weist die von
cin.get()
eingelesenen Eingaben ch
zu. Solange nicht EOF eingelesen wird, wird der
Meldungsstring ausgegeben. Allerdings wird die Ausgabe bis zum nächsten Zeilenende-Zeichen
gepuffert. Trifft das Programm auf EOF (eingegeben als Strg+Z unter
DOS oder Strg+D unter UNIX), wird die Schleife verlassen.
Beachten Sie, daß nicht jede Implementierung von istream
diese Version von get()
unterstützt, auch wenn sie inzwischen zum ANSI-Standard gehört.
Wenn ein Zeichen als Argument an get()
übergeben wird, wird dieses Zeichen mit
dem nächsten Zeichen im Eingabestrom gefüllt. Der Rückgabewert ist ein iostream
-
Objekt. Deshalb kann diese Form von get()
verkettet werden. Sehen Sie dazu Listing
16.5.
Listing 16.5: get() mit Parametern
1: // Listing 16.5 - get mit Parametern
2: #include <iostream.h>
3:
4: int main()
5: {
6: char a, b, c;
7:
8: cout << "Geben Sie drei Buchstaben ein: ";
9:
10: cin.get(a).get(b).get(c);
11:
12: cout << "a: " << a << "\nb: " << b << "\nc: " << c << endl;
13: return 0;
14: }
Geben Sie drei Buchstaben ein: elf
a: e
b: l
c: f
Zeile 6 erzeugt drei Zeichenvariablen. In Zeile 10 wird cin.get()
dreimal verkettet
aufgerufen. Zuerst ergeht der Aufruf an cin.get(a)
. Damit wird der erste Buchstabe in
a
abgelegt und cin
kehrt zurück. Anschließend wird cin(b)
aufgerufen und der nächste
Buchstabe wird in b
abgelegt. Zum Schluß wird cin.get(c)
aufgerufen und der dritte
Buchstabe in c
abgelegt.
Da cin.get(a)
als cin
ausgewertet wird, hätten Sie auch folgendes schreiben können:
cin.get(a) >> b;
In dieser Form wird cin.get(a)
als cin
ausgewertet, so daß der zweite Teil cin >> b;
lautet.
Verwenden Sie den Eingabe-Operator ( Verwenden Sie |
Statt mit den Elementfunktionen get()
und getline()
kann man auch mit dem Eingabe-Operator
(>>
) ein Zeichen-Array füllen.
Die letzte Form von get()
übernimmt drei Parameter. Der erste Parameter ist ein Zeiger
auf einen Zeichen-Array, der zweite Parameter die maximale Anzahl der einzulesenden
Zeichen plus eins und der dritte Parameter das Terminierungszeichen.
Wenn Sie als zweiten Parameter 20 eingeben, wird get()
19 Zeichen einlesen und
den String, der im ersten Parameter gespeichert wird, mit dem Nullzeichen abschließen.
Bei dem dritten Parameter, dem Terminierungszeichen, handelt es sich standardmäßig
um das Zeichen für eine neue Zeile ('\n'
). Stößt das Programm auf ein Terminierungszeichen,
bevor die maximale Anzahl an Zeichen eingelesen wurde, wird eine
Null geschrieben und das Terminierungszeichen bleibt im Puffer stehen.
Listing 16.6 zeigt, wie diese Form von get()
eingesetzt wird.
Listing 16.6: get mit einem Zeichen-Array verwenden
1: // Listing 16.6 - get mit einem Zeichen-Array verwenden
2: #include <iostream.h>
3:
4: int main()
5: {
6: char stringOne[256];
7: char stringTwo[256];
8:
9: cout << "Ersten String eingeben: ";
10: cin.get(stringOne,256);
11: cout << "stringOne: " << stringOne << endl;
12:
13: cout << " Zweiten String eingeben: ";
14: cin >> stringTwo;
15: cout << "StringTwo: " << stringTwo << endl;
16: return 0;
17: }
Ersten String eingeben: Die Zeit ist gekommen
stringOne: Die Zeit ist gekommen
Zweiten String eingeben: Fuer alle guten
StringTwo: Fuer
Die Zeilen 6 und 7 erzeugen zwei Zeichen-Arrays. In Zeile 9 wird der Anwender aufgefordert,
einen String einzugeben. Zeile 10 ruft cin.get()
auf. Der erste Parameter
ist der zu füllende Puffer und der zweite Parameter die maximale Anzahl der Zeichen,
die get()
akzeptiert, plus 1 für den zusätzlichen Speicherplatz des Nullzeichens ('\0'
).
Der vorgegebene dritte Parameter ist das Zeichen für die neue Zeile.
Der Anwender gibt ein »Die Zeit ist gekommen.« Da der Anwender seine Eingabe mit
einem Zeilenumbruch abschließt, wird diese Zeile in der Variablen stringOne
mit einer
abschließenden Null abgelegt.
Zeile 13 fordert den Anwender auf, einen weiteren String einzugeben. Diesmal wird der Eingabe-Operator verwendet. Da der Eingabe-Operator alles bis zum nächsten Leerzeichen einliest, wird in dem zweiten String nur der String »Fuer« mit einem abschließenden Zeichen gespeichert. Dies war jedoch nicht Ihre ursprüngliche Absicht.
Dies Problem läßt sich auch mit getline()
lösen, wie Listing 16.7 zeigt.
1: // Listing 16.7 - getline
2: #include <iostream.h>
3:
4: int main()
5: {
6: char stringOne[256];
7: char stringTwo[256];
8: char stringThree[256];
9:
10: cout << "Ersten String eingeben: ";
11: cin.getline(stringOne,256);
12: cout << "stringOne: " << stringOne << endl;
13:
14: cout << "Zweiten String eingeben: ";
15: cin >> stringTwo;
16: cout << "stringTwo: " << stringTwo << endl;
17:
18: cout << "Dritten String eingeben: ";
19: cin.getline(stringThree,256);
20: cout << "stringThree: " << stringThree << endl;
21: return 0;
22: }
Ersten String eingeben: eins zwei drei
stringOne: eins zwei drei
Zweiten String eingeben: vier fuenf sechs
stringTwo: vier
Dritten String eingeben: stringThree: fuenf sechs
Untersuchen Sie dieses Beispiel sorgfältig, es birgt einige Überraschungen. Die Zeilen 6 bis 8 deklarieren drei Zeichen-Arrays.
In Zeile 10 wird der Anwender aufgefordert, einen String einzugeben, der dann von
getline()
eingelesen wird. Wie get()
übernimmt getline()
einen Puffer und die maximale
Anzahl der einzulesenden Zeichen. Während jedoch bei get()
das abschließende
Zeichen für die neue Zeile im Eingabepuffer gelassen wird, wird es bei getline()
gelesen und gleich verworfen.
Zeile 14 enthält erneut eine Aufforderung zur Eingabe, diesmal unter Verwendung des
Eingabe-Operators. Der Anwender gibt »vier fünf sechs« ein, und das erste Wort »vier«
wird in stringTwo
abgelegt. Anschließend wird die Aufforderung »Dritten String eingeben«
angezeigt und getline()
zum Einlesen aufgerufen. Da »fuenf sechs« noch im Eingabepuffer
stehen, wird dieser Reststring bis zum Zeichen für die neue Zeile ausgelesen.
Damit wird getline()
beendet, und der String in stringThree
wird in Zeile 20
ausgegeben.
Der Anwender bekommt keine Chance, den dritten String einzugeben, da der zweite
Aufruf von getline()
den Reststring im Eingabepuffer einliest, der nach dem Aufruf
des Eingabe-Operators in Zeile 15 im Puffer verblieben ist.
Der Eingabe-Operator (>>)
liest die Zeichen bis zum ersten Leerzeichen ein und legt
das Wort im Zeichen-Array ab.
Die Elementfunktion get()
ist überladen. In einer Version übernimmt sie keine Parameter
und liefert den Wert des Zeichens zurück, das sie empfängt. In der zweiten Version
übernimmt sie eine Referenz auf ein einzelnes Zeichen und liefert das istream
-
Objekt als Referenz zurück.
In der dritten und letzten Version übernimmt get()
ein Zeichen-Array, die Anzahl der
einzulesenden Zeichen und ein Terminierungszeichen (standardmäßig das Zeichen für
die neue Zeile). Diese Version von get()
liest solange Zeichen in das Array, bis die
maximale Anzahl der übergebenen Zeichenzahl minus eins oder das Terminierungszeichen
erreicht wurde. Trifft get()
auf das Terminierungszeichen, bleibt das Zeichen
im Eingabepuffer und die Einleseoperation wird beendet.
Die Elementfunktion getline()
übernimmt drei Parameter: den zu füllenden Puffer,
ein Zeichen mehr als die maximale Anzahl der Zeichen und das Terminierungszeichen.
Die getline()
-Funktion arbeitet genauso wie get()
mit den gleichen Parametern.
Der einzige Unterschied ist, daß getline()
das Terminierungszeichen liest und
verwirft.
Gelegentlich werden Sie die verbleibenden Zeilen einer Zeile (oder einer Datei) ignorieren
wollen. Dies läßt sich mit der Elementfunktion ignore()
realisieren. ignore()
übernimmt zwei Parameter: die maximale Anzahl der Zeichen, die ignoriert werden
sollen und das Terminierungszeichen. Wenn Sie ignore(80,'\n')
schreiben,
werden
alle, maximal aber 80 Zeichen, auf dem Weg zum nächsten Neue-Zeile-Zeichen verworfen.
Das Zeichen für die neue Zeile wird ebenfalls verworfen und die ignore()
-Anweisung
endet. Sehen Sie dazu das Listing 16.8.
1: // Listing 16.8 - ignore()
2: #include <iostream.h>
3:
4: int main()
5: {
6: char stringOne[255];
7: char stringTwo[255];
8:
9: cout << "Ersten String eingeben:";
10: cin.get(stringOne,255);
11: cout << "stringOne: " << stringOne << endl;
12:
13: cout << "Zweiten String eingeben: ";
14: cin.getline(stringTwo,255);
15: cout << "stringTwo: " << stringTwo << endl;
16:
17: cout << "\n\nNeuer Versuch...\n";
18:
19: cout << "Ersten String eingeben: ";
20: cin.get(stringOne,255);
21: cout << "stringOne: " << stringOne<< endl;
22:
23: cin.ignore(255,'\n');
24:
25: cout << "Zweiten String eingeben: ";
26: cin.getline(stringTwo,255);
27: cout << "stringTwo: " << stringTwo<< endl;
28: return 0;
29: }
Ersten String eingeben: Es war einmal
stringOne: Es war einmal
Zweiten String eingeben: stringTwo:
Neuer Versuch...
Ersten String eingeben: Es war einmal
stringOne: Es war einmal
Zweiten String eingeben: ein wunderschoenes
stringTwo: ein wunderschoenes
Die Zeilen 6 und 7 erzeugen zwei Zeichen-Arrays. Zeile 9 fordert den Anwender zur
Eingabe auf. Der Anwender tippt »Es war einmal« ein und betätigt die Eingabetaste. In
Zeile 10 wird dieser String mit get()
eingelesen. get()
füllt stringOne
und endet mit
der neuen Zeile. Das Zeichen für die neue Zeile bleibt im Eingabepuffer.
Zeile 13 enthält eine weitere Aufforderung zur Eingabe, aber getline()
in Zeile 14
liest das Zeichen für neue Zeile, das im Puffer steht, und bricht direkt danach ab, bevor
der Anwender eine Eingabe vornehmen kann.
In Zeile 19 wird der Anwender erneut zu einer Eingabe aufgefordert, die in diesem
Fall aus der gleichen ersten Zeile besteht. Diesmal wird jedoch in Zeile 23 ignore()
verwendet, um das Zeichen für die neue Zeile »aufzufressen«. Aus diesem Grund ist
der Eingabepuffer bei dem Aufruf von getline()
in Zeile 26 leer und der Anwender
kann mit der Eingabe seiner Geschichte fortfahren.
Das Eingabe-Objekt cin
weist noch zwei weitere Methoden auf, die gelegentlich ganz
nützlich sein können: peek()
und putback()
. peek()
betrachtet das nächste Zeichen
aber liest es nicht ein, und putback()
schreibt ein Zeichen in den Eingabestrom. In Listing
16.9 sehen Sie, wie sich diese Methoden anwenden lassen.
Listing 16.9: peek() und putback()
1: // Listing 16.9 - peek() und putback()
2: #include <iostream.h>
3:
4: int main()
5: {
6: char ch;
7: cout << "Geben Sie einen Satz ein: ";
8: while ( cin.get(ch) )
9: {
10: if (ch == '!')
11: cin.putback('$');
12: else
13: cout << ch;
14: while (cin.peek() == '#')
15: cin.ignore(1,'#');
16: }
17: return 0;
18: }
Geben Sie einen Satz ein: Jetzt!ist#es!Zeit#fuer!ein#wenig!Spass#!
Jetzt$istes$Zeitfuer$einwenig$Spass$
Zeile 6 deklariert eine Zeichenvariable ch
und Zeile 7 fordert den Anwender auf, einen
Satz einzugeben. Der Zweck dieses Programms ist es, alle Ausrufezeichen (!
) in Dollar-Zeichen
($
) umzuwandeln und die Pfund-Zeichen (#
) zu entfernen.
Dies Programm durchläuft eine Schleife, aus der es erst austritt, wenn es das Zeichen
EOF einliest (Strg+C unter Windows und Strg+Z oder Strg+D unter anderen Betriebssystemen).
Denken Sie jedoch daran, daß cin.get()
eine 0
für EOF zurückliefert. Ist
das aktuelle Zeichen ein Ausrufezeichen, wird es verworfen und statt dessen ein $
-Zeichen
in den Eingabepuffer gestellt, das beim nächsten Mal eingelesen wird. Ist das aktuelle
Zeichen kein Ausrufezeichen, wird es ausgegeben. Danach wird das nächste Zeichen
untersucht, und wenn es sich dabei um ein Pfund-Zeichen handelt, wird es
gelöscht.
Dieser Weg ist zwar nicht unbedingt der effizienteste (und es wird auch kein Pfund- Symbol gefunden, das als erstes Zeichen auftritt), aber das Beispiel zeigt zumindest, wie diese Methoden funktionieren. Sie sind relativ schwer zu durchschauen. Deshalb sollten Sie sich jetzt nicht allzu viele Gedanken darum machen, sondern sie in Ihrer Trickkiste verstauen, bis Sie sie vielleicht eines Tages gebrauchen können.
peek()
undputback()
werden in der Regel zum Parsen von Strings oder anderer Daten eingesetzt, zum Beispiel beim Schreiben eines Compilers.
Bisher haben Sie cout
zusammen mit dem überladenen Ausgabe-Operator (<<
) verwendet,
um Strings, Integer und andere numerische Daten auf dem Bildschirm auszugeben.
Es ist dabei möglich, die Daten zu formatieren, spaltenweise auszurichten und
numerische Zahlen in dezimaler und hexadezimaler Schreibweise auszugeben. In diesem
Abschnitt möchte ich Ihnen zeigen, wie das geht.
Sie haben bereits sehen können, daß bei der Ausgabe von endl
der Ausgabepuffer geleert
wird. endl
ruft die cout
-Elementfunktion flush()
auf, die alle im Puffer abgelegten
Daten ausgibt. Sie können flush()
auch direkt aufrufen, indem Sie die Elementfunktion
flush()
aufrufen oder an cout
schicken:
cout << flush
Dies kann ganz nützlich sein, wenn Sie sicherstellen wollen, daß der Ausgabepuffer leer ist und auf dem Bildschirm ausgegeben wurde.
Genau wie der Eingabe-Operator mit get()
und getline()
ergänzt werden kann, gibt
es für den Ausgabe-Operator die Funktionen put()
und write()
.
Mit Hilfe der Funktion put()
wird ein einzelnes Zeichen auf dem Ausgabegerät ausgegeben.
Da put()
eine ostream
-Referenz zurückliefert und cout
ein ostream
-Objekt ist,
können Sie put()
verketten wie beim Ausgabe-Operator. Listing 16.10 veranschaulicht
dies Konzept.
1: // Listing 16.10 - put()
2: #include <iostream.h>
3:
4: int main()
5: {
6: cout.put('H').put('e').put('l').put('l').put('o').put('\n');
7: return 0;
8: }
Hello
Einige Compiler haben Schwierigkeiten, auf diese Weise Text auszugeben. Wenn Ihr Compiler das Wort Hello nicht ausgibt, sollten Sie dieses Listing einfach überspringen.
Zeile 6 wird wie folgt ausgewertet: cout.put('H')
gibt den Buchstaben H
auf dem Bildschirm
aus und liefert das cout
-Objekt zurück. Was übrigbleibt, ist:
cout.put('e').put('l').put('l').put('o').put('\n');
Als nächstes wird der Buchstabe e
ausgegeben und cout.put('l')
bleibt übrig. So
geht es weiter. Die Buchstaben werden ausgegeben und cout
wird jedes Mal zurückgegeben,
bis das letzte Zeichen ('\n'
) ausgegeben ist und die Funktion zurückkehrt.
Die Funktion write()
ist fast identisch zum Ausgabe-Operator, mit der Ausnahme,
daß sie einen Parameter übernimmt, der der Funktion die maximale Anzahl der auszugebenden
Zeichen mitteilt. Ein Beispiel finden Sie in Listing 16.11.
1: // Listing 16.11 - write()
2: #include <iostream.h>
3: #include <string.h>
4:
5: int main()
6: {
7: char One[] = "Einer fuer alle";
8:
9:
10:
11: int fullLength = strlen(One);
12: int tooShort = fullLength -4;
13: int tooLong = fullLength + 6;
14:
15: cout.write(One,fullLength) << "\n";
16: cout.write(One,tooShort) << "\n";
17: cout.write(One,tooLong) << "\n";
18: return 0;
19: }
Einer fuer alle
Einer fuer
Einer fuer alle i?!
Die letzte Zeile der Ausgabe kann bei Ihrem Computer zuerst erscheinen.
Zeile 7 erzeugt einen Satz. Zeile 11 setzt den Integer-Wert fullLength
auf die Länge
dieses Satzes. tooShort
erhält diese Länge von fullLength
minus vier Zeichen und
tooLong
die Länge von fullLength
plus sechs Zeichen.
Zeile 15 gibt den kompletten Satz mit Hilfe von write()
aus. Die Länge der Ausgabe
wird auf die eigentliche Länge des Satzes gesetzt und der Satz korrekt ausgegeben.
Zeile 16 gibt den Satz erneut aus. Diesmal ist die Ausgabe jedoch vier Zeichen kürzer als der Originalsatz.
Zeile 17 gibt ebenfalls den Satz aus, wobei write()
angewiesen wird, zusätzliche sechs
Zeichen auszugeben. Zusätzlich zu dem Satz werden auf dem Bildschirm die nächsten
6 Byte des angrenzenden Speicherplatzes ausgegeben.
Der Ausgabestrom verfügt über eine Reihe von Statusflags, die festlegen, welche Basis
(dezimal oder hexadezimal) verwendet wird, wie groß die Felder werden müssen und
mit welchem Zeichen die Felder aufgefüllt werden. Ein Statusflag ist ein Byte, dessen
einzelnen Bits jeweils eine eigene Bedeutung zugewiesen wird. In Kapitel 21 werden
wir noch genauer diskutieren, wie Sie solche Bits manipulieren können. Die ostream
-
Flags können mit Elementfunktionen und Manipulatoren gesetzt werden.
Die Standardbreite einer Ausgabe ist immer gerade so groß, daß die Zahl, das Zeichen
oder der String im Ausgabepuffer vollständig ausgegeben werden kann. Sie können
die Breite mit width()
ändern. Da width()
eine Elementfunktion ist, muß sie über ein
cout
-Objekt aufgerufen werden, wodurch allerdings nur die Breite des nächsten Ausgabefeldes
geändert wird. Danach gilt wieder der Standardwert. Zur Veranschaulichung
schauen Sie sich Listing 16.12 an.
Listing 16.12: Die Breite der Ausgabe anpassen
1: // Listing 16.12 - Breite der Ausgabe anpassen
2: #include <iostream.h>
3:
4: int main()
5: {
6: cout << "Start >";
7: cout.width(25);
8: cout << 123 << "< Ende\n";
9:
10: cout << "Start >";
11: cout.width(25);
12: cout << 123<< "< Weiter >";
13: cout << 456 << "< Ende\n";
14:
15: cout << "Start >";
16: cout.width(4);
17: cout << 123456 << "< Ende\n";
18:
19: return 0;
20: }
Start > 123< Ende
Start > 123< Weiter >456< Ende
Start >123456< Ende
Die erste Ausgabe in den Zeilen 6 bis 8 gibt die Zahl 123 in einem Feld aus, dessen Länge in Zeile 7 auf 25 festgesetzt wurde. Das Ergebnis sehen Sie in der ersten Ausgabezeile.
Die zweite Ausgabezeile gibt den Wert 123 in dem gleichen Feld (Größe 25) aus. Anschließend
erfolgt die Ausgabe des Wertes 456. Beachten Sie, daß 456 in einem Feld
steht, das auf eine Breite zurückgesetzt wurde, die genau paßt. Wie bereits oben erwähnt,
gilt die width()
-Anweisung nur für die nächste Ausgabe.
Die letzte Ausgabe zeigt, daß Sie zwar eine Breite angeben können, die kleiner als die
Ausgabe ist, der Aufruf der width()
-Methode dann aber ohne Wirkung bleibt.
Normalerweise füllt cout
den leeren Raum, der durch einen Aufruf von width()
erzeugt
wurde, wie oben gezeigt mit Leerzeichen. Vielleicht wünschen Sie jedoch diesen
Bereich mit anderen Zeichen zu füllen, zum Beispiel Sternchen. Dazu müssen Sie
fill()
aufrufen und das gewünschte Füllzeichen als Parameter übergeben. Ein Beispiel
hierfür sehen Sie in Listing 16.13.
1: // Listing 16.13 - fill()
2:
3: #include <iostream.h>
4:
5: int main()
6: {
7: cout << "Start >";
8: cout.width(25);
9: cout << 123 << "< Ende\n";
10:
11:
12: cout << "Start >";
13: cout.width(25);
14: cout.fill('*');
15: cout << 123 << "< Ende\n";
16: return 0;
17: }
Start > 123< Ende
Start >******************123< Ende
Die Zeilen 7 bis 9 zeigen die gleiche Funktionalität wie das vorige Beispiel. Das Ganze wird dann in den Zeilen 12 bis 15 wiederholt. Diesmal wird jedoch in Zeile 14 ein Sternchen als Füllzeichen festgesetzt. Das Ergebnis sehen Sie in der Ausgabe.
iostream
-Objekte halten ihren Status in Flags fest. Sie können diese Flags durch den
Aufruf von setf()
und die Übergabe einer der vordefinierten Aufzählungskonstanten
setzen.
Man spricht davon, daß Objekte einen Status haben, wenn einige oder alle Daten des Objekts eine Bedingung darstellen, die sich im Laufe des Programms ändern kann.
So können Sie zum Beispiel angeben, daß Nachkommanullen angezeigt werden sollen
(so daß 20,00 nicht zu 20 abgekürzt wird). Um Nachkommanullen einzuschalten, geben
Sie folgenden Befehl ein setf(ios::showpoint)
.
Der Gültigkeitsbereich der Aufzählungskonstanten beschränkt sich auf die iostream
-
Klasse (ios
). Deshalb werden sie mit voller Qualifizierung ios::flagname
aufgerufen
(z.B. ios::showpoint
).
Um vor positiven Zahlen das Plus-Zeichen (+
) anzeigen zu lassen, verwenden Sie
ios::showpos
. Um die Ausrichtung der Ausgabe zu ändern, verwenden Sie ios::left
,
ios::right
oder ios::internal
.
Und schließlich können Sie die Basis der anzuzeigenden Zahlen mit ios::dec
(dezimal),
ios::oct
(oktal - Basis acht), oder ios::hex
(hexadezimal - Basis sechszehn) setzen.
Diese Flags können auch direkt an den Ausgabe-Operator geschickt werden -
siehe Listing 16.14. Zusätzlich stellt Ihnen das Listing den Manipulator setw()
vor, der
die Ausgabebreite festlegt und darüber hinaus mit dem Ausgabe-Operator verkettet
werden kann.
1: // Listing 16.14 - setf
2: #include <iostream.h>
3: #include <iomanip.h>
4:
5: int main()
6: {
7: const int number = 185;
8: cout << "Die Zahl lautet " << number << endl;
9:
10: cout << "Die Zahl lautet " << hex << number << endl;
11:
12: cout.setf(ios::showbase);
13: cout << "Die Zahl lautet " << hex << number << endl;
14:
15: cout << "Die Zahl lautet " ;
16: cout.width(10);
17: cout << hex << number << endl;
18:
19: cout << "Die Zahl lautet " ;
20: cout.width(10);
21: cout.setf(ios::left);
22: cout << hex << number << endl;
23:
24: cout << "Die Zahl lautet " ;
25: cout.width(10);
26: cout.setf(ios::internal);
27: cout << hex << number << endl;
28:
29: cout << "Die Zahl lautet:" << setw(10) << hex << number << endl;
30: return 0;
31: }
Die Zahl lautet 185
Die Zahl lautet b9
Die Zahl lautet 0xb9
Die Zahl lautet 0xb9
Die Zahl lautet 0xb9
Die Zahl lautet 0x b9
Die Zahl lautet:0x b9
Zeile 7 initialisiert die konstante int
-Zahl mit dem Wert 185
. Die Ausgabe erfolgt in
Zeile 8.
In Zeile 10 wird der Wert erneut angezeigt. Diesmal wird jedoch der Manipulator hex
angehängt, wodurch der Wert in hexadezimaler Schreibweise als b9
ausgegeben wird.
(Der hexadezimale Wert b
steht für 11. 11 mal 16 ist gleich 176. Addieren Sie dazu
9, erhalten sie einen Gesamtwert von 185.)
Zeile 12 setzt das Flag showbase
. Damit wird der Präfix 0x
, wie in der Ausgabe zu sehen,
an alle hexadezimalen Zahlen angehängt.
Zeile 16 setzt die Größe auf 10, und der Wert wird ganz nach rechts verschoben. Zeile 20 setzt die Größe erneut auf 10, doch diesmal erfolgt die Ausrichtung am linken Rand (Zeile 21) und die Zahl wird linksbündig ausgegeben.
Zeile 25 setzt die Größe auf 10 und die Ausrichtung auf internal
. Als Ergebnis wird
0x
linksbündig ausgegeben und die Zahl b9
steht am rechten Rand.
Zum Schluß wird in Zeile 29 der Verkettungsoperator setw()
verwendet, um die Breite
auf 10 zu setzen. Danach wird der Wert erneut ausgegeben.
Die meisten C++-Implementierungen stellen auch die Standard-E/A-Bibliotheken von
C einschließlich der printf()
-Anweisung zur Verfügung. Doch auch wenn die Funktion
printf()
in einigen Fällen einfacher anzuwenden ist als cout
, ist von ihrem Gebrauch
dennoch abzuraten.
So bietet printf()
keine Typensicherheit. Leicht vertut man sich und läßt einen Integer
als Zeichen anzeigen und umgekehrt. Außerdem unterstützt printf()
keine Klassen.
Deshalb können Sie der Funktion auch nicht beibringen, wie Ihre Klassendaten
auszugeben sind; statt dessen müssen Sie jedes Klassenelement einzeln mit printf()
ausgeben.
Auf der anderen Seite fällt das Formatieren mit printf()
wesentlich leichter, da Sie die
Formatierungszeichen direkt in die printf()
-Anweisung aufnehmen können. Da
printf()
also durchaus seine Anwendungsbereiche hat und viele Programmierer noch
häufig diese Funktion einsetzen, werde ich sie in diesem Abschnitt kurz beschreiben.
Für die Verwendung von printf()
müssen Sie sicherstellen, daß Sie die Header-Datei
stdio.h
eingebunden haben. In seiner einfachsten Form übernimmt printf()
als ersten
Parameter einen Formatierungsstring und dazu eine Reihe von Werten für die
verbleibenden Parameter.
Der Formatierungsstring besteht aus Text und Konvertierungsspezifizierern. Alle Konvertierungsspezifizierer müssen mit einem Prozentzeichen (%) beginnen. Die geläufigsten Konvertierungsspezifizierer sind in Tabelle 16.1 zusammengefaßt.
Jeder dieser Konvertierungsspezifizierer kann durch eine Größen- und Genauigkeitsangabe
ergänzt werden. Diese werden als Fließkommazahl angegeben, wobei die Stellen
links des Dezimalzeichens die Größe und die Stellen rechts des Dezimalzeichens
die Genauigkeit angeben. So lautet der Spezifizierer für einen 5stelligen Integer-Wert
%5d
. Und mit %15.5f
definieren Sie den Spezifizierer für eine 15stellige Fließkommazahl,
bei der die letzten fünf Stellen dem Dezimalanteil gewidmet sind. Listing 16.15
zeigt mehrere Anwendungsbeispiele für printf()
.
Listing 16.15: Ausgabe mit printf()
1: #include <stdio.h>
2: int main()
3: {
4: printf("%s","hello world\n");
5:
6: char *phrase = "Hello again!\n";
7: printf("%s",phrase);
8:
9: int x = 5;
10: printf("%d\n",x);
11:
12: char *phraseTwo = "Hier einige Werte: ";
13: char *phraseThree = " und dann diese: ";
14: int y = 7, z = 35;
15: long longVar = 98456;
16: float floatVar = 8.8f;
17:
18: printf("%s %d %d %s %ld %f\n",phraseTwo,y,z,
phraseThree,longVar,floatVar);
19:
20: char *phraseFour = "Formatiert: ";
21: printf("%s %5d %10d %10.5f\n",phraseFour,y,z,floatVar);
22: return 0;
23: }
hello world
Hello again!
5
Hier einige Werte: 7 35 und dann diese: 98456 8.800000
Formatiert: 7 35 8.800000
Die erste printf()
-Anweisung in Zeile 4 erfolgt in der Standardform: printf()
gefolgt
von einem Konvertierungsspezifizierer in Anführungszeichen (hier %s
) und einem Wert
(hier der String), der in den Konvertierungsspezifizierer eingefügt werden soll.
Mit %s
geben Sie zu verstehen, daß es um einen String geht; der Wert für diesen String
ist in diesem Falle das String-Literal »hello world«.
Die zweite printf()
-Anweisung ist zu der ersten identisch. Allerdings wird hier ein
Zeiger auf char
statt eines String-Literals verwendet.
Die dritte printf()
-Anweisung in Zeile 10 verwendet den Konvertierungsspezifizierer
für Integer-Werte und erhält als Wert die Integer-Variable x
. Die vierte printf()
-Anweisung
in Zeile 18 ist etwas komplizierter. Hierbei werden sechs Werte aneinandergehängt.
Zuerst werden die jeweiligen Konvertierungsspezifizierer angegeben und anschließend,
durch Kommata getrennt, die dazugehörigen Werte.
In Zeile 21 schließlich werden Formatspezifikationen verwendet, um die Größe und die Genauigkeit festzulegen. Wie Sie sehen können, geht das alles etwas einfacher als die Arbeit mit Manipulatoren.
Wie jedoch bereits erwähnt, findet für printf()
leider keine Typenprüfung statt, und
die Funktion kann auch nicht als friend
oder Elementfunktion einer Klasse deklariert
werden. Wenn Sie also die verschiedenen Datenelemente einer Klasse ausgeben wollen,
müssen Sie der printf()
-Anweisung jede Zugriffsmethode explizit übergeben.
Können Sie zusammenfassen, wie man die Ausgabe manipulieren kann?
Antwort: Um die Ausgabe in C++ zu formatieren, verwendet man eine Kombination aus Sonderzeichen, Ausgabemanipulatoren und Flags.
Die folgenden Sonderzeichen werden in den Ausgabe-String mit eingeschlossen, der mit dem Ausgabe-Operator an
cout
gesendet wird.
\ddd
(Oktalzahl) - ASCII-Zeichencout << "\aEin Fehler ist aufgetreten\t"
Es ertönt ein Signal, eine Fehlermeldung wird ausgegeben und es erfolgt ein Sprung zum nächsten Tab-Stop. Manipulatoren werden zusammen mit dem
cout
-Operator verwendet. Für Manipulatoren, die Argumente übernehmen, müssen Sie die Header-Dateiiomanip.h
in Ihre Datei mit einbinden.Die folgende Liste führt die Manipulatoren auf, die keine Argumente übernehmen:
flush
- leert den Ausgabe-Puffer
endl
- fügt eine neue Zeile ein und leert den Ausgabe-Puffer
oct
- setzt die Basis der Ausgabe auf oktal
dec
- setzt die Basis der Ausgabe auf dezimal
hex
- setzt die Basis der Ausgabe auf hexadezimalDie folgenden Manipulatoren übernehmen Argumente (aus
iomanip.h
):
setbase
(base) - setzt die Ausgabebasis (0 = dezimal, 8 = oktal, 10 = dezimal, 16 = hexadezimal)
setw
(Größe) - setzt die minimale Größe des Ausgabefeldes
setfill
(ch) - das zu verwendende Füllzeichen, wenn die Größe definiert wird
setprecision
(p) - setzt die Genauigkeit für Fließkommazahlen
setiosflags
(f) - setzt ein oder mehrereios
-Flags
resetiosflags
(f) - setzt ein oder mehrereios
-Flags neucout << setw(12) << setfill('#') << hex << x << endl;
In diesem Beispiel wird die Feldbreite auf 12 gesetzt, als Füllzeichen wird '#' gewählt, die Ausgabe erfolgt hexadezimal und ausgegeben wird der Wert von 'x'. Das Zeichen für eine neue Zeile wird im Puffer abgelegt und der Puffer wird geleert. Alle Manipulatoren bis auf
flush
,endl
undsetw
bleiben solange gültig, bis sie geändert werden, oder das Ende des Programms erreicht wurde.setw()
nimmt nach dem aktuellencout
wieder den Standardwert an.Die folgenden
ios
-Flags können zusammen mit den Manipulatorensetiosflags
undresetiosflags
verwendet werden:
ios::left
- richtet die Ausgabe innerhalb der angegebenen Feldbreite links aus
ios::right
- richtet die Ausgabe innerhalb der angegebenen Feldbreite rechts aus
ios::internal
- Vorzeichen wird links ausgerichtet, Wert rechts
ios::hex
- hexadezimale Ausgabe
ios::showbase
- fügtOx
an hexadezimale Zahlen und0
an oktale Zahlen an
ios::showpoint
- fügt gegebenenfalls Nachkomma-Nullen für die Kennzeichnung der gewünschten Genauigkeit an
ios::uppercase
- Zahlen der hexadezimalen und wissenschaftlichen Schreibweise in Großbuchstaben ausgeben
ios::showpos
-das +-Zeichen für positive Zahlen anzeigen
ios::scientific
- Fließkommazahlen in wissenschaftlicher Schreibweise
ios::fixed
- Fließkommazahlen in dezimaler SchreibweiseWeitere Informationen entnehmen Sie bitte der Header-Datei
ios.h
und Ihren Compiler-Handbüchern.
Streams stellen eine Möglichkeit dar, Daten, die von der Tastatur oder der Festplatte
eingelesen und auf dem Bildschirm oder in eine Datei auf der Festplatte ausgegeben
werden, einheitlich zu behandeln. In beiden Fällen können Sie die Eingabe- und Ausgabe-Operatoren
oder die zugehörigen Funktionen und Manipulatoren verwenden.
Um Dateien zu öffnen und zu schließen, können Sie wie in den folgenden Abschnitten
beschrieben, ifstream
- und ofstream
-Objekte verwenden.
Die Objekte, mit denen man von Dateien liest oder in Dateien schreibt, werden als ofstream
-Objekte bezeichnet. Sie leiten sich von den bisher verwendeten iostream
-Objekten
ab.
Bevor Sie in eine Datei schreiben können, müssen Sie zuerst ein ofstream
-Objekt erstellen
und dann dieses Objekt mit einer bestimmten Datei auf Ihrer Festplatte verbinden.
Damit Sie ofstream
-Objekte verwenden können, müssen Sie sicherstellen, daß
Sie die Header-Datei fstream.h
in Ihr Programm einbinden.
Da
iostream.h
infstream.h
bereits enthalten ist, müssen Sieiostream
nicht mehr explizit mit aufnehmen.
iostream
-Objekte enthalten Flags, die Ihnen Auskunft über den Status Ihrer Eingabe
und Ausgabe geben. Sie können diese Flags mit den Boole'schen Funktionen eof()
,
bad()
, fail()
und good()
abfragen. Die Funktion eof()
liefert true
zurück, wenn das
iostream
-Objekt auf das Ende der Datei (EOF) gestoßen ist. Die Funktion bad()
liefert
true
zurück, wenn Sie eine ungültige Operation versuchen. fail()
liefert immer dann
true
zurück, wenn bad()
ebenfalls true
ist oder eine Operation fehlgeschlagen ist.
Und schließlich gibt es noch die Funktion good()
, die jedes Mal true
zurückliefert,
wenn alle drei anderen Funktionen zu false
ausgewertet werden.
Um die Datei meineDatei.cpp
mit einem ofstream
-Objekt zu öffnen, müssen Sie eine
Instanz eines ofstream
-Objekts deklarieren und den Dateinamen als Parameter übergeben.
ofstream fout("meineDatei.cpp");
Um die Datei für die Eingabe zu öffnen, gehen Sie auf genau die gleichen Art und
Weise vor, verwenden aber ein ifstream
-Objekt.
ifstream fin("meineDatei.cpp");
Beachten Sie, daß fin
und fout
Namen sind, die Sie selbst vergeben. In diesem Fall
wurden die Bezeichner fout
und fin
gewählt, um die Beziehung zu cout
beziehungsweise
cin
widerzuspiegeln.
Eine wichtige Funktion für Dateistreams ist close()
. Jedes Dateistream-Objekt, das
Sie erzeugen, öffnet eine Datei - entweder zum Lesen oder zum Schreiben (oder für
beides). Achten Sie daher darauf, daß Sie die Datei mit close()
schließen, nachdem
Sie die Lese- oder Schreiboperationen beendet haben. Damit stellen Sie sicher, daß
die Datei nicht beschädigt wird und daß die ausgegebenen Daten auf die Festplatte geschrieben
werden.
Nachdem die Streamobjekte mit Dateien verbunden wurden, können Sie wie alle anderen Streamobjekte verwendet werden. Sehen Sie dazu Listing 16.16.
Listing 16.16: Dateien zum Lesen und Schreiben öffnen
1: #include <fstream.h>
2: int main()
3: {
4: char fileName[80];
5: char buffer[255]; // fuer die Benutzereingabe
6: cout << "Dateiname: ";
7: cin >> fileName;
8:
9: ofstream fout(fileName); // zum Schreiben oeffnen
10: fout << "Diese Zeile wird direkt in die Datei geschrieben...\n";
11: cout << "Bitte Text fuer die Datei eingeben: ";
12: cin.ignore(1,'\n'); // Neue Zeile nach dem Dateinamen entfernen
13: cin.getline(buffer,255); // Benutzereingabe einlesen
14: fout << buffer << "\n"; // und in die Datei schreiben
15: fout.close(); // Datei schliessen, bereit zum erneuten Oeffnen
16:
17: ifstream fin(fileName); // zum Lesen erneut oeffnen
18: cout << "So lautet der Inhalt der Datei:\n";
19: char ch;
20: while (fin.get(ch))
21: cout << ch;
22:
23: cout << "\n***Ende des Dateiinhalts.***\n";
24:
25: fin.close(); // Ordnungssinn zahlt sich aus
26: return 0;
27: }
Dateiname: test1
Bitte Text für die Datei eingeben: Dieser Text wird in die Datei geschrieben!
So lautet der Inhalt der Datei:
Diese Zeile wird direkt in die Datei geschrieben...
Dieser Text wird in die Datei geschrieben!
***Ende des Dateiinhalts.***
Zeile 4 richtet einen Puffer für den Dateinamen ein und Zeile 5 einen weiteren Puffer
für die Benutzereingabe. Zeile 6 fordert den Anwender auf, einen Dateinamen einzugeben,
der dann in den Puffer filename
geschrieben wird. Zeile 9 erzeugt das ofstream
-Objekt fout
, das mit dem neuen Dateinamen verbunden ist. Damit wird die Datei
geöffnet. Existiert die Datei bereits, wird ihr Inhalt verworfen.
In Zeile 10 wird Textstring direkt in die Datei geschrieben. Zeile 11 fordert den Anwender zur Eingabe auf. Das Zeichen für Neue Zeile, das von der Eingabe des Dateinamens übrig geblieben ist, wird in Zeile 12 gelöscht und die Benutzereingabe wird vorerst in einem Puffer gespeichert (Zeile 13). In Zeile 14 wird die Eingabe dann zusammen mit dem Zeichen für eine Neue Zeile in die Datei geschrieben. Zeile 15 schließt die Datei.
In Zeile 17 wird die Datei erneut geöffnet - diesmal jedoch im Lesemodus - und der Inhalt wird zeichenweise in den Zeilen 20 und 21 eingelesen.
Standardmäßig wird eine Datei, die noch nicht existiert, beim Öffnen erst einmal erzeugt.
Existiert sie bereits, wird ihr Inhalt gänzlich gelöscht. Wenn Sie von diesem
Standardverhalten abweichen wollen, müssen Sie explizit ein zweites Argument an
den Konstruktor Ihres ofstream
-Objekts übergeben.
ios::app
- hängt sich an das Ende der bestehenden Dateien an, anstatt deren Inhalt
zu löschen.
ios::ate
- springt zum Ende der Datei, gleichwohl Sie überall in die Datei Daten
schreiben können.
ios::trunc
- die Vorgabe. Bereits bestehende Dateien werden vorab gelöscht.
ios::nocreate
- wenn die Datei nicht existiert, schlägt der Öffnen-Befehl fehl.
ios::noreplace
- wenn die Datei bereits existiert, schlägt der Öffnen-Befehl fehl.
Beachten Sie, daß app
kurz für append
(anhängen) steht, ate
für at end
(am Ende) und
trunc
für truncate
(abschneiden). Listing 16.17 zeigt, wie append
verwendet wird, indem
die Datei aus Listing 16.16 neu geöffnet und dann etwas angehängt wird.
Listing 16.17: An das Ende einer Datei anhängen
1: #include <fstream.h>
2: int main() // liefert bei Fehler 1 zurueck
3: {
4: char fileName[80];
5: char buffer[255];
6: cout << "Bitte Dateiname erneut eingeben: ";
7: cin >> fileName;
8:
9: ifstream fin(fileName);
10: if (fin) // existiert bereits?
11: {
12: cout << "Aktueller Dateiinhalt:\n";
13: char ch;
14: while (fin.get(ch))
15: cout << ch;
16: cout << "\n***Ende des Dateiinhalts.***\n";
17: }
18: fin.close();
19:
20: cout << "\nDie Datei " << fileName <<
" im Anhaenge-Modus oeffnen...\n";
21:
22: ofstream fout(fileName,ios::app);
23: if (!fout)
24: {
25: cout << "Es ist nicht moeglich, " << fileName <<
" zum Anhaengen zu oeffnen.\n";
26: return(1);
27: }
28:
29: cout << "\nBitte Text fuer die Datei eingeben: ";
30: cin.ignore(1,'\n');
31: cin.getline(buffer,255);
32: fout << buffer << "\n";
33: fout.close();
34:
35: fin.open(fileName); // bestehendes fin-Objekt erneut verwenden!
36: if (!fin)
37: {
38: cout << "Es ist nicht moeglich, " << fileName <<
" zum Lesen zu oeffnen.\n";
39: return(1);
40: }
41: cout << "\nSo lautet der Inhalt der Datei:\n";
42: char ch;
43: while (fin.get(ch))
44: cout << ch;
45: cout << "\n***Ende des Dateiinhalts.***\n";
46: fin.close();
47: return 0;
48: }
Bitte Dateiname erneut eingeben: test1
Aktueller Dateiinhalt:
Diese Zeile wird direkt in die Datei geschrieben...
Dieser Text wird in die Datei geschrieben!
***Ende des Dateiinhalts.***
Die Datei test1 im Anhaenge-Modus oeffnen
Bitte Text für die Datei eingeben: Mehr Text für die Datei!
So lautet der Inhalt der Datei:
Diese Zeile wird direkt in die Datei geschrieben...
Dieser Text wird in die Datei geschrieben!
Mehr Text für die Datei!
***Ende des Dateiinhalts.***
Der Anwender wird erneut aufgefordert, den Dateinamen einzugeben. Diesmal wird
in Zeile 9 ein ifstream
-Objekt zum Schreiben in die Datei erzeugt. In Zeile 10 wird getestet,
ob die Datei geöffnet werden konnte, und wenn die Datei bereits existiert, wird
ihr Inhalt in den Zeilen 12 bis 16 ausgegeben. Beachten Sie, daß if(fin)
identisch ist
zu if (fin.good())
.
Danach wird die Eingabedatei geschlossen und die gleiche Datei wird in Zeile 22 erneut
geöffnet, diesmal jedoch im Anhänge-Modus. Wie nach jedem Öffnen-Vorgang
üblich, wird die Datei auch hier wieder überprüft, um sicherzustellen, daß die Datei
korrekt geöffnet wurde. Beachten Sie, daß if(!fout)
gleichbedeutend ist mit if
(fout.fail())
. Vom Anwender wird eine Texteingabe angefordert und in Zeile 33
wird die Datei erneut geschlossen.
Zum Schluß wird, wie in Listing 16.16, die Datei im Lese-Modus geöffnet. Diesmal
muß jedoch fin
nicht noch einmal deklariert werden, es braucht lediglich der Dateiname
erneut zugewiesen zu werden. Auch hier wird der Öffnen-Vorgang getestet (in Zeile
36), und wenn alles korrekt ist, wird der Inhalt der Datei auf dem Bildschirm ausgegeben
und die Datei endgültig geschlossen.
Einige Betriebssysteme wie DOS unterscheiden zwischen Textdateien und binären
Dateien. Textdateien speichern alles als Text (wie der Name bereits verrät). Demzufolge
werden große Zahlen wie 54.325 als ein numerischer String ('5', '4', '.', '3',
'2', '5')
gespeichert. Dies mag nicht sehr effizient sein, hat aber den Vorteil, daß
der Text von einfachen Programmen, wie unter DOS üblich, gelesen werden kann.
Damit das Dateisystem besser zwischen Textdateien und binären Dateien unterscheiden
kann, gibt es in C++ das Flag ios::binary
. Auf vielen Systemen wird dieses Flag
ignoriert, da alle Daten im binären Format abgelegt werden. In einigen eher empfindlichen
Systemen ist das Flag ios::binary
nicht gültig und läßt sich nicht kompilieren!
Binäre Dateien können nicht nur Integer und Strings speichern, sondern auch ganze
Datenstrukturen. Sie können alle Daten auf einmal mit der Methode write()
von fstream
ausgeben.
Haben Sie write()
verwendet, können Sie die Daten mit read()
wieder zurückholen.
Jede dieser Funktionen erwartet jedoch einen Zeiger auf ein Zeichen, so daß Sie die
Adresse Ihrer Klasse erst zu einem Zeiger auf char
umwandeln müssen.
Das zweite Argument zu diesen Funktionen ist die Anzahl der Zeichen, die geschrieben
werden sollen. Diese Angabe können Sie mit sizeof
ermitteln. Beachten Sie, daß
die Daten und nicht die Methoden ausgegeben werden. Zurückgeholt werden ebenfalls
nur die Daten. Listing 16.18 zeigt, wie der Inhalt einer Klasse in eine Datei geschrieben
wird.
Listing 16.18: Eine Klasse in eine Datei schreiben
1: #include <fstream.h>
2:
3: class Animal
4: {
5: public:
6: Animal(int weight, long days):itsWeight(weight),
itsNumberDaysAlive(days){}
7: ~Animal(){}
8:
9: int GetWeight()const { return itsWeight; }
10: void SetWeight(int weight) { itsWeight = weight; }
11:
12: long GetDaysAlive()const { return itsNumberDaysAlive; }
13: void SetDaysAlive(long days) { itsNumberDaysAlive = days; }
14:
15: private:
16: int itsWeight;
17: long itsNumberDaysAlive;
18: };
19:
20: int main() // liefert bei Fehler 1 zurueck
21: {
22: char fileName[80];
23:
24:
25: cout << "Bitte Dateinamen eingeben: ";
26: cin >> fileName;
27: ofstream fout(fileName,ios::binary);
28: if (!fout)
29: {
30: cout << "Es ist nicht moeglich, " << fileName <<
" zum Schreiben zu oeffnen.\n";
31: return(1);
32: }
33:
34: Animal Bear(50,100);
35: fout.write((char*) &Bear,sizeof Bear);
36:
37: fout.close();
38:
39: ifstream fin(fileName,ios::binary);
40: if (!fin)
41: {
42: cout << "Es ist nicht moeglich, " << fileName <<
" zum Lesen zu oeffnen.\n";
43: return(1);
44: }
45:
46: Animal BearTwo(1,1);
47:
48: cout << "BearTwo Gewicht: " << BearTwo.GetWeight() << endl;
49: cout << "BearTwo Tage: " << BearTwo.GetDaysAlive() << endl;
50:
51: fin.read((char*) &BearTwo, sizeof BearTwo);
52:
53: cout << "BearTwo Gewicht: " << BearTwo.GetWeight() << endl;
54: cout << "BearTwo Tage: " << BearTwo.GetDaysAlive() << endl;
55: fin.close();
56: return 0;
57: }
Bitte Dateinamen eingeben: Animals
BearTwo Gewicht: 1
BearTwo Tage: 1
BearTwo Gewicht: 50
BearTwo Tage: 100
Die Zeilen 3 bis 18 deklarieren eine einfache Animal
-Klasse. Die Zeilen 22 bis 32 erzeugen
eine Datei und öffnen sie im binären Modus. In Zeile 34 wird ein Tier (ein Animal
-Objekt) mit einem Gewicht von 50 erzeugt, das 100 Tage alt ist. Diese Daten werden
in Zeile 35 in die Datei geschrieben.
Zeile 37 schließt die Datei, und Zeile 39 öffnet sie wieder zum Lesen im Binärmodus.
In Zeile 46 wird ein zweites Tier mit dem Gewicht von 1 erzeugt, das nur einen Tag
alt ist. Die Daten von der Datei werden in das neue Animal
-Objekt eingelesen und
überschreiben damit die bereits bestehenden Daten (Zeile 51).
In vielen Betriebssystemen, wie zum Beispiel DOS oder UNIX, hat der Anwender die Möglichkeit, bereits zu Programmbeginn Parameter an Ihr Programm zu übergeben. Diese werden auch als Befehlszeilenoptionen bezeichnet und werden in der Regel durch Leerzeichen in der Befehlszeile getrennt. Zum Beispiel:
EinProgramm Param1 Param2 Param3
Diese Parameter werden nicht direkt an main()
übergeben. Statt dessen wird der
main()
-Funktion eines jeden Programms zwei Parameter übergeben. Der erste Parameter
ist die Anzahl der Argumente in der Befehlszeile. Da der Programmname selbst
mitzählt, hat jedes Programm mindestens einen Parameter. Die als Beispiel gedachte
Befehlszeile über diesem Absatz hat demnach vier Parameter. (Der Name EinProgramm
plus die drei Parameter ergeben zusammen vier Befehlszeilenargumente).
Der zweite Parameter, der an main()
übergeben wird, ist ein Array von Zeigern auf
Zeichenstrings. Da ein Array-Name ein konstanter Zeiger auf das erste Element im
Array ist, können Sie dieses Argument als Zeiger auf einen Zeiger auf char
, als Zeiger
auf einen Array von char
oder als ein Array von Arrays von char
deklarieren.
In der Regel wird das erste Argument als argc
(argument count = Argumentenzähler)
bezeichnet. Sie können aber auch einen beliebigen anderen Namen wählen. Das zweite
Argument trägt oft den Namen argv
(argument vector = Argumentenvektor). Aber
auch dies ist nur Konvention.
Es ist üblich, argc
zu prüfen, um sicherzustellen, daß Sie die erwartete Anzahl von Argumenten
erhalten haben, und argv
zu benutzen, um auf die Strings selbst zuzugreifen.
Beachten Sie, daß argv[0]
der Name des Programms ist und argv[1]
der erste
Parameter für das Programm, dargestellt als ein String. Wenn Ihr Programm zwei
Zahlen als Argumente übernimmt, müssen Sie diese Zahlen in Strings umwandeln. Listing
16.19 veranschaulicht die Verwendung von Befehlszeilenargumenten.
Listing 16.19: Befehlszeilenargumente
1: #include <iostream.h>
2: int main(int argc, char **argv)
3: {
4: cout << "Uebergeben wurden " << argc << " Argumente...\n";
5: for (int i=0; i<argc; i++)
6: cout << "Argument " << i << ": " << argv[i] << endl;
7: return 0;
8: }
TestProgram C++ in 21 Tagen
Uebergeben wurden 5 Argumente...
Argument 0: TestProgram.exe
Argument 1: C++
Argument 2: in
Argument 3: 21
ArgumeNT 4: Tagen
Sie müssen diesen Code entweder von der Befehlszeile aus starten (das heißt vom DOS-Fenster aus) oder die Befehlszeilenparameter in Ihrem Compiler setzen (schlagen Sie dazu in Ihrer Compiler-Dokumentation nach).
Die Funktion main()
deklariert zwei Argumente: argc
ist ein Integer, der die Anzahl
der Befehlszeilenargumente enthält, und argv
ist ein Zeiger auf ein Array von Strings.
Jeder String in dem Array, auf den argv
zeigt, ist ein Befehlszeilenargument. Beachten
Sie, daß argv
genausogut als char *argv[]
oder char argv[][]
deklariert werden
könnte. Es ist eine Frage des Programmierstils, wie Sie argv
deklarieren. Auch wenn
es in diesem Programm als ein Zeiger auf einen Zeiger deklariert ist, werden Array-Indizes
benutzt, um auf die einzelnen Strings zuzugreifen.
Zeile 4 verwendet argc
, um die Anzahl der Befehlszeilenargumente auszugeben: insgesamt
fünf einschließlich des Programmnamens.
In den Zeilen 5 und 6 werden die Befehlszeilenargumente nacheinander ausgegeben,
wobei die nullterminierten Strings über Array-Indizes angesprochen und an cout
übergeben
werden.
Häufiger werden Befehlszeilenargumente jedoch dazu verwendet, einen Dateinamen als Befehlszeilenargument zu übernehmen. Sehen Sie dazu Listing 16.18 in geänderter Fassung.
Listing 16.20: Befehlszeilenargumente
1: #include <fstream.h>
2:
3: class Animal
4: {
5: public:
6: Animal(int weight, long days):itsWeight(weight),
itsNumberDaysAlive(days){}
7: ~Animal(){}
8:
9: int GetWeight()const { return itsWeight; }
10: void SetWeight(int weight) { itsWeight = weight; }
11:
12: long GetDaysAlive()const { return itsNumberDaysAlive; }
13: void SetDaysAlive(long days) { itsNumberDaysAlive = days; }
14:
15: private:
16: int itsWeight;
17: long itsNumberDaysAlive;
18: };
19:
20: int main(int argc, char *argv[]) // liefert bei Fehler 1 zurueck
21: {
22: if (argc != 2)
23: {
24: cout << "Aufruf: " << argv[0] << " <dateiname>" << endl;
25: return(1);
26: }
27:
28: ofstream fout(argv[1],ios::binary);
29: if (!fout)
30: {
31: cout << "Es ist nicht moeglich, " << argv[1] <<
" zum Schreiben zu oeffnen.\n";
32: return(1);
33: }
34:
35: Animal Bear(50,100);
36: fout.write((char*) &Bear,sizeof Bear);
37:
38: fout.close();
39:
40: ifstream fin(argv[1],ios::binary);
41: if (!fin)
42: {
43: cout << "Es ist nicht moeglich, " << argv[1] <<
" zum Lesen zu oeffnen.\n";
44: return(1);
45: }
46:
47: Animal BearTwo(1,1);
48:
49: cout << "BearTwo Gewicht: " << BearTwo.GetWeight() << endl;
50: cout << "BearTwo Tage: " << BearTwo.GetDaysAlive() << endl;
51:
52: fin.read((char*) &BearTwo, sizeof BearTwo);
53:
54: cout << "BearTwo Gewicht: " << BearTwo.GetWeight() << endl;
55: cout << "BearTwo Tage: " << BearTwo.GetDaysAlive() << endl;
56: fin.close();
57: return 0;
58: }
BearTwo Gewicht: 1
BearTwo Tage: 1
BearTwo Gewicht: 50
BearTwo Tage: 100
Die Deklaration der Animal
-Klasse entspricht der aus Listing 16.18. Diesmal wird der
Anwender jedoch nicht im Programm aufgefordert, einen Dateinamen einzugeben,
statt dessen werden Befehlszeilenargumente verwendet. Zeile 2 deklariert main()
mit
zwei Parametern: der Anzahl der Befehlszeilenargumente und einem Zeiger auf das
Array mit den Strings der Befehlszeilenargumente.
In den Zeilen 22 bis 26 stellt das Programm sicher, daß die erwartete Anzahl an Argumenten (exakt zwei) empfangen wurden. Wenn der Anwender es versäumt, einen Dateinamen anzugeben (oder mehr als einen Dateinamen übergibt), wird eine Fehlermeldung ausgegeben:
Aufruf: TestProgramm <dateiname>
Dann wird das Programm verlassen. Beachten Sie, daß Sie Dank der Verwendung
von argv[0]
anstelle eines hartkodierten Programmnamens dieses Programm mit jedem
Namen kompilieren können. Die Fehlermeldung ist immer korrekt.
In Zeile 28 versucht das Programm, die angegebene Datei für die binäre Ausgabe zu
öffnen. Es gibt keinen Grund, den Dateinamen dazu in einen lokalen temporären Puffer
zu kopieren. Er kann direkt durch den Zugriff auf argv[1]
verwendet werden.
Diese Technik kommt in Zeile 40 erneut zum Einsatz, wenn die gleiche Datei zur Eingabe erneut geöffnet wird, und sie wird ebenfalls in den Zeilen 31 und 43 in den Anweisungen zu den Fehlerbedingungen verwendet, wenn die Dateien nicht geöffnet werden können.
Heute habe ich Ihnen erklärt, was Streams sind, und Ihnen die globalen Objekte cout
und cin
beschrieben. Das Ziel der istream
- und ostream
-Objekte ist es, die Arbeit, die
beim Schreiben an die Gerätetreiber und beim Puffern der Ein- und Ausgabe anfällt,
zu kapseln.
In jedem Programm werden vier Standardstreamobjekte erzeugt: cout
, cin
, cerr
und
clog
. Diese Objekte können von vielen Betriebssystemen »umgeleitet« werden.
Das istream
-Objekt cin
wird für die Eingabe verwendet und meist zusammen mit dem
überladenen Eingabe-Operator (>>
) eingesetzt. Das ostream
-Objekt cout
wird für die
Ausgabe verwendet und meist zusammen mit dem überladenen Ausgabe-Operator (<<
)
eingesetzt.
Jedes dieser Objekte verfügt über eine Vielzahl von Elementfunktionen wie get()
und
put()
. Da die am häufigsten verwendete Version dieser Methoden eine Referenz auf
ein Streamobjekt zurückliefert, lassen sich diese Operatoren und Funktionen problemlos
miteinander verketten.
Der Status der Streamobjekte kann mit Hilfe von Manipulatoren verändert werden. Diese können die Einstellungen für die Formatierung und Anzeige verändern und verschiedene andere Attribute der Streamobjekte setzen.
Für die Ein- und Ausgabe in und aus Dateien sind die fstream
-Klassen verantwortlich,
die sich von den Stream-Klassen ableiten. Dabei unterstützen diese Objekte nicht nur
die normalen Eingabe- und Ausgabe-Operatoren, sondern auch read()
und write()
zum Speichern und Zurückholen großer binärer Objekte.
Frage:
Woher wissen Sie, wann Sie den Ausgabe- oder den Eingabe-Operator
verwenden müssen und wann die anderen Elementfunktionen der
stream
-Klassen zum Einsatz kommen?
Antwort:
Im allgemeinen ist es einfacher den Eingabe- und den Ausgabe-Operator zu
verwenden. Man gibt ihnen den Vorzug, wenn genau ihr Verhalten gewünscht
wird. In den eher seltenen Fällen, wo diese Operatoren nicht ausreichen (wie
z.B. einen String aus mehreren Wörtern einzulesen), können die anderen
Funktionen eingesetzt werden.
Frage:
Was ist der Unterschied zwischen cerr
und clog
?
Antwort:
cerr
wird nicht gepuffert. Alles, was an cerr
geschrieben wird, wird direkt ausgegeben.
cerr
bietet sich vor allem bei Fehlern an, die auf dem Bildschirm
ausgegeben werden. Allerdings kann es etwas auf Kosten der Leistung gehen,
Protokolle auf die Festplatte zu schreiben. clog
puffert seine Ausgabe und
kann deshalb effizienter eingesetzt werden.
Frage:
Wozu hat man das Stream-Konzept entwickelt, wo doch printf()
so gut
funktioniert hat?
Antwort:
printf()
unterstützt leider weder das strenge Typensystem von C++ noch benutzerdefinierte
Klassen.
Frage:
In welchem Fall kommt putback()
zum Einsatz?
Antwort:
Wenn man mit einer Lese-Operation prüfen möchte, ob ein Zeichen gültig ist,
das Zeichen selbst aber für eine andere Lese-Operation (vielleicht ein anderes
Objekt) im Puffer belassen werden soll. Am häufigsten wird dies beim Parsen
einer Datei der Fall sein. So ist es beispielsweise gut möglich, daß der C++-
Compiler putback()
verwendet.
Frage:
In welchen Fällen kommt ignore()
zum Einsatz?
Antwort:
ignore()
wird meist nach get()
verwendet. Da get()
das Terminierungszeichen
im Puffer läßt, ist es nicht unüblich, direkt an den Aufruf von get()
einen
Aufruf von ignore
(1, '\1'
) anzuschließen. Auch dies ist häufig beim Parsen
der Fall.
Frage:
Meine Freunde verwenden printf()
in ihren C++-Programmen. Kann
ich das auch?
Antwort:
Aber sicher. Dadurch wird einiges einfacher, leider aber zu Lasten der Typensicherheit.
Der Workshop enthält Quizfragen, die Ihnen helfen sollen, Ihr Wissen zu festigen, und Übungen, die Sie anregen sollen, das eben Gelernte umzusetzen und eigene Erfahrungen zu sammeln. Versuchen Sie, das Quiz und die Übungen zu beantworten und zu verstehen, bevor Sie die Lösungen in Anhang D lesen und zur Lektion des nächsten Tages übergehen.
cin.get(),
und wo liegen die Unterschiede?
cin.read()
und cin.getline()
?
long
mit Hilfe des Ausgabe-Operators?
ofstream
-Objekts?
ios::ate
?
cin
, cout
, cerr
und clog
verwendet.
putback()
und ignore()
auskommt.
© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH