Dieser und noch weitere Artikel wurde von CStoll erstellt.


Folgende Themen werden von diesem Artikel berührt:


Druckversion des Artikels


Vorbemerkungen

In C werden Daten über die Funktionsfamilie um printf() und scanf() ein- bzw. ausgegeben. Dieser Ansatz hat einige Probleme und Nachteile:
Mit den neuen Möglichkeiten von C++ gibt es deshalb einen anderen Ansatz, der diese Probleme weitgehend umschifft - Streams. Die Ein- und Ausgabe über C++-Streams ist typsicher, fehlertolerant und erweiterbar. Dank eines gemeinsamen Interfaces ist es möglich, unabhängig voneinander sowohl neue Datentypen zu verarbeiten als auch eigene Datenquellen einzubinden.

Inhalt
  1. Basisklassen
  2. Ein- und Ausgabeformatierung
  3. Allgemeine Steuerfunktionen
  4. File-Streams
  5. String-Streams
  6. Stream-Puffer
  7. Erweiterungen


1 Basisklassen

Alle Streamklassen der C++-Bibliothek stammen von einem kleinen Satz an Grundklassen ab, die alle elementaren Funktionen zur Verfügung stellen.

Anmerkung: Alle Klassen, die mit "basic_" beginnen, sind Templates. Ihre Parameter sind der verwendete Zeichentyp und seine Character-Traits. Letzteres ist eine Hilfsklasse, die Operationen zum Vergleichen, Kopieren und Suchen von Zeichenketten bereitstellt. Der Defaultwert für die Traits-Klasse verarbeitet Zeichenketten im C-Stil - indem die Methoden zurückgeführt werden auf die C-Funktionen memcmp(), memcpy(),... bzw. ihre wchar_t-Äquivalente.
Von jeder dieser Klassen existiert eine Spezialisierung für char (diese hat denselben Namen ohne das "basic_"-Präfix) und für wchar_t (deren Name hat das Präfix "w").

Die Character-Traits werden intensiver von den String-Klassen genutzt, deshalb werde ich bei der Behandlung von Strings auch näher auf sie eingehen. Die (für die Stream-Bibliothek) wichtigsten Elemente der Traits-Klassen sind die Typdefinitionen char_type (der verwendete Zeichentyp) und int_type (ein Hilfstyp, der alle Zeichen und einen zusätzlichen Wert als End-of-File Zeichen enthält) sowie die Methode eof(), die diesen Spezialwert zurückliefert. Alle Methoden der Stream-Bibliothek, die Einzelzeichen als Rückgabe liefern sollen, haben den Rückgabetyp traitT::int_type und geben im Fehlerfall den Wert traitT::eof() zurück.

1.1 Grundklassen

C++:
class ios_base;
template<typename charT,typename traitT=char_traits<charT> >
class basic_ios : public ios_base;
 
typedef basic_ios<char>    ios;
typedef basic_ios<wchar_t> wios;
Diese beiden Klassen stellen die grundlegenden Definitionen für alle Streams zur Verfügung. ios_base enthält alle Definitionen, die unabhängig vom verwendeten Zeichensatz sind (z.B. die Modi, mit denen eine Datei geöffnet werden kann, oder Statusflags für einen Stream), basic_ios und seine Spezialisierungen enthalten alles, was vom Zeichentyp abhängig ist (z.B. der verwendeten Stream-Puffer).

1.2 Ein- bzw. Ausgabestreams

C++:
template<typename charT,typename traitT=char_traits<charT> >
class basic_istream : public virtual basic_ios;
template<typename charT,typename traitT=char_traits<charT> >
class basic_ostream : public virtual basic_ios;
template<typename charT,typename traitT=char_traits<charT> >
class basic_iostream : public basic_istream<charT,traitT>, public basic_ostream<charT,traitT>;
Diese Klassen definieren die nötigen Operationen für die Eingabe (istream) bzw. die Ausgabe (ostream) von Daten. Die Klasse iostream fasst Ein- und Ausgabeoperationen zu einem Stream zusammen, der in beide Richtungen genutzt werden kann.

Eingabe-Operationen:

Ausgabe-Operationen:

