1 Grundsätzliches

Um Daten eines Programms permanent speichern zu können, müssen diese aus dem flüchtigen Arbeitsspeicher in eine Datei auf einem beliebigen Datenträger (Festplatte, CD-ROM, USB-Stick, etc.) transferiert werden.

Wenn per Programmanweisung (siehe später) Daten in eine Datei geschrieben werden, dann werden diese Daten nicht unbedingt sofort auf dem externen Datenspeicher gespeichert. Vielmehr werden sie zunächst in einem Dateipuffer abgelegt, der sich im Arbeitsspeicher (Hauptspeicher, RAM) befindet. Erst wenn der Dateipuffer voll ist wird der Inhalt in die Datei geschrieben. Alternativ kann dieser Vorgang auch per Programmanweisung erzwungen werden.

Die Daten in der Datei kann man sich als Aufeinanderfolge einzelner Datensätze vorstellen, ähnlich einem eindimensionalen Array. Die Datensatzgröße kann dabei von einem Byte bis beliebig vielen Bytes reichen.

Eine besondere Bedeutung kommt den Markierungen BOF (Begin Of File) und EOF (End Of File) zu. BOF markiert den Dateianfang , EOF das Dateiende. Jede Datei, egal ob Inhalte vorhanden sind oder nicht, hat die Markierungen BOF und EOF.

Der Zugriff auf eine Datei erfolgt über einen Dateizeiger (grafisch als Pfeil dargestellt). Dieser Zeiger steht beim Öffnen einer Datei an der Position, an der die nächste Dateioperation beginnt. Nach dem Öffnen einer Datei zeigt er auf die erste Position, also BOF.

2 Ablauf einer beliebigen Dateioperation

AWP - File IO

3 Dateioperationen

3.1 Datei-Stream öffnen: fopen_s()

Eine Datei muss Immer erst geöffnet werden, bevor mit Ihr gearbeitet werden kann (siehe Skizze oben).

Syntax:

errno_t fopen_s(FILE** pFile, const char *Dateiname, const char *Zugriffsmodus);

Bedeutung der Parameter:

Parameter Beschreibung
FILE** pFile Ein Zeiger auf den Dateizeiger, der den Zeiger auf die geöffnete Datei erhält. Das genau Ist der Dateizeiger, von dem oben schon die Rede war, und der von allen weiteren Dateifunktionen benötigt wird!
const char *dateiname Angabe des Dateinamens der zu verarbeitenden Datei. Der Name muss in doppelten Anführungszeichen stehen.
Beispiele: “C:\Projekte\Prozessdaten.dat” oder “..\Prozessdaten.dat”
const char *Zugriffsmodus Mit dem Zugriffsmodus wird angegeben, ob die Datei zum Lesen, Schreiben oder Anhängen geöffnet werden soll.

Bedeutung der Zugriffsmodi:

Bewirkt r w a
Datei ist lesbar x    
Datei ist beschreibbar   x x
Vorhandener Dateiinhalt wird gelöscht und Inhalte an den Anfang der Datei geschrieben   x  
Vorhandener Dateiinhalt bleibt erhalten und neue Inhalte werden ans Ende der Datei geschrieben     x

r = read, w = write, a = append

Bedeutung des Rückgabewertes:
Der Rückgabewert der Funktion ist NULL, wenn es erfolgreich war oder ein Fehlercode, wenn ein Fehler auftritt.

Beispiel:

FILE* fpDatei = nullptr;
fopen_s(&fpDatei, "Kunde.dat", "r"); // Öffnen im Lesemodus

3.2 Datei-Stream schließen: fclose()

Die Funktion fclose() schließt eine Datei, die zuvor mit fopen_s() geöffnet wurde. Dies ist notwendig, da die Anzahl der geöffneten Dateien begrenzt ist. Zudem wird eine im Schreibmodus geöffnete Datei erst dann beschrieben, wenn der Puffer voll ist. Ist der Puffer nur teilweise voll und das Programm beendet sich mit einem Fehler, dann sind die Daten im Puffer verloren.

Syntax

int fclose(FILE *stream);

Bedeutung der Parameter:

Parameter Beschreibung
FILE* stream Der File-Stream der geschlossen werden soll.

