C++ Kurs

Dateizugriffe

Die Themen:

Genauso wie für die Standard Ein- und Ausgabe Streams (cin bzw. cout) verwendet werden, werden für Dateizugriffe unter C++ ebenfalls Streams eingesetzt. Im Folgenden werden Sie deshalb viele Gemeinsamkeiten zwischen der Standard Ein-/Ausgabe und der Datei Ein-/Ausgabe finden.

Bevor auf die Behandlung von Dateien eingegangen werden kann, müssen vorab noch zwei Begriffe geklärt werden: die Textdatei und die Binärdatei. Die Unterscheidung ist deshalb notwendig, da die beiden Dateitypen unterschiedlich gehandhabt werden.

Zuvor aber noch zwei Hinweise:

HinweisDaten, die in Dateien abgelegt werden, werden oft auch als persistente Daten bezeichnet, da sie zwischen zwei Programmläufen ihre Werte beibehalten.
HinweisWenn im Dateinamen auch deren Pfad mit angegeben werden muss, so verwenden Sie stets einen Slash / anstelle eines Backslash \ als Trennzeichen für die Verzeichnisnamen! Der Slash innerhalb einer Pfadangabe ist sogar auch unter WINDOWS gültig. Denn, ein einfaches Backslash-Zeichen innerhalb eines Strings leitet immer eine Escape-Sequenz ein!

Text- und Binärdatei

Textdatei

Textdateien sind Dateien die nur ASCII-Zeichen enthalten. Diese Dateien können mit jedem Editor bearbeitet werden der reine Textdateien erzeugen kann. Numerische Daten werden in diesen Dateien ebenfalls als ASCII-Zeichen abgelegt.

Aktion:
Schreiben des Strings "Emil Maier"
Schreiben des short-Wertes 100
Schreiben des long-Wertes 0x11223344

Dateiinhalt:
Hex-Darstellung ASCII-Darstellung
45 6D 69 6C 20 4D 61 69 65 72 Emil Maier
0x31 0x30 0x30 100
0x32 0x38 0x37 0x34 0x35 0x34 0x30 0x32 0x30   287454020
AchtungDiese Dateiform ist die einzige Möglichkeit, Daten zwischen unterschiedlichen Plattformen auszutauschen, da das Dateiformat der im Anschluss erwähnten Binärdatei nicht standardisiert ist.

Binärdatei

Im Gegensatz dazu sind Binärdateien Dateien, in denen Daten in binärer Form, also in der Form wie sie im Speicher des Rechners liegen, abgelegt sind. Diese Daten können in der Regel nicht mit einem Editor bearbeitet werden, da Editoren nur Dateien mit ASCII-Zeichen sinnvoll darstellen können. Der Aufbau einer Binärdatei ist, wie bereits erwähnt, aber nicht standardisiert, d.h. unterschiedliche Systeme können Daten unterschiedlich ablegen.

Die Ausgabe von Text in eine Binärdatei unterscheidet sich nicht von der Ausgabe in einer Textdatei. Der Unterschied wird erst bei der Ausgabe von numerischen Daten deutlich.

Aktion:
Schreiben des Strings "Emil Maier"
Schreiben des short-Wertes 100
Schreiben des long-Wertes 0x11223344

Dateiinhalt:
Hex-Darstellung ASCII-Darstellung
45 6D 69 6C 20 4D 61 69 65 72 Emil Maier
0x64 0x00 d.
0x44 0x33 0x22 0x11 D3".
HinweisWenn viele numerische Daten verarbeitet werden müssen und der Datenaustausch mit anderen Systemen nicht relevant ist, so werden diese Daten am besten in einer Binärdatei abgelegt. Dies kann die Größe der Datei erheblich reduzieren. Nehmen wir einmal an, es soll ein 4-stelliger short-Wert 1000-mal in einer Datei ablegen werden. In einer Textdatei werden dafür 1000*5 Bytes, gleich 5000 Bytes, benötigt. Das zusätzliche 5. Byte wird als Trennzeichen zwischen den einzelnen Werten benötigt, damit die Daten später auch wieder eingelesen werden können. Die gleiche Datenmenge innerhalb einer Binärdatei belegt dagegen nur 1000*2 Bytes, gleich 2000 Bytes, da ein short-Wert lediglich 2 Bytes benötigt und in einer Binärdatei kein Trennzeichen zwischen den einzelnen Werten notwendig ist (wie wir gleich noch sehen werden).