Anmerkung: Die Stream-Operatoren >> und << geben als Ergebnis den verarbeiteten Stream zurück. Auf diese Weise lassen sich mehrere Ein- bzw. Ausgabeoperationen zu einer Operator-Kette koppeln.

1.3 Stream-Puffer

C++:
template<typename charT,typename traitT=char_traits<charT> >
class basic_streambuf;
Ein Stream-Puffer wird verwendet, um eine Verbindung zwischen dem Stream und seiner unterliegenden Datenrepräsentation herzustellen. Die Streamklassen formatieren ihre Eingaben zu einer charT-Folge und schreiben sie in den Puffer bzw. parsen die vom Puffer erhaltenen charT's, um ihre Ausgabedaten zu erzeugen. Der Puffer wiederum leitet die Anfragen z.B. an die Festplatte, den Monitor oder ein Datenbank-System weiter.

Ich werde den Einsatz von Stream-Puffern im Kapitel 6 ausführlicher beschreiben.

1.4 Standard-Stream-Objekte

Analog zu den IO-Objekten aus C - stdin, stdout und stderr - gibt es auch in der Stream-Bibliothek einige spezielle Objekte, die mit den Standard-IO-Kanälen verknüpft sind:
clog schreibt normalerweise auf denselben Kanal wie cerr, puffert seine Daten jedoch zwischen (cerr arbeitet ungepuffert - jede Ausgabe landet sofort auf dem Ausgabekanal).

Für den Umgang mit Multibyte-Zeichensätzen exisiteren außerdem die wchar_t-Versionen dieser Standard-Streams wcin, wcout, wcerr und wclog.

1.5 Stream-Header

Die Stream-Bibliothek wurde aus verschiedenen Gründen auf mehrere Header verteilt. Auf diese Weise müssen nur die Teile eingebunden werden, die tatsächlich benötigt werden:


2 Ein- und Ausgabeformatierung

Die Arbeit der Stream-Operatoren >> und << kann angepasst werden, indem man vorher den Status des Streams geeignet setzt. Neben den Formatierungsmethoden, die von den Stream-Klassen bereitgestellt werden, können fast alle Formate auch über Manipulatoren gesetzt werden - das sind spezielle "Objekte", die in die Operatorkette eingeschoben werden können, um den verwendeten Stream anzupassen:
C++:
1
2
3
4
5
6
7
8
//Anpassung über Methoden:
double val=3.14;
cout.precision(5);
cout<<val;
 
//Anpassung über Maipulator:
double val=3.14;
cout<<setprecision(5)<<val;
Manipulatoren, die einen Parameter benötigen - wie das oben gegebene setprecision() - sind im Header <iomanip> definiert. Parameterlose Manipulatoren werden zusammen mit den Streamklassen definiert, die sie benötigen.

2.1 allgemeine Flags

Die meisten Formatflags des Streams sind zu einem Flag-Feld zusammengefasst. Dieses kann per Bit-Operatoren komplett oder teilweise angepasst werden:
Zur Flag-Bearbeitung gibt es die Manipulatoren "setiosflags(f)" (ruft setf(f) auf) und "resetiosflags(m)" (verwendet setf(0,m)). Außerdem haben viele der Flag-Werte einen eigenen Manipulator, mit dem sie gesetzt bzw. zurückgesetzt werden können.

Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Flag        Maske        Wirkung                       setzen     rücksetzen
                                                          (Manipulator)
 
boolalpha   -            bool als "true" bzw. "false"  boolapha   noboolalpha
                         ein/ausgeben
showpos     -            '+'-Vorzeichen ausgeben       showpos    noshowpos
uppercase   -            'E' und Hex-Ziffern in        uppercase  nouppercase
                         Großbuchstaben
showpoint   -            Dezimalpunkt immer anzeigen   showpoint  noshowpoint
showbase    -            Zahlenpräfix (0 bzw. 0x)      showbase   noshowbase
                         anzeigen
skipws      -            Whitespaces überspringen      skipws     noskipws
unitbuf     -            Ausgabe ungepuffert           unitbuf    nounitbuf
 