Bedeutung des Rückgabewertes:
Die Funktion liefert bei fehlerfreier Funktion den Wert O zurück.

Beispiel: fclose(fpDatei);

3.3 Schreiben einer bestimmten Anzahl von Bytes in eine Datei: fwrite()

Das Schreiben In Dateien kann auf unterschiedliche Arten erfolgen. In diesem Abschnitt wird das blockweise Schreiben beschrieben. Dabei werden so viele Byte eingelesen bzw. geschrieben, wie beim Aufruf der entsprechenden Routine angegeben wurden.

Syntax

size t fwrite(const void *ptr, size_t size, size_t n, FILE *stream);

Der Rückgabewert der Funktion ist die Anzahl der geschrieben Speicherobjekte.

Bedeutung der Parameter

Parameter Beschreibung
const void *ptr Hier erwartet die Funktion die Anfangsadresse des zu schreibenden Speicherbereichs. Das kann die Adresse einer gewöhnlichen Variable (z. B. vom Typ int) sein, genauso gut aber auch die Anfangsadresse eines Arrays eines beliebigen Datentyps oder gar die Adresse einer Strukturvariablen von einem selbst definierten Datentyp.
size_t size Das ist der Parameter für die Größe eines zu schreibenden Speicherblocks. Bei einer int-Variablen müsste man hier 4 angeben, bei double 8 und bei einer Variablen eines selbst definierten Datentyps eben die Größe der entsprechenden Datenstruktur. Da die Größe von der Rechnerarchitektur abhängig ist, ist es möglich mit der Funktion sizeof(Datentyp) die erforderliche Größe vom Compiler bestimmen zu lassen.
size_t n Die Anzahl von zu schreibenden Speicherblöcken. Handelt es sich um eine einzelne Variable, muss hier immer 1 stehen. Im Falle eines Arrays muss die Zahl der zu speichernden Arrayelemente angegeben werden.
FILE *stream Das ist der Dateizeiger, der nach dem Aufruf fopen_s() auf einen Dateipuffer im Arbeitsspeicher (und damit letztlich auf die Datei an sich) zeigt.

Bedeutung des Rückgabewertes:
Liefert die ANzahl der Geschriebenen Speicherobjekte (nicht Bytes!) zurück.

Beispiel

fwrite(&iZahl, 4, 1, fpDatei);                              // oder
fwrite(&iZahlenreihe[0], sizeof(int), 5, fpDatei);          // oder
fwrite(iZahlenreihe, sizeof(iZahlenreihe[0]), 5, fpDatei);

3.4 Lesen einer bestimmten Anzahl von Bytes aus einer Datei: fread()

Syntax

size_t fread(void *ptr, size_t size, size_t n, FILE *stream);

Bedeutung der Parameter:
Die Bedeutung der Parameter und des Rückgabewertes ist identisch zu der der Funktion fwrite(). Lediglich der erste Parameter unterscheidet sich. Hier geben Sie den Beginn des Speicherbereiches an, in den die Datei geschrieben werden soll. Wichtig dabei ist: Die Größe des Zieldatenbereichs muss mindestens so groß sein, wie die Größe der zu lesenden Daten!

Beispiel

fread(&iZahl, 4, 1, fpDatei);                               // oder
fread(&iZahlenreihe[0], sizeof(int), 5, fpDatei);           // oder
fread(iZahlenreihe, sizeof(iZahlenreihe[0]), 5, fpDatei);

3.5 Test auf Dateiende: feof() (Array mit unbekannter Größe)

Die bisherige Lösung funktioniert nur, solange Sie die Anzahl an zu lesenden Datensätzen kennen. Ist diese unbekannt, dann können Sie beim Elnlesen nicht die Anzahl der Blöcke vorgeben. Sie können aber nach jedem Lesevorgang prüfen, ob das Dateiende (EOF) bereits erreicht wurde.

Syntax

int feof(FILE *stream);

Bedeutung des Parameters

Parameter Beschreibung
FILE *stream Das ist der Dateizeiger, der nach dem Aufruf von fopen_s() auf einen Dateipuffer im Arbeitsspeicher (und damit letztlich auf die Datei an sich) zeigt.

Bedeutung des Rückgabewertes:
Die Funktion liefert O ( = false) zurück, wenn das Dateiende noch nicht erreicht wurde. Bei erreichtem Dateiende liefert sie einen Zahlenwert<> O zurück(= true).