Anwenden von Dateistreams

Um Dateien mit Hilfe von Streams verarbeiten zu können, sind folgende Schritte notwendig:

Zusätzlich ist noch die Datei fstream mittels #include <fstream> einzubinden.

Ausgabestream-Objekt

Ausgabestream-Objekt definieren

Damit Daten in eine Datei geschrieben werden können, muss zunächst ein Ausgabestream-Objekt wie folgt definiert werden:

ofstream strName;

ofstream ist die Klasse, die für die Dateiausgabe verwendet wird und strName ist der Name des Stream-Objekts, welcher frei wählbar ist. Klassen und Objekte werden zwar erst später behandelt, aber stellen Sie sich unter der obigen Anweisung im Prinzip die Definition einer Variablen strName mit dem Datentyp ofstream vor.

HinweisDer Stream ofstream ist ebenfalls im Namensraum std definiert. Sie müssen also entweder vor jeder Verwendung dieses Streams den Namensraum std:: angegeben oder aber durch eine using-Anweisung den Namensraum des Streams explizit einbinden. In den nachfolgenden Ausführungen wird, der Übersichtlichkeit wegen, davon ausgegangen, dass Sie den Namensraum std mittels using namespace std; eingebunden haben.

Ausgabestream-Objekt mit Datei verbinden

Ein so definiertes Stream-Objekt strName besitzt aber noch keine Verbindung zu irgendeiner Datei. Diese Verbindung erfolgt erst durch den Aufruf der Memberfunktion

void ofstream::open(const char *pFName, ios::openmode mode=ios::out);

Der erste Parameter pFName ist ein Zeiger auf den Namen der Datei, die mit dem Stream verbunden werden soll.

AchtungUnd noch mal, weil's so wichtig ist: Wenn innerhalb des Dateinamens auch eine Pfadangabe steht und diese Backslash-Zeichen enthält, denken Sie immer daran, dass Sie die Backslash-Zeichen verdoppeln müssen, da das Backslash-Zeichen innerhalb von Strings eine Escape-Sequenz einleitet! Einfacher und portabler wird es, wenn Sie anstelle des Backslashs \ einen Slash / verwenden.

Der zweite Parameter mode enthält den Mode, in dem die Datei geöffnet werden soll. Er ist ein enumerated Datentyp (wird später noch erklärt) und kann eine sinnvolle Kombination aus folgenden Werten sein:

mode Bedeutung
ios::out Öffnen einer Datei zum Schreiben (default).
ios::trunc Öffnen einer Datei, wobei der ursprüngliche Inhalt verworfen wird (default).
ios::ate Öffnen einer Datei und positionieren des Schreibzeigers auf das Dateiende. Der Schreibzeiger kann bei Bedarf neu positioniert werden (seekp(...)) und die Daten werden dann an der aktuellen Position eingefügt.
ios::app Öffnen einer Datei und positionieren des Schreibzeigers auf das Dateiende. Die neuen Daten werden immer am Ende der Datei eingefügt, unabhängig davon ob der Schreibzeiger inzwischen neu positioniert wurde.
ios::binary Öffnen einer Datei im Binärmodus. Ohne diese Angabe wird die Datei als Textdatei interpretiert.

Beim Aufruf der Memberfunktion open(...) kann der zweite Parameter auch entfallen. In diesem Fall wird die Datei für die Ausgabe im Textmodus geöffnet und der ursprüngliche Inhalt wird verworfen.

HinweisWenn eine Datei zum Schreiben geöffnet und der Mode dabei explizit vorgegeben wird, so muss auch immer dem Mode ios::out mit angegeben werden!

Es gibt auch noch eine zweite Möglichkeit, einen Ausgabestream zu definieren und diesen dabei gleichzeitig mit einer Datei zu verbinden.

ofstream myFile(const char* pFName, ios::openmode mode = ios::out);

Hier werden beim Definieren des Stream-Objekts die zu öffnende Datei und der Mode gleich mit angegeben.