left        adjustfield  linksbündige Ausgabe          left
right,0     adjustfield  rechtsbündige Ausgabe         right
internal    adjustfield  interne Ausgabe (Vorzeichen   internal
                         links, Zahl rechts)
 
oct         basefield    Oktale Ein-/Ausgabe           oct
dec         basefield    Dezimale Ein-/Ausgabe         dec
hex         basefield    Hexadezimale Ein-/Ausgabe     hex
0           basefield    Ausgabe dezimal, Eingabe je
                         nach Präfix (0 oder 0x)
 
fixed       floatfield   dezimale Notation             fixed
scientific  floatfield   wissenschaftliche Notation    scientific
0           floatfield   "beste" Variante ausgesucht


2.2 Feldbreite und Füllzeichen

Die Feldbreite des Streams bestimmt die Mindestgröße für die nächste Ausgabeoperation oder die Maximalgröße für die nächste Eingabe (besonders wichtig für die Eingabe in char-Arrays). Ihr aktueller Wert kann über die Methode strm.width() abgefragt und über die Methode strm.width(w) oder den Manipulator setw(w) gesetzt werden.

Das Füllzeichen wird verwendet, wenn eine Ausgabeoperation weniger Zeichen benötigen würde als die Feldbreite angibt. In dem Fall wird die Ausgabe je nach Einstellungen der adjustfield-Flags vorne oder hinten mit dem gegebenen Zeichen gefüllt. Das aktuelle Füllzeichen kann über die Methode strm.fill() abgefragt und über die Methode strm.fill(c) oder den Manipulator setfill(c) gesetzt werden.

Die Feldbreite gilt nur für den nächsten Aufruf von operator<< oder operator>> und wird anschließend auf 0 zurückgesetzt; das Füllzeichen gilt so lange, bis es neu gesetzt wird. Standardwert ist 0 für die Feldbreite und Space als Füllzeichen.

2.3 Gleitkomma-Präzission

Im Gegensatz zu den printf()-Formatstrings gilt die Präzissions-Angabe des Streams nur für Gleitkomma-Ausgaben. Sie bestimmt (je nach Belegung der floatfield-Flags), wie viele signifikante Ziffern bzw. Nachkommastellen der Zahl ausgegeben werden:
Code:
1
2
3
4
5
6
7
8
9
Einstellung  Präzision  421           0.0123456789
Normal       2          4.2e+02       0.012
             6          421           0.0123457
showpoint    2          4.2e+02       0.012
             6          421.000       0.0123457
fixed        2          421.00        0.01
             6          421.000000    0.012346
scientific   2          4.21e+02      1.23e-02
             6          4.210000e+02  1.234568e-02
Die Präzision kann über die Methode strm.precision() abgefragt und über die Methode strm.precision(p) oder den Maipulator setprecision(p) gesetzt werden. Sie bleibt so lange gültig, bis sie mit einem neuen Wert überschrieben wird.

2.4 weitere Manipulatoren

Es gibt noch einige weitere Manipulatoren, die auf die Streams angewendet werden können:
Für eigene (parameterlose) Manipulatoren kann jede Funktion verwendet werden, die einen entsprechenden Stream übernimmt und zurückgibt. Der entsprechende Ein-/Ausgabe-Operator ruft diese Funktion auf und gibt den von ihr zurückgegebenen Stream weiter.

Der folgende Manipulator kann z.B. verwendet werden, um eine Eingabezeile zu ignorieren:
C++:
1
2
3
4
5
6
7
8
9
template<typename charT,typename traitT>
inline basic_istream<charT,traitT>& ignoreLine(basic_istream<charT,traitT>& strm)
{
  strm.ignore(std::numeric_limits<int>::max(),strm.widen('\n'));
  return strm;
}
 
...
cin>>ignoreLine;//ignoriere eine Zeile der Eingabe
Parametrisierte Manipulatoren sind etwas komplizierter aufgebaut. Hierfür sieht der C++-Standard keine einheitliche Funktionsweise vor - Hauptsache es funktioniert. Eine Möglichkeit wäre es, eine Klasse zu defnieren, deren Konstruktor die notwendigen Parameter entgegennimmt und deren operator<< bzw. operator>> die nötigen Manipulationen vornimmt.