Wichtig zu wissen ist hier: Das Flag, dessen Wert die Funktion feof() zurückliefert, wird immer nur durch eine Dateifunktion verändert, die den Dateizeiger positioniert.

Beispiel

FILE *fpDatei = nullptr; // Dateizeiger
T_Artikel tArtikel[100]; // Platz für 100 Artikel
T_Artikel tArtikelBuffer; // Platz für einen temporären Artikel
int iAktlndex = 0;

// Öffnen der Datei im Dateimodus „read"
fopen_s(&fpDatei, "Artikel.sbd", "r");

// Prüfen, ob Öffnen erfolgreich war
if (fpDatei == nullptr)
{
    cout << "Fehler: Die Datei konnte nicht geöffnet werden!";
}
else
{
    // NOTE: Dieser Block enthält die EOF Prüfung
    do
    {
        //Zunächst wird in einen Buffer gelesen
        fread(&tArtikelBuffer ,sizeof(T_Artikel), 1, fpDatei);
        if(!feof(fpDatei)) // wenn EOF noch nicht erreicht
        {
            tArtikel[iAktlndex] = tArtikelBuffer; //Puffer ins Array
            iAktindex++; // Erhöhe Zähler um 1
        }
    } while(!feof(fpDatei)); // Solange Dateiende nicht erreicht

    //Datei schließen
    fclose(fpDatei);
}

// Ausgabe der gelesenen Daten auf dem Bildschirm
for(int i=0; i < iAktlndex; i++){
    cout <<"Datensatz"<< i+l << ": "<< adMesswerte[i];
}

4 Beispiel für Test auf Dateiende

Nehmen wir an, dass die Datei, aus der wir lesen wollen, leer ist. In diesem Fall steht der Dateizeiger nach dem Öffnen zunächst auf BOF. Mit einem Aufruf von feof() werden wir also false zurück geliefert bekommen. Daraus zu schließen, dass eine anschließende Leseoperation einen gültigen Datensatz liefert, wäre natürlich falsch, denn die Datei ist ja erst leer gelesen wird. Deshalb muss [z. die B. mit Reihenfolge fread()] und dann Einlesen mithilfe von feof() geprüft wird, ob das Dateiende erreicht ist. Falls ja, muss der mit der letzten Leseoperation “eingelesene” Datensatz verworfen werden.

Betrachten wir der Deutlichkeit halber eine Datei mit Datensätzen, kurz DS:

  1. Nach dem Öffnen steht der Dateizeiger wieder auf BOF. Nun wird mittels fread()-Aufruf der erste Datensatz gelesen.
  2. Nach dem ersten Aufruf von fread() steht der Dateizeiger auf dem ersten Datensatz und die Prüfung feof() ist false.
  3. Entsprechend nach dem zweiten Aufruf von fread() auf dem zweiten Datensatz und die Prüfung feof() ist erneut false.
  4. Nun erfolgt ein dritter Aufruf von fread(). Das führt nun dazu, dass der Dateizeiger auf die Ende-Markierung EOF gesetzt wird. Somit liefert die Funktion feof() erst nach dem dritten Aufruf von fread() tatsächlich true!

Hätten wir die Prüfung auf Dateiende jeweils vor fread() durchgeführt, damit also auch vor dem dritten Aufruf von fread(), hätte feof() false ergeben und wir wären fälschlicherweise davon ausgegangen, dass die Datei noch (mindestens) einen Datensatz enthält!

5 Vollständiges Datei-Beispiel für Schreiben fwrite()

FILE *fpDatei = nullptr;
double adMesswerte[25];
int iAnzahl;

// Öffnen der Datei im Dateimodus "write"
fopen_s(&fpDatei, "Messwerte.sbd", "w");

// Prüfen, ob Öffnen erfolgreich war
if (fpDatei == nullptr)
    cout « "Fehler: Die Datei konnte nicht geöffnet werden!";
else {
    iAnzahl = fwrite(adMesswerte, sizeof(double), 25, fpDatei);
    if (iAnzahll= 25){
        cout << "Nicht alle Werte konnten gespeichert werden!";
    }
}
fclose(fpDatei);