C++ Tutorial

 Dateizugriffe

Für den Zugriff auf Dateien können ebenfalls Streams eingesetzt werden. Die für das Schreiben und Lesen von Daten zur Verfügung stehenden Streams ofstream bzw. ifstream sind gleichfalls in der Standardbibliothek definiert. Damit der Compiler diese kennt, ist die Header-Datei <fstream> einzubinden.

Text- und Binardatei

Beim Bearbeiten von Dateien ist zwischen einer Textdatei und einer Binärdatei zu unterscheiden.

Eine Textdatei enthält nur ASCII-Zeichen. Diese Datei kann mit jedem Editor bearbeitet werden und numerische Daten sind ebenfalls als Text abgelegt. Die Textdatei ist die einzige Möglichkeit, Daten per Datei zwischen unterschiedlichen Systemen auszutauschen.

Im Gegensatz dazu ist eine Binärdatei eine Datei, in denen Daten in binärer Form, also in der Form wie sie im Speicher des Rechners liegen, abgelegt sind. Der Aufbau einer Binärdatei ist nicht standardisiert, benötigt bei numerischen Daten dafür aber weniger Platz auf der Festplatte.

Daten in Datei ablegen

Ausgabestream-Objekt definieren

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

std::ofstream myFile;

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

Ausgabestream-Objekt mit Datei verbinden

Um das Stream-Objekt mit einer Datei zu verbinden, wird die Memberfunktion open() aufgerufen:

myFile.open(pFName, mode);

Der erste Parameter pFName ist ein char-Zeiger auf den Dateinamen und mode enthält den Modus, 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 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.

Der zweite Parameter mode kann auch weggelassen werden. In diesem Fall wird die Datei für die Ausgabe im Textmodus geöffnet und der ursprüngliche Dateiinhalt wird verworfen.

Alternativ kann ein Ausgabestream gleich bei seiner Definition mit einer Datei verbunden werden.

std::ofstream myFile(pFName, mode);

Da nicht auszuschließen ist, dass beim Öffnen einer Datei einmal etwas schief geht, sollte nach der Verbindung des Streams mit einer Datei stets eine Fehlerabfrage erfolgen. Wie dieses Abfangen von Fehlern erfolgt, ist im nachfolgenden Beispiel dargestellt.

// Textdatei oeffnen
std::ofstream outFile("c:/temp/xxx.txt");
// Fehler abfangen
if(!outFile)
{
   // Fehlerbehandlung durchfuehren
}

Datei-Verbindung aufheben

Sind die Daten in der Datei abgelegt (wird gleich beschrieben), muss die Verbindung des Streams zur Datei mittels der Memberfunktion close() aufgehoben werden.

myFile.close();

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

#include <iostream>
#include <format>
#include <fstream>

int main()
{
   // Datei im Binaermodus oeffnen
   std::ofstream outFile("c:/temp/xxx.bin", std::ios::out | std::ios::binary);
   // Fehler abfangen
   if(!outFile)
   {
      std::cout << "Fehler beim Oeffnen der Datei!\n"; exit(1);
   }
   // Daten in Datei ablegen
   // .....
   // Datei schliessen
   outFile.close();

   // Weitere Datei oeffnen, jetzt im Textmodus
   outFile.open("c:/temp/yyy.txt");
   // Fehler abfangen
   if(!outFile)
   {
      std::cout << "Fehler beim Oeffnen der Datei!\n";
      exit(2);
   }
   // Daten in Datei ablegen
   // ...
   // Datei schliessen
   outFile.close();
}

Schreiben in Textdatei

Das Schreiben in eine Textdatei erfolgt prinzipiell gleich wie die Ausgabe auf die Standardausgabe. Anstelle des Stream-Objekts cout wird nun jedoch der Name des Dateistream-Objekts angegeben. Dabei ist zu beachten, dass beim Schreiben der Daten zwischen diesen mindestens ein Trennzeichen (Leerzeichen, Zeilenvorschub o.ä.) steht, da sie ansonsten später nicht wieder richtig eingelesen werden können.

#include <iostream>
#include <format>
#include <fstream>

int main()
{
   // Datei im Textmodus oeffnen
   std::ofstream outFile("c:/temp/xxx.txt");
   // Fehler abfangen
   if(!outFile)
   {
      std::cout << "Fehler beim Oeffnen der Datei!\n";
      exit(1);
   }
   // Daten in Datei ablegen
   unsigned long ulvar = 0x12345678UL;
   outFile << std::format("Datum: {}\n",ulvar);
   // Datei schliessen
   outFile.close();
}

Schreiben in Binärdatei

Das Schreiben eines einzelnen Bytes in eine Binärdatei erfolgt mit der Memberfunktion put(...)

myFile.put(data);

wobei data das zu schreibende Byte ist.

Um Daten, die aus mehreren Bytes bestehen, in einer binären Datei abzulegen, wird die Memberfunktion

myFile.write(pBuffer, bytes);

eingesetzt. pBuffer ist ein const char-Zeiger auf den Beginn des Datenblocks 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.