2.5 erweiterte Flags

Eigene Datentypen benötigen mitunter eigene Formatbeschreibungen. Um diese nutzen zu können, besitzt ein Stream ein Array von Hilfsflags, deren Bedeutung vom Programmierer festgelegt werden kann:

Anmerkung: Der gesamte Mechanismus um xalloc() und register_callback() ist nicht bis zum Ende gedacht worden. Dazu gehört, dass es nicht möglich ist, Callbacks zu "unregistrieren", und dass xalloc()-Indizes in Zusammenarbeit mit DLLs Fehler produzieren können. Als Alternativmöglichkeit bietet es sich an, die existierenden Formatflags für eigene Datentypen neu zu interpretieren oder die Darstellung über globale Variablen zu steuern.

Weitere Informationen zum Umgang mit den iword()-Flags und zum Aufbau von Manipulatoren überlasse ich einem Kollegen.

2.6 Spezialbehandlung bestimmter Typen

Einige der eingebauten Datentypen werden gesondert behandelt, wenn sie über die Stream-Operatoren gelesen bzw. geschrieben werden:

bool

Standardmäßig werden bool-Werte numerisch behandelt - 'false' wird als 0 gelesen und geschrieben, 'true' als 1. Alle anderen Eingabewerte gelten als Fehler.
Alternativ kann das Flag 'boolalpha' gesetzt werden, was bewirkt, dass bool-Werte als "true" bzw. "false" behandelt werden (bzw. die lokalisierte Version davon, z.B. "wahr" und "falsch" in einem deutschen Locale).

char* und std::string

Ein char-Pointer wird als Beginn eines Nullterminierten C-Strings interpretiert. Bei Eingaben wird so lange gelesen, bis der Stream auf ein Whitespace trifft. Der Anwender ist selber dafür verantwortlich, dass hinter dem Zeiger genug Platz bereitsteht - notfalls kann die Eingabelänge per width() beschränkt werden. Im Gegensatz dazu wächst ein String mit, um genug Platz für die Eingabe zur Verfügung zu stellen.

Wenn der einzugebende Text auch Leerzeichen enthalten darf, können die Memberfunktionen get() und getline() zur Eingabe in char-Arrays bzw. die globale Funktion getline() für Strings verwendet werden.

void*

Alle anderen Pointer-Typen werden nach void* interpretiert. Bei der Ein-/Ausgabe von Zeigern wird deren Adresse in einem Implementationsabhängigen Format geschrieben bzw. interpretiert.

Achtung: Eine Adresse ist normalerweise nur gültig, solange das dazugehörige Objekt nicht freigegeben wurde.

Stream-Puffer

Stream-Puffer können auch direkt auf einen Stream umgeleitet werden. In dem Fall werden alle verfügbaren Daten aus dem Puffer geschrieben bzw. alle vorhandenen Daten in den Puffer weitergeleitet. Das ist vermutlich die schnellste Methode, eine Datei zu kopieren:
C++:
fin >> noskipws >> fout.rdbuf();
//oder
fout << fin.rdbuf();


stream& f(stream&)

Funktionen, die auf der entsprechenden Streamklasse ausgeführt werden können, werden als Manipulator interpretiert. Der zugehörige Stream-Operator ruft die angegebene Funktion mit seinem linken Operand auf und gibt den Rückgabewert dieses Aufrufes zurück.

eigene Typen

Benutzerdefinierte Typen können die Stream-Operatoren überschreiben, um die eigene Ausgabe zu kontrollieren. Diese Arbeit ist im Grunde recht primitiv, allerdings sind einige Stolpersteine zu beachten.

Bei der Ausgabe könnte es Probleme mit der width()-Angabe des Streams geben, wenn die Daten aus mehreren Einzelteilen bestehen, die hintereinander geschrieben werden müssen. Da die Feldbreite nur für die nächste formatierte Ausgabe gilt, würde das erste Element des Ausgabewertes mit dieser Breite ausgegeben und alle übrigen Elemente anschließend lückenlos angehängt werden. Als Lösung kann ein Stringstream als Zwischenspeicher genutzt werden:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
template<typename charT,typename traitT>
basic_ostream<charT,traitT>& operator<<(basic_ostream<charT,traitT>& strm, const Data& val)
{
  //Stringstream - übernimmt Formate vom Ausgabestream
  basic_ostringstream<charT,traitT> s;
  s.copyfmt(strm);
  s.width(0);
 
  //alle Elemente von val nach s schreiben
 
  strm<<s.str();
  return strm;
}


