Ströme und Dateien in C++
-
Da hier relativ häufig Fragen über die Dateibehandlung in C++
auftauchen, habe ich mich dazu entschieden mal ein paar Sätze
über das Thema zu schreiben. Ich will diesen Thread
dann später den C++ FAQs hinzufügen und bitte deshalb jeden,
der der Meinung ist, dass ich etwas wichtiges vergessen habe,
dieses zu ergänzen.In C++ wird die Ein- und Ausgabe über sogenannte Ströme (engl. Streams)
realisiert. Durch das Einbinden des Headers <iostream> erhält man vier
bereits instanziierte Stromobjekte.
Diese sind:- cout: ein gepufferter Stream der der Standardausgabe (i.A. Bildschirm)
zugeordnet ist (in C: stdout). - cin: ein gepufferter Stream der der Standardeingabe (i.A. Tastatur)
zugeordnet ist (in C: stdin) - cerr: ein ungepufferter Stream der der Standardfehlerausgabe (i.A. Bildschirm)
zugeordnet ist (in C: stderr) - clog: gepufferter Stream der der Standardprotokollausgabe (i.A. Bildschirm)
zugeordnet ist.
Die Eingabestreams sind Instanzen der Klasse istream oder von Klassen
die von istream abgeleitet wurden. Die Basisklasse der Ausgabestreams
ist ostream.In Ausgabestreams schreibt man mittels des bitweise-linksshift "<<",
aus Eingabeströmen ließt man mittels des bitweise-rechtsshift ">>" Operators.
Diese beiden Operatoren sind für alle Standarddatentypen implementiert.
Für alle nicht Standarddatentypen kann man diese beiden Operatoren überladen.
Dies ermöglicht einem jeden Datentypen immer auf die gleiche Art und Weise ein-
und auszugeben.
Da die Ein-/Ausgabe über gepufferte Ströme abläuft, muß es eine Möglichkeit
geben diese Puffer zu leeren (engl. to flush). Das leeren eines Ausgabepuffers
erzielt man durch "flush". Mittels "endl" fügt man dem Puffer ein NewLine-Zeichen
hinzu und leert danach den Puffer.
Bsp:#include <iostream> using namespace std; int main() { cout << "Ich mag C++ " << 10 << " mal lieber als Paprika!" << endl; return 0; }
Zur Navigation in Strömen gibt es die Methoden seekg und seekp. Seekg setzt
den Lesezeiger in Eingabeströmen. Seekp setzt den Schreibzeiger in Ausgabeströmen.
Beide Zeiger können relativ zu einem Offset positioniert werden.
Gültige Angaben hierfür sind:- ios::beg: Suche vom Beginn des Streams
- ios::curr: Suche von der aktuellen Position
- ios: Suche vom Ende des Streams
Die aktuelle Position im Stream erhält man über die Methoden tellg und tellp.
Da die Dateibehandlung in C++ ebenfalls über Ströme realisiert ist, gilt das oben
geschriebene hier ebenfalls. So läßt sich z.B. mit "<<" in eine Datei schreiben
und mit ">>" kann aus einer Datei gelesen werden.
Weitere wichtige Funktionen sind:- open(): Öffnet eine Datei und verbindet sie mit dem Strom
- is_open(): liefert true falls der Dateistrom mit einer gültigen
Datei verbunden ist. - close(): Trennt den Strom von einer Datei
- read(char* puch, int nCount) : Ließt nCount Bytes aus dem Eingabestrom und
schreibt sie nach puch. - gcount(): Liefert die Anzahl der beim letzten Aufruf von read gelesenen Zeichen.
- write(const char* puch, int nCount): Schreibt nCount Bytes aus puch in den Stream
Hier vier Beispiele die das hantieren mit Dateien ein wenig verdeutlichen.
Das erste Beispiel liest eine Datei zeilenweise ein und speichert
jede Zeile in einem string-Vector. Als letztes wird der Inhalt des
Vektors (und damit der Inhalt der Datei) nach cout geschrieben.
Benötigte Header: <iostream>, <fstream>, <vector>, <string> und <iterator>using namespace std; ifstream FileIn("Main.cpp"); if (FileIn) // Falls FileIn gültig ist. { vector<string> Contents; // Container für die einzelnen Zeilen // Solange kein Fehler auftritt und nicht eof for (string ReadString; getline(FileIn, ReadString); ) Contents.push_back(ReadString); // Aktuelle Zeile in den Vektor einfügen // Alle Elemente des Vektors ausgeben. ostream_iterator<string> Out(cout, "\n"); copy(Contents.begin(), Contents.end(), Out); }
Das zweite Beispiel öffnet eine Datei im binär-Modus und liest ihren Inhalt
in einen Puffer. Nach dem Lesen werden zwei Zeichen ab Offset 7 durch die
letzten beiden Zeichen der Datei ersetzt.
Benötigte Header: <iostream>, <fstream>using namespace std; fstream FileBin("d:\\cdtemp\\streams\\test.dat", ios::in|ios::out|ios::binary); if (FileBin.is_open()) { // 1. Dateigröße bestimmen. FileBin.seekg(0, ios::end); unsigned long FileSize = streamoff(FileBin.tellg()); FileBin.seekg(0, ios::beg); // 2. Puffer anlegen und Datei einlesen. char* pBuffer = new char[FileSize]; FileBin.read(pBuffer, FileSize); // 3. An Stelle 7 (relativ zum Beginn) zwei Bytes ersetzen. FileBin.seekp(7, ios::beg); FileBin.write(pBuffer+FileSize-2, 2); // Nachher wieder aufräumen. delete [] pBuffer; }
Das dritte Beispiel zeigt wie man eine Datei kopieren kann.
Erforderliche Header: <fstream>using namespace std; // Quelldatei ifstream FileInCopy("d:\\cdtemp\\uncle_kracker-follow_me.mp3", ios::binary); // Zieldatei ofstream FileOutCopy("d:\\cdtemp\\uncle_kracker-follow_me.mp3.bak", ios::binary); if (FileInCopy) FileOutCopy << FileInCopy.rdbuf();
Das letzte Beispiel zeigt wie man Datensätze schreiben und lesen kann.
Erforderliche Header: <iostream> und <fstream>using namespace std; // Einen Person-Datensatz definieren struct PERSON { char VorName[20]; char NachName[20]; int TelNr; }; // fstream damit die Datei zuerst schreibend und dann lesend geöffnet werden kann. fstream File("Person.dat", ios::out|ios::binary); if (File.is_open()) { // Nur zwei Datensätze schreiben. // Hier wäre eine Fehlerprüfung angebracht for (int i = 0; i < 2 ; ++i) { PERSON Pers; cout << "Vorname: "; cin >> Pers.VorName; cout << "Nachname: "; cin >> Pers.NachName; cout << "TelNr: "; cin >> Pers.TelNr; // Aktuelle Person in der Datei speichern. File.write((const char*)&Pers, sizeof(Pers)); } File.close(); // Datei schließen } // Datei zum Lesen öffnen. File.open("Person.dat", ios::in|ios::binary); if (File.is_open()) { PERSON Pers; while (File.read((char*)&Pers, sizeof(Pers))) cout << Pers.VorName << " " << Pers.NachName << " " << Pers.TelNr << endl; }
Die Ströme in C++ sind ein sehr weitläufiges Thema. Das hier geschriebene
zeigt deshalb nur einen winzig kleinen Ausschnitt.Jetzt seit ihr gefragt: Was soll hier unbedingt noch erwähnt werden?
Wo habe ich was falsches gesagt?Update:
Immer mal wieder taucht die Frage auf, warum die Angaben ios::nocreate bzw. ios::noreplace als Open-Flags auf Compiler A nicht aber auf Compiler B funktionieren. Die Antwort ist einfach:
Weder ios::nocreate noch ios::noreplace sind Standard-C++. Diese beiden Open-Flags waren bei den veralteten <fstream.h>-Strömen weit verbreitet. Sie gehörten aber zu keiner Zeit zu den Standard-Strömen.Manchmal sind diese Flags aber ganz praktisch. Man kann sie wie folgt simulieren:
int main() { // nocreate fstream f("Datei.txt", ios::in); if (f.good()) { // ok. Datei existiert. f.close(); f.open("Datei.txt", ios::out); if (f.is_open()) { // Mit Datei arbeiten } } else { cout << "Datei existiert nicht." << endl; } // noreplace fstream f2("Datei.txt", ios::in); if (!f2) { // ok. Datei existiert nicht. Neu erstellen f.clear(); f.open("Datei.txt", ios::out); if (f.is_open()) { // mit Datei arbeiten } } else { cout << "Datei existiert bereits!" << endl; } }
[ Dieser Beitrag wurde am 30.08.2002 um 01:12 Uhr von Dimah editiert. ]
[ Dieser Beitrag wurde am 13.01.2003 um 18:31 Uhr von HumeSikkins editiert. ]
[ Dieser Beitrag wurde am 30.03.2003 um 23:34 Uhr von HumeSikkins editiert. ]
[ Dieser Beitrag wurde am 08.04.2003 um 14:37 Uhr von HumeSikkins editiert. ]
- cout: ein gepufferter Stream der der Standardausgabe (i.A. Bildschirm)
-
Ich trag mal was dazu bei:
Man kann bei stream noch testen, ob die letzte Aktion geklappt hat. Das kann sehr nützlich sein, wenn man z.B. der Anwender bei
int i; cin >> i;
keine Zahl eingibt.
Es gibt folgende Funktionen:
ios::good // alles ok
ios::bad // Stream ist 'kaputt' / grober Fehler
ios::eof // Dateiende
ios::fail // einlesen bzw. ausgeben hat nicht geklappt
-
Man kann den cout-stream auch leicht auf eine Datei umleiten. Das ist oft recht sinnvoll, wenn ein Consolenprojekt größer wird.
ofstream file;
cout.rdbuf(file.rdbuf())Die Methode rdbuf() liefert den aktuellen Buffer zurück und setzt den Stream auf den als Parameter übergebenen Buffer.
Leider kann man das mit der Funktion rdbuf() eines ofstream-Objekts nicht machen, da die Methode überschrieben ist. Es geht aber auch irgendwie, weiß nur nicht genau wie. Vielleicht hat ja jemand ne Ahnung.
-
Jo, einfach nach ios casten, dann geht es. Ich habs aber auch erst durch die "StreamRedirect"-Klasse festgestellt, die ich wegen dieses Beitrags getestet habe: http://www.c-plusplus.net/ubb/cgi-bin/ultimatebb.cgi?ubb=get_topic&f=15&t=000850
ios& stream = cout; // oder ein fstream etc.
stream.rdbuf( another_stream.rdbuf() );