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 |
FILE *fopen(char *name, |
== NULL wenn Fehler, |
Zeichen |
int fgetc(FILE *stream); |
== EOF wenn Fehler/Dateiende, |
Daten |
int fscanf(FILE *stream, |
== EOF wenn Fehler/Dateiende, |
Zeichen |
int fputc(int c, |
== EOF wenn Fehler, |
Daten |
int fprintf(FILE *stream, |
< 0 wenn Fehler, |
Dateiende |
int feof(FILE *stream); |
!= 0 wenn Dateiende, |
Datei |
int fclose(FILE *stream); |
== EOF wenn Fehler, |
alle Dateien |
int _fcloseall(void); |
== EOF wenn Fehler, |
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
Nächstes Kapitel: 12. Dynamische Speicherverwaltung