11. Datei-Ein- und Ausgabe in C

Das Einlesen von Dateien und das Schreiben in Dateien wird im allgemeinen Datei-Ein- und Ausgabe genannt. Genau wie bei der Bildschirmausgabe und der Eingabe über Tastatur ist auch hier von Strömen die Rede, in die hineingeschrieben und/oder aus denen gelesen wird. Die allgemeine Vorgehensweise ist dabei immer gleich. Zuerst muss die Datei geöffnet werden, dann wird die Position gesucht, ab der entweder gelesen bzw. geschrieben wird, schließlich wird die eigentliche Aktion durchgeführt, nämlich das Lesen oder Schreiben der Daten und zum Schluss wird die Datei wieder geschlossen. Ganz ähnlich gehen Sie ja auch bei einer Textverarbeitung vor: Textdatei öffnen (Einlesen), Änderungen vornehmen, speichern (Schreiben) und wieder schließen.

Der Zugriff auf Dateien wird hier durch einen Zeiger auf einen Datenstrom durchgeführt; der Datentyp lautet FILE *. Verwendet wird er wie ein normaler Datentyp. Im folgenden Beispiel wird eine Zeigervariable namens Datei für einen Dateizugriff definiert (es können nur Zeiger verwendet werden!).

Beispiel:

FILE *Datei;

Mit dieser Anweisung ist noch kein Dateiname und kein Pfad spezifiziert, aber damit ist eine Zeiger-Variable geschaffen, die später auf den Datenstrom einer Datei zeigt. Die Spezifizierung von Dateiname und Pfad geschieht erst beim Öffnen einer Datei. Dabei wird der Datenstrom geöffnet und der zuvor definierte Zeiger auf FILE zeigt auf den Datenstrom. Anschließend wird bei jedem Dateizugriff dieser Datenstrom-Zeiger angegeben, um festzulegen, in welche Datei geschrieben bzw. aus welcher Datei gelesen werden soll.

11.1. Öffnen und Schließen von Dateien

Zum Öffnen einer Datei wird die fopen-Funktion verwendet. Nur hier werden Name und Pfad der Datei angegeben. Ferner muss der Modus (siehe Tabelle weiter unten) mitgeteilt werden, in dem die Datei geöffnet werden soll.

Bei der Angabe des Pfades können unter Windows die Backslashs wahlweise als doppelte Backslashs ('\\') oder als einfache Slashs ('/') angegeben werden; letzteres ist zu empfehlen, da hiermit die Kompatibilität zu Linux-Systemen erhalten bleibt.

Beispiel:

kap11_01.c

01 #include <stdio.h>
02
03 int main()
04 {
05    FILE *Datei;
06
07    Datei = fopen("U:/TEST.TXT", "rb");
08    if (Datei == NULL)
09       printf("Datei konnte nicht geöffnet werden!\n");
10    else
11    {
12       /*
13       ...                 Lesen bzw. Schreiben von Daten
14       */
15       fclose(Datei);   /* Datei schließen                */
16    }
17
18    return 0;
19 }

In diesem Programm wird - wie oben bereits erläutert - ein Zeiger auf den Datenstrom definiert. Dann wird die fopen-Funktion angewendet mit dem Dateinamen (inkl. Pfad) und dem Dateimodus als Parameter. Das Ergebnis ist ein Zeiger auf den Datenstrom dieser Datei und wird in der Variablen Datei gespeichert. Anschließend wird dieser Zeiger geprüft, ob er gleich dem NULL-Zeiger ist. Dies ist dann der Fall, wenn beim Öffnen der Datei ein Fehler aufgetreten ist und die Datei nicht geöffnet werden konnte (z.B. weil die Datei nicht existiert). Ist die Datei geöffnet, können Daten aus der Datei gelesen bzw. in die Datei geschrieben werden (genaueres dazu in den nächsten zwei Abschnitten). Zum Schluss wird die Datei mit der fclose-Funktion wieder geschlossen. Als Parameter wird hier der Zeiger auf den Datenstrom übergeben. Auch die fclose-Funktion hat ein Ergebnis, das gleich 0 ist, wenn beim Schließen der Datei kein Fehler aufgetreten ist, und sonst ungleich 0 ist. Hier wird aber dieses Ergebnis nicht verarbeitet.

