Wissen zu Ein- und Ausgabestreams

Modernes C++  –  0 Kommentare

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.