C++-Programmierung: Dateizugriff

Alte Seite
Diese Seite gehört zum alten Teil des Buches und wird nicht mehr gewartet. Die Inhalte sollen in das neue Buch einfließen: C++-Programmierung/ Inhaltsverzeichnis.

Programmiert man mit Toolkits wie Qt, lassen sich Dateizugriffe sehr einfach und schnell realisieren. Was aber, wenn systemnahe Programmierung vonnöten ist - z. B. innerhalb eines Konsolenprogramms.

Hier muss genau wie in C unterschieden werden zwischen Text- und Binärdateien und zwischen verschiedenen Modi, die Datei zu bearbeiten.

Textdateien sind - ganz grob gesagt - alles, was sich mit einem Editor wie emacs, KWrite oder dem Windows-Notepad betrachten lässt. Dabei kann eine Textdatei im weiteren Sinne auch eine Ansammlung von Zahlen (wie Messdaten) oder anderen Werten (Konfigurationsdatei) sein.

Zu den Binärdateien dagegen gehören all die Dateien, in denen die Zahlenwerte auf der Festplatte keine Buchstaben darstellen (d. h. nicht nach etwa ASCII oder Unicode codiert), sondern anderweitig Werte repräsentieren. Das könnten zum Beispiel Bilder, MP3-Dateien oder gar kompilierte Programme sein.

Die verschiedenen Modi sind Lesen, Überschreiben und Anhängen mit verschiedenen Optionen.

Umsetzung Bearbeiten

Üblicherweise werden zur Bearbeitung von Dateien Streams benutzt. Diese werden von den Standardklassen ifstream zum Lesen, ofstream zum Schreiben und fstream zur Verfügung gestellt. Dabei repräsentiert jeweils ein Streamobjekt eine geöffnete Datei.

Die fstream-Klasse implementiert Ein- und Ausgabe, aber gehört offiziell nicht zum ANSI-Standard. Außerdem benötigen die Dateizugriffsklassen die Einbindung von iostream.

Zum Öffnen der Datei verwendet man die Methode open(), zum Schließen ganz analog close(). open() hat die folgende Syntax:

void open(const char* filename, ios_base::openmode mode);

filename gibt hier den Dateinamen an. Dieser kann relativ oder absolut angegeben werden, also z. B. "./Datei.txt" oder eben "/home/user/Documents/Datei.txt" bzw. "C:\\Eigene\ Dateien\\Datei.txt".

Der zweite Parameter mode gibt an, wie die Datei geöffnet werden soll. Die wichtigsten Modi in ANSI-C++ sind:

Modus (im Namensraum ios_base::) Funktion
in (Standard bei ifstream) nur Lesen
out (Standard bei ofstream) nur Schreiben (existierende Dateien werden überschrieben; nicht Existierende erzeugt)
out | app nur Schreiben (an existierende Dateien wird angehängt, nicht Existierende werden erzeugt)
in | out Lesen und Schreiben (Datei muss existieren)
in | out | trunc Lesen und Schreiben (existierende Dateien werden überschrieben; nicht Existierende erzeugt)
in | out | app Lesen und Schreiben (an existierende Dateien wird angehängt, nicht Existierende werden erzeugt)
nocreate nur Öffnen, wenn Datei existiert (gehört nicht zum ANSI-C++)
noreplace eine existierende Datei nicht öffnen (gehört nicht zum ANSI-C++)

Wenn man eine Binärdatei bearbeiten möchte, muss man noch | binary anhängen, aber mehr Beispiele dazu später.

Beispiel: Bearbeitung einer Textdatei mit ifstream und ofstream Bearbeiten

#include <iostream>                             // Standardstream-Funktionaliät einbinden
#include <fstream>                              // ofstream und ifstream einbinden

using namespace std;

int main(void)
{
  ifstream Quelldatei;                          // neuen Lese-Stream erzeugen
  Quelldatei.open("Datei_1.txt", ios_base::in); // Datei_1.txt öffnen

  if (!Quelldatei)                              // Fehler beim Öffnen?
    cerr << "Eingabe-Datei kann nicht geöffnet werden\n";
  else {                                        // falls es geklappt hat ...
    ofstream Zieldatei("Datei_2.txt");          // ja, richtig. Mit Dateinamen im Konstruktor wird die Datei implizit geöffnet
    if (!Zieldatei)                             // Fehler beim Öffnen?
      cerr << "Ausgabe-Datei kann nicht geöffnet werden\n"; 
    else {                                      // falls es funktioniert hat 
      char c;                                   // und jetzt, jedes Zeichen ...
      while (Quelldatei.get(c)) {               // ... einzeln ...
        Zieldatei.put(c);                       // ... in die Zieldatei schreiben.
      }
    }
  }
 return 0;
}

In diesem Beispiel wird die Datei Datei_1.txt im Stream Quelldatei geöffnet. Nachdem überprüft wird, ob die Datei geöffnet werden konnte, wird die Zieldatei Datei_2.txt erstellt und alle Zeichen einzeln aus der Quelldatei gelesen und in die Zieldatei geschrieben.

Man kann auch mithilfe der Stream-Operatoren Dateien bearbeiten und auch aus ihnen lesen.

ofstream Zieldatei("Datei.txt");
Zieldatei << "Dies ist der neue Text\n";
Zieldatei.close();

ifstream Quelldatei("Text.txt");
//Quelldatei >> stringvariable;
getline (Quelldatei, stringvariable);
Quelldatei.close();

Und möchte man Lesen und Schreiben (sog. Random Access), muss man die Modi mittels |-Operator koppeln:

ifstream Dateistream("beispiel.txt", ios_base::in | ios_base::out);
Dateistream.write("Hallo Welt!\n");
cout << Dateistream;
Dateistream.close();

Wahlfreier Zugriff auf die Streams Bearbeiten

Man muss nicht unbedingt eine Datei überschreiben oder an schon vorhandenen Text anhängen. Man kann sich die Lese- und Schreibposition auch ganz genau aussuchen. Das funktioniert indem man einen Positionszeiger verwendet. Zuerst ein Beispiel.

ifstream Datei("Datei1.txt", ios_base::in | ios_base::out);
pos_type laenge;                // unser Positionszeiger
Datei.seekg(0, ios_base::end);  // setze die Leseposition auf 0 gemessen vom Dateiende
laenge = Datei.tellg();         // ermittle die aktuelle Leseposition
cout << laenge << " Bytes\n";   // und gib sie aus.

Datei.seekp(10, ios_base::beg); // setze die Schreibposition auf das zehnte Zeichen vom Dateianfang
Datei.put('a');                 // ersetze den Buchstaben an dieser Position durch ein "a"
Datei.close();                  // und schließe die Datei wieder.

Schon relativ klar, oder? Die Seek-Funktionen (seekg und seekp) setzen den Lese- bzw. Schreibzeiger auf eine bestimmte Position. Diese ist gemessen in negativen oder positiven Werten am Dateianfang (ios_base::beg), an der aktuellen Position (ios_base::cur) oder am Dateiende (ios_base::end).

Die Funktionen tellg() und tellp() geben die Position des Lese- bzw. Schreibzeigers zurück. g und p am Ende der Funktionsnamen stehen für get und put.