Am Programmende werden alle geöffneten Dateien automatisch geschlossen, von daher wäre die fclose-Funktion nicht nötig. Das Weglassen der fclose-Funktion birgt aber in größeren Programmen eine Fehlerquelle und ist deshalb kein sauberer Programmierstil! Alternativ können alle noch offenen Dateien mit der _fcloseall-Funktion auf einen Schlag geschlossen werden (Aufruf: _fcloseall();).

Hier nun die Tabelle mit den verschiedenen Modi zum Öffnen von Dateien:

Modus

Parameter

Lesemodus (read)

"r"

Schreibmodus (write)

"w"

Anhängen (append)

"a"

Binärmodus (binary)

"b"

Textmodus (text)

"t"


Die Modi können auch kombiniert werden. Z.B.

Modus

Parameter

binäres Lesen

"rb"

Anhängen an Textdatei

"at"

Lesen und Schreiben einer Textdatei

"rwt"


11.2. Ausgabe in Dateien

Daten können formatiert oder unformatiert in Dateien geschrieben werden. Das formatierte Schreiben gleicht der Bildschirmausgabe. Der Befehl lautet hier fprintf; die komplette Syntax sieht wie folgt aus:

int fprintf(FILE *stream, char *format[, arguments]);

Der Unterschied gegenüber dem printf-Befehl besteht aus dem "f", das vor den Befehl gesetzt wird und aus dem zusätzlichen Parameter, der den Datenstrom, also die Datei angibt. Die weiteren Parameter haben sich nicht verändert (siehe Kapitel Datenausgabe auf dem Bildschirm).

Das folgende Beispiel erzeugt eine neue Datei und schreibt 10 Zahlen in diese Datei hinein. Die Zahlen werden beim Schreiben in die Datei auch auf dem Bildschirm ausgegeben.

Beispiel:

kap11_02.c

01 #include <stdio.h>
02
03 int main()
04 {
05    FILE *Datei;
06    int i, Zahl[10] = { 7, 3, 9, 15, 4, 27, 98, 34, 85, 62};
07    char fname[] = "U:/TEST.TXT";
08
09    Datei = fopen(fname, "w");
10    if (Datei == NULL)
11       printf("Datei nicht erzeugt/geoeffnet!\n");
12    else
13    {
14       for (i = 0; i < 10; i++)
15       {
16          fprintf(Datei, "%i\n", Zahl[i]);
17          printf("Zahl %i: %i\n", i, Zahl[i]);
18       }
19       fclose(Datei);
20    }
21
22    return 0;
23 }

Für das unformatierte Schreiben gibt es den fputc-Befehl, der Daten zeichenweise in eine Datei schreibt. Dies ist gerade bei Texten oder bei Daten mit verschiedenen bzw. unbekannten Datentypen interessant. Die Syntax für diesen Befehl lautet wie folgt:

int fputc(int c, FILE *stream);

Es wird das zu schreibende Zeichen und der Zeiger auf den Datenstrom als Parameter angegeben. Der Datenstrom wird angegeben, um die Datei auszuwählen, in die das Zeichen geschrieben werden soll. Als Ergebnis dieser Funktion wird das geschriebene Zeichen zurückgegeben. Das Zeichen (Parameter und Rückgabewert) ist eine ganze Zahl (int), die als unsigned char interpretiert werden muss. Wird eine negative Zahl - z.B. der Wert EOF (= -1) - zurückgegeben, ist ein Fehler aufgetreten. Ein Beispiel für das zeichenweise Schreiben ist am Ende des Kapitels.

11.3. Einlesen von Dateien

Auch das Einlesen aus Dateien kann formatiert oder unformatiert geschehen. Das formatierte Lesen geschieht analog zum formatierten Schreiben mit dem fscanf-Befehl. Die vollständige Syntax lautet:

int fscanf(FILE *stream, char *format[, arguments]);

Auch hier ist der Unterschied zum scanf-Befehl (Einlesen von der Tastatur) nur das vorne angehangende "f" und der Datenstrom als zusätzlicher Parameter. Alles andere ist identisch mit dem bereits bekannten scanf-Befehl (siehe Kapitel Dateneingabe über die Tastatur).

Das folgende Beispiel liest aus der Datei U:\TEST.TXT, die im vorigen Beispiel erzeugt wurde, die 10 Zahlen wieder ein und gibt diese auf dem Bildschirm aus.

