Wissen zu Ein- und Ausgabestreams
Im heutigen Blogbeitrag geht es um notwendiges Wissen zu Ein- und Ausgabestreams. Insbesondere beschäftigt er sich mit formatierter und unformatierter Ein- und Ausgabe.
- Rainer Grimm
Im heutigen Blogbeitrag geht es um notwendiges Wissen zu Ein- und Ausgabestreams. Insbesondere beschäftigt er sich mit formatierter und unformatierter Ein- und Ausgabe.
Funktionen zur Ein- und Ausgabe
C++ besitzt vier vordefinierte Streamobjekte, die mit der Tastatur oder dem Monitor verbunden sind.
Mit diesen lässt sich ein interaktives Programm schreiben, das Zahlen von der Kommandozeile einliest und die Summe ausgibt.
// Iostreams.cpp
#include <iostream>
int main(){
std::cout << std::endl;
std::cout << "Type in your numbers(Quit with an arbitrary character): " << std::endl;
int sum{0};
int val;
while ( std::cin >> val ) sum += val;
std::cout << "Sum: " << sum << std::endl;
std::cout << std::endl;
}
Das Programm zeigt die Streamoperatoren << und >> sowie den Streammanipulator std::endl in der Anwendung.
- Der Insert-Operator << schiebt Zeichen auf den Ausgabestream std::cout.
- Der Extract-Operator >> zieht Zeichen vom Eingabestream std::cin.
- Beide Operatoren können verkettet werden, da sie Referenzen auf sich selbst zurückgeben.
std::endl ist ein Streammanipulator, da er ein (\n)-Zeichen auf den Ausgabestream std::cout schiebt und insbesondere den Ausgabepuffer leert.
Die Tabelle stellt die am häufigsten verwendeten Manipulatoren vor.
Eingabe
Eingabestreams lassen sich in C++ formatiert mit dem Extraktor (>>) und unformatiert mit expliziten Methoden lesen.
Formatierte Eingabe
Der Extraktion-Operator (>>)
- ist fĂĽr alle built-in-Datentypen und Strings vordefiniert,
- lässt sich für eigene Datentypen definieren,
- lässt sich durch Formatangaben konfigurieren
Das nächste Codeschnipsel zeigt, wie einfach sich zwei int's einlesen lassen.
#include <iostream>
...
int a, b;
std::cout << "Two natural numbers: " << std::endl;
std::cin >> a >> b; // < 2000 11>
std::cout << "a: " << a << " b: " << b;
std::cin ignoriert per Default fĂĽhrende Leerzeichen.
Unformatierte Eingabe
FĂĽr die unformatierte Eingabe auf einem Eingabestream gibt es mehrere Methoden.
std::string besitzt eine getline-Funktion
Die getline-Funktion fĂĽr den String besitzt einen groĂźen Vorteil gegenĂĽber der getline-Funktion des istream. Die Funktionen verwalten ihren Speicher automatisch. Dieses Argument trifft auch auf die get-Funktion des istream zu: is.get(buf, num) . Auch bei dieser Funktion muss der Speicher fĂĽr den Puffer buf explizit bereitgestellt werden.
// inputUnformatted.cpp
#include <fstream>
#include <iostream>
#include <string>
int main(){
std::cout << std::endl;
std::string line;
std::cout << "Write a line: " << std::endl;
std::getline(std::cin, line); // (1)
std::cout << line << std::endl;
std::cout << std::endl;
std::ifstream inputFile("test.txt");
while ( std::getline(inputFile, line, ';') ) { // (2)
std::cout << line << std::endl;
}
}
Das Programm liest in Zeile (1) von std::cin; im Gegensatz dazu, liest es in Zeile (2) von der Datei text.txt.
Der Einfachheit halber besitzt das Programm keine Fehlerbehandlung. Die Details zur Fehlerbehandlung lassen sich in meinem letzten Artikel C++ Core Guidelines: Iostreams nachlesen. Die Datei test.txt enthält Zahlen, die durch ein ";"-Zeichen getrennt sind.
Ausgabe
Wie ich bereits in meinem letzten Artikel C++ Core Guidelines: Iostreams versprochen habe, geht es jetzt um die Format-Spezifier, die du kennen solltest oder zumindest wissen musst, wo du sie finden solltest.
Wichtige Format-Spezifier
Oft höre ich selbst von erfahrenen C++-Entwicklern Klagen in meinen Schulungen, dass die Arithmetik in C++ nicht präzise genug ist. Der Grund ist aber fast immer nicht C++, sondern der Default-Format-Spezifier, der bei dem Iostream zum Einsatz kommt.
Zuerst einmal, kann das Format mittels Manipulatoren und Flags spezifiziert werden.
Manipulatoren und Flags
// formatSpecifier.cpp
#include <iostream>
int main(){
std::cout << std::endl;
int num{2011};
std::cout << "num: " << num << "\n\n";
std::cout.setf(std::ios::hex, std::ios::basefield); // (1)
std::cout << "hex: " << num << std::endl;
std::cout.setf(std::ios::dec, std::ios::basefield); // (1)
std::cout << "dec: " << num << std::endl;
std::cout << std::endl;
std::cout << std::hex << "hex: " << num << std::endl; // (2)
std::cout << std::dec << "dec: " << num << std::endl; // (2)
std::cout << std::endl;
}
In den Zeilen (1) kommen Flags und in den Zeilen (2) Manipulatoren zum Einsatz.
Aus dem Blickwinkel der Lesbarkeit und Wartbarkeit des Codes, ziehe ich Manipulatoren deutlich vor.
Manipulatoren fĂĽr die Iostreams
Jetzt geht es aber los mit den wichtigen Manipulatoren. Die folgenden Tabellen stellen die wichtigsten Formatangaben vor. Die Formatangaben eines Manipulators bleiben bestehen. Das gilt mit Ausnahme der Feldbreite (field with), die nach ihrer Anwendung wieder zurĂĽckgesetzt wird.
Die Manipulatoren ohne Argument benötigen die Headerdatei <iostream>, die mit Argumenten die Headerdatei <iomanip>.
- Boolsche Werte
- Feldbreite und FĂĽllzeichen
- Textausrichtung
- Positive Vorzeichen und GroĂźbuchstaben
- Numerische Basis
- FlieĂźkommazahlen
FĂĽr FlieĂźkommazahlen gibt es besondere Regeln:
- Die Anzahl der signifikanten Stellen (Nachkommastellen) ist per Default 6.
- Wenn die Anzahl der signifikanten Stellen nicht ausreicht, erfolgt die Ausgabe im wissenschaftlichen Format.
- FĂĽhrende oder folgende Nullen werden nicht ausgegeben.
- Der Dezimalpunkt wird, wenn möglich, nicht ausgegeben.
Nach so viel Theorie, sind jetzt die Format-Spezifier in Aktion zu bewundern.
// formatSpecifierOutput.cpp
#include <iomanip>
#include <iostream>
int main(){
std::cout << std::endl;
std::cout << "std::setw, std::setfill and std::left, right and internal: " << std::endl;
std::cout.fill('#');
std::cout << -12345 << std::endl;
std::cout << std::setw(10) << -12345 << std::endl;
std::cout << std::setw(10) << std::left << -12345 << std::endl;
std::cout << std::setw(10) << std::right << -12345 << std::endl;
std::cout << std::setw(10) << std::internal << -12345 << std::endl;
std::cout << std::endl;
std::cout << "std::showpos:" << std::endl;
std::cout << 2011 << std::endl;
std::cout << std::showpos << 2011 << std::endl;
std::cout << std::noshowpos << std::endl;
std::cout << "std::uppercase: " << std::endl;
std::cout << 12345678.9 << std::endl;
std::cout << std::uppercase << 12345678.9 << std::endl;
std::cout << std::nouppercase << std::endl;
std::cout << "std::showbase and std::oct, dec and hex: " << std::endl;
std::cout << 2011 << std::endl;
std::cout << std::oct << 2011 << std::endl;
std::cout << std::hex << 2011 << std::endl;
std::cout << std::endl;
std::cout << std::showbase;
std::cout << std::dec << 2011 << std::endl;
std::cout << std::oct << 2011 << std::endl;
std::cout << std::hex << 2011 << std::endl;
std::cout << std::dec << std::endl;
std::cout << "std::setprecision, std::fixed and std::scientific: " << std::endl;
std::cout << 123.456789 << std::endl;
std::cout << std::fixed << std::endl;
std::cout << std::setprecision(3) << 123.456789 << std::endl;
std::cout << std::setprecision(4) << 123.456789 << std::endl;
std::cout << std::setprecision(5) << 123.456789 << std::endl;
std::cout << std::setprecision(6) << 123.456789 << std::endl;
std::cout << std::setprecision(7) << 123.456789 << std::endl;
std::cout << std::setprecision(8) << 123.456789 << std::endl;
std::cout << std::setprecision(9) << 123.456789 << std::endl;
std::cout << std::endl;
std::cout << std::setprecision(6) << 123.456789 << std::endl;
std::cout << std::scientific << std::endl;
std::cout << std::setprecision(6) << 123.456789 << std::endl;
std::cout << std::setprecision(3) << 123.456789 << std::endl;
std::cout << std::setprecision(4) << 123.456789 << std::endl;
std::cout << std::setprecision(5) << 123.456789 << std::endl;
std::cout << std::setprecision(6) << 123.456789 << std::endl;
std::cout << std::setprecision(7) << 123.456789 << std::endl;
std::cout << std::setprecision(8) << 123.456789 << std::endl;
std::cout << std::setprecision(9) << 123.456789 << std::endl;
std::cout << std::endl;
}
Die Ausgabe des Programm sollte als Erklärung für das Programm formatSpecifierOutput.cpp ausreichen.
Wie geht's weiter?
Wenn du zu stark synchronisierst, verlierst du. Im Falle des Iostreams bedeutet dies, dass du Performanz verlierst. In meinem nächsten Artikel präsentiere ich konkrete Zahlen zu dieser Aussage.