#include <iostream>
#include <format>
#include <fstream>

int main()
{
   // Datei im Binaermodus oeffnen
   std::ofstream outFile("c:/temp/xxx.bin",std::ios::out|std::ios::binary);
   // Fehler abfangen
   if(!outFile)
   {
      std::cout << "Fehler beim Oeffnen der Datei!\n";
      exit(1);
   }
   // Daten in Datei ablegen
   char letter = 'A';
   const char *pText = "Datum: ";
   unsigned long ulvar = 0x12345678UL;
   outFile.put(letter);
   outFile.write(pText,strlen(pText));
   outFile.write(reinterpret_cast<const char*>(&ulvar),sizeof ulvar);
   // Datei schliessen
   outFile.close();
}

Daten aus Datei einlesen

Eingabestream-Objekt definieren

Ein Eingabestream-Objekt wird mit folgender Anweisung definiert:

std::ifstream myFile;

ifstream ist wiederum die Klasse, die für die Dateneingabe verwendet wird und myFile der Name des Stream-Objekts.

Eingabestream-Objekt mit Datei verbinden

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

myFile.open(pFName, mode);

Der erste Parameter pFName ist ein Zeiger auf den Dateinamen und mode enthält den Modus, in dem die Datei geöffnet werden soll. 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 der zweite Parameter weggelassen werden. In diesem Fall wird die Datei im Textmodus geöffnet.

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

std::ifstream myFile(pFName, mode);

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 aufheben

Die Dateiverbindung wird wiederum mit der Memberfunktion

myFile.close();

aufgehoben.

#include <iostream>
#include <format>
#include <fstream>

int main()
{
   // Datei im Textmodus oeffnen
   std::ifstream inFile("c:/temp/xxx.txt");
   // Fehler abfangen
   if(!inFile)
   {
      std::cout << "Fehler beim Oeffnen der Datei!\n";
      exit(1);
   }
   // Daten aus Datei einlesen
   // ....
   // Datei schliessen
   inFile.close();
}

Lesen aus Textdatei

Das Lesen aus einer Textdatei erfolgt 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.

#include <iostream>
#include <format>
#include <fstream>

int main()
{
   // Datei im Textmodus oeffnen
   std::ifstream inFile("c:/temp/xxx.txt");
   // Fehler abfangen
   if(!inFile)
   {
      std::cout << "Fehler beim Oeffnen der Datei!\n";
      exit(1);
   }
   // Daten aus Datei lesen
   unsigned long ulvar;
   char text[80];
   inFile >> text >> ulvar;
   // Und Daten ausgeben
   std::cout << std::format("{} {}\n",text,ulvar);
   // Datei schliessen
   inFile.close();
}

Lesen aus Binärdatei

Das Lesen eines einzelnen Bytes erfolgt mit der Memberfunktion

myFile.get(data);

Das ausgelesene Byte ist nach der Ausführung der Memberfunktion in der Variable data abgelegt.

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

myFile.read(pBuffer, 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.

#include <iostream>
#include <format>
#include <fstream>

int main()
{
   // Datei im Binaermodus oeffnen
   std::ifstream inFile("c:/temp/xxx.bin",std::ios::in|std::ios::binary);
   // Fehler abfangen
   if(!inFile)
   {
      std::cout << "Fehler beim Oeffnen der Datei!\n";
      exit(1);
   }
   // Daten aus Datei auslesen
   char letter;
   char text[80];
   unsigned long ulvar;
   inFile.get(letter);
   inFile.read(text,7);
   inFile.read(reinterpret_cast<char*>(&ulvar),sizeof ulvar);
   // Daten ausgeben
   std::cout << std::format("{}{}{:#x}\n",letter,text,ulvar);
   // Datei schliessen
   inFile.close();
}

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. Die Abfrage, ob das Dateiende erreicht wurde, erfolgt mit der Memberfunktion

myFile.eof();

Diese Memberfunktion liefert den Wert true zurück, wenn das Dateiende erreicht wurde. Damit eof() das Erreichen des Dateiendes feststellen kann, muss vorher ein Leseversuch stattgefunden haben.

// Schleife um alle Daten aus einer Datei einzulesen
do
{
   // Datum einlesen
   inFile >> data;
   // Wenn Dateiende nicht erreicht
   if (!inFile.eof())
      std::cout << std::format("{}, ",data);
} while (!inFile.eof()); // Ende der Schleife

ignore() und seekX() Memberfunktion

Mit der Memberfunktion ignore() können Daten beim Einlesen übersprungen werden.

myFile.ignore(count, delim);

count ist die Anzahl der zu überspringenden Zeichen und delim definiert, bis zu welchem Zeichen maximal der Sprung erfolgt.

// Ueberspringe 10 Zeichen, aber maximal bis zum nächsten Semikolon
infile.ignore(10,';')

Die Memberfunktion seekg() und seekp() dienen dazu, die internen Dateizeiger zu verschieben. Mehr dazu im Buch oder wieder unter https://en.cppreference.com.