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
  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
  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
  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
  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