Beispiel:

kap11_03.c

01 #include <stdio.h>
02
03 int main()
04 {
05    FILE *Quelle;
06    int i, Zahl[10];
07    char fname[] = "U:/TEST.TXT";
08
09    Quelle = fopen(fname, "r");
10    if (Quelle == NULL)
11       printf("Quelldatei nicht geoeffnet!\n");
12    else
13    {
14       for (i = 0; i < 10; i++)
15       {
16          fscanf(Quelle, "%i", &Zahl[i]);
17          printf("Zahl %i: %i\n", i, Zahl[i]);
18       }
19       fclose(Quelle);
20    }
21
22    return 0;
23 }

Für das unformatierte Einlesen gibt es den fgetc-Befehl, der die Datei zeichenweise einliest. Dies ist dann interessant, wenn der Aufbau der Datei nicht bekannt ist. Die Syntax für diesen Befehl lautet wie folgt:

int fgetc(FILE *stream);

Es wird nur der Zeiger auf den Datenstrom als Parameter angegeben, um die Datei auszuwählen, von der ein Zeichen gelesen werden soll. Als Ergebnis dieser Funktion wird das gelesene Zeichen zurückgegeben. Die Funktion gibt ein int zurück, das als unsigned char interpretiert werden muss. Wird eine negative Zahl - z.B. der Wert EOF (= -1) - zurückgegeben, ist das Dateiende erreicht oder es ist ein Fehler aufgetreten. Ein Beispiel für das zeichenweise Einlesen ist am Ende des Kapitels.

11.4. Zusammenfassung

Hier nun eine Zusammenfassung aller Befehle für die Datei-Ein- und Ausgabe. Alle diese Befehle benötigen die Headerdatei stdio.h. Danach wird noch einmal ein Beispiel gezeigt.

Befehl

Funktionsprototyp

Funktionsergebnis

Datei
öffnen

FILE *fopen(char *name,
  char *mode);

== NULL wenn Fehler,
sonst Zeiger auf Datenstrom

Zeichen
lesen

int fgetc(FILE *stream);

== EOF wenn Fehler/Dateiende,
sonst das gelesene Zeichen

Daten
lesen

int fscanf(FILE *stream,
  char *format[, arguments]);

== EOF wenn Fehler/Dateiende,
sonst Anzahl der gelesenen Felder

Zeichen
schreiben

int fputc(int c,
  FILE *stream);

== EOF wenn Fehler,
sonst das geschriebene Zeichen

Daten
schreiben

int fprintf(FILE *stream,
  char *format[, arguments]);

< 0 wenn Fehler,
sonst Anzahl der geschriebenen Bytes

Dateiende
prüfen

int feof(FILE *stream);

!= 0 wenn Dateiende,
== 0 wenn kein Dateiende

Datei
schließen

int fclose(FILE *stream);

== EOF wenn Fehler,
== 0 wenn kein Fehler

alle Dateien
schließen

int _fcloseall(void);

== EOF wenn Fehler,
sonst Anzahl der geschlossenen Dateien


Das folgende Beispiel kopiert die Datei U:\TEST.TXT nach U:\TEST.BAK, erzeugt also eine Sicherheitskopie der Originaldatei. Dabei wird die Originaldatei zeichenweise eingelesen und auch zeichenweise in die Zieldatei geschrieben.

Beispiel:

kap11_04.c

01 #include <stdio.h>
02
03 int main()
04 {
05    FILE *Quelle, *Ziel;
06    int i;
07    char fname1[] = "U:/TEST.TXT", fname2[] = "U:/TEST.BAK";
08
09    Quelle = fopen(fname1, "rb");
10    if (Quelle == NULL)
11       printf("Quelldatei nicht geoeffnet!\n");
12    else
13    {
14       Ziel = fopen(fname2, "wb");
15       if (Ziel == NULL)
16          printf("Zieldatei nicht geoeffnet!\n");
17       else
18       {
19          while ((i = fgetc(Quelle)) != EOF)
20             fputc(i, Ziel);
21          fclose(Ziel);
22       }
23       fclose(Quelle);
24    }
25
26    return 0;
27 }



Voriges Kapitel: 10. Präprozessor-Befehle