Eingabe-Operatoren haben vor allem das Problem, wie die Eingaben kontrolliert werden müssen. Außerdem sollten sie Fehleingaben an den Stream weitermelden, indem sie notfalls das failbit setzen. Sie sollten ihr Zielargument erst mit Werten füllen, wenn alle Eingabewerte fehlerfrei eingelesen werden konnten.
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<typename charT,typename traitT>
basic_istream<charT,traitT>& operator>>(basic_istream<charT,traitT>& strm, Data& val)
{
  //alle wichtigen Elemente aus strm lesen und merken
  //Fehlerbehandlung
  if(/*ungültiger Wert*/)
  {
    strm.setstate(ios::failbit);
    return strm;
  }
 
  //Wertzuweisung (nur bei fehlerfreier Ausführung)
  if(strm)
    val = Data(...);
  return strm;
}


Beide Operatoren müssen als globale Funktionen geschrieben werden, da der linke Operand vom vorgegebenen Typ basic_ios<> (bzw. abgeleiteten Klassen) ist und deshalb nicht unter der Kontrolle des Programmierers liegt. Um eine komplette Klassenhierarchie typabhängig lesen/schreiben zu können, ist der Einsatz von virtuellen Hilfsfunktionen hilfreich:
C++:
1
2
3
4
5
6
7
8
9
10
class base
{
public:
  virtual void print(ostream& strm) const;
  virtual void scan(istream& strm);
}
ostream& operator<<(ostream& strm, const base& val)
{val.print(strm);return strm;}
istream& operator>>(istream& strm, base& val)
{val.scan(strm);return strm;}


3 Allgemeine Steuerfunktionen

3.1 Stream-Status

Der Status des Streams gibt an, in welchem Zustand er sich intern befindet. Er kann durch verschiedene Operationen auf dem Stream geändert werden. Der Status setzt sich aus vier möglichen Werten zusammen:
Diese Flags sind Werte des Typs ios_base::iostate und können über Bit-Operationen miteinander verknüpft werden (ob hinter iostate ein enum, ein Ganzzahltyp oder z.B. ein bitset<> steht, legt der Standard nicht fest).

Der Status eines Streams kann mit verschiedenen Memberfunktionen kontrolliert oder gesetzt werden:
Solange ein Stream ein Fehlerflag gesetzt hat, ignoriert er alle weiteren Zugriffsversuche. Deshalb muss der Status per clear() zurückgesetzt werden, bevor weitere Ein- oder Ausgabeoperationen aufgerufen werden können.

Exceptions

Normalerweise wird bei Fehlern nur der Stream-Status gesetzt, der nach der kritischen Operation abgefragt und zurückgesetzt werden muss. Alternativ dazu können Streams auch so konfiguriert werden, dass sie bei Fehlern eine Exception vom Typ ios_base::failure werfen.

Die Methode exceptions() gibt die Statusflags zurück, die eine Exception auslösen, die Methode exceptions(state) aktiviert Exception-Verarbeitung für alle in 'state' enthaltenen Statusbits. Dabei kann auch sofort eine Exception geworfen werden, wenn sich der Stream bereits in einem Fehlerstatus befindet.

3.2 Positionierung

