- Öffnen und Schließen
- Lesen und Schreiben
- Zustandsbeobachtung
- Dateizugriffe nach ANSI-C
- Dateisystemkommandos
- Datei-Eigenschaften ermitteln
fstream
Für die Dateioperationen werden Objekte der Klasse fstream verwendet. Wird in die Datei nur geschrieben, kann stattdessen die Klasse ofstream verwendet werden. Für reine Eingabedateien bietet sich die Klasse ifstream an. Auf die Objekte dieser Klassen können Ein- und Ausgabeoperatoren (>> und <<) angewandt werden. Vor einem Zugriff muss die Datei mit der Elementfunktion open() geöffnet werden. Sie können die Aufgabe des Öffnens auch dem Konstruktor der fstream-Klasse überlassen, indem Sie dem Objekt bei ihrer Definition den Dateinamen als Parameter übergeben. Nach der Bearbeitung der Datei muss sie wieder mit der Elementfunktion close() geschlossen werden. Diese Aufgabe übernimmt aber auch automatisch der Destruktor, so dass Sie close() nur dann aufrufen müssen, wenn Sie eine Datei schließen wollen, bevor das Objekt aufgelöst wird.#include <fstream> using namespace std; int main() { fstream f; f.open("test.dat", ios::out); f << "Dieser Text geht in die Datei" << endl; f.close(); }
Öffnen und Schließen
Konstruktor
Im Listing wird zunächst ein Objekt der Klasse fstream definiert. Danach wird durch Aufruf der Funktion open() die Datei geöffnet. Die nächste Zeile leitet einen String in die Datei um, und zuletzt schließt der Aufruf von close() die Datei wieder.Parameter von open()
Die Parameter der Funktion open() sind zuerst der Name der Datei, die geöffnet werden soll. Als zweiter Parameter wird angegeben, in welchem Modus die Datei geöffnet wird. Im Beispiel wird die Datei zur Ausgabe verwendet. Die Parameter von open() können auch gleich bei der Definition des Objekts mitgegeben werden. Damit wird ein Konstruktor aufgerufen, der gleich die Datei öffnet.fstream f("test.dat", ios::out);
Dateiname
Der Dateiname ist ein C-String, also als Zeiger auf char deklariert. In den meisten Fällen werden Sie Dateinamen aus Dateiauswahldialogen oder Benutzereingaben einfach übernehmen. Wird der Dateiname als Konstante im Programm definiert, ist daran zu denken, dass der Pfadtrenner Backslash zweimal geschrieben werden muss, da er ansonsten in einem String als Sonderzeichen interpretiert wird. Ansonsten empfiehlt sich die Verwendung eines Schrägstrichs als Pfadtrenner. Er wird auch unter MS-Windows als Pfadtrenner akzeptiert. Das ist nicht nur leichter zu lesen, sondern hat seine Vorteile in der Portierbarkeit zu UNIX-Systemen. Dort wird der Schrägstrich und nicht der Backslash als Pfadtrenner verwendet.Dateimodus
Der zweite Parameter gibt den Modus an, in dem die Datei geöffnet wird. Die Konstante ios::out im Beispielprogramm besagt, dass die Datei zum Schreiben geöffnet wird. Naheliegenderweise verwenden Sie ios::in, wenn Sie eine Datei zum Lesen verwenden möchten. Der Modus ios::trunc bedeutet, dass die Datei beim Öffnen geleert wird. Das ist nur sinnvoll, wenn die Datei geschrieben werden soll.ios::app
Ähnlich verhält es sich mit dem Modus ios::app. Damit werden alle Ausgaben an die Datei angehängt. Auch dieser Modus wird sinnvollerweise in Kombination mit ios::out verwendet. Dieser Schreibmodus hat vor allem beim Schreiben von Protokolldateien eine besondere Bedeutung. So ist gewährleistet, dass die Daten immer hinten an die Datei angehängt werden. Anstatt zunächst die Größe der Datei und daraus die Schreibposition zu ermitteln, reicht ein einfacher Schreibaufruf aus. Der Modus ios::ate bewirkt, dass der Positionszeiger beim Öffnen der Datei zunächst auf das Ende der Datei gesetzt wird. Um mehrere Modi zu kombinieren, werden sie bitweise mit ODER verknüpft. Sie setzen also einen einfachen, senkrechten Strich zwischen die Moduskonstanten. [Dateimodi]Konstante | Bedeutung |
---|---|
ios::in | Zum Lesen |
ios::out | Zum Schreiben |
ios::trunc | Datei wird beim Öffnen geleert |
ios::app | Geschriebene Daten ans Ende anhängen |
ios::ate | Positionszeiger ans Ende setzen |
ofstream und ifstream
Es gibt zwei spezielle Stream-Klassen. Die Klasse ifstream impliziert als Modus beim Öffnen der Datei ios::in. Die Klasse ist also auf das Lesen von Dateien spezialisiert. Das Gegenstück ist ofstream, die für Dateien zuständig ist, die nur geschrieben werden. Hier braucht beim Öffnen der Datei ios::out nicht explizit genannt werden.binary
Standardmäßig geht C++ davon aus, dass eine Datei eine Textdatei ist. Das führt dazu, dass Zeilenvorschübe je nach Plattform unterschiedlich behandelt werden. In einer UNIX-Umgebung wird ein Zeilenvorschub mit dem ASCII-Zeichen 10 (Line Feed) codiert. Ein Macintosh (Classic) verwendet 13 für Carriage Return, und die Betriebssysteme aus dem Hause Microsoft benutzen eine Kombination aus beiden. Beim Lesen und Schreiben von Dateien werden dabei gegebenenfalls Anpassungen durchgeführt, die aber fatal sind, wenn es sich gar nicht um Texte, sondern um binäre Daten handelt. Um dies zu verhindern, wird beim Öffnen reiner Datendateien die Option ios::binary verwendet.Schließen
Eine Datei wird automatisch durch den Destruktor geschlossen. Insofern ist das Aufrufen der Funktion close() eigentlich überflüssig. Das explizite Schließen hätte sogar den Nachteil, dass es ein Stream-Objekt gibt, das keinen Zugriff mehr auf eine Datei hat. Insofern ist ein Öffnen durch den Konstruktor und ein Schließen durch den Destruktor etwas eleganter. Auf der anderen Seite wird ein kluger Programmierer eine zum Schreiben geöffnete Datei gern wieder schnellstmöglich schließen wollen. Eine zum Schreiben geöffnete Datei ist verletzbar, solange sie nicht wieder geschlossen ist. Stürzt das Programm vor dem Schließen der Datei ab, kann deren Inhalt defekt sein.Lesen und Schreiben
Der Sinn und Zweck einer Datei ist es, Daten aufzunehmen. Dabei unterscheiden sich Textdateien von Datendateien. Textdateien lassen sich durch die Ein- und Ausgabeoperatoren bearbeiten. Sie können solche Dateien mit einem beliebigen Editor lesen und bearbeiten. Datendateien werden dagegen typischerweise mit Datenblöcken fester Länge beschrieben und gelesen.Datenstrom
Die Stream-Objekte für Dateien lassen sich für die Ein- und Ausgabe genauso behandeln, wie Sie es von cin und cout her kennen. Das folgende Beispiel zeigt noch einmal die Ausgabe in eine Datei mit Hilfe des Ausgabeoperators:fstream Datei(..., ios::out); ... Datei << "Dies geht in eine Datei" << Variable << endl;
Natürlich können für die Dateiausgabe auch Manipulatoren zur Formatierung der Ausgabe verwendet werden. Das Lesen aus Dateien kann auf analoge Weise über den Eingabeoperator erfolgen.
Datei auslesen
Der Eingabeoperator >> arbeitet mit fstream-Objekten in der gleichen Weise wie mit cin. Die Datei wird so gelesen, als käme ihr Inhalt von der Tastatur. Dazu gehört, dass Leerzeichen, Tabulatoren und Zeilenvorschübe als Eingabetrenner interpretiert werden. Das gilt auch, wenn der Datenstrom in Zeichenketten-Variablen fließt.Textzeile lesen
Um dennoch aus den Dateien Textzeilen mit Leerzeichen auslesen zu können, wird auch hier die Elementfunktion getline() verwendet. Als erster Parameter wird ein Zeiger auf char übergeben. Die Funktion arbeitet also mit den klassischen C-Strings. Der zweite Parameter ist die maximale Anzahl von Zeichen, die in den Puffer passt.[Auslesen einer Datei]
#include <fstream> #include <iostream> using namespace std; int main(int argc, char *argv[]) { fstream f; char cstring[256]; f.open(argv[1], ios::in); while (!f.eof()) { f.getline(cstring, sizeof(cstring)); cout << cstring << endl; } f.close(); }
Daneben gibt es noch eine globale Funktion namens getline(), die ein Objekt vom Typ ifstream als ersten Parameter hat. Diese globale Funktion arbeitet auch mit der Standardklasse string und akzeptiert ein Objekt dieser Klasse als zweiten Parameter. Das Beispiel oben würde dann so aussehen:
[Auslesen einer Datei (getline.cpp)]
#include <fstream> #include <iostream> #include <string> using namespace std; int main(int argc, char *argv[]) { ifstream f; // Datei-Handle string s; f.open(argv[1], ios::in); // Öffne Datei aus Parameter while (!f.eof()) // Solange noch Daten vorliegen { getline(f, s); // Lese eine Zeile cout << s << endl; // Zeige sie auf dem Bildschirm } f.close(); // Datei wieder schließen }
Datendateien
Zum Schreiben von binären Daten eignet sich das Verfahren der Datenumleitung allerdings weniger. Hier werden die Funktionen read() und write() verwendet. Damit lassen sich feste Datenblöcke lesen und schreiben. Für die Datenblöcke werden meistens Objekte als Hauptspeicherpuffer verwendet.tDaten Daten; fstream f(..., ios::out|ios::binary|ios::in); f.write(&Daten, sizeof(Daten)); ... f.read(&Daten, sizeof(Daten));
Mit dem Aufruf von fwrite() wird das Objekt Daten in eine Datei ab der aktuellen Position geschrieben. Mit dem späteren Aufruf von read() wird aus der Datei in ein Objekt Daten gelesen.
Datenpuffer
Eine Klasse, die als Datenpuffer verwendet wird, sollte natürlich auch alle Daten enthalten. Sobald die Klasse Zeiger enthält, werden Hauptspeicheradressen auf die Daten und nicht die Daten selbst gesichert. Solche Zeiger sind nicht immer offen zu sehen, sondern verbergen sich manchmal hinter Datentypen. So enthält beispielsweise ein Objekt der Klasse string Verweise auf die Zeichenkette, aber nicht die Zeichenkette selbst. Dementsprechend eignet sich für eine Dateipuffer eher ein klassischer C-String, also ein festes Array von char, als ein string oder ein Zeiger auf einen char.Beispiel
Das folgende Testprogramm sichert den ersten Parameter, mit dem das Programm aufgerufen wurde, in der Datei testdatei. Wird das Programm ohne Parameter aufgerufen, wird der zuletzt abgelegte Name wieder aus der Datei gelesen und auf dem Bildschirm ausgegeben. Das Programm liegt in zwei Versionen vor. In der Version, in der ein C-String zur Aufnahme der Daten in tDaten verwendet wird, funktioniert es einwandfrei. Entfernen Sie dagegen die Kommentarzeichen der Variablendefinition für Daten und kommentieren die bisherige Variablendefinition aus, werden die Daten in einem string-Objekt abgelegt, weil nun die Klasse tStrDaten verwendet wird. Das Programm scheitert, weil die Zeichenkette nicht mehr innerhalb der Grenzen von tStrDaten liegt und damit nicht in die Datei geschrieben wird.[Test: Datenstruktur sichern (testfstr.cpp)]
#include <fstream> #include <string> using namespace std; #include <string.h> // Diese Klasse nimmt die Zeichenkette im klassischen // char-Array auf und enthält damit die Daten. class tDaten { public: void Set(char *para) { strncpy(data, para, sizeof(data)); data[sizeof(data)-1] = 0; } void Show() { cout << data << endl; } private: char data[255]; }; // Hier werden die C++-Strings verwendet. Allerdings enthält // ein Objekt vom Typ string nicht die eigentlichen Daten. class tStrDaten { public: void Set(char *para) { data = para; } void Show() { cout << data << endl; } private: string data; }; int main(int argc, char**argv) { // tStrDaten Daten; // Damit funktioniert es nicht! tDaten Daten; // Damit funktioniert es! // Eine Datein namens testdatei wird geöffnet fstream f("testdatei", ios::out|ios::binary|ios::in); // Wurde ein Parameter beim Aufruf übergeben? if (argc>=2) { Daten.Set(argv[1]); // In Daten ablegen // Objekt in Datei speichern f.write((char *)&Daten, sizeof(Daten)); } // Kein Argument? Dann Datei auslesen! if (argc==1) { // Dateiinhalt in Objekt lesen f.read((char *)&Daten, sizeof(Daten)); Daten.Show(); // ... und anzeigen } }
Versionen
Denken Sie auch daran, dass Dateistrukturen im Allgemeinen langlebiger als Programmversionen sind. Sobald die Struktur des Dateipuffers geändert wird, sind die Dateien, die von der vorherigen Version des Programms geschrieben wurden, nicht mehr lesbar. Das wird dann kritisch, wenn die vorherige Version an den Kunden gegangen ist. Der Anwender wird erwarten, dass nach einem Update die alte Datenstruktur mindestens gelesen werden kann. Jeder Prozess [1] verwaltet für jede geöffnete Datei einen Positionszeiger. Dieser Zeiger steht nach dem Öffnen standardmäßig am Anfang der Datei und bewegt sich durch Lesen oder Schreiben immer weiter in Richtung Dateiende. Um die Position zu ändern, bietet die Klasse ifstream die Funktion seekg() an. Die Klasse ofstream verwendet dazu die Funktion seekp(). Damit kann an jeder beliebigen Stelle der Datei ein Datenblock gelesen oder geschrieben werden. Dazu wird zunächst ein Seek-Aufruf abgesetzt und anschließend gelesen oder geschrieben. Der erste Parameter der Seek-Funktionen ist die Position, die der Dateizeiger bekommen soll. Optional kann ein zweiter Parameter angegeben werden. Dieser bestimmt, aus welcher Richtung die neue Position berechnet werden soll. Dort können die folgenden Konstanten stehen: [Positionsrichtung]Wert | Bedeutung |
---|---|
ios::beg | Vom Dateianfang aus gesehen |
ios::cur | Von der aktuellen Dateiposition aus gesehen |
ios::end | Vom Dateiende aus gesehen |
Positionsbestimmung
Mit den parameterlosen Elementfunktionen tellg() und tellp() kann ermittelt werden, an welcher Position der Dateizeiger derzeit steht.Puffer leeren
Dateizugriffe werden deutlich schneller, wenn das Betriebssystem nicht sofort alle Daten auf die Festplatte schreibt, sondern sie so lange im Hauptspeicher puffert, bis mit einem Durchgang mehrere Blöcke auf einmal geschrieben werden können. Dieses Verhalten ist heutzutage auf allen Systemen Standard. Manchmal soll aber der Datensatz sofort auf die Platte geschrieben werden. Dies wird durch Aufruf der Elementfunktion flush() der Klasse fstream erzwungen.Zustandsbeobachtung
Bei der Arbeit mit Dateien können diverse Probleme entstehen, auf die das Programm vorbereitet sein sollte. So könnte eine Datei, die das Programm lesen will, gar nicht existieren oder kürzer sein als erwartet. Beim Schreiben könnte das Schreibrecht fehlen oder die Platte voll sein.good()
Das Stream-Objekt kann jederzeit mit der Elementfunktion good() gefragt werden, ob bei der letzten Aktion Fehler festgestellt worden sind. Die Funktion good() hat keine Parameter und liefert einen booleschen Wert zurück. Alternativ kann auch das Stream-Objekt selbst abgefragt werden. Enthält sie eine 0, entspricht das dem Rückgabewert false der Funktion good().fstream Datei; ... if (Datei.good()) { // Alles super ... if (Datei) { // Auch gut
Das folgende Programm öffnet eine Datei, schreibt einen Datensatz hinein, geht wieder an den Anfang der Datei zurück und liest dann den zuvor geschriebenen Satz. Dabei prüft es jeden Schritt direkt nach seiner Ausführung.
fstream f("test.dat", ios::out|ios::binary|ios::in); if (!f.good()) { cerr << "Fehler beim Öffnen von test.dat" << endl; } f.write(&Daten, sizeof(Daten)); if (!f.good()) { cerr <<"Fehler beim Schreiben von test.dat"<< endl; } f.seekg(0, ios::beg); f.read(&Daten, sizeof(Daten)); if (!f.good()) { cerr << "Fehler beim Lesen von test.dat" << endl; }
Dateiende
Wird aus dem Beispiel die Zeile mit dem Seek-Befehl herausgenommen, so würde der anschließende Lesebefehl einen Fehler verursachen, wenn die Datei zu Anfang leer war. Durch den Schreibbefehl steht der Dateipositionszeiger am Ende der Datei. Der folgende Lesebefehl kann nicht mehr genug Daten aus der Datei lesen, um die Puffervariable Daten zu füllen. Dieser Zustand wird als EOF (End Of File) bezeichnet. Das Erreichen des Dateiendes ist keine Katastrophe. Es ist durchaus üblich, eine Datei Satz für Satz auszulesen, bis das Ende der Datei erreicht wird. Um das Dateiende von anderen Fehlern zu unterscheiden, gibt es eine eigene Funktion namens eof(). Sie liefert so lange den Rückgabewert false, bis das Ende der Datei überschritten wurde. Die folgende Schleife liest also alle Daten einer Datei:while (!f.eof()) { f.read(&Daten, sizeof(Daten)); } f.clear();
Fehlerbits löschen
Nachdem ein Fehler aufgetreten ist, liefert die Funktion good() diesen Fehler so lange zurück, bis durch den Aufruf von clear() der Fehlerzustand des Stream-Objekts zurückgesetzt wurde. Im obigen Beispiel wurde dies nach dem Ende der Schleife durchgeführt. Die Schleife wurde ja durch einen EOF verlassen.fail()
Neben dem Dateiende gibt es noch die Möglichkeit, dass das Schreiben oder Lesen fehlschlägt, weil der Aufruf im gewünschten Umfang nicht abgewickelt werden konnte. In einem solchen Fall liefert die Elementfunktion fail() von fstream den Wert true zurück.bad()
Ein schwerwiegender Fehler kann durch Abfrage der Elementfunktion bad() von fstream festgestellt werden. Nach dem Auftreten eines solchen Fehlers werden weitere Operationen scheitern.Exceptions auslösen
Durch den Aufruf der Elementfunktion exceptions() von fstream können Sie festlegen, welche Fehlerzustände eine Ausnahmebehandlung auslösen sollen. Als Parameter können die Konstanten- ios::failbit,
- ios::badbit und
- ios::eofbit
fstream datei; datei.exceptions(fstream::eofbit | fstream::failbit | fstream::badbit); try { // ... Dateiverarbeitung } catch(const fstream::failure& e) { cout << "Exception" << endl; cout << "Grund: " << e.what() << endl; cout << "Fehlernummer: " << e.code() << endl; }Mit catch fangen Sie die Exception vom Typ fstream::failure und können diese mit what nach einer Fehlerbeschreibung und mit code nach einer Fehlernummer fragen.
Zum Thema Ausnahmebehandlungen finden Sie an anderer Stelle ausführlichere Informationen. Dabei wird der Umgang mit den Ausnahmen von fstream in einem eigenen Abschnitt behandelt.
Dateizugriffe nach ANSI-C
Dateien gibt es ja nun nicht erst, seit es C++ gibt, und so ist es nahe liegend, dass bereits C Funktionen zur Verfügung stellt, um Dateien zu bearbeiten. Viele C++-Programmierer haben mit C angefangen, und so findet man auch in C++-Programmen häufig diese Art der Dateibehandlung. Die Kenntnis dieser Dateizugriffe ist wichtig, wenn Sie ein älteres Programm erweitern oder korrigieren sollen. Neuere Programme sollten mit den fstream-Klassen geschrieben werden. Die Vorteile der neuen Klassen sind:- Die Operationen werden auf Typsicherheit überprüft.
- Sie können eine Ausnahmebehandlung verwenden.
- Wenn Sie ostream und istream verwenden, kann bereits beim Kompilieren sichergestellt werden, dass nicht versehentlich in Dateien geschrieben wird, die nur zum Lesen geöffnet wurden.
Handles
Da C keine Klassen kennt, erfolgen die Zugriffe auf Dateien über eine Sammlung von Funktionen. Beim Öffnen der Datei bekommen Sie ein so genanntes Handle (engl. Handgriff) zurück. Diesen Wert müssen Sie bei jedem Zugriff auf die Datei übergeben, damit das Betriebssystem weiß, mit welcher Datei Sie arbeiten. Dieses Handle ist bei den ANSI-C-Funktionen ein Zeiger auf den Typ FILE. Das folgende Beispiel zeigt, wie ein Datensatz in eine Datei geschrieben und anschließend wieder ausgelesen wird.[Dateibehandlung]
#include <stdio.h> int main() { FILE *f; f = fopen("test.dat", "rwb"); if (f) { fwrite(puffer, sizeof(puffer), 1, f); fseek(f, 0, SEEK_SET); fread(puffer, sizeof(puffer), 1, f); fclose(f); } }
Öffnen und schließen
Die Datei wird durch den Aufruf von fopen() geöffnet und durch den Aufruf von fclose() geschlossen. Die Funktionen haben die folgenden Prototypen:FILE *fopen(const char* DateiName, const char* Modus); int fclose(FILE *DateiHandle);
Der erste Parameter von fopen() ist der Dateiname. Der zweite Parameter ist der Öffnungsmodus, der als Zeichenkette eines oder mehrerer Buchstaben übergeben wird. Tabelle (tabfilemodus) zeigt eine Übersicht. [Öffnungsmodi bei fopen()]
Zeichen | Bedeutung |
---|---|
r | Zum Lesen öffnen |
w | Datei leeren und zum Schreiben öffnen |
a | Daten werden angehängt |
r+ | Neben dem Lesen auch das Schreiben zulassen |
w+ | Datei leeren und zum Schreiben und Lesen öffnen |
b | Binärdatei (keine Konvertierung der Zeilenendezeichen) |
Lesen und schreiben
Die Funktion fwrite() schreibt einen Datenblock in die Datei. Die Funktion fread() liest einen Datenblock aus der Datei. Die Funktionen haben folgende Prototypen:size_t fread(void *Puffer, size_t Groesse, size_t n, FILE *f); size_t fwrite(void *Puffer, size_t Groesse, size_t n, FILE *f);
Parameter
Der erste Parameter ist ein Zeiger auf den Puffer, in dem die Daten im Hauptspeicher liegen. Da der Parametertyp ein Zeiger auf void ist, kann die Adresse einer beliebigen Speicherstruktur übergeben werden. Die nächsten beiden Parameter beschreiben die Größe des Puffers. Zunächst wird die Größe jedes Blocks angegeben, dann die Anzahl der Blöcke. Werden Datendateien verwendet, deren Puffer eine Datenstruktur ist, wird der Parameter Groesse typischerweise durch ein sizeof() der Klasse bestimmt und die Anzahl n auf 1 gesetzt. Wird dagegen eine Textdatei verarbeitet, ist es sinnvoll, Groesse auf 1 zu setzen und die Anzahl n auf die gewünschte Puffergröße zu setzen. Der Rückgabewert beider Funktionen ist die Anzahl der Blöcke, die tatsächlich verarbeitet werden konnten. Stimmt n also mit dem Rückgabewert nicht überein, ist etwas schief gegangen. Der letzte Parameter ist das Datei-Handle.Textdateien
Wenn Sie mit Textdateien arbeiten, gibt es zwei spezielle Funktionen, die für diesen Zweck etwas einfacher in der Handhabung sind: fgets() und fputs(). Die Funktion fgets() liest eine Textzeile in einen Puffer. Die Länge wird durch einen Parameter begrenzt, um einen Pufferüberlauf zu vermeiden. Die Funktion fputs() schreibt eine Zeichenkette in die Datei. Dabei wird die Länge durch eine 0 begrenzt, so wie das bei C-Strings üblich ist:char *fgets(char *Puffer, int Groesse, FILE *f); char *fputs(char *Puffer, FILE *f);
Mit der Funktion fprintf() ist es möglich, die Daten formatiert in eine Datei zu schreiben. Abgesehen von dem Datei-Handle entsprechen die Parameter exakt der Funktion printf().
char *fprintf(FILE *f, char *Format, ...);
Positionieren
Mit der Funktion fseek() kann der Dateipositionszeiger verschoben werden. Bei den C-Dateien gibt es nur einen gemeinsamen Positionszeiger für das Lesen und Schreiben.int fseek(FILE *f, long Offset, int RelPos);
Der erste Parameter ist das Datei-Handle, der zweite ist der Abstand, und der dritte ist eine Konstante, die beschreibt, worauf sich der Abstand bezieht.
[fseek(): relative Position]
Konstante | Bedeutung |
---|---|
SEEK_SET | Ab dem Anfang der Datei |
SEEK_CUR | Ab der aktuellen Position |
SEEK_END | Ab dem Ende der Datei |
End Of File
Die Funktion feof() ermittelt, ob das Ende der Datei erreicht wurde. Als einzigen Parameter hat die Funktion das Datei-Handle. Der Rückgabewert ist 0, wenn das Ende der Datei nicht erreicht wurde.int feof(FILE *f);
Dateisystemkommandos
Die folgenden Aufrufe gehören zum ANSI-Standard für C und C++ und ermöglichen dem Programm den grundlegenden Umgang mit dem Dateisystem.Datei löschen: remove
Die Funktion remove() löscht die Datei, deren Name ihr als Parameter übergeben wird.#include <stdio.h> int remove(const char *dateiname);
Die globale Variable errno
Die Funktion gibt 0 bei Erfolg und --1 bei einem Fehler zurück. Die Fehlerursache steht in der Variablen errno. Die meisten Systemaufrufe liefern einen Rückgabewert kleiner als 0, wenn etwas schief gelaufen ist. Ist der Rückgabewert nicht aussagekräftig genug, wird die Fehlerursache in der globalen Variable errno kodiert. Die Konstanten, die errno annehmen kann, stehen in der Header-Datei errno.h zur Verfügung. So kann beim Aufruf von remove() unter anderem eine der folgenden Konstanten in der Variable errno zu finden sein: [Fehlerkonstanten]Konstante | Bedeutung |
---|---|
EACCES | Fehlende Schreibberechtigung |
ENOENT | Diese Datei gibt es nicht. |
EROFS | Datei befindet sich auf einem schreibgeschützten Medium. |
Umbenennen: rename
Die Funktion zum Umbenennen von Dateien heißt rename(). Unter UNIX ist es mit dieser Funktion auch möglich, Dateien innerhalb des gleichen Dateisystems zu verschieben.#include <stdio.h> int rename(const char *dateiname, const char *neuname);
Die Funktion gibt 0 bei Erfolg und --1 bei einem Fehler zurück. Die Fehlerursache steht in der Variablen errno.
Weitere Funktionen
Die Kommandos zum Umgang mit Verzeichnissen gehören leider nicht zum ANSI-Standard. Unter UNIX sind sie in jedem Fall verfügbar und nach POSIX [2] standardisiert. Auch unter Windows werden sie von vielen Compilern unterstützt. Aus diesem Grund sind hier die Stichwörter aufgezählt, die Sie benötigen, um in der Hilfe Ihres Compilers nachzuschlagen, ob die Funktionen unterstützt werden. Unter UNIX geben Sie das Kommando man, gefolgt von der Funktion, an und erhalten den Prototyp und die Beschreibung der Funktion. [Verzeichnisfunktionen]Funktion | Wirkung |
---|---|
mkdir | Erzeugt ein Verzeichnis |
chdir | Wechselt das Verzeichnis |
rmdir | Löscht ein leeres Verzeichnis |
opendir | Öffnet ein Verzeichnis zur Suche nach Dateien |
readdir | Liest den nächsten Eintrag im Verzeichnis |
closedir | Schließt das Verzeichnis wieder |
Verzeichnis
Das Auslesen eines Verzeichnisses ist zwar durch den ANSI-Standard nicht abgedeckt, aber immerhin durch POSIX. Dieser Standard für UNIX wird auch von einigen C-Compilern implementiert. Da das Auslesen von Verzeichnissen immer wieder benötigt wird, soll es dennoch zumindest kurz behandelt werden. Hier folgt ein kleines Programm, das alle Dateinamen eines Verzeichnisses auf dem Bildschirm ausgibt.[Auslesen eines Verzeichnisses (dir.cpp)]
#include <dirent.h> #include <iostream> using namespace std; int main() { DIR *hdir; struct dirent *entry; hdir = opendir("."); do { entry = readdir(hdir); if (entry) { cout << entry->d_name << endl; } } while (entry); closedir(hdir); }
Der Zeiger auf DIR ist das benötigte Handle auf das Verzeichnis. Die Funktion opendir() erhält als Parameter den Verzeichnisnamen. Bei manchen Compilern gab es Schwierigkeiten, andere Verzeichnisse als das aktuelle Verzeichnis auszulesen. Das sollten Sie bei Ihrem Compiler testen. Nach dem Eröffnen des Verzeichnisses liefert die Funktion readdir() entweder einen Zeiger auf eine Variable der Verzeichniseintragsstruktur dirent oder eine 0, wenn kein weiterer Eintrag im Verzeichnis existiert. Zu guter Letzt wird das Verzeichnis wieder geschlossen. Das einzige im POSIX-Standard garantierte Element von dirent ist der Dateiname unter d_name. Das ist allerdings auch ausreichend, denn über den Namen lassen sich beispielsweise mit der Funktion stat() weitere Informationen ermitteln.
Datei-Eigenschaften ermitteln
Der UNIX-Systemaufruf stat() ist mit der C-Bibliothek auch auf andere Systeme portiert worden. Allerdings gehört diese Funktion nicht zum ANSI-Standard. Mit diesem Aufruf können Informationen über Dateien erlangt werden. Der Funktionsaufruf ist unter UNIX auf jeden Fall verfügbar. Unter Windows verstehen ihn die Borland-Compiler und natürlich die GNU-Compiler. Visual C++ bietet den Aufruf unter dem Namen _stat() an.stat()
Mit den Funktionen stat() und fstat() können Sie Informationen über eine Datei ermitteln. Die Ergebnisse werden in einer Struktur vom Typ stat[3] abgelegt. Sie müssen eine Variable dieses Typs anlegen und deren Adresse der Funktion stat() übergeben. Die Funktion hat folgenden Prototyp:#include <sys/types.h> #include <sys/stat.h> int stat(char *dateiname, struct stat *puffer);
Die Ergebnisse stehen in der Variablen von Typ stat, auf die der Parameter puffer zeigt. Die Definition der Struktur beinhaltet die folgenden Elemente:
struct stat { dev_t st_dev /* (P) Device, auf dem die Datei liegt */ ushort st_ino /* (P) i-node-Nummer */ ushort st_mode /* (P) Dateityp */ short st_nlink /* (P) Anzahl der Links der Datei */ ushort st_uid /* (P) Eigentuemer-User-ID (uid) */ ushort st_gid /* (P) Gruppen-ID (gid) */ dev_t st_rdev /* Major- und Minornumber, falls Device */ off_t st_size /* (P) Größe in Byte */ time_t st_atime /* (P) Zeitpunkt letzter Zugriffs */ time_t st_mtime /* (P) Zeitpunkt letzte Änderung */ time_t st_ctime /* (P) Zeitpunkt letzte Statusänderung */ };
Die Bestandteile dieser Struktur können sich je nach System unterscheiden. Die mit (P) gekennzeichneten Elemente sind aber unter UNIX zwingend von POSIX vorgeschrieben.
- st_dev und st_ino
- Unter UNIX beschreiben st_dev und st_ino eindeutig den Ort einer Datei. st_dev ist das Device, bei Festplatten also die Partition. st_ino bezeichnet den i-node, eine unter UNIX eindeutige Kennnummer für Dateien einer Partition. Unter Windows wird in st_dev die Nummer des Laufwerks hinterlegt. st_ino ist immer 0.
- st_mode
-
Die rechten zwölf Bits von st_mode beschreiben, ob das Programm die Datei
schreiben, lesen oder ausführen kann. Unter UNIX können mit diesen Informationen
die Rechte für Benutzer, Gruppen und den Rest der Welt unterschieden werden.
Tabelle beschreibt, wie die rechten neun Bits von st_mode
die Rechte kodieren. [4]
[Rechte für eine Datei]
Benutzer Gruppe Welt Lesen 4 4 4 Schreiben 2 2 2 Ausführen 1 1 1 Konstante Dateityp S_IFSOCK Sockets S_IFLNK Symbolische Links S_IFREG Reguläre Dateien S_IFBLK Block-Devices S_IFDIR Verzeichnisse S_IFCHR Char-Devices S_IFIFO FIFOs [Dateityp und Rechte bestimmen]
#include <sys/types.h> #include <sys/stat.h> int main(int argc, char **argv) { struct stat Status; int Dateityp; stat(argv[1], &Status); printf("Dateirechte: %o \n", Status.st_mode & ~S_IFMT); Dateityp = Status.st_mode & S_IFMT; switch (Dateityp) { case S_IFREG: puts("Datei"); break; case S_IFLNK: puts("Symbolischer Link"); break; case S_IFDIR: puts("Verzeichnis"); break; default: puts("Sonstiges"); } }
Die Konstanten S_IFREG (reguläre Dateien) und S_IFDIR (Verzeichnisse) werden auch unter Windows unterstützt. Damit können also Dateien und Verzeichnisse unterschieden werden. Die anderen Dateitypen sind unter Windows unbekannt.
- st_nlink
- In st_nlink steht, wie viele harte Links auf die Datei zeigen. Da Windows keine Links anlegen kann, ist dieser Wert dort immer 1.
- st_uid und st_gid
- Mit st_uid und st_gid werden der Besitzer und die Besitzergruppe ermittelt. Der Wert ist eine Zahl, nämlich die, die in der Datei /etc/passwd bzw. in /etc/group festgelegt wird. Unter Windows steht hier immer 0.
- st_rdev
- In st_rdev ist die Major- und Minornummer codiert, sofern es sich bei der Datei um ein Device handelt. Unter Windows steht hier die Laufwerksnummer.
- st_size
- Sofern es sich bei der Datei um eine reguläre Datei handelt, finden Sie in st_size die Größe in Bytes.
- st_atime, st_mtime und st_ctime
- Jeder lesende oder schreibende Zugriff auf die Datei aktualisiert den Wert st_atime. Jede Veränderung des Dateiinhalts wird in st_mtime notiert. Der Zeitpunkt der Änderungen des Benutzers, der Rechte, der Linkzahl oder von Ähnlichem (also von allem, was nicht den Inhalt betrifft) wird in st_ctime festgehalten.
- [1]
- Ein Prozess ist ein gestartetes Programm. Wird ein Programm mehrfach gestartet, laufen mehrere Prozesse desselben Programms.
- [2]
- POSIX (Portable Operating System Interface) ist eine Familie von Standards für die UNIX-Schnittstellen, die vom IEEE (Institute for Electrical und Electronic Engineers) verbindlich festgelegt wurden.
- [3]
- Unter Visual C++ heißt auch die Struktur _stat.
- [4]
- Die fehlenden Bits sind extrem UNIX-spezifisch.