Inhaltsverzeichnis[Anzeigen]

 

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 (ALERT! deprecated ALERT!) 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 streams class ostream
  • im std Namensraum sind im wesentlichen vier Kanäle definiert
    StreamC ÄquivalentDevicegepuffert
    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)
  • Manipulatoren sind zum Manipulieren des Streams da
    ManipulatorClassBedeutung
    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

IOStreamsGlobal.gif

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<>
  • MOVED TO... 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<>

  • MOVED TO... 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

StreamC ÄquivalentDevicegepuffert
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
    MOVED TO... man kann einen Stream gleichzeitig mit C++ Streams und C Streams prozessieren (vgl. Bibliotheken)

Header

  • iosfwd Vorausdeklaration der Stream Klassen
  • streambuf: Streampuffer
  • istream: alles zu basic_istream<> und basic_iostream<>
  • ostream: basic_ostream<>
  • iostream: Deklaration der globale Stream Objekte
    MOVED TO... es sollte meistens ausreichen, in der Header Datei iosfwd 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
  • ALERT! die Ausgabereihenfolge der Ausgabeoperators ist von links nach rechts definiert, die seiner Argument aber undefiniert ALERT!
    • daher ist folgendes Programmfragment undefiniert, da kein Sequenzpunkt zwischen den Argumenten liegt:
int x=5;
std::cout << --x << ":" << ++x << std::endl;
  • ALERT!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 MOVED TO... 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:
    KonstanteBedeutung
    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
  • ALERT! 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
    • ALERT! 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 MethodeBedeutung
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() entspricht stream.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
  • ALERT! 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 FunktionBedeutung
    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
    1. (std::cin >> x) gibt eine Referenz auf (std::cin) zurück
    2. (std::cin ) wird mittels operator void*() auf eine void* Pointer gecastet, der nur im Zustand std::ios_base::goodbit = NULL ist
    3. 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
    1. aufgrund von Präzedenzen entspricht ( std::cin >> x ) dem Ausdruck ( (std::cin) >> x )
    2. ALERT! (!!std::cin) = (std::cin) ALERT!, da der rechte Ausdruck den Zustand des Streams zurückgibt, also einen boolschen Wert und die rechte Seite eine Stream darstellt
  • TIP um den Fehlerzustand abzufragen, wähle fail(), operator!(), operator void*()
  • TIP 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 oder std::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
    • TIP setzte failbit und badbit für Ausgabestreams
    • TIP 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 Funktionliest bisAnzahl der Zeichenhängt Terminalsymbol anPufferRü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

FunktionAnzahlRü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ück
  • istream& 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
  • ALERT! 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_basenamespace 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:
MemberfunktionBedeutung
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:
ManipulatorBedeutung
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.
flagManipulatorMemberfunktionDarstellungDefaultStreamtyp
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:
MemberfunktionBedeutungStreamtyp
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:
ManipulatorBedeutungStreamtyp
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.
MaskeFlagBedeutungManipulatorMemberfunktionStreamtyp
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:
Ausrichtungwidth()-420.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.
    • ALERT! Als einziger Flagstatus wird die Feldbreite nach jeder Ein- und Ausgabe zurückgesetzt.
    • TIP 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.
FlagManipulatorMemberfunktionBedeutungStreamtyp
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
MaskeflagManipulatorMemberfunktionBedeutungStreamyp
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.
flagManipulatorMemberfunktionBedeutung
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.
MaskeflagManipulatorMemberfunktionBedeutungStreamtyp
    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:

flagMemberfunktionManipulatorBedeutungStreamtyp
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 eine std::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 zu std::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 um
  • std.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
  • ALERT!die Definition der Manipulatoren mit Argumenten( z.B.: setw(val) ) ist implentationsspezifisch
    • MOVED TO... 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;

}
  • ALERT! 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.

CharakteristikumMemberfunktionManipulatorBeispiel MemberfunktionBeispiel 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.

image008.gif

  • Entsprechend muß natürlich die Streampuffer Struktur erweitert werden.

