C++-Programmierung: Dateizugriff
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
BearbeitenMan 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.