Streams sind das universelle Mittel zur Ein- und Ausgabe von Daten in C++.
Standard C stellt zur Ein- und Ausgabe die bekannte Famile der printf und scanf Funktionen aus stdio.h zur Verfügung. Diese erlauben jedoch nur die Ein-/Ausgabe der eingebauten Datentypen, wie int, long, double und float. Für C++ mit der Möglichkeit, neue Datentypen zu definieren (und der Notwendigkeit, diese auch ausgeben zu wollen), reicht dies nicht mehr aus. Ein neues universelles und erweiterbares Ein-/Ausgabesystem wurde deshalb geschaffen: Streams (Stream (engl.): Bach, Strom).
Jedes C++ Programm verfügt von Haus aus bereits über die Streams std::cin für die Standardeingabe, std::cout für die Standardausgabe und std::cerr für die Standardfehlerausgabe. Sie werden durch
#include <iostream>
dem Programm bekannt gemacht und liegen als Komponente der Standard Template Library (STL) im Namensraum std. Die Verwendung dieser Streams ist natürlich nur dann sinnvoll, wenn das Programm in einer Textkonsole läuft. Streams lassen sich jedoch auch mit Dateien oder Zeichenketten verbinden, diese File Streams und String Streams werden wir in einer späteren Artikelfolge kennenlernen.
Die Ausgabe erfolgt durch Verwendung des Output Stream Operators <<. Auf der linken Seite dieses Operators steht ein Output Stream, auf der rechten Seite der auszugebende Ausdruck. Da der Operator wiederum einen Output Stream zurückliefert, lassen sich mehrere Ausgabeoperationen hintereinander schreiben:
#include <iostream> using namespace std; int i = 434564; double pi = 4.0 * atan(1.0); string l( 50, '-'); string name; cout << " i = " << i << "\npi = " << pi << "\n"; cerr << l << "\n" << "Error" << "\n" << l << "\n";
Analog wird die Dateneingabe mittels des Input Stream Operators >> durchgeführt:
cin.tie( &cout); string name; cout << "enter name: "; cin >> name; cout << "\nHello " << name << "!\n";
Die »tie«-Anweisung bewirkt eine Verkopplung der Streams cin und cout, damit sichergestellt wird, dass die Eingabeaufforderung erscheint, bevor das System für das Lesen der Eingabe blockiert.
Selbstverständlich soll für den Anwender nun kein Unterschied darin bestehen, ob er eingebaute oder selbstdefinierte Datentypen ein- und ausgeben will. Angenommen, es gäbe einen nutzerdefinierten Typ point, dann muss der folgende Programmcode möglich sein:
point p1( 12, 5); cout << "p1 = " << p1 << endl; point p2; cout << "enter point: "; cin >> p2; cout << "p2 = " << p2 << endl;
Damit dies funktionieren kann, muss der Entwickler neuer Datentypen die für deren Ein-/Ausgabe notwendige Funktionalität selbst programmieren. Dies erfolgt durch die Definition von Input und Output Stream Operatoren für diese Datentypen. Wichtig ist dabei, dass dies vollkommen im Anwendercode passieren kann, es ist keine Änderung der iostream-Library erforderlich!
Für den oben verwendeten Datentyp point könnte die Definition der Operatoren so aussehen:
class point { public: point() : x_(0), y_(0) {}; point( int x, int y) : x_(x), y_(y) {}; //... private: int x_, y_; friend ostream& operator<< (ostream &os, const point &p); }; ostream& operator<< (ostream &os, const point &p) { return os << '(' << p.x_ << ',' << p.y_ << ')'; } istream& operator>> (istream &is, point &p) { char c; int x, y; is >> c; if ('(' == c) { is >> x >> c; if (',' == c) { is >> y >> c; if (')' != c) is.clear(ios::badbit); // set error state } else is.clear(ios::badbit); // set error state } else is.clear(ios::badbit); // set error state if (is) p = point( x, y); return is; }
Insbesondere die Kodierung des Eingabeoperators ist nicht trivial, es müssen alle möglichen Fehlersituationen abgefangen werden. Tritt ein falsches Zeichen im Eingabestrom auf, so wird hier der Streamstatus auf »bad« gesetzt.
Im nächsten Artikel zum Thema »Streams« wird es um die Formatierung der Ein- und Ausgabe gehen.