image009.gif

  • 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.

    FlagBedeutung
    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

 

  • ALERT! Der Unterschied zwischen std::ios::app and std::ios::ate besteht darin, das bei std::ios::app nur Daten an die Datei angehängt werden können, während bei std::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üpfungBedeutungC 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.

    MemberfunktionBedeutung
    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();

}

}

 

  • ALERT! 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.

    KlasseMemberfunktionBedeutung
    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
  • ALERT! Diese Positionierung ist in den globalen Streams nicht definiert.
  • ALERT! tellg() und tellp() geben keine integralen Typ, sondern ein Wert vom Typ std::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 auch std::streampos verwendet werden
  • Um die relative Streamposition zu setzen, kann eine der drei folgenden Konstanten verwendet werden.

    KonstanteBedeutung
    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);

 

  • ALERT! 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] );

}

 

  • ALERT! 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

image010.gif

  • 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 Header iosfwd 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 FunktionBedeutung
    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.
  • HELP 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.
    1. Schlägt die Konvertierung fehl, wird ein leerer Strings zurückgegben.
    2. In der Funktion kann man explizit if ( iss.fail() ) { std::cout << Konvertierung schlug fehlt << std::endl; } anwenden.
    3. 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::coutzuerst geflush werden werden.

  • Zwei Methoden stehen zur Verfügung um Streams zu synchronisieren.

    MemberfuntionBedeutung
    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.

    MemberfunktionBedeutung
    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

ALERT!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.
  • Das folgende kleine Programmfragment bringt es nochmals auf den Punkt:
{
std::ofstream file ("cout.txt");
std::cout.rdbuf( file.rdbuf() );

}

 

MOVED TO... file geht out of scope MOVED TO... file.rdbuf() wird destruiert MOVED TO........

  • 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() );
}

 

MOVED TO... file2 geht out of scope MOVED TO... file2.rdbuf() wird destruiert MOVED TO........

  • Nur insofern man einen eigenen Puffer erzeugt und diesem einem Stream mittels rdbuf() zuweist, muß man dieses Puffer auch wieder destruieren.
  • TIP Der Stream stream vom Typ basic_iostream hält sich einen Pointer auf seine erzeugten Puffer in stream.basic_iostream.rdbuf(). Dieser unterscheidet sich vom dem Pointer auf stream.rdbuf(), denn wir mit unseren obigen BeispieleStreamBufferUnbuffered immer referenziert haben. Diese beiden Pointer zeigen beim konstruieren des Streams auf den gleichen Puffer. Durch Aufruf von stream.rdbuf("Zeiger auf neuen Puffer") wird nur stream.rdbuf() modifiziert. ALERT! Der Stream kann daher zwei Puffer referenzieren, wobei als Interface zum Stream klassischerweise strm.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:
    1. stream.operator << (object) für Standard Datentypen
    2. 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()
  • ALERT!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:
    1. die Implementierung hängt nicht mehr vom Charaktertyp char ab
    2. es wird geprüft, ob das Zeichen zwischen den zwei Zahlen ein '/' ist
    3. ganze Zahlen n werden als 'n/1' interpretiert
    4. der Nenner 0 führt zu einem Setzen des std::ios::failbit Flags
    5. 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
    1. fehlerhafte Eingabe soll schon im Operator berücksichtigt werden
    2. 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) als 3/4 interpretiert, hingegen führt 3 /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:
  • TIPTrenne 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 und scanFrom 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
      • ALERT! 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

    MemberfunktionBedeutungRückgabewert
    sputc(c) Schickt c an den Streampuffer traits_type:to_int_type(c) oder traits_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 versus traits_type::char_type
    • traits_type::char_type repräsentiert den Zeichentyp, mit dem der Streampuffer instanziert wurde -in der Regel char
    • traits_type::int_type besteht aus traits_type::char_type und traits_type::eof()
    • muss traits_type::eof() als Zeichentyp berücksichtigt werden, werden die Zeichen als Elemente vom Typ traits_type::int_type interpretiert, sonst als traits_type::char_type
      • beim Einlesen einer Datei muss traits_type::eof() berücksichtigt werden MOVED TO... traits_type::int_type
      • der Streampuffer stellt alle Zeichen ohne traits_type::eof() zur Verfügung MOVED TO... traits_type::char_type
    • um die kontextabhängige Sicht auf den Zeichentyp zu unterstützen, existieren die zwei Konvertierungsmethoden to_int_type() und to_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

    MemberfunktionBedeutung
    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() und sgetn(b,n) traits_type::eof() zurück

Locales und Pufferposition