Sehen wir uns beide Möglichkeiten nun anhand eines Beispiels einmal an.

PgmHeader
// 1. Möglichkeit
#include <fstream>
using namespace std;
...
// main() Funktion
int main ()
{
   // Stream-Objekt definieren
   ofstream myFile;
   // und anschliessend mit Datei verbinden
   myFile.open ("d:\\tmp\\test.dat");
   ...
}
PgmHeader
// 2. Möglicheit
#include <fstream>
using namespace std;
...
// main() Funktion
int main ()
{
   // Stream-Objekt definieren und gleich mit Datei verbinden
   ofstream myFile ("d:/tmp/test.dat", ios::out|ios::binary);
   ...
}

Im ersten Fall wird zunächst das Stream-Objekt definiert und dann mit open(...) mit einer Datei verbunden. Bitte beachten Sie, dass vor dem Namen der Memberfunktion open(...) der Name des Stream-Objekts steht und danach ein Punkt folgt. Dies ist die allgemeine Syntax für den Aufruf einer Memberfunktion für ein bestimmtes Objekt.

Im zweiten Fall wird das Stream-Objekt bei seiner Definition gleich mit einer Datei verbunden. Im Beispiel wird die Datei zur Abwechslung einmal im Binärmodus geöffnet. Dabei ist zu beachten, dass hier der Modus ios::out explizit mit angegeben werden muss. Eine alleinige Angabe von ios::binary würde die Datei im Lesemodus öffnen, da dies die Standard-Einstellung ist.

Da nicht ausgeschlossen werden kann, dass beim Öffnen einer Datei auch einmal etwas schief geht, sollte nach der Verbindung des Streams mit einer Datei stets eine Fehlerabfrage erfolgen. Wie dieses Abfangen von Fehlern erfolgt, ist nachfolgende dargestellt.

PgmHeader
#include <fstream>
using namespace std;
...
// main() Funktion
int main ()
{
   // Stream-Objekt definieren und anschliessend mit Datei verbinden
   ofstream myFile;
   myFile.open ("d:/tmp/test.dat");
   // Abfrage auf Fehler
   if (!myFile)
   {
      ... // Hier Fehlerbehandlung durchführen
   }
}

Diese if-Abfrage (die im Detail später noch behandelt wird) ist sowohl bei Text- als auch bei Binärdateien einzusetzen, aber erst, nachdem eine Datei mit einem Stream-Objekt verbunden wurde. Beachten Sie das kleine Ausrufezeichen vor dem Objekt-Namen myFile, dies ist der NOT-Operator.

Datei-Verbindung mit Ausgabestream-Objekt aufheben

Nach dem die (gleich noch beschriebene) Verarbeitung der Datei abgeschlossen ist, muss die Verbindung des Streams zur Datei wieder aufgehoben werden. Hierbei gibt es ebenfalls zwei verschiedene Verfahren.

Im ersten Fall wird die Verbindung zur Datei einfach durch den Aufruf der Memberfunktion

void ofstream::close();

aufgehoben. Das Stream-Objekt besteht danach weiterhin und es kann mittels erneuten Aufrufs von open(...) eine weitere Datei mit dem gleichen Stream-Objekt verbunden werden.