In einigen Stream-Klassen ist es möglich, beliebig durch die Daten zu navigieren. Dafür bieten die Basisklassen geeignete Methoden zur Positionierung:
((die ...g-Funktionen beziehen sich auf die Lese-Position (get), die ...p-Funktionen auf die Schreibposition (put) des Streams - auch wenn nicht jeder Stream beide Positionen unabhängig voneinander einstellen kann)

3.3 Verbindung von Streams

Es gibt zwei Möglichkeiten, zwei bestehende Streams miteinander zu koppeln. Eine lose Kopplung erfolgt über die Methode tie() - diese übernimmt einen Ausgabestream und bewirkt, dass vor jeder Lese- oder Schreiboperation der übergebene Stream geflusht wird. Auf diese Weise kann die Arbeit von zwei Streams miteinander synchronisiert werden.

Eine enge Kopplung ist möglich, indem mehrere Streams denselben Puffer verwenden. Auf diese Weise ist es auch möglich, die Standard-Streams cin und cout auf einen internen Stream umzubiegen. Dazu besitzen alle Streams die Methode rdbuf(), die in Kapitel 6 angesprochen wird.

3.4 Kopplung mit C-Streams

Per Default arbeiten die Standard-Streams cin, cout etc. synchron mit den entsprechenden Standardkanälen der C-Bibliothek (stdin, stdout und stderr). In Programmen, in denen die Streams mit den C-Funktionen printf() und scanf() kombiniert werden, kann das durchaus erwünscht sein. Da diese Synchronisation jedoch Zusatzaufwand bedeutet, kann sie bei Bedarf abgeschaltet werden, indem am Programmanfang die statische Methode ios_base::sync_with_stdio(false) aufgerufen wird.

Achtung: Dieser Aufruf muss erfolgen, bevor das Programm andere Ein-/Ausgabe-Operationen verwendet.

3.5 Internationalisierung

Um die Ein- und Ausgaben an die nationalen Formate anzupassen, verwenden die Streams Locales, die wiederum aus einzelnen Facetten zusammengesetzt werden. Die Methode getloc() gibt das Locale zurück, das gerade von einem Stream verwendet wird, die Methode imbue(loc) setzt ein neues Locale.

Locales können auf verschiedenen Wegen aus vorgegebenen Locales zusammengesetzt werden:
Das globale Locale des Programms wird als Defaultwert verwendet, wenn irgendwo im Programm ein Locale benötigt wird. Außerdem steuert es auch die Arbeitsweise von C-Funktionen wie printf() oder toupper().

Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Kategorie  Facette       Verwendung
 
numeric    num_get<>     Zahleneingabe
           num_put<>     Zahlenausgabe
           numpunct<>    Symbole für Zahlen (Vorzeichen etc.)
time       time_get<>    Zeiteingabe
           time_put<>    Zeitausgabe
monetary   money_get<>   Währungseingabe
           money_put<>   Währungsausgabe
           moneypunct<>  Symbole für Währungen
ctype      ctype<>       Zeicheninformationen (isalpha, toupper etc.)
           codecvt<>     Zeichenumwandlung
collate    collate<>     Stringvergleiche
messages   messages<>    Fehlermeldungen
Zur leichteren Verarbeitung von Zeichen besitzen Streams die Methoden widen(c) und narrow(c,def), mit denen einzelne Zeichen zwischen char und dem verwendeten Zeichensatz umgewandelt werden könnnen. widen(c) konvertiert c in den Zeichensatz des Streams, narrow(c,def) konvertiert c nach char (def wird zurückgegeben, wenn es keinen passenden char-Wert gibt).

Anmerkung: Locales überladen auch den operator(), der zwei Strings mit den Methoden der collate<>-Facette vergleicht. Auf diese Weise kann ein Locale als Vergleichskriterium an Container oder Algorithmen übergeben werden.

4 File-Streams

C++:
1
2
3
4
5
6
7
8
9
template<typename charT,typename traitT=char_traits<charT> >
class basic_ifstream : public basic_istream<charT,traitT>;
template<typename charT,typename traitT=char_traits<charT> >
class basic_ofstream : public basic_ostream<charT,traitT>;
template<typename charT,typename traitT=char_traits<charT> >
class basic_fstream : public basic_iostream<charT,traitT>;
 
template<typename charT,typename traitT=char_traits<charT> >
class basic_filebuf : public basic_streambuf<charT,traitT>;
File-Streams dienen zur Arbeit mit Dateien. Sie werden beim Öffnen mit einer bestehenden oder neu angelegten Datei im unterliegenden Betriebssystem verknüpft und lesen/schreiben anschließend von bzw. auf die Festplatte.
Es ist durchaus möglich, dass die Filestreams sich intern auf FILE* zurückführen lassen, die für die C-Dateiarbeit verwendet wurden. Allerdings sollte man sich nicht darauf verlassen.

5 String-Streams

C++:
1
2
3
4
5
6
7
8
9
template<typename charT,typename traitT=char_traits<charT> >
class basic_istringstream : public basic_istream<charT,traitT>;
template<typename charT,typename traitT=char_traits<charT> >
class basic_ostringstream : public basic_ostream<charT,traitT>;
template<typename charT,typename traitT=char_traits<charT> >
class basic_stringstream : public basic_iostream<charT,traitT>;
 
template<typename charT,typename traitT=char_traits<charT> >
class basic_stringbuf : public basic_streambuf<charT,traitT>;
String-Streams verwenden einen String (std::string bzw. std::wstring) als internen Puffer und können deshalb genutzt werden, um Zahlen in eine Textform umzuwandeln, die anschließend intern weiterverwendet werden kann, oder um eingegebene Zeichenfolgen nach Zahlen zu parsen. Auch die Boost-Funktion lexical_cast<>() verwendet String-Streams, um Datentypen ineinander umzuwandeln.

Ein String-Stream bietet direkten Zugriff auf den verarbeiteten String über die Methode str() (Ausgabe des Puffers) und str(val) (Setzen des Puffers). Indem ein leerer String übergeben wird, kann der Puffer auch komplett gelöscht werden.

Achtung: Im Gegensatz zu den STL-Containern löscht die Methode clear() NICHT den Inhalt des Pufferstrings, sondern setzt nur eventuelle Fehlerflags zurück.

5.1 char*-Streams

C++:
class istrstream : public istream;
class ostrstream : public ostream;
class strstream : public iostream;
 
class strstreambuf : public streambuf;
Die char*-Streams verwenden ein char-Array als internen Puffer und können theoretisch genauso verwendet werden wie die Stringstreams. Da der direkte Umgang mit "nackten" Pointern extrem unsicher ist, sollte jeder vernünftige Programmierer einen großen Bogen um die char*-Streams machen - Stringstreams leisten dasselbe und sind wesentlich problemloser zu verwenden.

6 Stream-Puffer

Stream-Puffer bilden die Schnittstelle zwischen einem Stream und seinen externen Daten. Ein Stream wandelt die übergebenen Werte in eine Folge von chars um (dazu nutzt er die Methoden seines Locale), die er auf den Puffer schreibt. In der Gegenrichtung erhält er eine Zeichenfolge, die er passend parsen kann. Der Puffer überträgt diese Zeichenfolgen wiederum in eine externe Datendarstellung.

Von jedem Stream kann man über die Methode rdbuf() auf seinen zugeordneten Streambuffer zugreifen bzw. ihn mit rdbuf(buffer) an einen existierenden Puffer koppeln. Auf diese Weise können mehrere Streams ihre Ausgaben auf denselben Kanal leiten - da Formateinstellungen an den Stream gebunden sind, ermöglicht diese Methode es auch, schnell zwischen verschiedenen Formaten zu wechseln:
C++:
1
2
3
4
5
6
7
8
9
ostream hexout(cout.rdbuf());//zweiten Stream auf cout "aufschalten"
hexout.copyfmt(cout);
hexout<<hex<<showbase;//Format anpassen
 
for(int i=0;i<100;++i)
{
  cout<<i<<'\t';//dezimale Ausgabe
  hexout<<i<<endl;//hex-Ausgabe
}

Eine andere Anwendungsmöglichkeit der Stream-Buffer ist es, die Standardkanäle im Programm umzuleiten:
C++:
1
2
3
4
5
6
7
8
9
//Achtung: alten Puffer aufheben!!
streambuf* outbuf = cout.rdbuf();
ofstream file("test.txt");
cout.rdbuf(file.rdbuf());
 
//Ausgaben nach 'cout' landen nun in der Datei
 
//Puffer wiederherstellen
cout.rdbuf(outbuf);
Achtung: Lagern Sie unbedingt den alten Stream-Puffer zwischen - sobald 'file' aus dem Scope fällt, wird dessen Puffer gelöscht und cout wird unbrauchbar.

6.1 Schnittstelle zum Stream

Aus Sicht des Streams bietet der Puffer eine Sammlung von Methoden zur Ein- und Ausgabe von Zeichensequenzen:
Im Gegensatz zu einem Stream haben die Puffer keinen eigenen Fehlerstatus. Ihre Methoden geben allerdings jeweils den EOF-Wert ihrer Traits-Klasse zurück, wenn etwas schief gegangen ist.

6.2 Ausgabesteuerung

Für die Ausgabe verwendet der Puffer einen Zwischenspeicher, den er mit drei Hilfszeigern kontrolliert:
Jede Schreiboperation bewegt pptr() um einen Schritt nach vorne. Wenn er das Ende des Speichers erreicht, wird die virtuelle Methode overflow() aufgerufen, die den Zwischenspeicher in die externen Daten überträgt.
Der Default-Konstruktor von basic_streambuf initialisiert alle drei Lesezeiger mit NULL, was bewirkt, dass jede Schreiboperation direkt an overflow() weitergeleitet wird.
Zusätzlich kann auch die virtuelle Hilfsmethode xsputn() überschrieben werden, die von sputn() aufgerufen wird, um einen kompletten Datenblock zu schreiben. Die vorgegebene Version schreibt jedes Zeichen einzeln über sputc() (und overflow()) in den Puffer, aber eine optimierte Version könnte diesen Zwischenschritt umgehen.

6.3 Eingabesteuerung

Auch für die Eingabe wird ein Zwischenspeicher verwendet, der von drei Hilfszeigern verwaltet wird:
Allerdings ist die Steuerung der Eingabe etwas komplizierter aufgebaut, da mehr Sonderfälle beachtet werden müssen.

Die Leseoperationen sgetc(), snextc() und sbumpc() lesen jeweils ein Zeichen aus und schieben gptr() entsprechend weiter und rufen die virtuelle Methode underflow() auf, die den Lesepuffer neu auffüllen muss. Diese Methode kümmert sich jedoch nicht um das Weiterschieben der Lesezeiger - dafür benötigt man entweder einen echten Zwischenspeicher oder die Hilfsmethode uflow().
Die "Unlese"-Operationen sungetc() und sputbackc() schreiben je ein Zeichen zurück in den Puffer und schieben gptr() einen Schritt nach vorne. Wenn er den Anfang des Speichers erreicht, wird die virtuelle Methode pbackfail() aufgerufen, die möglicherweise ältere Eingaben rekonstruieren könnte (oder einfach nur einen Fehler zurückmeldet).

7 Erweiterungen

Wie bei allen Bestandteilen der STL ist es problemlos möglich, eigene Erweiterungen mit den IO-Streams zu kombinieren.

7.1 neue Datentypen

Um einen eigenen Datentyp einzubinden, müssen lediglich die Stream-Operatoren >> und << geeignet überladen werden. Beispiele dazu finden sich im Kapitel 2.6. Zusätzlich können auch spezielle Manipulatoren definiert werden, um die Verarbeitung der Daten im Detail zu kontrollieren.

7.2 neue Datenquellen

Für eine eigene Datenquelle benötigt man eine Ableitung von basic_streambuf<>, die die Methoden overflow() bzw. underflow() (siehe Kapitel 6.2 bzw. 6.3) geeignet überlädt, um die externe Datenrepräsentation anzusteuern. Anschließend kann man entweder die passend konstruierten Stream-Puffer an einen basic_istream<>, basic_ostream bzw. basic_iostream<> ankoppeln oder eigene Streamklassen definieren, die von diesen Klassen abgeleitet werden und die Pufferverwaltung selber übernehmen:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class mybuf : public std::streambuf
{
public:
  mybuf(const std::string& name);
  ...
};
 
class omystream : public std::ostream
{
protected:
  mybuf buf;
public:
  omystream(const std::string& name) : buf(name), std::ostream(&buf) {}
};

Sie können Kommentare zu diesem Artikel im Forum schreiben. (Eine Registrierung ist nicht notwendig.)

Logo-Design: MastaMind Webdesign