MemberfunktionBedeutung
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 oder ios_base::in sein
  • rpos kann vom Wert ios_base::begin, ios_base::end oder ios_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 nach std::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

    sbbuffers.gif

Ausgabepuffer

  • der Ausgabepuffer wird durch drei Zeiger prozessiert, wobei
    • pbase() put base den Anfang
    • pptr() put pointer die aktuelle Position
    • epptr() 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, bis pptr() == 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() und sync() 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 MOVED TO... sync() muß beim ungepufferten Puffer nicht überladen werden
  • das Non Virtual Interface ( NVI ) Prinzip gibt und daher unsere Schnittstellen vor

 

MemberfunktioinBedeutung
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.
    1. der Outputstream, der den Rahmen für die erweiterte Funktionalität bietet
    2. der Streampuffer, der die Aufrufe an den Filter weiterleitet
    3. 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 MOVED TO... 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 nach traits_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
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
  • ALERT! der bisherige Ansatz besitzt aber noch deutliche Einschränkungen:
  1. keine Strings können bearbeitet werden MOVED TO... puffern der Daten
  2. kein sauberes Interface zum Schreiben auf den originären Streampuffer MOVED TO... Kapslung durch ein internen Puffer
  3. die Funktoren können nicht verkettet werden MOVED TO... Einführung eines Joininsertor
  4. 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;
}

};

 

  • TIP 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
    1. aufwändiges Interface
    2. explizites Berechnen und Setzten der Position des next Zeigers ist nötig
    3. Puffergröße ist eine statische Größe MOVED TO... C Interface
    4. Puffer wächst nicht dynamisch MOVED TO... C++ Interface
  • MOVED TO... 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
    1. der zu filternde Streampuffer wird direkt durch den Insertor initialisiert
    2. 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:
        1. Puffer dynamisch allokieren und dem Insertor zuweisen MOVED TO... aufwändig und Gefahr eines memory leaks
        2. Puffer statisch allokieren und dem Insertor zuweisen MOVED TO... aufwändig
        3. Puffer als Member von Insertor MOVED TO... sehr gefährlich, denn durch das Löschen des Insertor ist das anschließenden flushen des Puffer als Member des Insertor natürlich undefiniert
#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 MOVED TO... 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 aus bufferOut->data() auf den zu filternden Streampuffer - das externe Device - geschoben
JoinInsertor
  • dieser erlaubt es beliebig lange Insertorketten zu bilden
  • seine virtuelle Methode getFilteredBuffer(...) bildet getFilteredBuffer(...) 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() und sbumpc() .
  • Existiert der interne Puffer beim initialen Aufruf noch nicht oder ist er leer, so sorgen die virtuellen und nicht öffentlichen Methoden underflow() oder uflow()dafür, daß die Puffer gefüllt werden.
    • sgetc() ruft die Methode underflow() auf, da durch sie das Zeichen nur dargestellt, aber nicht konsumiert wird
    • sbumpc() hingegen ruft implizit die Methode uflow() 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 sein
    • sungetc() dekrementiert nur den Pufferzeiger.
    • Während sputback(c) beim nächsten Leseaufruf c zurückgibt, wird durch sungetc() das letzte Zeichen nochmals dargestellt.
    • die virtuelle, nicht öffentliche Methode pbackfail sorgt dafür , das die Zeigerpositionen gemäss sputback(c) und sungetc() 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.

    MethodeBedeutungReturnwertAufrufer
    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 durch underflow() 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 und pbackfail()
  • lege eine Zeichenpuffer der Länge 1 an
  • fülle diesen Zeichenpuffer mit einem Zeichen des externen Devices, falls underflow() oder uflow() aufgerufen wird und gib dieses Zeichen zurück
  • gib dies Zeichen bei jedem weiteren Aufruf von underflow() oder pbackfail()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
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 );

 

  • MOVED TO...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());
}

};

 

Mentoring

Stay Informed about my Mentoring

 

Rezensionen

Tutorial

Besucher

Heute 61

Gestern 1497

Woche 7604

Monat 32463

Insgesamt 3331375

Aktuell sind 49 Gäste und keine Mitglieder online

Kubik-Rubik Joomla! Extensions

Abonniere den Newsletter (+ pdf Päckchen)

Beiträge-Archiv

Sourcecode

Neuste Kommentare