PgmHeader
#include <fstream>
using namespace std;
...
// main() Funktion
int main ()
{
   // Stream-Objekt definieren und mit Datei verbinden
   ofstream myFile;
   myFile.open ("d:/tmp/test.dat");
   ...
   // Dateiverbindung aufheben
   myFile.close();
   ...
   // Stream erneut mit einer Datei verbinden
   myFile.open("c:/anydir/anotherfile.dat);
}

Die zweite Möglichkeit besteht darin, das Stream-Objekt selbst zu zerstören. Wenn ein Stream-Objekt, wie im nachfolgenden Beispiel dargestellt, innerhalb eines Blocks {...} definiert wird, so kann die Datei zunächst bei der Definition des Stream-Objekts mit diesem verbunden werden. Wird am Blockende das Stream-Objekt dann zerstört, so wird automatisch auch dessen Verbindung zur Datei aufgehoben. Mehr zu Blöcken innerhalb eines Programms und der Gültigkeit von Variablen und Objekt später im Kapitel Gültigkeitsbereiche.

PgmHeader
#include <fstream>
using namespace std;
...
// main() Funktion
int main ()
{
   ...
   // Blockbegin
   {
      ofstream myFile("d:/tmp/test.dat");
      ...
   }
   // Blockende, hier wird das Stream-Objekt zerstoert!
   ...
}

Schreiben in Datei

Textdatei

Das Schreiben in eine Textdatei erfolgt prinzipiell gleich wie die Ausgabe auf die Standardausgabe. Anstelle des Stream-Objekts cout wird nun lediglich der Name des Dateistream-Objekts angegeben. Alle zu schreibenden Daten werden, genauso wie bei cout, durch den Operator << in den Stream übertragen. Dabei ist aber zu beachten, dass zwischen den zu schreibenden Daten mindestens ein Trennzeichen (Leerzeichen, Zeilenvorschub o.ä.) stehen muss, da die Daten ansonsten später nicht mehr eingelesen werden können.

Dadurch, dass sowohl cout als auch ofstream die gleiche Basisklasse basic_ostream besitzen, stehen für die Dateiausgabe auch dieselben Manipulatoren, wie z.B. hex, setw(...) oder setprecision(...), zur Verfügung.

PgmHeader
#include <fstream>
using namespace std;

// Variablen definieren
auto var1 = 10, var2 = 20;

// main() Funktion
int main ()
{
   // Stream-Objekt definieren und mit Datei verbinden
   ofstream myFile;
   myFile.open ("d:/tmp/test.dat");
   // Wenn Stream ohne Fehler mit Datei verbunden
   if (myFile)
   {
      // Daten schreiben
      myFile << var1 << ' ';
      myFile << setw(5) << var2 << endl;
      // Datei schliessen
      myFile.close();
   }
}

Binärdatei

Sollen numerische Daten binär in einer Datei abgelegt werden, so muss beim Verbinden der Datei mit den Stream-Objekt der Mode ios::out | ios::binary angegeben werden.

AchtungBeachten Sie, dass Sie hier sowohl ios::out als auch ios::binary angeben müssen. Ferner sollten Sie immer daran denken, dass Binärdateien nicht für den Datenaustausch zwischen unterschiedlichen Plattformen geeignet sind. Dies gilt sogar auch für Programme, die auf dem gleichen Betriebssystem laufen, aber mit unterschiedlichen Compilern erstellt wurden. So kann z.B. ein Programm, das mit dem MICROSOFT C++ Compiler erzeugt wurde ein anderes Format für eine Binärdatei verwenden als das gleiche Programm, das mit dem MinGW C++ Compiler erstellt wurde.

Das Schreiben eines einzelnen Bytes in eine Binärdatei erfolgt mit der Memberfunktion

ostream& put(char data);

wobei data ist das zu schreibende Byte ist.

HinweisDie Angabe des Returnwertes ostream& bei den obigen Memberfunktionen ist kein Schreibfehler, d.h. die Memberfunktionen put(...), und das gleich im Anschluss beschriebene write(...), liefern eine Referenz auf ein Objekt der Klasse ostream (oder einer davon abgeleiteten Klasse) zurück.

Sollen Daten, die aus mehreren Bytes bestehen (short, long usw.), in einer binären Datei abgelegt werden, so wird hierfür die Memberfunktion

ostream& write(const char *pBuffer, streamsize bytes);

eingesetzt. pBuffer ist ein const char-Zeiger auf den Beginn des Datenblocks, der die zu schreibenden Bytes enthält und bytes gibt die Anzahl der zu schreibenden Bytes an. Da die Memberfunktion einen const char-Zeiger erwartet, muss in der Regel eine Typkonvertierung vorgenommen werden wenn numerische Daten abgespeichert werden (siehe Beispiel). Wird diese Typkonvertierung vergessen, so erzeugt der Compiler eine entsprechende Fehlermeldung beim Übersetzen des Programms.

PgmHeader
#include <fstream>
using namespace std;

// Variablen definieren
auto var1 = 10, var2 = 20;

// main() Funktion
int main ()
{
   // Stream-Objekt definieren und mit Datei verbinden
   ofstream myFile;
   myFile.open ("d:/tmp/test.dat", ios::out|ios::binary);
   // Wenn Stream ohne Fehler mit Datei verbunden
   if (myFile)
   {
      // Daten schreiben
      myFile.write(reinterpret_cast<char*>(&var1), sizeof(var1));
      myFile.write(reinterpret_cast<char*>(&var2), sizeof(var2));
      // Datei schliessen
      myFile.close();
   }
}
HinweisWenn Sie sich das Beispiel oben genauer ansehen, werden Sie feststellen, dass dort nur eine Typkonvertierung nach char* erfolgt und nicht nach const char*. Ein char* kann immer anstelle eines const char* übergeben werden, aber nicht umgekehrt! Das const char* bei der Signatur der Memberfunktion write(...) sagt hier nur aus, dass die Memberfunktion den Inhalt des übergebenen Puffers nicht verändert.

Beim Schreiben von Daten in Binärdateien können die Daten unmittelbar hintereinander geschrieben werden, d.h. es sind keine Trennzeichen wie bei einer Textdatei notwendig um später die Daten auch wieder einlesen zu können.

Eingabestream-Objekt

Eingabestream-Objekt definieren

Ein Eingabestream-Objekt wird mit folgender Anweisung definiert:

ifstream strName;

ifstream ist wiederum die Klasse, die für die Dateieingabe verwendet wird und strName der (beliebige) Name des Stream-Objekts. Das so definierte Stream-Objekt strName besitzt ebenfalls noch keine Verbindung zu irgendeiner Datei.

Eingabestream-Objekt mit Datei verbinden

Nach dem das Stream-Objekt erstellt wurde, kann es mit einer Datei verbunden werden. Dies erfolgt durch den Aufruf der Memberfunktion

void ifstream::open(const char *pFName, ios::openmode mode=ios::in);

Der erste Parameter pFName ist ein char-Zeiger auf den Namen der zu öffnenden Datei und der zweite Parameter mode gibt an, in welchem Mode die Datei zu öffnen ist. Er kann eine sinnvolle Kombination aus folgenden Werten sein:

mode Bedeutung
ios::in Öffnen einer Datei zum Lesen
ios::binary Öffnen einer Datei im Binärmodus. Ohne diese Angabe wird die Datei als Textdatei interpretiert.

Beim Aufruf der Memberfunktion kann auch der zweite Parameter weggelassen werden. In diesem Fall wird die Datei für die Eingabe im Textmodus geöffnet.

Und auch hier besteht die Möglichkeit, einen Eingabestream gleich bei seiner Definition mit einer Datei zu verbinden.

ifstream myFile(const char* pFName,  ios::openmode mode = ios::in);

Hier werden bei der Definition des Stream-Objekts die zu öffnende Datei und der Modus gleich mit angegeben.

Sehen wir uns beide Möglichkeiten anhand eines Beispiels wieder an.

PgmHeader
// 1. Möglichkeit
#include <fstream>
using namespace std;
...
// main() Funktion
int main ()
{
   // Stream-Objekt definieren
   ifstream myFile;
   // und anschliessend mit Datei verbinden
   myFile.open ("d:/tmp/test.dat");
   // Fehlerabfrage!
   if (!myFile)
   {
      ...     // Fehlerbehandlung
   }
   ...
}
PgmHeader
// 2. Möglichkeit
#include <fstream>
using namespace std;
...
// main() Funktion
int main ()
{
   // Stream-Objekt definieren und gleichzeitig mit Datei verbinden
   ifstream myFile("d:/tmp/test.dat", ios::in|ios::binary);
   // Fehlerabfrage!
   if (!myFile)
   {
      ...     // Fehlerbehandlung
   }
   ...
}

Im ersten Fall wird zunächst das Stream-Objekt definiert und dann mit open(...) mit einer Datei verbunden während im zweiten Fall das Stream-Objekt bei seiner Definition gleich mit einer Datei verbunden wird.

Und auch beim Öffnen einer Datei zum Lesen können natürlich Fehler auftreten. Prüfen Sie deshalb immer ab, ob die Datei erfolgreich mit dem Stream verbunden werden konnte.

Datei-Verbindung mit Eingabestream-Objekt aufheben

Für das Aufheben der Dateiverbindung und das Löschen des Stream-Objekts gilt das Gleiche wie beim Ausgabestream.

Lesen aus Datei

Textdatei

Das Lesen aus einer Textdatei erfolgt auch hier prinzipiell gleich wie das Lesen von Daten von der Standard-Eingabe mittels cin. Anstelle des Streamobjekts cin steht hier nun der Name des Eingabestream-Objekts. Alle zu lesenden Daten werden ebenfalls durch den Operator >> aus dem Stream ausgelesen.

PgmHeader
#include <fstream>
using namespace std;

// Variablen definieren
int var1, var2;

// main() Funktion
int main ()
{
   // Stream-Objekt definieren und mit Datei verbinden
   ifstream myFile;
   myFile.open ("d:/tmp/test.dat");
   // Hier Fehler abfangen!

   // Daten lesen
   myFile >> var1 >> var2;
   // Datei schliessen
   myFile.close();
}

Da sowohl cin als auch ifstream die gleiche Basisklasse basic_istream besitzen, stehen für das Einlesen von Daten aus einer Datei auch dieselben Manipulatoren, wie z.B. hex oder oct, zur Verfügung.

Binärdatei

Das Lesen eines einzelnen Bytes erfolgt dann mit der Memberfunktion

istream& get(char& data);

Das ausgelesene Byte ist nach der Ausführung der Memberfunktion in der Variable data abgelegt. Beachten Sie, dass nach dem Datentyp char der Operator & steht. Dies ist hier nicht der Adressoperator sondern get(...) erhält eine Referenz auf die Variable, in der das eingelesene Zeichen abzulegen ist. In ihrem Programm übergeben Sie die einzulesende Variable einfach an die Memberfunktion, also z.B. myFile.get(charVar);.

Sollen Daten, die aus mehreren Bytes bestehen (short, long usw.), aus einer Binärdatei ausgelesen werden, so ist hierfür die Memberfunktion

istream& read(char *pBuffer, streamsize bytes);

einzusetzen. pBuffer ist ein char-Zeiger auf den Beginn des Datenblocks, in dem die aus der Datei ausgelesenen Bytes abgelegt werden sollen und bytes gibt die Anzahl der zu lesenden Bytes an. Da die Memberfunktion wiederum einen char-Zeiger erwartet, muss in der Regel eine entsprechende Typkonvertierung erfolgen wenn numerische Daten eingelesen werden (siehe Beispiel).

PgmHeader
#include <fstream>
using namespace std;

// Variablen definieren
int var1, var2;

// main() Funktion
int main ()
{
   // Stream-Objekt definieren und mit Datei verbinden
   ifstream myFile;
   myFile.open ("d:/tmp/test.dat", ios::in|ios::binary);
   // Hier Fehler abfangen!

   // Daten lesen
   myFile.read(reinterpret_cast<char*>(&var1), sizeof(var1));
   myFile.read(reinterpret_cast<char*>(&var2), sizeof(var2));
   // Datei schliessen
   myFile.close();
}

Schreiben und Lesen

Und auch das ist möglich: eine Datei kann zum Schreiben und Lesen geöffnet werden. Hierzu wird ein Stream-Objekt vom Typ fstream eingesetzt, das wie folgt definiert wird.

fstream strName;

fstream ist der Stream, der für die Ausgabe und das Einlesen von Daten aus einer Datei verwendet wird und strName der (beliebige) Name des Stream-Objekts. Das so definierte Stream-Objekt strName besitzt aber ebenfalls noch keine Zuordnung zu irgendeiner Datei. Um das Stream-Objekt mit einer Datei zu verbinden, wird entweder die bereits bekannte open(...) Memberfunktion verwendet oder aber bei der Definition des Stream-Objekts wird gleich der Dateiname mit angegeben (genauso wie ifstream bzw. ofstream). Lediglich für den Parameter mode sind hier andere Kombinationen zugelassen:

mode Bedeutung
ios:in Öffnen einer Datei zum Schreiben
ios:out Öffnen einer Datei zum Lesen
ios::binary Öffnen einer Datei im Binärmodus. Ohne diese Angabe wird die Datei als Textdatei interpretiert.
ios:app Öffnen einer Datei und positionieren des Schreibzeigers auf das Dateiende. Die neuen Daten werden immer ans Ende der Datei eingefügt, unabhängig davon, ob der Schreibzeiger inzwischen neu positioniert wurde.
ios::ate Öffnen einer Datei und positionieren des Schreibzeigers auf das Dateiende. Der Schreibzeiger kann bei Bedarf neu positioniert werden (seekp(...)) und die Daten werden dann an der neuen Position eingefügt.
ios::trunc Öffnen eine Datei, wobei der ursprüngliche Inhalt wird verworfen.

Soll eine Datei zum Schreiben und Lesen geöffnet werden, so ist der mode-Parameter immer anzugeben.

AchtungSoll eine noch nicht existierende Datei zum Schreiben und Lesen geöffnet werden, so muss der Modus ios::trunc mit angegeben werden.

Das Lesen und Schreiben der Daten sowie das Schließen der Datei erfolgt mit den bereits in den vorherigen Abschnitten erläuterten Memberfunktionen.

HinweisAlle 3 Streams (ofstream, ifstream und fstream) besitzen eigentlich noch einen 3. Parameter, der den gleichzeitigen Zugriff auf eine Datei von mehreren Streams aus kontrolliert. Dieser Parameter ist aber nur für fortgeschrittene Dateioperationen interessant und wird deshalb hier nicht weiter betrachtet.

Sonstige Dateioperationen

eof Memberfunktion

In der Praxis wird in den meisten Fällen nicht von vornherein bekannt sein, wie viele Daten in einer Datei abgelegt sind. Sollen nun sämtliche Daten eingelesen werden, muss also irgendwie festgestellt werden, wann das Dateiende erreicht ist und somit alle Daten eingelesen sind. Die Abfrage, ob das Dateiende erreicht wurde, erfolgt mit der Memberfunktion

bool basic_ios::eof()

Diese Memberfunktion liefert den Wert true zurück wenn das Dateiende erreicht wurde.

PgmHeader
...
// Stream-Objekt definieren und mit Datei verbinden
ifstream myFile("d:/test.dat");
// Einlesen aus Datei
myFile >> data1 >> ...;
// Prüfen, ob Dateiende noch nicht erreicht
if (!myFile.eof())
{
   ... // Daten aus Datei auswerten
}
...

Damit eof() das Erreichen des Dateiendes feststellen kann, muss vorher ein Leseversuch stattgefunden haben.

ignore Memberfunktion

Die Memberfunktion ignore(...), welche schon im vorherigen Kapitel Eingabestream cin erläutert wurde, kann zum Überspringen von Daten verwendet werden.

PgmHeader
#include <fstream>
#include <iostream>

// main() Funktion
int main()
{
   // Feld zum Einlesen eines Strings
   char array[100];

   // Stream-Objekt definieren und mit Datei verbinden
   std::ifstream infile("test.dat");
   // Wenn Datei geöffnet werden konnte
   if (infile)
   {
      // Erstes Wort einlesen
      infile >> array;
      std::cout << "1. Wort: " << array << std::endl;
      // Rest der 1. Zeile (max. 100 Zeichen) ueberspringen
      infile.ignore(100,'\n');
      // Erstes Wort der 2. Zeile einlesen
      infile >> array;
      std::cout << "2. Wort: " << array << std::endl;
   }
}

seek Memberfunktionen

In Dateistreams lassen sich die internen Dateizeiger (genau genommen sind es Pufferzeiger) beeinflussen, um einen wahlfreien Zugriff auf die in der Datei abgelegten Daten zu erhalten. Hierzu werden die beiden Memberfunktionen

basic_istream& seekg(ios::off_type offset, ios_base::seek_dir dir);
basic_ostream& seekp(ios::off_type offset, ios_base::seek_dir dir);

eingesetzt. Die Memberfunktion seekg(...) dient zum Positionieren des Lese-Dateizeigers und die Memberfunktion seekp(...) zum Positionieren des Schreib-Dateizeigers, d.h. beide Dateizeiger lassen sich unabhängig voneinander positionieren. offset gibt die Anzahl der Bytes an, um die der Dateizeiger von der Position dir aus bewegt werden soll. Für dir ist einer der folgenden Werte zulässig:

Wert Bedeutung
ios::cur Dateizeiger ab der aktuellen Position verschieben
ios::beg Dateizeiger ab Dateianfang verschieben
ios::end Dateizeiger ab Dateiende verschieben

Beachten Sie bitte, dass der Dateizeiger bei Angabe von ios::beg nur in positiver Richtung und bei Angabe von ios::end nur in negativer Richtung verschoben werden kann!

Im nachfolgenden Beispiel werden zunächst die Werte 1...5 in eine Binärdatei, die zum Lesen und Schreiben geöffnet ist, geschrieben. Anschließend werden mittels den erwähnten seek Memberfunktionen verschiedene Werte aus der Datei ausgelesen. Beachten Sie dazu die Kommentare im Listing. Zum Schluss wird der vorletzte Wert verändert und dann alle Daten zur Kontrolle nochmals ausgegeben.

PgmHeader
// Beispiel zu wahlfreien Dateizugriff
#include <fstream>
#include <iostream>
#include <cstdlib>

// Bezeichner aus std-Namensraum einbinden
using std::cout;
using std::endl;
using std::fstream;
using std::ios;

// Dateiname
auto const pFILENAME = "test.dat";

// main() Funktion
int main()
{
   // Stream-Objekt definieren und mit binärer Datei verbinden
   fstream inOutFile(pFILENAME,ios::binary|ios::in|ios::out|ios::trunc);
   // Fehler abfangen
   if (!inOutFile)
   {
      cout << "Fehler beim Öffnen der Datei " << pFILENAME << endl;
      exit (1);
   }

   // Werte 1...5 in Datei schreiben
   // Mit einer Schleife geht das später einfacher!

   auto var = 1;
   inOutFile.write (reinterpret_cast<char*>(&var),sizeof(var));
   var++;
   inOutFile.write (reinterpret_cast<char*>(&var),sizeof(var));
   var++;
   inOutFile.write (reinterpret_cast<char*>(&var),sizeof(var));
   var++;
   inOutFile.write (reinterpret_cast<char*>(&var),sizeof(var));
   var++;
   inOutFile.write (reinterpret_cast<char*>(&var),sizeof(var));

   // Auf Dateianfang zurück
   inOutFile.seekg(static_cast<ios::off_type>(0),ios::beg);
   // 1. Datum einlesen
   inOutFile.read(reinterpret_cast<char*>(&var),sizeof(var));
   cout << "1. Wert: " << var << endl;

   // Auf letzten Wert positionieren
   inOutFile.seekg(-static_cast<ios::off_type>(sizeof(var)),ios::end);
   inOutFile.read(reinterpret_cast<char*>(&var),sizeof(var));
   cout << "Letzter Wert: " << var << endl;

   // Auf vorletzten Wert positionieren
   inOutFile.seekg(-static_cast<ios::off_type>(sizeof(var)*2),ios::end);
   inOutFile.read(reinterpret_cast<char*>(&var),sizeof(var));
   cout << "Vorletzter Wert: " << var << endl;

   // Vorletzten Wert überschreiben
   cout << "Überschreibe jetzt vorletzten Wert!" << endl;
   var = -1;
   inOutFile.seekp(-static_cast<ios::off_type>(sizeof(var)*2),ios::end);
   inOutFile.write(reinterpret_cast<char*>(&var),sizeof(var));

   // Und nun alle Werte auslesen
   cout << "Datei enthält jetzt:\n";
   // Lesezeiger zuerst auf Dateianfang
   inOutFile.seekg(static_cast<ios::off_type>(0),ios::beg);
   // Die nachfolgende while-Schleife wird später noch behandelt.
   // Nun alles wieder einlesen bis zum Dateiende

   do
   {
      inOutFile.read(reinterpret_cast<char*>(&var),sizeof(var));
      // Wenn nicht Dateiende erreicht, eingelesen Wert ausgeben
      if (!inOutFile.eof())
         cout << var << " ";
   } while(!inOutFile.eof());
   cout << endl;
}
Programmausgabe 1. Wert: 1
 Letzter Wert: 5
 Vorletzter Wert: 4
 Überschreibe jetzt vorletzten Wert!
 Datei enthält jetzt: 1 2 3 -1 5

Beispiel und ÜbungUnd hier geht's zum Beispiel und zur Übung.