Inhaltsverzeichnis
<iostream>
Die Verwendung von Flags zur Kontrolle eines Stromzustands
ist jedenfalls eher eine Studie über Implementierungstechniken
als über Schnittstellen-Design.— Bjarne Stroustrup [C++ 3rd edn, 21.4.1]</file>
Ein- und Ausgabeströme
C++ bietet eine einheitliche Schnittstelle für ein-
und ausgehende Datenströme.
Dabei ist es gleichgültig, woher die Daten kommen (std::istream
)
oder wohin sie gehen (std::ostream
), ob Geräte (Konsole),
Dateien oder Zeichenketten.
Die Kontrolle des Stromzustands,
Positionierung
bei wahlfreiem Zugriff und
Formatierung
werden einheitlich gehandhabt.
Eingabe | Ausgabe | beides | |
allgemein | std::istream | std::ostream | std::iostream |
Dateien | std::ifstream | std::ofstream | std::fstream |
Zeichenketten | std::istringstream | std::ostringstream | std::stringstream |
Stromtypen für wchar_t
-Zeichen wird noch jeweils ein w
vorangestellt.
Standardkanäle sind während der Programmlaufzeit mit Stromvariablen verbunden:
std::cin | Eingabe-Konsole (Tastatur) |
std::cout | Ausgabe-Konsole (Bildschirm) |
std::cerr | Fehlerausgabe ungepuffert |
std::clog | Fehlerausgabe gepuffert |
Ausgabe
Ausgabeströme std::ostream
nehmen Daten aller Typen entgegen,
für die der Ausgabeoperator «
überladen ist,
und formatieren deren Ausgabe als Folge von Zeichen.
Sie schreiben unformatierte Zeichen und Rohspeicher,
besonders für Binärdateien.
void ausgabe(std::ostream& out, double d) { out << d; // formatierte Ausgabe out.put(' '); // Zeichenausgabe out.write( (char*) &d, sizeof(d) ); // Rohspeicher }
Eingabe
Eingabeströme std::istream
wandeln eingegebene (zulässige) Zeichenfolgen
in Werte jener Typen, für die der Eingabeoperator »
überladen ist.
Führende Leerzeichen werden vom Eingabeoperator »
stets verschluckt.
Darüber hinaus lassen sich Einzelzeichen
und Rohspeicher, meist aus Binärdateien, unformatiert einlesen.
void eingabe(std::istream& in, double& d) { in >> d; // formatierte Eingabe char c = in.get(); // Einzelzeichen, auch ' ' in.get(c); // dto. in.putback(c); // zurückschieben c = cin.peek(); // nachschauen, aber nicht einlesen in.read( (char*) &d, sizeof(d) ); // Rohspeicher }
Das Einlesen von Zeichenketten verlangt besondere Sorgfalt. Führende Leerzeichen werden übergangen, das Einlesen endet beim ersten whitespace danach.
char str[10]; // lang genug? cin >> str; // kann und wird schief gehen
Die Methoden get()
und getline()
lesen auch führende whitespaces in die Zeichenkette ein,
begrenzen deren Länge auf maximal n-1
Zeichen
und hängen stets eine '\0
' an.
Werden keine oder n-1
Zeichen vor dem Erreichen
des ende
-Zeichens gelesen,
wird das failbit
gesetzt (siehe Stromzustand).
int zeichenketteneingabe(std::istream& in, char* s, int n, char ende='\n') { in.getline(s, n, ende); // ende wird verschluckt in.getline(s, n); // ende am Zeilenumbruch '\n' in.get(s, n, ende); // ende wird nicht entfernt in.get(s, n); // geht schief, wenn schon bei '\n' return in.gcount(); // Anzahl zuletzt gelesener Zeichen }
Stromzustand
Der Stromzustand von Ein- und Ausgabeströmen ist erfragbar.
Er wird intern durch Flags goodbit
,
badbit
, failbit
und eofbit
aus der Basisklasse std::ios_base
repräsentiert.
Er kann mit rdstate()
abgefragt werden.
Während clear(state)
den Zustand komplett überschreibt,
setzt setstate(state)
einzelne Bits zusätzlich.
std::ios_base::iostate zustand(std::istream& s) { if (s) cout << "ok "; if (s.good()) cout << "gut "; if (s.eof() ) cout << "noch gut, aber am Dateiende "; if (s.fail()) cout << "Aktion schiefgegangen, " "Strom noch verwendbar "; if (s.bad() ) cout << "Strom total durcheinander "; return s.rdstate(); }
Ein guter Stromzustand ist Voraussetzung, aber keine Garantie für den Erfolg weiterer Aktionen mit dem Strom. Bei nicht gutem Zustand haben Ein- und Ausgaben keine Wirkung.
In der Voreinstellung werfen die Ströme keine Ausnahmen. Mit der Methode exceptions(flags)
können sie jedoch dazu gebracht werden,
bei Fehlern eine von system_error std::ios_base::failure
-Ausnahme zu werfen:
void lass_es_krachen(std::istream& in) { std::ios_base::iostate old = in.exceptions(); // goodbit bedeutet: keine Ausnahmen werfen try { in.exception(std::ios_base::failbit); // wirft Ausnahme sofort, falls failbit schon gesetzt ist int x; in >> x; // wirft Ausnahme, wenn keine Zahl im Strom ist } catch(std::ios_base::failure& e) { std::cerr << "Fehler in Eingabe: " << e.what() << " Fehlercode: " << e.code() << '\n'; } in.exceptions(old); }
Ein- und Ausgabe selbstdefinierter Typen sollten so definiert werden, dass Variablen beim Einlesen nicht in einen undefinierten Zustand geraten können und der einzulesende Wert erst dann geändert wird, wenn die Eingabe erfolgreich war.
struct Punkt { int x, y; }; std::istream& operator>>(std::istream& is, Punkt& p) { int x, y; if (is >> x >> y) { p.x = x; p.y = y; } return is; }
Positionierung und wahlfreier Zugriff
Ströme mit wahlfreiem Zugriff können die Lage der
aktuellen Lese- und Schreibposition im Strom erfragen und ändern.
Das ist vor allem für Binärdateien wichtig.
Die Position kann absolut oder
relativ zu Anfang std::ios_base::beg
,
aktueller Position std::ios_base::cur
oder Ende der Datei std::ios_base::end
gesetzt werden.
long filesize(std::istream& is) { long pos = is.tellg(); // alte Position merken is.seekg(0, std::ios_base::beg); // zum Anfang long beg = is.tellg(); is.seekg(0, std::ios_base::end); // zum Ende long end = is.tellg(); is.seekg(pos); // alte Position wiederherstellen return end-beg; }
Bei Ausgabeströmen arbeiten tellp()
und seekp()
sinngemäß.
Formatierung
Das Verhalten der Eingabeströme und das Aussehen der Ausgaben (Formatierung)
wird durch Schalter (Flags) in der Basisklasse std::ios_base
gesteuert.
void formatierung(std::istream& in) { long alt = in.flags(); // abfragen long neu = ios_base::skipws; // führende Leerzeichen verschlucken in.flags(alt | neu); // Schalter setzen in.setf(neu); // Flags setzen in.unsetf(neu); // Flags zurücksetzen }
Ganzzahlen sind in drei Positionssystemen (Basis, 8, 10, 16) darstellbar. Programmierer verwechseln immer Weihnachten mit Helloween, weil OCT 31 = DEC 25:
void ganzzahlbasis(std::ostream& out) { int i = 25; out.setf(std::ios_base::hex, std::ios_base::basefield); out << i << ' '; out.setf(std::ios_base::oct, std::ios_base::basefield); out << i << ' '; out.setf(std::ios_base::dec, std::ios_base::basefield); out << i << endl; // Ausgabe: 19 31 25 }
Die Methode setf(flag, mask)
wirkt wie
flags(flags()&~mask | flag&mask)
.
Dadurch werden alle Flags in mask
zurückgestellt
und flag
neu gesetzt.
Dies verhindert, dass zwei einander widersprechende Schalter gleichzeitig gesetzt werden.
Folgende Schalterkonstanten aus std::ios_base
beeinflussen die Formatierung:
left internal right | Ausrichtung bei vorgegebener Ausgabeweite in adjustfield |
dec hex oct | Ganzzahlbasis für Ein- / Ausgabe in basefield |
showbase | Ganzzahlbasis bei Ausgabe anzeigen: 0x19 031 25 |
fixed scientific | Fließkomma als Festkomma / mit Exponent ausgeben in floatfield |
showpoint | Dezimalpunkt und Nachkommanullen bis zu festgelegter Genauigkeit anzeigen |
showpos | positives Vorzeichen immer anzeigen |
uppercase | Ausgabe X und E als Großbuchstabe |
boolalpha | Ausgabe von Wahrheitswerten als Wort entsprechend Locale |
unitbuf | nach jeder Ausgabeoperation Puffer leeren |
skipws | führende Leerzeichen bei Eingabe mit » verschlucken |
Gleitkommazahlen zeigen den Dezimalpunkt und folgende Nullen nur,
wenn showpoint
gesetzt ist.
Die Ausgabegenauigkeit kann mit der Methode precision(n)
gesteuert werden.
Mit der Standardeinstellung 0 werden bis zu 6 Stellen angezeigt.
Im fixed
-Format ist n
die Zahl der Nachkommastellen,
im scientific
-Format die Gesamtzahl der zählenden Stellen.
void genauer(std::ostream& out) { int n = out.precision(); out.precision(2*n); }
Die Ausgabe eines Wertes nimmt immer mindestens soviele Zeichen ein, wie zur exakten Ausgabe notwendig sind. Die Ausgabeweite kann für die unmittelbar nächste Ausgabe vergrößert werden.
out.width(8);
Ist die befohlene Breite größer als notwendig,
wird die Ausgabezeichenfolge an den Rändern ausgerichtet
und mit Füllzeichen (Standardwert '
') aufgefüllt.
Nach jeder Ausgabe wird die Weite auf den Standardwert 0 zurückgesetzt,
alle anderen Einstellungen sind bleibend.
-1.23___ // left (Standard), width(8) -___1.23 // internal ___-1.23 // right
Das Füllzeichen lässt sich abfragen und ändern:
char c = out.fill(); out.fill('_');
Formatierung mit Manipulatoren
Manipulatoren bieten elegantere Schreibweisen zur Formatsteuerung:
int manipuliere(std::ostream& out) { int i = 123; double d = 123.456789; std::cout << std::hex << i << ' ' << std::dec << std::showpos << i << '\n'; std::cout << std::setw(12) << std::right << i << '\n'; std::cout << std::setw(12) << std::internal << i << '\n'; std::cout << std::setw(12) << std::setprecision(3) << d << '\n'; std::cout << std::setw(12) << std::fixed << d << '\n'; std::cout << std::setw(12) << std::scientific << d << '\n'; std::cout << std::setw(12) << std::hexfloat << d << '\n'; // C++11 std::cout << std::setw(12) << std::defaultfloat << d << '\n'; // C++11 }
Es gibt folgende parameterlose Manipulatoren:
left internal right | Ausrichtung breiter Ausgaben |
dec hex oct | Wechsel der Zahlenbasis bei Ein-/Ausgabe |
showbase noshowbase | Ganzzahlbasis 0… oder 0x… ausgeben |
defaultfloat fixed hexfloat scientific | Fließkommaausgabe normal / als Festkommazahl / mit Exponent / mit hexadezimaler Mantisse |
showpoint noshowpoint | Dezimalpunkt und folgende Nullen ausgeben |
showpos noshowpos | positives Vorzeichen ausgeben |
uppercase nouppercase | Groß-/klein-Ausgabe von X und E |
boolalpha noboolalpha | Wahrheitswerte als Wort / Zahl |
endl ends | gibt '\n ' bzw. '\0 ' aus und leert den Puffer |
flush | leert den Ausgabepuffer |
skipws noskipws | Leerzeichen vor Eingabe verschlucken |
ws | verschluckt whitespaces der Eingabe |
Manipulatoren mit Parametern sind in <iomanip> definiert:
setbase(base) | Wechsel der Ganzzahlbasis (8, 10, 16) |
setprecision(n) | Genauigkeit von Fließkommazahlen |
setw(width) | Ausgabebreite |
setfill© | Füllzeichen festlegen |
setiosflags(f) | Setzen und |
resetiosflags(f) | Rücksetzen von Flags |
quoted(s) | Ein-/Ausgabe von std::string s mit Leerzeichen in "Gänsefüßchen" (C++14) |