IOStreams
- wurden schon lange vor der Standardisierung von C++ benützt
- ist ein auf Erweiterbarkeit ausgelegtes Framework
- erfuhr aber einige Veränderungen zu den alten Stream Klassen
- Internationalisierung wird unterstützt
- Charakterstreams ( deprecated ) wurden durch Stringsstreams ersetzt
- Exceptionbehandlung ist integriert
- IOStream Bibliothek wurden durch Templates implementiert
- sind im Namensraum std
Überblick
- ein Stream Objekt ist ein stream of data mit Eigenschaften, die durch Klassen definiert werden
- es existiert Eingabe streams
class istream
und Ausgabe streamsclass ostream
- im std Namensraum sind im wesentlichen vier Kanäle definiert
Stream C Äquivalent Device gepuffert std::cin stdin Tastatur ja std::cout stdout Monitor ja std::cerr stderr Monitor nein std::clog Monitor ja - die Operatoren
>>
und<<
- mittels
>>
werden die Zeichen auf den Stream geschoben (Inserter) - mittels
<<
werden die Zeichen aus dem Stream extrahiert (Extraktor)
- mittels
- Manipulatoren sind zum Manipulieren des Streams da
Manipulator Class Bedeutung std::endl ostream Ausgabe von '\n' und leeren des Ausgabepuffers std::ends ostream Ausgabe von '\0' std::flush ostream Leeren des Ausgabepuffers std::ws istream Lesen und Entfernen von whitespace
Elementare Stream Klassen und Objekte
ios_base
- definiert Eigenschaften der Streams, unabhängig vom Charakter Typ und Charakter Traits wie seinen "state" und seine "format flags"
- es werden chars und wchars ( zwei Chars für Multibyte Charakter Dateien ) als Charaktertypen zur Verfügung gestellt
basic_ios<>
- legt die Eigenschaften für alle Streams fest, die vom Charakter Typ und den Charakter Traits abhängen
- ferner enhält dieser noch einen Streampuffer basis_streambuf<>
- Formatierung von Daten
namespace std{
template <class charT,
class traits = char_traits<charT> >
class basic_ios;
}
namespace std{
typedef basic_ios<char> ios;
typedef basic_ios<wchar_t> wios;
}
basic_istream<>
- Stream für Lesen von Daten
- leitet sich virtuell von
basic_ios
ab
basic_ostream<>
- Stream für Lesen von Daten
- leitet sich virtuell von
basic_ios
ab
basic_iostream<>
- kann sowohl fürs Lesen und Schreiben verwendet werden
namespace std{
template <class charT,class traits = char_traits<charT> >
class basic_istream;
template <class charT,class traits = char_traits<charT> >
class basic_ostream;
template <class charT,class traits = char_traits<charT> >
class basic_iostream;
}
namespace std{
typedef basic_istream<char> istream;
typedef basic_istream<wchar_t> wistream;
typedef basic_ostream<char> ostream;
typedef basic_ostream<wchar_t> wostream;
typedef basic_iostream<char> iostream;
typedef basic_iostream<wchar_t> wiostream;
}
basic_streambuf<>
- das Interface fürs Lesen und Schreiben von Daten
- die Schnittstelle zur Erweiterung von IOStreams (Kommunikation mit Sockets,GUIs ...)
- Zusammenhang mit
basic_istream
explicit basic_istream(__streambuf_type* __sb){
this->init(__sb);
_M_gcount = streamsize(0);
}
- Beispiel zur tee Funktionalität:
#include <streambuf>
#include <fstream>
#include <iostream>
//teebuf is unbufferd; because of defaultconstruktor of std::streambuf
class teebuf: public std::streambuf {
public:
typedef std::char_traits<char> traits_type;
typedef traits_type::int_type int_type;
teebuf(std::streambuf* sb1, std::streambuf* sb2): m_sb1(sb1),m_sb2(sb2) {}
// overflow will be invoked in case of output
// sputc sends the char c to the underlying buffer
virtual int_type overflow(int_type c) {
if (m_sb1->sputc(c) == traits_type::eof() ||
m_sb2->sputc(c) == traits_type::eof()) return traits_type::eof();
return c;
}
private:
std::streambuf* m_sb1;
std::streambuf* m_sb2;
};
int main() {
std::ofstream logfile("logfile.txt");
teebuf myTeebuf(std::cout.rdbuf(), logfile.rdbuf());
std::ostream log(&myTeebuf);
log << "1" << std::endl;
log << "2" << std::endl;
log << "3" << std::endl;
}
Globale Stream Objekte
Neben den Globalen Stream Objekte für chars gibt es noch globale Stream Objekte für wide chars
Stream | C Äquivalent | Device | gepuffert |
---|---|---|---|
std::wcin | stdin | Tastatur | ja |
std::wcout | stdout | Monitor | ja |
std::wcerr | stderr | Monitor | nein |
std::wclog | Monitor | ja |
- die vorgestellten C++ Streams sind mit den entsprechenden C Streams synchronisiert
man kann einen Stream gleichzeitig mit C++ Streams und C Streams prozessieren (vgl. Bibliotheken)
Header
iosfwd
Vorausdeklaration der Stream Klassenstreambuf
: Streampufferistream
: alles zu basic_istream<> und basic_iostream<>ostream
: basic_ostream<>iostream
: Deklaration der globale Stream Objekte
es sollte meistens ausreichen, in der Header Dateiiosfwd
zu inkludieren und in der Implementierungsdatei die richtigen Header einzufügen
Operatoren << und >> für formatierten IO
Ausgabeoperator <<
- die Klasse
basic_ostream<>
definiert den Ausgabeoperator für alle fundamentalen Typen, so zum Beispiel auch für char*, void* und bool <<
kann natürlich auch für eigenen Typen überladen werden- im Gegensatz zur den C Ausgabeoperationen, bei denen man das Format mitgeben muß, werden die auszugebenden Objekte auf die richtige Funktion abgebildet
- da der Ausgabeoperator immer eine Referenz auf dem Ausgabestream zurückgibt, können Ausgabeoperatoren hintereinander verkettet werden
- die Ausgabereihenfolge der Ausgabeoperators ist von links nach rechts definiert, die seiner Argument aber undefiniert
- daher ist folgendes Programmfragment undefiniert, da kein Sequenzpunkt zwischen den Argumenten liegt:
int x=5;
std::cout << --x << ":" << ++x << std::endl;
- falls die Präzedenz des auszugebenden Objekts niedriger ist als die Präzedenz der Ausgabeoperators (entspricht der Präzedenz des Shift Operators), sollte der auszugebende Ausruck geklammert werden
- dies gilt insbesondere für alle logischen Ausdrücke:
int a,b;
std::cout << a == b << std::endl; // wird als ( (std::cout << a) ( == ) ( b << std::endl ) ) interpretiert
Inputoperator >>
- verhält sich entsprechend dem Ausgabeoperator
- per default wird beim Lesen von Daten whitespace übersprungen
Besondere Typen für die Ein- und Ausgabe:
bool:
- per default wird true und false zu 1 und 0 konvertiert
- Werte, die werder 0 noch 1 sind führen beim Lesen zum Setzten des ==ios::failbit=='s
- mittels des Setzen des
ios::boolalpha
Bits, wird der boolsche Wert als true ode false repäsentiert - bindet man darüberhinaus noch ein German local zu dem Stream, so bewirkt
bool b= 1;
std::cout.imbue( locale("de_DE"));
std::cout << ios::noboolalpha << b << " == " << ios::boolalpha << b << std::endl;
die Ausgabe von "1 == richtig"
char*
- beim Lesen eines C-String wird wird umgebender whitespace übersprungen
- um einen Pufferüberlauf zu verhindern, sollte explizit die Anzahl der einzulesenden Zeichen gesetzt werden
char buffer[81]; // 80 Zeichen und '\0'
std::cin >> std::setw(80) >> buffer;
void*
- mittels
void
kann die Adresse eines Objekts eingelesen und ausgegeben werden
char* cstring= "hello";
std::cout << "string \"" << cstring << "\" is located ag adress: " << static_cast <void*>( cstring) << std::endl;
erzeugt folgende Ausgabe: string "hello" is located at adress: 0x100000018
Stream Puffer
- es ist auch möglich direkt vom Streampuffer zu lesen oder zu schreiben dies ist die wohl schnellste Art mit C++ Mitteln Dateien zu kopieren
Zustand, State des Streams:
Konstanten
- der Stream besitzt immer einen von folgenden vier verschiedenen Zuständen:
Konstante Bedeutung goodbit alles ist in Ordnung; keines der anderen Bits ist gesetzt eofbit End-of-file wurde erreicht; Ende einer Eingabesequenz failbit Fehler; I/O Aktion war nicht erfolgreich badbit "Fatal Error"; undefinierter Zustand - goodbit besitzt per Defintion den Wert 0; somit sind alle anderen Bits auf 0 gesetzt
- Aktion mit dem Stream haben dann nur eine Auswirkung, wenn der Stream im goodbit Status ist
streambuf.h
#define _IOS_GOOD 0
#define _IOS_EOF 1
#define _IOS_FAIL 2
#define _IOS_BAD 4
......
enum io_state {
goodbit = _IOS_GOOD,
eofbit = _IOS_EOF,
failbit = _IOS_FAIL,
badbit = _IOS_BAD };
eofbit
Beim Einlesen einzelner Zeichen wird das eofbit gesetzt, falls ein Leseversucht hinterdem letzten gültigen Zeichen stattfindet.
failbit
- zeigt eine Fehler beim Lesen oder Schreiben von Daten an
- falscher formatierter Leseversuch eines Zeichens als int
- Leseversuch über die Datei hinaus
- öffnen einer Datei schlug fehl
- der Wert einer Variable ist undefiniert, wenn der Leseversuch aus dem Ausgabestream fehlschlug
- der Stream befindet sich aber noch in definierten Zustand und kann durch das Setzen des goodbits weiterbenützt werden
eofbif/failbit
- beim Setzten des eofbits beim zeichenweisen Einlesen wird auch das failbit gesetzt, da die IO-Aktion auch fehlschlug
- hingegen wird beim Lesen von Zeichensequenzen nur das eofbit gesetzt, da der String "zuEnde" ein gültige Eingabe darstellt
- gesetzte eofbits haben keine Auswirkung auf Ausgabestreams, jedoch auf Eingabe- und Bidrektionale Streams
badbit:
- der Zustand des Streams ist undefiniert und dieser kann daher nicht mehr weiterverwendet werden
- die Größe des Streampuffers kann aufgrund Speichermangels nicht angepaßt werden
- Code conversion des Streampuffers schlug fehl
- eine Komponente des Streams warf eine Exception (vgl: traits, locales)
Zugriff auf den Zustand des Streams:
Member Methode | Bedeutung |
---|---|
good() | gibt goodbit als boolschen Wert zurück |
eof() | gibt eofbit als boolschen Wert zurück |
fail() | gibt true zurück, falls ein Fehler auftrat (failbit oder badbit) gesetzt |
bad() | gibt badbit als boolschen Wert zurück |
rdstate() | gibt den Streamzustand als Variable vom Typ iostate zurück |
clear() | setzt alle flags auf 0 |
clear(state) | setzt alle flags auf 0 und sets danach das flag state |
setstate(state) | setzt das flag state |
- es genügt ein Aufruf von
stream.fail()
, um sich das Auftreten eines Fehler anzeigen zu lassen stream.clear()
entsprichtstream.setstate(std::ios_base::goodbit)
und setzt den Stream wieder in einen integren Zustand- mittels Bitoperation kann der Zustand eines Streams explizit modifizieren werden:
if ( stream.rdstate() & std::ios_base::failbit ){
stream.clear( stream.rdstate() & ~std::ios_base::failbit);
}
- die Flags können auch dazu benützt werden, explizit Exceptions für bestimmte Zustände freizuschalten
- im Gegensatz zu den C Streams ist es mit den C++ Streams nicht möglich bei Auftreten eines Streamfehlers (failbit,eofbit oder badbit) weiter mit dem Stream zu operieren; zuerst muß der Stream in einen guten Zustand gesetzt werden
Boolsche Ausdrücke
- zwei Funktionen existieren, um Streams in boolschen Ausdrücken zu benützen
Member Funktion Bedeutung operator void* () gibt true zurück, falls kein Fehler auftrat; entspricht fail() operator !() gibt true zurück, falls ein Fehler auftrat ; entspricht fail()
operator void*()
- Compilermagic, oder wie kommt man von Ausdrücken der Form (std::cin >>x) bzw. (std::cout << x ) nach bool
- (std::cin >> x) gibt eine Referenz auf (std::cin) zurück
- (std::cin ) wird mittels operator void*() auf eine void* Pointer gecastet, der nur im Zustand std::ios_base::goodbit = NULL ist
- der void* Pointer kann mittels Promotion zum Typ bool implizit konvertiert werden
operator !()
- in Ausdrücken der Form if ( !(std::cin >> x ) ) wird operator!() mit den entsprechenden cast und Konvertierungen verwendet
- aufgrund von Präzedenzen entspricht ( std::cin >> x ) dem Ausdruck ( (std::cin) >> x )
- (!!std::cin) = (std::cin) , da der rechte Ausdruck den Zustand des Streams zurückgibt, also einen boolschen Wert und die rechte Seite eine Stream darstellt
- um den Fehlerzustand abzufragen, wähle
fail()
,operator!()
,operator void*()
- um den Fehler von Eingabeoperation abzufragen, wähle
!good()
Exceptions
- es können zwei Typen von Exceptions auftreten:
ios_base::failure
, wenn der Stream nicht mehr im goodbit Status ist- andere Exceptions, die durch von IOStreams referenzierte Objekte geworfen werden
- Exceptions wurde in C++ eingeführt, nachdem sich schon die Streams etabliert hatten
- per default werfen die Streams daher keine Exception
- jedoch können die Streams explizit für Exceptions zu einem Streamzustand freigeschaltet werden
Member Funktion Bedeutung exceptions(flags) Setzt die Flags, die eine Exception auslösen können exceptions() gibt die Flags zurück, die eine Exception auslösen können - der Stream strm wirft durch
strm.exception(std::ios::failtbit|std::ios::badbit) eine Exception, falls der Zustand des Streams in ==std::ios::failbit
oderstd::ios::badbit
geht oder sich bereits in diesem Fehlerstatus befand - unter der Annahme, dass der ursprüngliche Stream nicht im Zustand
std::ios::goodbit
war, bewirkt das Setzen der alten Bits einen Programmabbruch
ios::iostate oldState= strm.rdstate(); // strm soll nicht im Zustand std::ios::goodbit sein
strm.exceptions( std::ios::goodbit ); // sorge für die Integrität des Streams
strm.exceptions( std::ios::failbit | std::ios::badbit ); // setze explizit die potentiell zu werfenden Exceptions
....
strm.setstate( oldState ); // => Exception wird geworfen
strm.exceptions(std::ios::goodbit)
bewirkt, dass keine Exceptions geworfen wird- setzte failbit und badbit für Ausgabestreams
- setzte eofbit, failbit und badbit für Eingabestreams
Standard Input/Output Funktionen für unformatierten IO
Im Gegensatz zu formatierter Ein/Ausgabe kann mit dem externen Device auch unformatiert kommunizieren werden
Eingabe
Zeichensequenzen einlesen
Member Funktion | liest bis | Anzahl der Zeichen | hängt Terminalsymbol an | Puffer | Rückgabe |
---|---|---|---|---|---|
get(buf,num) | ohne newline und eof | bis zu min(num-1,newline,eof ) Zeichen | ja | C-String | istream |
get(buf,num,delim) | ohne delim oder eof | bis zu min(num-1,delim,eof) Zeichen | ja | C-String | istream |
getline(buf,num) | einschließlich newline oder eof | bis zu min(num-1,newline,eof) Zeichen | ja | C-String | istream |
getline(buf,num,delim) | einschließlich delim oder eof | bis zu min(num-1,delim,eof) Zeichen | ja | C-String | istream |
getline(istream, string) | einschließlich '\n' | lies die ganze Zeile | nein | std::string | istream |
getline(istream,string,delim) | einschließlich '\n' oder delim | liest bis zu "min( '\n',delim)" | nein | std::string | istream |
read(buf,num) | eof | num Zeichen | nein | C-String | istream |
readsome(buf,num) | eof | bis zu min(num,eof) Zeichen | nein | C-String | Anzahl der eingelesen Zeichen |
- die get* Funktionen enhalten als Defaultbegrenzer '\n', ausser er wird explizit ein Begrenzer delim angegeben
- für die C-Style Varianten muß man dafür Sorge, dass genügen Speicher allokiert wurde:
const int max_size= 100;
char buf[max_size];
std::cin.getline(buf,num);
...
std::string buf2;
getline(std::cin,buf2);
Zeichen einlesen
- mittels
istream& istream::get(char& c)
können einzelne Zeichen vom Stream gelesen werden
Statusinformationen
- durch
streamsize istream::gcout() const
bekommt man die Anzahl der Zeichen, die beim letzten unformatierten Einlesen gelesen wurden inklusive des Begrenzers - mittels
int istream::peek()
ist es möglich, sich das nächste Zeichen anzeigen lassen, das man einlesen würde, ohne es zu extrahieren
Zeichen ignorieren
Funktion | Anzahl | Rückgabe |
---|---|---|
istream& istream::ignore() | überliest nächstes Zeichen | istream |
istream& istream::ignore(streamsize count) | überliest min(count,eof) Zeichen | istream |
istream& istream::ignore(streamsize count, int delim) | überliest min(count,delim) Zeichen | istream |
Überlies den Rest der Zeile
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
#include <limits>
#include <iostream>
int main(){
int age = 0;
while ((std::cout << "How old are you? ") && !(std::cin >> age)) {
std::cout << "That's not a number; ";
std::cin.clear(); // set in good state
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // flush the buffer
}
std::cout << "You are " << age << " years old\n";
}
Überlies den Rest von cin
cin.ignore( numeric_limits<std::streamsize>::max());
Zeichen auf den Stream zurückschieben
istream& isteam::unget()
schiebt das letzte Zeichen auf den Stream zurückistream& istream::putback(char c )
- schiebt das letzte Zeichen auf den Stream zurück, falls das letzte gelesen Zeichen c war
- im Fehlerfall wird das badbit gesetzt
- der Standard garantiert nur, daß maximal ein Zeichen zwischen zwei Leseoperationen zurückgeschoben werden darf; jegliche Erweiterung ist "implemtation defined"
Ausgabe
ostream& ostream::put(char c)
- schreibt das Zeichen c auf den Stream
- gibt den Stream zurück, dessen Zustand man dann prüfen kann
ostream& ostream::write(const char* str, streamsize count)
- schreibt count Zeichen auf den Stream
- gibt den Steam zurück, dessen Zustand man dann prüfen kann
- das Stringendzeichen wird mitgeschrieben
- falls str weniger als count Zeichen enthält, ist das Verhalten undefiniert
ostream& ostream::flush()
- schreibt die Daten auf das externe Device, indem es den internen Puffer leert
Formatierte Ein-und Ausgabe
- Mittels Methoden der Streams oder Objekten, die man auf die Streams schiebt, kann auf das Formatieren und Parsen der Daten Einfluß genommen werden.
- Obwohl ich im weitern gerne vom formatieren der Daten spreche, meine ich natürlich sowohl das Lesen der Daten als auch das Schreiben der Daten.
- Die Vorgehensweise, Objekte auf den Stream zu schieben, bezeichnet man als Manipulation des Streams.
- Beide Möglichkeiten erlauben es, die Formatparameter/Formatflags des Streams zu modifizieren.
- Alle vordefinierten Formatparmeter können mit beiden Mechanismen angesprochen werden.
Mittels Memberfunktionen
#include <string>
#include <fstream>
#include <iostream>
#define Format(A) T << #A << std::endl; A
int main() {
std::ofstream T("Format.out");
Format(int i = 47;)
Format(float f = 2300114.414159;)
Format(std::string s = "science-computing";)
Format(T.setf(std::ios::unitbuf);)
Format(T.setf(std::ios::showbase);)
Format(T.setf(std::ios::uppercase | std::ios::showpos);)
Format(T << i << std::endl;) // Default is dec
Format(T.setf(std::ios::hex, std::ios::basefield);)
Format(T << i << std::endl;)
Format(T.setf(std::ios::oct, std::ios::basefield);)
Format(T << i << std::endl;)
Format(T.unsetf(std::ios::showbase);)
Format(T.setf(std::ios::dec, std::ios::basefield);)
Format(T.setf(std::ios::left, std::ios::adjustfield);)
Format(T.fill('0');)
Format(T << "fill char: " << T.fill() << std::endl;)
Format(T.width(10);)
T << i << std::endl;
Format(T.setf(std::ios::right, std::ios::adjustfield);)
Format(T.width(10);)
T << i << std::endl;
Format(T.setf(std::ios::internal, std::ios::adjustfield);)
Format(T.width(10);)
T << i << std::endl;
Format(T << i << std::endl;) // Without width(10)
Format(T.unsetf(std::ios::showpos);)
Format(T.setf(std::ios::showpoint);)
Format(T << "prec = " << T.precision() << std::endl;)
Format(T.setf(std::ios::scientific, std::ios::floatfield);)
Format(T << std::endl << f << std::endl;)
Format(T.unsetf(std::ios::uppercase);)
Format(T << std::endl << f << std::endl;)
Format(T.setf(std::ios::fixed, std::ios::floatfield);)
Format(T << f << std::endl;)
Format(T.precision(20);)
Format(T << "prec = " << T.precision() << std::endl;)
Format(T << std::endl << f << std::endl;)
Format(T.setf(std::ios::scientific, std::ios::floatfield);)
Format(T << std::endl << f << std::endl;)
Format(T.setf(std::ios::fixed, std::ios::floatfield);)
Format(T << f << std::endl;)
Format(T.width(10);)
T << s << std::endl;
Format(T.width(40);)
T << s << std::endl;
Format(T.setf(std::ios::left, std::ios::adjustfield);)
Format(T.width(40);)
T << s << std::endl;
}
Mittels Manipulatoren
#include <string>
#include <fstream>
#include <iomanip>
#include <iostream>
#define Manipulator(A) trc << #A << std::endl; A
int main() {
std::ofstream trc("Manipulator.out");
Manipulator(int i = 47;)
Manipulator( float f = 2300114.414159;)
Manipulator( std::string s = "Is there any more?";)
Manipulator(trc << std::setiosflags(std::ios::unitbuf
| std::ios::showbase | std::ios::uppercase
| std::ios::showpos);)
Manipulator(trc << i << std::endl;)
Manipulator(trc << std::hex << i << std::endl;)
Manipulator(trc << std::oct << i << std::endl;)
Manipulator( trc << std::left ;)
Manipulator(trc << std::resetiosflags(std::ios::showbase)
<< std::dec << std::setfill('0');)
Manipulator(trc << "fill char: " << trc.fill() << std::endl;)
Manipulator(trc << std::setw(10) << i << std::endl;)
Manipulator(trc << std::right;)
Manipulator(trc << std::setw(10) << i << std::endl;)
Manipulator(trc << std::internal;)
Manipulator(trc << std::setw(10) << i << std::endl;)
Manipulator(trc << i << std::endl;) // Without std::setw(10)
Manipulator( trc << std::resetiosflags(std::ios::showpos)
<< (std::showpoint)
<< "prec = " << trc.precision() << std::endl;)
Manipulator(trc << std::scientific;)
Manipulator(trc << f << std::resetiosflags(std::ios::uppercase) << std::endl;)
Manipulator(trc << std::fixed;)
Manipulator(trc << f << std::endl;)
Manipulator(trc << std::setprecision(20);)
Manipulator(trc << "prec = " << trc.precision() << std::endl;)
Manipulator(trc << f << std::endl;)
Manipulator(trc << std::scientific;)
Manipulator(trc << f << std::endl;)
Manipulator(trc << std::fixed ;)
Manipulator(trc << f << std::endl;)
Manipulator(trc << std::setw(10) << s << std::endl;)
Manipulator(trc << std::setw(40) << s << std::endl;)
Manipulator(trc << std::left;)
Manipulator(trc << std::setw(40) << s << std::endl;)
}
Format Flags
- Die Formatflags sind in der Basisklasse
ios_base
definiert. - Da der
ios_base
namespace im ios namespace enthalten ist, können die flags statt über std::ios_base:: auch einfacher über std::ios angesprochen werden.- Beispiel: std::ios_base::boolalpha entspricht std::ios::boolalpha
- Um die Flags einfacher anzusprechen, sind sie in Gruppen eingeteilt.
Allgemeine Flagmanipulationen
- Folgende Memberfunktionen stehen zur Verfügung um die Flags zu setzen:
Memberfunktion | Bedeutung |
---|---|
setf(flags) | setzt die Flags hinzu und gibt die vorherigen Flagstatus zurück |
setf(flags,mask) | setzt die Flags zur Gruppe mask neu und gibt den vorherigen Flagstatus zurück |
unsetf(flags) | setzte die Flags zurück |
flags() | gibt den Flagstatus zurück |
flags(flags) | setzt den Flagstatus flags und gibt den alten Status zurück |
copyfmt(stream) | kopiert den Formatstatus von stream |
- Im Header
iomanip
sind die Manipulator definiert, um die Flags zu modfizieren:
Manipulator | Bedeutung |
---|---|
std::setiosflags(flags) | setzt flags indem die Methode setf(flags) aufgerufen wird |
std::resetioslfags(mask) | setzte alle flags der Gruppe mask zurück indem die Methode setf(0,mask) aufgerufen wird |
Boolsche Werte
- Die textuelle Repräsentation boolscher Werte kann zwischen 0/1 und false/true gewählt werden, wobei 0/1 die Defaulteinstellung darstellt.
flag | Manipulator | Memberfunktion | Darstellung | Default | Streamtyp |
---|---|---|---|---|---|
boolalpha | std::boolalpha | std::setf(std::ios::boolalpha) | false/true | nein | IO |
boolalpha | std::noboolalpha | std::unsetf(std::ios::boolalpha) | 0/1 | ja | IO |
Feldbreite, Füllcharakter und Ausrichtung
- Folgende Methoden erlauben einem die Modfikation der entsprechenden Flags:
Memberfunktion | Bedeutung | Streamtyp |
---|---|---|
width() | gibt die aktuelle Feldbreite zurück | IO |
width(val) | gibt die aktuelle Feldbreite zurück und setzt sie auf val | IO |
fill() | gibt den aktuellen Füllcharakter zurück | O |
fill(c) | setzt den Füllcharakter auf c | O |
- Ensprechendes ist natürlich auch über Manipulatoren möglich:
Manipulator | Bedeutung | Streamtyp |
---|---|---|
std::setw(val) | setzt die Feldbreite für In/Out to val | IO |
std::setfill(c) | setzt c als Füllcharakter | O |
- Für das Alignment stehen drei flags der Grupppe adjustfield zur Verfügung.
Maske | Flag | Bedeutung | Manipulator | Memberfunktion | Streamtyp |
---|---|---|---|---|---|
adjustfield | left | links Ausrichtung | std::left | std::ios::setf(std::ios::left , std::ios::adjustfield) | O |
right | rechts Ausrichtung | std::right | std::ios::setf(std::ios::right , std::ios::adjustfield) | O | |
internal | links Ausrichtung des Vorzeichens und rechts Ausrichtung des Wertes | std::internal | std::ios::setf( std::ios::internal , std::ios::adjustfield) | O |
- Per Default ist besteht das Füllzeichen aus dem Leerzeichen und die Feldbreite ist auf 0 gesetzt.
- Um das Zusammenspiel von Feldbreite und Ausrichtung zu Verdeutlichung, folgende Tabelle, wobei das Füllzeichen durch
.
repräsentiert wird:
Ausrichtung | width() | -42 | 0.12 | "Q" | 'Q' | "1234567" |
---|---|---|---|---|---|---|
left | 6 | -42... | 0.12 | Q..... | Q ..... | 1234567 |
right | 6 | ...-42 | ..0.12 | .....Q | .....Q | 1234567 |
internal | 6 | -...42 | ..0.12 | .....Q | .....Q | 1234567 |
- Anmerkungen zur Feldbreite:
- Die Länge des darzustellenden Wertes überstimmt die Feldbreite.
- Als einziger Flagstatus wird die Feldbreite nach jeder Ein- und Ausgabe zurückgesetzt.
- Die Feldbreiter kann dazu benützt werden, um die maximale Anzahl der einzulesenden Zeichen zu setzten.
char buffer[81];
// read, at most 80 characters
std::cin >> std::setw( sizeof(buffer) ) >> buffer;
Positives Vorzeichen und Großbuchstaben bei numerischen Werten
- Per default werden numerische Werte mit Kleinbuchstaben und ohne positives Vorzeichen ausgegeben.
- Dies Verhalten kann aber angepasst werden.
Flag | Manipulator | Memberfunktion | Bedeutung | Streamtyp |
---|---|---|---|---|
showpos | std::showpos | std::setf(std::ios::showpos) | stellt postives Vorzeichen dar | O |
showpos | std::nowhowpos | std::unsetf(std::ios::showpos) | stellt kein pos. Vorzeichen dar | O |
uppercase | std::uppercase | std::setf(std::ios::uppercase) | benützt Großbuchstaben für numerisch Werte | O |
uppercase | std::nouppercase | std::unsetf(std::ios::uppercase) | benützt Kleinbuchstaben für numerische Werte | O |
Numerische Basis
- Ganzzahlige Werte werden per default als Dezimalzahlen ausgeben.
- Beim Einlesen setzen die ersten Zeichen die Basis fest.
- 0 : oktal
- 0x: hexadezimal
- Rest: dezimal
Maske | flag | Manipulator | Memberfunktion | Bedeutung | Streamyp |
---|---|---|---|---|---|
basefield | oct | std::oct | std::setf(std::ios::oct , std::ios::basefield) | schreibt und liest oktal | IO |
dec | std::dec | std::setf(std::ios::dec , std::ios::basefield) | schreibt und liest dezimal | IO | |
dec | std::hex | std::setf(std::ios::hex , std::ios::basefield) | schreibt und liest hexadezimal | IO |
- Mittels des flags showbase kann man sich die Basis ausgeben lassen.
flag | Manipulator | Memberfunktion | Bedeutung | |
---|---|---|---|---|
showbase | std::showbase | std::setf(std::ios::showbase) | zeigt numerische Basis an | O |
std::noshowbase | std::unsetf(std::ios::showbase) | keine numerische Basis wird angegeben | O |
Fließpunkt Zahlen
- Default für Fließkommazahlen:
- die Anzahl der signifikanten Nachkommastellen ist 6
- führende Nullen von der Dezimalpunkt und/oder folgende Nullen werden nicht ausgegeben
- der Dezimalpunkt wird, falls möglich, nicht ausgeben
- falls
precision()
für die Anzahl der signifikanten Nachkommastellen ausreicht, wird die Zahl dezimal, sonst wissentschaftlich ausgegeben
- Darüber hinaus kann man die Ausgabe eines Dezimalpunktes und die Genauigkeit der darzustellenden Zahl festsetzen.
Maske | flag | Manipulator | Memberfunktion | Bedeutung | Streamtyp |
---|---|---|---|---|---|
std::setprecision(val) | precision(val) | setzt die Genauigkeit der Fließkommazahlen | O | ||
precision() | gibt die aktuelle Fließkommagenauigkeit zurück | O | |||
showpoint | std::showpoint() | std::ios::setf(std::ios::showpoint) | schreibt den Dezimalpunkt vor | O | |
showpoint | std::noshowpoint() | std::ios::unsetf(std::ios::showpoint) | schreibt kein Dezimalpunkt vor | O | |
floatfield | fixed | std::fixed | std::ios::setf(std::ios::fixed , std::ios::floatfield) | dezimale Ausgabe | O |
floatfield | scientific | std::scientific | std::ios::setf(std::ios::scientific , std::ios::floatfield) | wissentschaftliche Ausgabe | O |
- precision() bestimmt die Genauigkeit der signifikaten Stellen nach dem Dezimalpunkt, wobei der Wert gegebenfalls nicht abgeschnitten, sondern gerundet wird.
- Beispiele:
precision() 421.0 0.0123456778 Normal 2 4.2e+02 0.012 6 421 0.0123457 mit 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
Allgemeine Formatangaben:
flag | Memberfunktion | Manipulator | Bedeutung | Streamtyp |
---|---|---|---|---|
skipws | setf(std::ios::skipws) | std::skipws | überspringe führenden whitespace beim Lesen | I |
skipws | unsetf(std::ios::skipws) | std::noskipws | überspringe nicht führenden "whitespace" beim Lesen | I |
unitbuf | setf(std::ios::unitbuf) | std::unitbuf | leere den Ausgabepuffer nach jeder Schreiboperation | O |
unitbuf | unsetf(std::ios::unitbuf) | std::nounitbuf | leere den Ausgabepuffer nicht nach jeder Schreiboperator | O |
- per default ist beim Lesen
std::ios::skipws
gesetzt
Manipulatoren
Wie Manipulatoren funktionieren
- Vereinfachend ausgedrückt, besizt ein Ausgabeoperator folgende Form:
std::ostream& std::ostream:operator << ( std::ostream& (*op) ( std::ostream& ) ){
return (*op)(*this);
}
op
ist ein Pointer zu einer Funktion, die einestd::ostream
als Argument annimmt und wieder zurückgibt- eine Funktion, die diesem Interface genügt, kann daher als Manipulator verwendet werden
std::ostream& std::endl (std::ostream& strm){
strm.put('\n');
strm.flush();
return strm;
}
- der Aufruf
std::cout << std::endl;
wird durch den Shift Operator zustd::endl(std::cout)
transformiert - tatsächlich hängt die Implementierung von
std::endl
noch vom Charaktertyp, dem trait und dem Stream Charakter Satz ab
template<class charT, class traits>
std::basic_ostream<charT,traits>&
std::endl (std::basic_ostream<charT,traits>& strm){
strm.put(strm.widen('\n'));
strm.flush();
return strm;
}
strm.widen(c)
wandelt char c in denn Charaktertyp des streams umstd.narrow(c,def)
verhält sich zu widen konträr, indem es den Charakter c des Streams nach char konvertieren will; schlägt dies fehl, so wird def zurückgegeben- die Definition der Manipulatoren mit Argumenten( z.B.: setw(val) ) ist implentationsspezifisch
- die IO Streams können nur durch Manipulatoren ohne Argumente erweitert werden
Eigene Manipulatoren
Parameterlose Manipulatoren können unter Wahrung des Interfaces einfach hinzugefügt werden.
#include <istream>
#include <fstream>
#include <limits>
#include <iostream>
template <class charT, class traits>
inline
std::basic_istream<charT,traits>&
ignoreLine (std::basic_istream<charT,traits>& strm){
//skip until end-of-line
strm.ignore(std::numeric_limits<int>::max(),strm.widen('\n'));
return strm;
}
int main(){
std::ifstream fileStream("dummy.txt");
int num= 0;
while ( fileStream >> ignoreLine ) std::cout << "Zeile: " << ++num << std::endl;
}
- Eigene Manipulatoren dürfen nicht zum std Namensraum hinzugefügt werden.
Effektor
Eine von Schwarz vorgestellte Technik erlaubt es jedoch, Manipulatoren mit Argumenten zu simulieren.
//Effector.cpp// Jerry Schwarz's "effectors."
#include <iostream>
#include <sstream>
#include <string>
// Put out a prefix of a string:
class PrefixString {
std::string str;
public:
PrefixString(const std::string& s, int width) : str(s, 0, width) {}
friend std::ostream& operator<<(std::ostream& os, const PrefixString& fw) {
return os << fw.str;
}
};
int main(){
for ( int i = 1; i <= 10 ; ++i ) std::cout << PrefixString("0123456789",i) << std::endl;
}
Memberfunktionen versus Manipulatoren
Obwohl die Mächtigkeit der Memberfuntionen der Mächtigkeit der Manipulatoren entspricht, gibt es doch einige Unterschiede.
Charakteristikum | Memberfunktion | Manipulator | Beispiel Memberfunktion | Beispiel Manipulator |
---|---|---|---|---|
Kompaktheit | mässig | gut | std::setf(std::ios::hex, std::ios::basefield) | std::hex |
Verkettung | nein | ja | std::cout << std::hex << i << std::endl; | |
Rückgabewert | urprüngliche Wert | Referenz auf Stream | width(10) | std::setwidth( 10 ) |
Erweiterbarkeit | nein | ja |
Erweiterung
- Boost besitzt eine Formatbibliothek , die die Praktikabilität der Formatstrings mit der Typsicherheit der C++ Streams verbindet.
Filestreams
- Die IOStreams sind natürlich mehr als eine Framework, das man nutzen kann, um eigene Streams zu schreiben.
- So existieren Implementierung neben den globalen Streams für Filestreams und Stringstreams.
- Auf die dritte Instanziierung durch Charakterstreams werde ich nicht eingehen, da sie deprecated ist.
- Die Filestreams erweitern die IO Streams um ein paar "konkrete" Klassen.
- Entsprechend muß natürlich die Streampuffer Struktur erweitert werden.
- Im header
iosfwd
sind alle typedefs definiert um Schreibarbeit zu sparen.
namespace std{
template <class charT, class traits= char_traits<charT> > class basic_ifstream;
typedef basic_ifstream<char> ifstream;
typedef basic_ifstream<wchar_t> wifstream;
template <class charT, class traits= char_traits<charT> > class basic_ofstream;
typedef basic_ofstream<char> ofstream;
typedef basic_ofstream<wchar_t> wofstream;
template <class charT, class traits= char_traits<charT> > class basic_fstream;
typedef basic_fstream<char> fstream;
typedef basic_fstream<wchar_t> wfstream;
template <class charT, class traits= char_traits<charT> > class basic_filebuf;
typedef basic_filebuf<char> filebuf;
typedef basic_filebuf<wchar_t> wfilebuf;
}
- Filestreams folgen dem RAII Idiom.
- Die Datei wird automatische im Konstruktor des Filestreams geöffnet und im Destruktor geschlossen.
- Das bedeutet insbesondere, daß die Ressource (Datei) geschlossen wird, wenn der Filestreams out of scope geht.
- Daher muß der Filestream im Gegensatz zu den C-Streams nicht explizit geschlossen werden.
- Benötigt man die Datei global, dann sollte der Filestream auf dem Heap angelegt werden.
std::ofstream* filePtr= new std::ofstream("test.txt");
...
delete filePtr;
#include <string>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <cstdlib>
void writeCharsetToFile( const std::string& filename );
void outputFileCharByChar( const std::string& filename );
void outputFileByBuffer( const std::string& filename );
int main(){
std::string filename("charset.out");
writeCharsetToFile(filename);
outputFileCharByChar( filename);
outputFileByBuffer( filename );
}
void writeCharsetToFile( const std::string& filename ){
std::ofstream file( filename.c_str() );
if ( !file ) {
std::cerr << "Can' t open file " << std::endl;
exit( EXIT_FAILURE );
}
for ( int i= 32; i < 256; ++i ){
file << "value: " << std::setw(3) << i << " "
<< "char : " << static_cast< char >(i) << std::endl;
}
// close the file automatically
}
void outputFileCharByChar( const std::string& filename ){
std::ifstream file ( filename.c_str() );
if ( !file ) {
std::cerr << "Can' t open file " << std::endl;
exit( EXIT_FAILURE );
}
char c;
while ( file.get(c))std::cout.put(c) ;
// close the file automatically
}
void outputFileByBuffer( const std::string& filename ){
std::ifstream file ( filename.c_str() );
if ( !file ) {
std::cerr << "Can' t open file " << std::endl;
exit( EXIT_FAILURE );
}
std::cout << file.rdbuf();
// close the file automatically
}
Bei bidrektionalen Streams muß explizit vor dem Kontextwechsel der Puffer geleert werden.
Dateiflags
- Mittels Dateiflags kann man den Modus genauer spezifizieren, mit dem man die Datei öffnen will.
Flag Bedeutung std::ios::in Öffnen zum Lesen (default für std::ifstream) std::ios::out Öffnen zum Schreiben (default für std:ofstream) std::ios::app hängt die Charakter immer an die Datei an append std::ios::ate Anfangspositons am Ende at end_ der Datei std::ios::trunc Löscht den ursprünglichen Dateieinhalt std::ios::binary Ersetzt keine spezielle Chrakter
- Der Unterschied zwischen
std::ios::app
andstd::ios::ate
besteht darin, das beistd::ios::app
nur Daten an die Datei angehängt werden können, während beistd::ios::ate
nur die Anfangsposition auf dem Ende der Datei steht. - Ist
std::ios::binary
gesetzt, so findet keine plattformabhängige Ersetzung/Interpretation von end of line und end of file statt. - Beim Eingabe- und Ausgabefilestream sind per default
std::ios::in
bzw.std:ios::out
gesetzt. - Hingegen sind beim bidirektionalen Filestream keine flags per default gesetzt.
- Schlägt das Öffnen einer Datei fehlt, d.h. wird das failbit gesetzt, kann es auch daran liegen, dass versucht wurde, die Datei mit einer ungültigen Kombination von Flags zu öffnen. So ist zum Beispiel std::ios::trunc | std::ios::app nicht erlaubt.
- Da die Flags mittels des | Operators verknüpft werden können, lässt sich die C Funktionalität mittels fopen nachbilden.
- Ich verwende im folgenden bitor für |, da | in Tabelle immer interpretiert wird.
Flag Verknüpfung Bedeutung C Mode std::ios::in Lesen (Datei muß existieren) "r" std::ios::out Löscht Dateiinhalt und schreibt (Datei wird gegebenfalls erzeugt) "w" std::ios::out bitor std::ios::trunc Löscht Dateiinhalt und schreibt (Datei wird gegebenfalls erzeugt) "w" std:ios::out bitor std::ios::app Hängt an (Datei wird gegebenfalls erzeugt) "a" std::ios::in bitor std::ios::out Lies und schreibt (Datei muss existieren) "r+" std::ios::in bitor std::ios::out bitor std::ios::trunc Löscht, liest und schreibt (Datei wird gegebenfalls erzeugt) "w+" - Neben dem impliziten Öffnen einer Datei im Konstruktor, kann eine Datei auch explizit mit Memberfunktion geöffnet werden.
Memberfunktion Bedeutung open(name) Öffnet eine Datei für einen Stream, benützt den Defaultmodus open(name,flags) Öffnet eine Datei für eine Stream, benützt den Defaultmodus close() Schließt die Datei des Streams is_open() gibt zurück, ob eine Datei geöffnet wurde
#include <fstream>
#include <iostream>
int main(int argc, char* argv[]){
std::ifstream file;
for ( int i= 1; i <= argc; ++i){
file.open(argv[i]);
char c;
while ( file.get(c)) std::cout.put(c);
// the status flags must be cleared to reuse the stream
file.clear();
file.close();
}
}
- Durch das Öffnen der Datei werden die Statusflags nicht zurückgesetzt. Daher müssen explizit die Statusflags zurückgesetzt werden um den Stream wieder nutzen zu können. Im konkreten Fall oben wird das eof-Bit und das failbit auf 0 gesetzt.
Wahlfreier Zugriff
- Der C++ Stream (externe Device) erlaubt einen wahlfreien Zugriff.
Klasse Memberfunktion Bedeutung basic_istream < > tellg() gibt die Leseposition zurück seekg(pos) setzt die Leseposition auf einen absoluten Wert seekg(offset,rpos) setzt die Leseposition auf einen relativen Wert basic_ostream& <> tellp() gibt die Schreibposition zurück seekp(pos) setzt die Schreibposition auf einen absoluten Wert seekp(offset,rpos) setzt die Schreibposition auf einen relativen Wert - Diese Positionierung ist in den globalen Streams nicht definiert.
-
tellg()
undtellp()
geben keine integralen Typ, sondern ein Wert vom Typstd::ios::pos_type
zurück, denn die logische und die tatsäche Position können unterschiedlich sein; vgl. (end-of-line) - Alternativ zu
std::ios::pos_type
kann auchstd::streampos
verwendet werden - Um die relative Streamposition zu setzen, kann eine der drei folgenden Konstanten verwendet werden.
Konstante Bedeutung std::ios::beg Position ist relativ zum Beginn std::ios::cur Position ist relativ zur aktuellen Position std::ios::end Position ist relativ zum Ende - Da der Offset ein integraler Typ ist, sind folgende Ausdrücke zulässig.
file.seekg( 0, std::ios::beg);
file.seekg(20, std::ios::cur);
file.seekg(-10,std::ios::end);
- Der Zugriff ausserhalb des Streams ist undefiniert.
#include <iostream>
#include <fstream>
#include <string>
void printFileTwice( const std::string& filename ){
std::ifstream file( filename.c_str() );
std::cout << file.rdbuf();
file.seekg(0);
std::cout << file.rdbuf() ;
}
int main( int argc, char* argv[] ){
for ( int i= 1; i <= argc; ++i ) printFileTwice( argv[i] );
}
- Ein wesentlicher Unterschied, die Datei direkt über den Streampuffer im Gegensatz zu einer Memberfunktion ausgeben zu lassen, besteht darin, dass ein Streampuffer den Streamzustand nicht ändern kann.
Stringstreams
- Stringstreams sind nicht mit einem externen Device verbunden.
- Sie nennen sich Stringstreams, da ihr Puffer durch einen String repräsentiert wird.
- Im Header
sstream
sind die Stringstreams definiert, hingegen im Headeriosfwd
werden sie deklariert
namespace std {
template<typename _CharT, typename _Traits = char_traits<_CharT>,
typename _Alloc = allocator<_CharT> >
class basic_istringstream;
typedef basic_istringstream<char> istringstream;
typedef basic_istringstream<wchar_t> wistringstream;
template<typename _CharT, typename _Traits = char_traits<_CharT>,
typename _Alloc = allocator<_CharT> >
class basic_ostringstream;
typedef basic_ostringstream<char> ostringstream;
typedef basic_ostringstream<wchar_t> wstringstream;
template<typename _CharT, typename _Traits = char_traits<_CharT>,
typename _Alloc = allocator<_CharT> >
class basic_istringstream;
typedef basic_stringstream<char> stringstream;
typedef basic_stringstream<wchar_t> wstringstream;
template<typename _CharT, typename _Traits = char_traits<_CharT>,
typename _Alloc = allocator<_CharT> >
class basic_stringbuf;
typedef basic_stringbuf<char> stringbuf;
typedef basic_stringbuf<wchar_t> wstringbuf;
- Zur Kommunikation mit dem Streampuffer/String stehen zwei Methoden zur Verfügung.
Member Funktion Bedeutung str() Gibt den Puffer als String zurück str(string) Setze den Puffer auf string - Beim Kontextwechsel vom Schreiben zum Lesen(Lesen zum Schreiben) muß der Stringpuffer nicht geflusht werden, da hier ja keine Synchronisation mit dem externen Device erforderlich ist.
- Welche Ausgabe erzeugt folgendes Programm ?
#include <iostream>
#include <sstream>
#include <bitset>
int main(){
std::ostringstream os;
os <<"dec: " << 15 << std::hex << " hex: " << 15 << std::endl;
std::cout << os.str() << std::endl;
std::bitset<15> b(5789);
os << "float: " << 4.67 << " bitset" << b << std::endl;
os.seekp(0);
os << "oct: " << std::oct << 15;
std::cout << os.str() << std::endl;
}
- die Klassiker:
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
template < class T > T StringTo ( const std::string& source ){
std::istringstream iss(source);
T ret;
iss >> ret;
return ret;
}
template< class T > std::string ToString(const T& n){
std::ostringstream tmp ;
tmp << n;
return tmp.str();
}
int main(){
std::cout << "5 = " << std::string("5") << std::endl;
std::cout << "5 = " << StringTo< int > ( "5") << std::endl;
std::cout << "5 + 6 = " << StringTo< int > ( "5") + 6 << std::endl;
std::string erg( ToString( StringTo< int > ( "5") + 6 ) );
std::cout << "5 + 6 = " << erg << std::endl;
std::cout << "5e10: " << std::fixed << StringTo < double > ( "5e10") << std::endl;
}
- Aufgrund des Funktionsarguments T, kann bei ToString der Templatetyp implizit abgeleitet werden.
- Will man den Erfolg der Konvertierung prüfen, bietet sich der Rückgabewert, Streamstatus und Exceptions als Strategie an.
- Schlägt die Konvertierung fehl, wird ein leerer Strings zurückgegben.
- In der Funktion kann man explizit
if ( iss.fail() ) { std::cout << Konvertierung schlug fehlt << std::endl; }
anwenden. - Am Elegantesten sind hier wohl Exceptions:
iss.exceptions( std::ios::failbit | std::ios::badbit );
....
try{
StringTo< int > ("5");
}
catch( ... ){
...
}
Eingabe und Ausgabestreams verbinden
Synchronisations der Streampuffer mittels tie()
- Um sicherzustellen, dass
std::cout << "Geben Sie Ihr Alter ein: ";
std::cin >> numb;
die Ausgabe geschrieben wird, bevor die Eingabe gelesen erwarten wird, muß std::cout
zuerst geflush werden werden.
- Zwei Methoden stehen zur Verfügung um Streams zu synchronisieren.
Memberfuntion Bedeutung tie() Gibt eine Zeiger auf den Ausgabestream, der an den Stream gebunden ist. tie(std::ostream* stream) Bindet stream and den Stream und gibt den alten gebunden Ausgabestream oder 0 zurück - Ein Stream kann höchstens an einen Ausgabestream gebunden sein.
- Jedoch kann ein Ausgabestreams an mehrere Streams gebunden werden.
- Folgende Verbindungen sind vordefiniert:
std::cin.tie( &std::cout );
- std::wcin.tie( &std::wcout );==
- Will man aus Performancegründen die Bindung lösen, so übergibt man als Argument 0 oder NULL.
std::cin.tie( static_cast < std::ostream* > (0))
- Auch das Binden eines Ausgabestreams an einen anderen Ausgabestreams ist möglich.
- So wird durch==std::cerr.tie( &std::cout )== std::cout geflusht, bevor std::cerr geschrieben wird.
Streams über den Streampuffer verbinden
- Mittels der Funktion rdbuf() besizt man Kontrolle über den dem Stream assoziierten Puffer.
Memberfunktion Bedeutung rdbuf() Gibt den Zeiger auf den Streampuffer zurück rdbuf(streambuf*) Der Stream verwendet nun streambuf* als Streampuffer und gibt den alten Streampuffer zurück - Es ist nur sinnvoll, einem Streampuffer mehreren Streams zur Verfügung zu stellen. Denn für einen Streams sukzessive neue Puffer zu registrieren ist aufgrund der Pufferung des Streams deutlich komplexer als einen neuen Stream zu defininieren.
#include <iostream>
#include <fstream>
int main(){
std::ostream hexout( std::cout.rdbuf() );
hexout.setf( std::ios::hex, std::ios::basefield );
hexout.setf( std::ios::showbase );
hexout << "hexout: " << 177 << " " ;
std::cout << "cout: " << 177 << " " ;
hexout << "hexout: " << -49 << " " ;
std::cout << "cout: " << -49 << " " ;
std::cout << std::endl;
}
Streams umleiten
- Der Streampuffer kann dazu benützt werden Streams umzuleiten.
- Hierzu kann es nötig sein, mittels der Methode
copyfmt(stream*)
die Formatflags des neuen Streams zu setzen.
#include <iostream>
#include <fstream>
void redirect( std::ostream& strm ){
std::ofstream file("redirect.txt");
std::streambuf* strm_buffer= strm.rdbuf();
strm.rdbuf( file.rdbuf() );
file << "One row for the file : " << std::endl;
strm << "One row for the stream: " << std::endl;
strm.rdbuf( strm_buffer );
}
int main(){
std::cout << "The first row : " << std::endl;
redirect( std::cout );
std::cout << "The second row: " << std::endl;
}
Lebenszeit von Streampuffern
Faustregel: Die Lebenszeit des Streampuffer hängt von der Lebenszeit seines Erzeuger ( Streams ) ab.
- Geht der Stream out of scope, so auch sein Streampuffer.
- Dies sollte man als konsequente Umsetzung des RAII Idioms ansehen.
- Da der Stream nicht der Erzeuger eines über rdbuf() zugeordneten Puffers ist, destruiert er auch nicht diesen Puffer, falls er out of scopegeht.
- Daraus ergibt sich für RedirectStream:
- Das Programm besässe undefiniertes Vehalten, falls der alte Puffer in der letzten Zeile von redirect() nicht wieder restauriert werden würde. Einerseits gäbe es in diesem Szenario kein externes device, dem strm(std::cout) zugeordent wäre. Anderseits würde beim automatischen destruieren des globalen Streams nach main () das flushen des Puffers eine Zugriffsverletzung ergeben.
- Daraus ergibt sich für RedirectStream:
- Das folgende kleine Programmfragment bringt es nochmals auf den Punkt:
{
std::ofstream file ("cout.txt");
std::cout.rdbuf( file.rdbuf() );
}
file geht out of scope file.rdbuf() wird destruiert .....
- Teilen sich mehrere Streams einen Puffer, so muß der den Puffer erzeugende Stream alle anderen Streams überleben.
std::ofstream file1 ("file1.txt");
{
std::ofstream file2( "file2.txt");
file1.rdbuf( file2.rdbuf() );
}
file2 geht out of scope file2.rdbuf() wird destruiert .....
- Nur insofern man einen eigenen Puffer erzeugt und diesem einem Stream mittels
rdbuf()
zuweist, muß man dieses Puffer auch wieder destruieren. - Der Stream stream vom Typ
basic_iostream
hält sich einen Pointer auf seine erzeugten Puffer instream.basic_iostream.rdbuf()
. Dieser unterscheidet sich vom dem Pointer aufstream.rdbuf()
, denn wir mit unseren obigen BeispieleStreamBufferUnbuffered immer referenziert haben. Diese beiden Pointer zeigen beim konstruieren des Streams auf den gleichen Puffer. Durch Aufruf vonstream.rdbuf("Zeiger auf neuen Puffer")
wird nurstream.rdbuf()
modifiziert. Der Stream kann daher zwei Puffer referenzieren, wobei als Interface zum Stream klassischerweisestrm.rdbuf( ... )
angesehen wird.
Bidrektionale Streams:
- Ein Stream vom Typ
std::fstream
kann gleichzeitig zum Lesen oder Schreiben verwendet werden. - Sein Modus wird über die Flags gesetzt.
- Will man ein Stream zum Lesen und Schreiben verwenden, muß man den Streampuffer vor dem Kontextswitch leeren. Unterläßt man dies, erhält man undefiniertes Verhalten.
- Der Streampuffer kann explizit durch Flushen oder implizit durch Setzen der Streamposition geleert werden.
- Eine Ausnahme dieser Regel stellt der Kontextswitch von Lesen zum Schreiben dar, falls die ganze Datei gelesen wurde.
#include <iostream>
#include <fstream>
#include <string>
int main(){
std::fstream fstr("InOutIn.txt", std::ios_base::in | std::ios_base::out );
std::string helloWorld="hello world";
std::string helloWorld2= "; append hello World";
fstr << helloWorld ;
if ( !fstr.good() ) std::cout << "stream status1: " << fstr.rdstate() << std::endl;
// flush the buffer
fstr.seekg(0, std::ios_base::beg);
if ( !fstr.good() ) std::cout << "stream status2: " << fstr.rdstate() << std::endl;
std::string hello;
getline( fstr, hello );
std::cout << hello << std::endl;
if ( !fstr.good() ){
std::cout << "stream status3: " << fstr.rdstate() << std::endl;
fstr.clear();
}
fstr << helloWorld2;
if ( !fstr.good() ) std::cout << "stream status4: " << fstr.rdstate() << std::endl;
}
Ein- und Ausgabe von eigenen Datentypen
- Die Ein- und Ausgabe von eigenen Datentypen soll dem Verhalten der Standard Datentypen nachempfunden werden.
- Die Mächtigkeit von C++ besteht gerade darin, die Ausgabe- Eingabeoperatoren für eigene Datentypen zu überladen.
- Folgender Datentyp soll daher exemplarisch streamfähig gemacht werden.
class Fraction{
public:
Fraction( int num, int denom= 0): numerator_(num), denominator_(denom){};
private:
int numerator_;
int denominator_;
};
Ausgabeoperator:
- Dazu müssen wir den Shiftoperator
<<
implementieren, sodass folgender Ausdruck unterstützt wird:= stream << object <<=
- Entsprechend der C++ Syntax führt dies zu folgender Evaluierung:
- stream.operator << (object) für Standard Datentypen
- operator << (stream,object) für eigene Datentypen
- Da der std Namensraum für Erweiterungen abgeschlossen ist, gilt dies natürlich insbesondere für die Streams. Insofern steht nur die zweite Variante zur Verfügung.
- Als ersten Ansatz definieren wir einen globalen Operator
<<
für die Klasse Fraction. - Zusätzlich benötigt Fraction noch zwei Zugriffsfunktionen, wollen wir nicht die globale Funktione zum friend erklären.
class Fraction{
...
int getNumerator() const{ return numerator_ ;}
int getDenominator() const{ return denominator_ ;}
...
};
inline std::ostream& operator << ( std::ostream& strm, const Fraction& f ){
strm << f.getNumerator() << "/" << f.getDenominator();
return strm;
}
Die Implementierung besitzt drei Nachteile:
- Fraction Objekte können nur von
std::ostream
ausgegeben werden. - Falls die Feldbreite gesetzt wird, wirkt diese auf den ersten Shiftoperator, da
width()
nach jeder Ein- und Ausgabeoperation auf 0 gesetzt wird.
Fraction vat_( 16,100 );
std::cout << "VAT : " << std::left << std::setw(10) << vat_ << "END" << std::endl;
erzeugt:
VAT : 16 /100END
im Gegensatz zu:
VAT : 16/100 END
- Mittels "get" Methoden müssen die Variablen umständlich referenziert werden.
Die ersten beiden Punkte lassen sich durch den neuen Ausgabeoperator lösen, den letzten Punkt werde ich später behandeln:
template <class charT, class traits>
inline
std::basic_ostream<charT,traits>&
operator << ( std::basic_ostream<charT,traits>& strm,
const Fraction& f ){
std::basic_ostringstream<charT,traits> s;
s.copyfmt(strm);
s.width(0);
s << f.getNumerator() << "/" << f.getDenominator();
strm << s.str();
return strm;
}
- durch
s.copyfmt(strm) ; s.width(0) ;
werden die Formatflags von strm an den Stringstream s explizit mit Breite 0 übertragen - das Füllen des Stringstreams geschieht daher mit
width(0)
- lediglich die ursprünglich gesetzte Breite wirkt nun auf den (String-)Ausdruck
strm << s.str()
- die Ausgabe ist nun von der Form
16/100 END
Eingabeoperator
- Obwohl der Eingabeoperator nach den gleichen Prinzipien wie der Ausgabeoperator erfolgt, müssen hier Lesefehler berücksichtigt werden, will man das Verhalten der vordefinierten Eingabeoperatoren nachprogrammieren.
- Ein erster Ansatz könnte von folgender Form sein:
inline
std::istream& operator >> (std::istream& strm, Fraction& f){
int n,d;
strm >> n ;
strm.ignore(); // skip '/'
strm >> d;
f= Fraction(n,d);
return strm;
}
- Im zweiten und stabileren Ansatz werden nun folgende Punkte berücksichtigt:
- die Implementierung hängt nicht mehr vom Charaktertyp char ab
- es wird geprüft, ob das Zeichen zwischen den zwei Zahlen ein '/' ist
- ganze Zahlen n werden als 'n/1' interpretiert
- der Nenner 0 führt zu einem Setzen des
std::ios::failbit
Flags - ein gültiger Bruch wird nur dann zurückgegeben, wenn alle Leseoperationen erfolgreich waren
- Gerade die letzten zwei Punkte entsprechen dem Verhalten der Standardeingabeoperatoren, denn
- fehlerhafte Eingabe soll schon im Operator berücksichtigt werden
- Einleseoperation sind insofernt atomar, daß sie entweder erfolgreich sind oder keine Auswirkung haben
template < class chatT, class traits >
inline
std::basic_istream < charT , traits& >
operator >> ( std::basic_istream < charT , traits >& strm, Fraction& f ){
int n,d;
strm >> n;
if ( strm.peek() == '/' ){
strm.ignore();
strm >> d;
}
else d= 1;
if ( d == 0 ){
strm.setstate( std::ios::failbit );
return strm;
}
if ( strm ) f= Fraction(n,d);
return strm;
- Ein paar Feinheiten sind hier noch nicht berücksichtigt, so wird der Ausdruck
3/ 4
(Leerzeichen vor der 4) als3/4
interpretiert, hingegen führt3 /4
( Leerzeichen nach der 3 ) zu einem Lesefehler
Ableitungshierachien
- Eine gängige Implementierung, um globalen Funktionen Zugriff auf private Daten oder Methoden zu erlauben, ist es, sie als friend zu deklarieren.
- Für einen Ausgabeoperator könnte das folgendermassen aussehen:
#include <iostream>
#include <string>
class Base{
public:
Base(): name_("Base"){}
private:
friend std::ostream& operator << ( std::ostream& , const Base& );
std::string name_;
};
std::ostream operator << ( std::ostream& strm, const Base& b ){
strm << "Mein Name ist: " << b.name_ << std::endl;
return strm;
}
int main(){
Base* b1= new Base;
std::cout << *b1 << std::endl;
- Ausgabe:
Mein Name ist: Base
- Will man seine Klassenstruktur um Derived erweitern, liefert folgende Ansatz leider nicht das gewünschte Resultat.
#include <iostream>
#include <string>
class Base{
public:
Base(): name_("Base"){}
private:
friend std::ostream& operator << ( std::ostream& , const Base& );
std::string name_;
};
class Derived: public Base{
public:
Derived(): name_("Derived"){}
private:
std::string name_;
};
std::ostream& operator << ( std::ostream& strm, const Base& b ){
strm << "Mein Name ist: " << b.name_ << std::endl;
return strm;
}
int main(){
Base* b1= new Base;
Base* b2= new Derived;
std::cout << *b1 << std::endl;
std::cout << *b2 << std::endl;
- Ausgabe:
Mein Name ist: Base
Mein Name ist: Base - Statische Bindung (nicht virtuell) führt nicht zu gewünschten Ergebnis und dynamische Bindung (virtuell) ist nicht möglich.
- Da friend Funktionen nicht virtuell sein dürfen virtual functions cannot be friends, muß man einen anderen Ansatz wählen.
- Wäre "virtuelle Freundchaft" möglich, wäre dies ein Aufbruch der Kapslung der Klassenhierachie.
- Lösung:
- Trenne das Interface von der Implementierung:
- Das Interface (Ausgabeoperator) wird als globale Funktion zur Verfügung gestellt.
- Die Implementierung wird als virtuelle Methode in der Klassenhierachie definiert und eventuell überschrieben.
#include <iostream>
#include <string>
class Base{
public:
Base(): name_("Base"){}
virtual std::string getName() const { return name_; }
private:
std::string name_;
};
class Derived: public Base{
public:
Derived(): name_("Derived"){}
virtual std::string getName() const { return name_; }
private:
std::string name_;
};
std::ostream& operator << ( std::ostream& strm, const Base& b ){
strm << "Mein Name ist: " << b.getName() << std::endl;
return strm;
}
int main(){
Base* b1= new Base;
Base* b2= new Derived;
std::cout << *b1 << std::endl;
std::cout << *b2 << std::endl;
- Ausgabe:
Mein Name ist: Base
Mein Name ist: Derived - Eine Implementierung für die Klasse Fraction sollte daher folgende Struktur besitzen:
class Fraction{
...
public:
virtual void printOn( std::ostream& strm) const;
virtual void scanFrom( std::istream& strm);
...
};
std::ostream& operator << ( std::ostream& strm, const Fraction& f ){
f.printOn( strm) ;
return strm;
}
std::istream& operator >> ( std::istream& strm, Fraction& f ){
f.scanFrom( strm );
retunr strm;
}
void Fraction::scanFrom( std::istream& strm){
...
numerator= n;
denominator= d;
}
- Hier übernehmen
printOn
undscanFrom
die Implementierung der Ein- und Ausgabe von Fraction. - Da sie Member von Fraction sind, können Sie direkt die privaten Variablen der Klasse referenzieren.
- Dieses Strukturprinzip ist unter dem Namen NVI (Non Virtual Interface) Idiom bekannt und stellt eine Variante des Schablonenpatterns (Überblick/Motivation) dar. Das NVI geht auf Herb Sutter zurück. Die Prinzipien des NVI sind:
- Die Basisklasse legt das Interface für die Operationen auf der Klassenstruktur fest.
- Die Implementierung wird an virtuelle Methoden delegiert.
- Da auf die Objekte über das Interface der Basisklasse zugegriffen wird, können die virtuellen Funktionen protected oder private deklariert werden.
- protected, falls für die virtuellen Funktionen eine Defaultimplemtierung vorgesehen ist
- private, falls die virtuellen Funktionen in jeder abgeleiteten Klasse implementiert werden sollen
- pure virtuelle Funktionen sind hier nicht möglich, da die Basisklasse eine konkrete Klasse sein muß
- Durch die Trennung von Interface und Implementierung besitzt die Klasse ein sehr stabiles Interface, denn Änderungen/Überschreibungen können auf den virtuellen Methoden implementiert werden.
Templates:
- Ein häufiges Problem bei Templates und friend Funktionen verdeutlicht folgender Code:
#include <iostream>
template<typename T>
class Foo {
public:
Foo( const T& value );
friend std::ostream& operator<< (std::ostream& o, const Foo<T>& x);
private:
T value_;
};
template<typename T>
Foo<T>::Foo(const T& value)
: value_(value)
{ }
template<typename T>
std::ostream& operator<< (std::ostream& o, const Foo<T>& x)
{ return o << x.value_; }
int main(){
Foo<int> var(1);
std::cout << "value : " << var << std::endl;
- Mein gcc ( 3.3.3 ) quittiert dies mit folgender erklärenden Fehlermeldung:
Template.cpp:7: warning: friend declaration `std::ostream&
operator<<(std::ostream, const Foo<T>&)' declares a non-template function
Template.cpp:7: warning: (if this is not what you intended, make sure the
function template has already been declared and add <> after the function
name here) -Wno-non-template-friend disables this warning
/tmp/ccfDT2Ti.o(.text+0x4a): In function `main':
: undefined reference to `operator<<(std::basic_ostream<char, std::char_traits<char> >&, Foo<int> const&)' - Es wird also ein nicht Template Funktion deklariert und eine Template Funktion definiert.
- Die Lösung besteht darin, den Compiler beim Parsen der Templatedefinition davon zu überzeugen, daß der operator
<<
selbst ein Template ist. Hierzu bieten sich drei Möglichkeiten an: - die Template friend Funktion vorwärts zu deklarieren:
template<typename T> class Foo; // pre-declare the template class itself
template<typename T> std::ostream& operator<< (std::ostream& o, const Foo<T>& x);
// define the template class
template<typename T>
class Foo {
public:
Foo( const T& value );
....
- die Template friend Funktion explizt als Templatefunktion auszuzeichnen (Empfehlung des gcc)
class Foo {
public:
Foo( const T& value );
....
friend std::ostream& operator<< <> (std::ostream& o, const Foo<T>& x);
...
};
- die Funktion explizit im Templatekörper zu definieren
class Foo {
public:
Foo( const T& value );
....
friend std::ostream& operator<< (std::ostream& o, const Foo<T>& x){
}
...
};
Regeln zum Überladen der Ein- und Ausgabeoperatoren
- das Ausgabeformat sollt durch den Eingabeoperator ohne Informationsverlust lesbar sein
- die Formatspezifikationen sollen berücksichtigt werden (vgl. width())
- falls ein Fehler auftritt, sollte der entsprechende Zustand gesetzt werden
- im Fehlerfall (beim Einlesen) sollte das Objekt nicht modifiziert werden
- die Ausgabe sollte nicht mit newline abgeschlossen werden (vgl. mehrere Ausgaben in einer Zeile)
- falls ein Formatfehler vorliegt, sollte keine Zeichen eingelesen werden
Streampuffer
- die Memberfunktion
rdbuf()
des Streams liefert ein Zeiger auf den Streampuffer zurück - die Streampuffer sind nach dem Non Virtual Interface ( NVI )Prinzip konstruiert
- public, nichtvirtuelle Methoden legen das stabile Interface zu den Streampuffer fest
- private, virtuelle Methoden dienen als definierte Schnittstellen, um die Streampuffer durch Ableiten anzupassen
Als Anwender
Ausgabe
- auf den Streampuffer kann man entweder ein oder mehrere Zeichen schreiben
Memberfunktion Bedeutung Rückgabewert sputc(c) Schickt c an den Streampuffer traits_type:to_int_type(c)
odertraits_type::eof()
sputn(s,n) Schickt n Zeichen der Sequenz const char_type* s an den Streampuffer Anzahl der geschriebenen Zeichen traits_type::int_type
versustraits_type::char_type
traits_type::char_type
repräsentiert den Zeichentyp, mit dem der Streampuffer instanziert wurde -in der Regel chartraits_type::int_type
besteht austraits_type::char_type
undtraits_type::eof()
- muss
traits_type::eof()
als Zeichentyp berücksichtigt werden, werden die Zeichen als Elemente vom Typtraits_type::int_type
interpretiert, sonst alstraits_type::char_type
- beim Einlesen einer Datei muss
traits_type::eof()
berücksichtigt werdentraits_type::int_type
- der Streampuffer stellt alle Zeichen ohne
traits_type::eof()
zur Verfügungtraits_type::char_type
- beim Einlesen einer Datei muss
- um die kontextabhängige Sicht auf den Zeichentyp zu unterstützen, existieren die zwei Konvertierungsmethoden
to_int_type()
undto_char_type()
- char enspricht int und wchar_t entspricht wint_t
Eingabe
- das Interface, um vom Streampuffer zu lesen, ist deutlich komplexer, da verschieden Operationen unterstützt werden
- folgende Operationen beziehen sich immer auf den Streampuffer und nicht auf das dem Streampuffer zugeordnete externe Device
Memberfunktion Bedeutung in_avail() gibt die Menge der Zeichen zurück, die minimal verfügbar sind sgetc() gibt das aktuelle Zeichen zurück, ohne es zu konsumieren sbumpc() gibt das aktuelle Zeichen und konsumiert es snextc() Konsumiert das aktuelle Zeichen und gibt das nächste Zeichen zurück sgetn(b,n) liest n Zeichen und speichert sie im Zeichenpuffer b sputback(c) gibt das Zeichen c an den Streampuffer zurück sungetc() springt zum vorherigen Zeichen - falls kein Zeichen gelesen werden konnte, geben die drei Funktionen
sbumpc()
,snextc()
undsgetn(b,n)
traits_type::eof()
zurück
Locales und Pufferposition
Memberfunktion | Bedeutung |
---|---|
pubimbue(loc) | bindet das Local loc an den Streampuffer |
getloc() | gibt das aktuelle Local zurück |
pubseekpos(pos) | setzt die aktuelle Position fürs Lesen und Schreiben auf die absolute Postition pos |
pubseekpos(pos,modus) | analog zu pubseekpos(pos) abhängig vom Modus (Lesen/Schreiben) des Streampuffers |
pubseekoff(offset,rpos) | relatives Setzten bezüglich rpos des aktuellen Streamposition |
pubseekoff(offset,rpos,modus) | analog zu pubseeekoff(offset,rpos), nur abhängig vom Streammodus |
pubsetbuf(b,n) | schreibe n Zeichen aus b auf den Streampuffer |
- modus kann vom Wert
ios_base::out
oderios_base::in
sein - rpos kann vom Wert
ios_base::begin
,ios_base::end
oderios_base::cur
sein - die Methode
pubsetbuf(b,n)
ist nur definiert, wenn der Streampuffer noch nicht benutzt wurde pubsetbuf(0,0)
setzt für Filestreampuffer einen ungepufferten Puffer
Iteratoren
- mittels
std::ostreambuf_iterator(dest)
erhält man einen Ausgabestreampuffer Iterator, wobei dest vom Typ Ausgabestream oder Zeiger auf einen Ausgabepuffer sein muss - durch diesen Iterator läßt sich der Streampuffer in den STL Algorithmen verwenden
#include <algorithm>
#include <iostream>
#include <string>
int main(){
std::ostreambuf_iterator<char> bufWriter( std::cout );
std::string hello("hello, world\n");
std::replace_copy( hello.begin(), hello.end(), bufWriter, 'h','H' ) ;
}
- Ausgabe:
Hello, world
- ensprechend erzeugt
std::istreambuf_iterator(source)
einen Eingabestreampuffer, der das erste Zeichen liest - durch
std::istreambuf ()
erhält man einen end-of-stream Iterator; d.h.std::ifstreambuf ()-1
ist das letzte Element im zum iterierenden Inputpuffer - das klassische Filter Framework, das alle eingelesen Zeichen von
std::cin
nachstd::cout
schreibt, läßt sich mittels Streamfunktionalität oder direkt mit dem Streampufferiteratoren implementieren: - Stream Variante
#include <iostream>
int main(){
char c;
while( std::cin.get(c)) std::cout.put(c);
}
- Iterator Variante
#include <iostream>
#include <iterator>
int main(){
std::istreambuf_iterator<char> inpos( std::cin );
std::istreambuf_iterator<char> endpos;
std::ostreambuf_iterator<char> outpos( std::cout );
while( inpos != endpos ){
*outpos= *inpos;
++inpos;
++outpos;
}
}
- da die Iterator Variante direkt auf den Streampuffern arbeitet, ist sie natürlich performanter
Do it yourself
Der Einfachheit halber werde ich mich auf den Charaktertyp char beschränken.
- Streampuffer lassen sich am einfachsten durch ein Bild darstellen
Ausgabepuffer
- der Ausgabepuffer wird durch drei Zeiger prozessiert, wobei
pbase()
put base den Anfangpptr()
put pointer die aktuelle Positionepptr()
end put pointer eine Postition nach dem letzten Zeichen liefert
- beim Schreiben auf des Puffers mit der Funktionen
sputc()
bzw.sputn()
werden die Zeichen auf den Puffer geschoben, bispptr() == epptr()
- nun wird die virtuelle, nicht öffentliche Methode
overflow()
des Streampuffers aufgerufen, damit die Zeichen auf das externe Device geschrieben werden können - der Puffer ist nun wieder leer und es gilt
pbase() == pptr()
- will man den Puffer explizit mit dem externen Device synchronisieren (flushen), so wird die nicht öffentliche, virtuelle Methoden
sync()
aufgerufen - die zwei Methoden
underflow()
undsync()
sind aus Streampuffersicht die Methoden, die wir überladen müssen, um einen neuen Streampuffer zu implementiern - falls der Streampuffer degeniert ist, d.h. nur aus einem Zeichen besteht, bedeutet jeden Schreiben auf ihn gleichzeitig ein Aufruf der Methoden overflow
sync()
muß beim ungepufferten Puffer nicht überladen werden - das Non Virtual Interface ( NVI ) Prinzip gibt und daher unsere Schnittstellen vor
Memberfunktioin | Bedeutung |
---|---|
virtual int_type overflow(int_type c ) | leere den Streampuffer |
virtual int sync() | synchronisiere den Puffer mit dem externen Device |
- ausgehend von TeeStream will ich nun einen filterndes Ausgabestreampuffer Framework entwickeln
- Filter: zwischen den ursprünglichen Streampuffer und das externe Device lassen sich neue Streampuffer als Filter dazwischen schalten
- Ausgabestreampuffer: der Ansatz zielt nur auf den Ausgabestreampuffer
- Framework: die Architektur liefert eine fest definierte Schnittstelle, an die man seine Erweiterungen anbringen kann
Ohne Puffer:
- alle Zeichen, die an den Streampuffer geschickt werden, sollen im ersten Ansatz direkt an das externe Device weitergeleitet werden
- die drei Komponenten, aus denen sich das Framework zusammensetzt sind.
- der Outputstream, der den Rahmen für die erweiterte Funktionalität bietet
- der Streampuffer, der die Aufrufe an den Filter weiterleitet
- der Filter bzw. Insertor, der die erweitertete Funktionalität zur Verfügung stellt
- aufgrund der Übersichtlichkeit habe ich alles in den Headern implementiert
Outputstream
#ifndef FILTERING_OUTSTREAM_H
#define FILTERING_OUTSTREAM_H
#include "Insertor.h"
#include "FilteringOutputStreambuf.h"
class FilteringOutstream
: private FilteringOutputStreambuf , public std::ostream{
public:
FilteringOutstream( std::streambuf* des, Insertor* insert ): FilteringOutputStreambuf ( des , insert ), std::ostream(this){}
virtual ~FilteringOutstream(){};
};
#endif // FILTERING_OUTSTREAM_H
- FilteringOutputStream erbt die Implementierung von FilteringOutputStreambuf und das Interace von
std::ostream
- durch das Ableiten von FilteringOutputStreambuf wird erreicht, daß öffentlichen Methoden von ihm zur Ausgabe der Zeichen verwendet werden können
FilteringOutputStreambuf
Durch das Überschreiben der Methode overflow()
können wir das Verhalten Streampuffers an unsere Bedürfnisse anpassen.
#ifndef FILTERING_OUTPUT_STREAMBUF_H
#define FILTERING_OUTPUT_STREAMBUF_H
#include <iostream>
#include "Insertor.h"
class FilteringOutputStreambuf: public std::streambuf {
public:
typedef std::char_traits<char> traits_type;
typedef std::char_traits<char>::char_type char_type;
typedef traits_type::int_type int_type;
FilteringOutputStreambuf(std::streambuf* extBuf , Insertor* insert_ ): extBuffer_( extBuf ), insertor_( *insert_ ){};
public:
virtual int_type overflow( int_type c = traits_type::eof() ){
if( !traits_type::eq_int_type( c, traits_type::eof()) ){
if ( insertor_( *extBuffer_ , traits_type::to_char_type(c) ) < 0 ) return traits_type::eof();
else return c;
}
return traits_type::not_eof(c);
}
private:
std::streambuf* extBuffer_;
Insertor& insertor_;
};
#endif // FILTERING_OUTPUT_STREAMBUF_H
- die drei typedefs am Anfang der Klasse führen die entsprechenden Zeichentypen abhängig vom
std::char_trait
ein- nur zur Methode overflow
- ein Rückgabetype der Form
traits_type::eof()
wird als failbit vom aufrufenden Stream interpretiert der Stream ist im bad state und blockiert daher jede Ausgabe - das Defaultargument
traits_type::eof()
erlaubt den Aufruf der Methode ohne Argument, um das explizite flushen zu gewährleisten - mittels
!(traits_type::eq_int_type( c, traits_type::eof() )
wird geprüft, ob das zu verarbeitende Zeichen vom Typ eof ist; da die Rückgabe von traits_type::eof() ein "reguläres" Zeichen, der Wert traits_type::eof() aber als failbit vom aufrufenden Stream interpretiert wird, muss das Zeichen nachtraits_type::not_eof(c)
gecastet werden - die eigentliche Funktionalität des Streampuffers ist in dem Funktoraufruf Insertor_(*extBuffer_ , traits_type::to_char_type(c)) implementiert, der bei einem Fehler
traits_type::eof()
zurückgibt - da der Streampufferfilter nur als Wrapper um den nativen Streampuffer versteht, wird das Zeichen im ergänzenden else an den gewrappten Puffer weitergegeben
- ein Rückgabetype der Form
- nur zur Methode overflow
Filter
Da der insertor_einen Zustand benötigt, bietet es sich an, diesen als Functor zu implementieren:
#include <streambuf>
class Insertor{
public:
typedef std::char_traits<char>::char_type char_type;
typedef std::char_traits<char>::int_type int_type;
typedef std::char_traits<char> traits;
int_type operator() (std::streambuf& dst, char_type ch ){
return processChar( dst, ch );
}
private:
virtual int_type processChar( std::streambuf& dst, char_type ch ){
return dst.sputc( ch );
}
- die Klasse folgt dem NVI - Prinzip um das Interface stabil zu halten und Variation auf der virtuellen Methode processChar anzubieten
- durch die Methode
int_type operator() (std::streambuf& dst, char_type ch)
wird die Klasse Insertor zum Funktor - die Funktionalität der Charakterverarbeitung sollte in processChar implementiert werden, denn die Defaultimplementierung reicht das Argument einfach an den originären Streampuffer mittels dessen öffentlichen Methode sputc durch
- ein paar Beispiele sollten deren Einsatz aufzeigen
- der bisherige Ansatz besitzt aber noch deutliche Einschränkungen:
- keine Strings können bearbeitet werden puffern der Daten
- kein sauberes Interface zum Schreiben auf den originären Streampuffer Kapslung durch ein internen Puffer
- die Funktoren können nicht verkettet werden Einführung eines Joininsertor
- schlechte Perfomande durch das prozessieren auf einzelnen Zeichen
Mit Puffer
Der klassische Ansatz für den Puffer:
- definiere ein char Array:
static const int bufSize= 16;
char_type buffer[bufSize];
- initialisiere den Puffer mit deinem Array im Konstruktor:
setp( buffer , buffer + bufSize ); // init the pufferposition
- falls Zeichen auf den Puffer geschrieben werden, passe den Pufferzeiger an
// write newChars to the streambuffer; beginning with the position pptr()
memcpy( pptr(), newChars, numberOfNewChars * sizeof( char_type ) );
pbump( numberOfNewChars ); // adjust the next pointer of the buffer
- wird der Puffer geleert, passe den Pufferzeiger an
pbump( - numberOfFlushedChars ); // adjust the nest pointer accoding to the flushed characters
So könnten demnach eine Implementierung aussehen, wobei in der Methode overflow()
ein Fehler ist.
/* The following code example is taken from the book
* "The C++ Standard Library - A Tutorial and Reference"
* by Nicolai M. Josuttis, Addison-Wesley, 1999
*
* (C) Copyright Nicolai M. Josuttis 1999.
* Permission to copy, use, modify, sell and distribute this software
* is granted provided this copyright notice appears in all copies.
* This software is provided "as is" without express or implied
* warranty, and with no claim as to its suitability for any purpose.
*/
#include <cstdio>
#include <streambuf>
// for write():
#ifdef _MSC_VER
# include <io.h>
#else
# include <unistd.h>
#endif
class outbuf : public std::streambuf {
protected:
static const int bufferSize = 10; // size of data buffer
char buffer[bufferSize]; // data buffer
public:
/* constructor
* - initialize data buffer
* - one character less to let the bufferSizeth character
* cause a call of overflow()
*/
outbuf() {
setp (buffer, buffer+(bufferSize-1));
}
/* destructor
* - flush data buffer
*/
virtual ~outbuf() {
sync();
}
protected:
// flush the characters in the buffer
int flushBuffer () {
int num = pptr()-pbase();
if (write (1, buffer, num) != num) {
return EOF;
}
pbump (-num); // reset put pointer accordingly
return num;
}
/* buffer full
* - write c and all previous characters
*/
virtual int_type overflow (int_type c) {
if (c != EOF) {
// insert character into the buffer
*pptr() = c;
pbump(1);
}
// flush the buffer
if (flushBuffer() == EOF) {
// ERROR
return EOF;
}
return c;
}
/* synchronize data with file/destination
* - flush the data in the buffer
*/
virtual int sync () {
if (flushBuffer() == EOF) {
// ERROR
return -1;
}
return 0;
}
};
- in
overflow()
wird müsste man noch prüfen, ob er Puffer voll ist (pptr() == epptr()
), bevor man in flushed - dieses "klassische" Interface hat meines Erachtens ein paar Nachteile
- aufwändiges Interface
- explizites Berechnen und Setzten der Position des next Zeigers ist nötig
- Puffergröße ist eine statische Größe C Interface
- Puffer wächst nicht dynamisch C++ Interface
- ich werde als Puffer einen std::string verwenden und diesen zum Member von Insertor erklären, denn dort findet das ganze Filtern statt
- ein weiterer Vorteil des std::string besteht darin, daß dieser neben den STL Algorithmen ein eigenes Interface bietet um ihn zu bearbeiten
OutputStream
- das Framework besteht wieder aus den drei Komponenten Outputstream, Streampuffer und Insertor
- die Implementierung des OutputStream im gepufferten Streampuffer unterscheidet sich nur in zwei Punkten von der vorherigen Version
- der zu filternde Streampuffer wird direkt durch den Insertor initialisiert
- die Ressource Insertor wird - der Einfachheit halber - durch einen auto_ptr verwaltet
- alle anderen Ansätze bergen durch die Abhängigkeiten vom Insertor und seinem Stringpuffer deutliche Nachteile bzw. Probleme, denn der Insertor muß dynamisch allokiert werden:
- Puffer dynamisch allokieren und dem Insertor zuweisen aufwändig und Gefahr eines memory leaks
- Puffer statisch allokieren und dem Insertor zuweisen aufwändig
- Puffer als Member von Insertor sehr gefährlich, denn durch das Löschen des Insertor ist das anschließenden flushen des Puffer als Member des Insertor natürlich undefiniert
- alle anderen Ansätze bergen durch die Abhängigkeiten vom Insertor und seinem Stringpuffer deutliche Nachteile bzw. Probleme, denn der Insertor muß dynamisch allokiert werden:
#ifndef FILTERING_OUTSTREAM_H
#define FILTERING_OUTSTREAM_H
#include "Insertor.h"
#include "FilteringOutputStreambuf.h"
#include <memory>
#include <iosfwd>
class FilteringOutstream
: private FilteringOutputStreambuf , public std::ostream{
public:
FilteringOutstream( std::auto_ptr <Insertor > insert ): FilteringOutputStreambuf ( insert ), std::ostream(this){}
virtual ~FilteringOutstream(){};
};
#endif // FILTERING_OUTSTREAM_H
FilteringOutputStreambuf
Neben der virtuellen Mehthode overflow()
muss jetzt noch die virtuelle Methode sync()
implementiert werden, um die Synchronisation zwischen Streampuffer und externen Device zu gewährleisten.
#ifndef FILTERING_OUTPUT_STREAMBUF_H
#define FILTERING_OUTPUT_STREAMBUF_H
#include <iostream>
#include <memory>
#include "Insertor.h"
class FilteringOutputStreambuf: public std::streambuf {
public:
typedef std::char_traits<char> traits_type;
typedef std::char_traits<char>::char_type char_type;
typedef traits_type::int_type int_type;
FilteringOutputStreambuf( std::auto_ptr <Insertor > insert_ ): insertor_( insert_){};
virtual ~FilteringOutputStreambuf(){
sync();
}
protected:
virtual int_type overflow( int_type c = traits_type::eof() ){
if( !traits_type::eq_int_type( c, traits_type::eof()) ){
if ( (*insertor_)( traits_type::to_char_type(c) ) < 0 ) return traits_type::eof();
else return c;
}
return traits_type::not_eof(c);
}
virtual int_type sync(){
return ( insertor_->flushBuffer() == traits_type::eof() )? -1 : 0 ;
}
private:
std::auto_ptr < Insertor > insertor_;
};
#endif // FILTERING_OUTPUT_STREAMBUF_H
- der wesentliche Unterschiede zum oben definierten FilteringOutputStreambuf besteht darin, daß dieser die Methode
sync()
besitzt, um sich mit dem externen Device zu synchronisieren - diese Synchronisation kann explizit durch ein flushen des Streams erzwungen werden oder implizit beim zerstören des Streamspuffers eintreten
- falls das Synchronisieren erfolgreich war, sollte ein Wert von 0 andererseits ein Wert von -1 zurückgegeben werden
- da sowohl der Puffer der Streampuffer als auch der zu filternde Streampuffer zur Initialisierung der Insertor verwendet werden, genügt es dem insertor_ als einziges Argument das zu prozessierende Zeichen mitzugeben
Insertor
#ifndef INSERTOR_H
#define INSERTOR_H
#include <cctype>
#include <memory>
#include <string>
#include <streambuf>
#include <iostream>
#include <iterator>
class Insertor{
public:
typedef std::char_traits<char>::char_type char_type;
typedef std::char_traits<char>::int_type int_type;
typedef std::char_traits<char> traits;
Insertor(){}
virtual ~Insertor(){}
Insertor( std::streambuf* origBuffer ): dst( origBuffer ){
bufferOut_ = new std::string;
bufferIn_ = new std::string;
}
friend class JoinInsertor;
int_type operator() ( char_type ch ){
return appendToInBuffer( std::string(1,ch) ) ;
}
int_type flushBuffer(){
*bufferOut_ = getFilteredBuffer( *bufferIn_ );
int leng= bufferOut_->length();
dst->sputn( bufferOut_->data() , leng);
*bufferIn_= "";
*bufferOut_= "";
return ( leng != 0 )? leng : traits::eof();
}
protected:
int_type appendToInBuffer( std::string subStr ){
*bufferIn_ += subStr;
return subStr.length();
}
private:
virtual std::string getFilteredBuffer( std::string str ) { return str; }
static std::string* bufferIn_;
static std::string* bufferOut_;
std::streambuf* dst;
};
- bufferIn_ wird mittels der Zeichen des zu filternden Streams gefüllt
- bufferOut_ hingegen enhält die modifizierten Zeichen, die zum externen Device geschickt werden
- durch die Trennung der originären von den gefilterten Daten ist es möglich, ein komplexeren Zugriff auf die gepufferten Daten anzubieten; um dies Interface müsste aber der gefilterte Stream noch erweitert werden zurückspielen der gefilterten Daten, sofern sie noch nicht geflusht wurden
- flushBuffer:
- diese Methode wird durch das explizite std::endl,sync() oder das implizite delete,out of scope synchronisieren des Puffers mit seinem extern Device aufgerufen
- die virtuelle Methode
getFilteredBuffer( .. )
produziert den gefilterten Charakterstream aus dem nativen Charakterstream - somit ist
getFilteredBuffer(...)
die einzige Methode, die durch Überladung in dem Framework angepaßt werden muß - mittels
dst->sputn(bufferOut_->data() ,leng);
werden leng Daten ausbufferOut->data()
auf den zu filternden Streampuffer - das externe Device - geschoben
JoinInsertor
- dieser erlaubt es beliebig lange Insertorketten zu bilden
- seine virtuelle Methode
getFilteredBuffer(...)
bildetgetFilteredBuffer(...)
nur auf seine zwei gewrappten Insertoren ab:
secondInsertor_->getFilteredBuffer( firstInsertor_->getFilteredBuffer( str ) )
- JoinInsertor muß zum friend erklärt werden, da er die private Methode getFilteredBuffer(...) ansprechen muß
- mittels JoinInsertor( Ins1, Ins2 ) wird dieser initalisiert
- um das Leben leiter zu machen, bietet es sich an, jedem Insertor ein Konstruktor ohne Streampuffer zur Verfügung zu stellen, da diese beiden Puffer bei der Insertorverkettung überflüssig sind
- erst der finale Insertor, der an den Stream gebunden wird, benötigt natürlich die beiden Puffer um Daten auf einen Streampuffer zu schreiben
Insertor* in0 = new ReplaceStringInsertor( strMap0);
Insertor* in1 = new ReplaceStringInsertor( strMap1);
Insertor* final = new JoinInsertor( outStream.rdbuf() , in0, in1 );
FilteringOutstream joinOut( final );
Verbesserungen
Trennung des Codes in Header- und Sourcedatei.
- Framework unabhängig vom Charaktertyp char implementieren
- Iteratoren verbessern
- Filter, die selbst Funktoren annehmen (vgl. STL)
- JoinInsertor einfacher zu initialisieren
Eingabepuffer
- Die zwei Funktionen, um Zeichen vom internen Puffer zu erhalten, sind
sgetc()
undsbumpc()
. - Existiert der interne Puffer beim initialen Aufruf noch nicht oder ist er leer, so sorgen die virtuellen und nicht öffentlichen Methoden
underflow()
oderuflow()
dafür, daß die Puffer gefüllt werden.sgetc()
ruft die Methodeunderflow()
auf, da durch sie das Zeichen nur dargestellt, aber nicht konsumiert wirdsbumpc()
hingegen ruft implizit die Methodeuflow()
auf, da durch sie das Zeichen vom konsummiert wird.
- Der Streampufferfunktionalität sichert zu, daß man mindestens ein Zeichen zum internen Puffer zurückzuschreiben kann.
sputbackc(c)
schreibt das Zeichen c zurück; dies muß nicht das zuletzt gelesen Zeichen seinsungetc()
dekrementiert nur den Pufferzeiger.- Während
sputback(c)
beim nächsten Leseaufruf c zurückgibt, wird durchsungetc()
das letzte Zeichen nochmals dargestellt. - die virtuelle, nicht öffentliche Methode
pbackfail
sorgt dafür , das die Zeigerpositionen gemässsputback(c)
undsungetc()
richtig gesetzt wird. - Darüber hinaus stellt
pbackfail
eine internen Puffer bereit.
- Zusammenfassend nochmals die drei virtuellen, nichtöffentlichen Methoden, die das Framework Streampuffer zur Verfügung stellt um es zu konfigurieren.
Methode Bedeutung Returnwert Aufrufer uflow() stellt ein Zeichen vom internen Puffer oder externen Device zur Verfügung Zeichen sbumpc() underflow() stellt ein Zeichen vom internen Puffer oder externen Device zur Verfügung Zeichen sgetc() pbackfail() stellt das letzte Zeichen der internen Puffers zur Verfügung Zeichen sputback() oder sungetc() - bei ihrer Verwendung müssen ein paar Dinge beachtet werden
uflow()
wird durchunderflow()
implementiert- beim ungepufferten Einlesen müssen beide Methoden separat definiert werden um ihrer verschiedenen Semantik zu genügen
- beim gepufferten Einlesen ist es ausreichend
underflow()
zu implementieren, da die Pufferpositionierng hier schon vom internen Puffer gehandelt wird pbackfail()
muß nur beim umgepuffeten Einlesen implementiert werden, da mindestens ein Zeichen laut Standard gepuffert werden muß
klasssische Vorgehensweise
ungepufferten Streampuffer
- definierte die Methoden
underflow()
,uflow
undpbackfail()
- lege eine Zeichenpuffer der Länge 1 an
- fülle diesen Zeichenpuffer mit einem Zeichen des externen Devices, falls
underflow()
oderuflow()
aufgerufen wird und gib dieses Zeichen zurück - gib dies Zeichen bei jedem weiteren Aufruf von
underflow()
oderpbackfail()
zurück- falls
uflow()
prozessiert wird, lies ein neues Zeichen vom externen Device ein, setze den Zeichenpuffer neu damit und gib dies Zeichen zurück - charBuf soll nun der Zeichenpuffer sein und takeFromBuf eine bool Variable
- falls
int_type unbuffered_streambuf::underflow(){
if ( takeFromBuf ) return traits_type:to_int_type(charBuf);
else{
char_type c;
if (char_from_device(c) <0 ) return traits_type::eof();
else{
takeFromBuf= true;
charBuf= c;
return traits_type::to_int_type(c);
}
}
}
int_type unbuffered_streambuf::uflow(){
if ( takeFromBuf ){
takeFromBuf= false;
return traits_type:to_int_type(charBuf);
}
else{
char_type c;
if (char_from_device(c) <0 ) return traits_type::eof();
else{
charBuf= c;
return traits_type::to_int_type(c);
}
}
}
int_type unbuffered_streambuf::pbackfail(){
if ( !takeFromBuf ){
if ( !traits_type::eq_int_type(c,traits_type::eof() ) char_buf= traits_type::to_char_type(c);
takeFromBuf= true;
return traits_type::to_int_type( charBuf );
}
else return traits_type::eof();
}
gepufferten Streampuffer
- definiere
underflow()
- lege ein Zeichen Array an der Länge bufferSize und der put back Länge pbSize an
static const int bufferSize= 10;
static const int pbSize= 4;
char buffer[bufferSize];
- initialisiere den internen Puffer:
setg( buffer + pbSize, buffer + pbSize, buffer + pbSize );
- durch das erste Nutzen des Inputstreambuffers wird die Methode
underflow()
aufgerufen, was zum Lesen von num Zeichen vom externen Device führt - es werden numPutback Zeichen auf den put backBereich des internen Puffers geschrieben
- letztendlich müssen die Zeiger auf den internen Puffer angepasst werden
setg( buffer + ( pbSize - numPutback) ,
buffer + pbSize ,
buffer + pbSize + num );
- nun können bis zu numPutback Zeichen wieder zurück auf den internen Puffer geschoben werden
- andernfalls gibt man ein Zeichen an den internen Puffer zurück, das schon an den Stream weitergeleitet wurde
/* The following code example is taken from the book
* "The C++ Standard Library - A Tutorial and Reference"
* by Nicolai M. Josuttis, Addison-Wesley, 1999
*
* (C) Copyright Nicolai M. Josuttis 1999.
* Permission to copy, use, modify, sell and distribute this software
* is granted provided this copyright notice appears in all copies.
* This software is provided "as is" without express or implied
* warranty, and with no claim as to its suitability for any purpose.
*/
#include <cstdio>
#include <cstring>
#include <streambuf>
// for read():
#ifdef _MSC_VER
# include <io.h>
#else
# include <unistd.h>
#endif
class inbuf : public std::streambuf {
protected:
/* data buffer:
* - at most, four characters in putback area plus
* - at most, six characters in ordinary read buffer
*/
static const int bufferSize = 10; // size of the data buffer
char buffer[bufferSize]; // data buffer
public:
/* constructor
* - initialize empty data buffer
* - no putback area
* => force underflow()
*/
inbuf() {
setg (buffer+4, // beginning of putback area
buffer+4, // read position
buffer+4); // end position
}
protected:
// insert new characters into the buffer
virtual int_type underflow () {
// is read position before end of buffer?
if (gptr() < egptr()) {
return traits_type::to_int_type(*gptr());
}
/* process size of putback area
* - use number of characters read
* - but at most four
*/
int numPutback;
numPutback = gptr() - eback();
if (numPutback > 4) {
numPutback = 4;
}
/* copy up to four characters previously read into
* the putback buffer (area of first four characters)
*/
std::memmove (buffer+(4-numPutback), gptr()-numPutback,
numPutback);
// read new characters
int num;
num = read (0, buffer+4, bufferSize-4);
if (num <= 0) {
// ERROR or EOF
return EOF;
}
// reset buffer pointers
setg (buffer+(4-numPutback), // beginning of putback area
buffer+4, // read position
buffer+4+num); // end of buffer
// return next character
return traits_type::to_int_type(*gptr());
}
};
Weiterlesen...