Ein- und Ausgabe von Dateien

Martin Kompf

Dieser Artikel beantwortet die Fragen: Wie kann man eine Datei in einem C++ Programm einlesen? Wie wird in eine Datei geschrieben? Wie lassen sich Fehler bei diesen Operationen abfangen?

All das wird anhand eines Beispielprogrammes besprochen, welches eine Textdatei wortweise einliest und die Wörter alphabetisch sortiert. Mehrfach vorkommende Wörter werden zusammengefaßt. Die als Ergebnis dieser Operationen enstehende Liste wird in eine zweite Textdatei ausgegeben.

Wird zum Beispiel die Eingabedatei a.txt mit dem Inhalt der braune Hund ist größer als der schwarze Hund verwendet, ergibt der Aufruf von fwords a.txt b.txt eine Textdatei b.txt, die folgende Zeilen enthält:

Hund
als
braune
der
ist
kleiner
schwarze

Zuerst müssen im Programm die notwendigen Headerdateien inkludiert werden. Für die Dateioperationen wichtig ist dabei fstream. Da das Programm auch Ausgaben auf Konsole machen soll, braucht man noch iostream. Für das Sortieren der Wörter lassen sich in der STL (Standard Template Library) mitgelieferte Routinen nutzen, dazu sind dann noch string, list und algorithm notwendig:

#include <fstream>
#include <iostream>
#include <string>
#include <list>
#include <algorithm>

Alle Namen in den STL-Headern sind im Namespace std deklariert. Die Anweisung

using namespace std;

importiert diese Namen in den globalen Namespace. Dadurch können später diese Namen ohne vorangestelltes std:: verwendet werden.

Jetzt kann die main Funktion des Programmes implementiert werden. Am Anfang müssen die notwendigen Programmparameter - die Namen von Eingabe- und Ausgabedatei - von der Kommandozeile gelesen werden. Guter Stil ist, bei fehlenden Kommandozeilenargumenten dem Benutzer eine kurze Hilfestellung auszugeben:

int main( int argc, char ** argv)
{
    if (argc != 3) {
        cerr << "usage: " << argv[0] << " infile outfile\n";
        exit(1);
    }
    char *infile = argv[1];
    char *outfile = argv[2];

Nun erfolgen die Deklaration der Variablen, die später die einzelnen Wörter und die Wortliste halten:

    list<string> words;
    string word;

und das Öffnen der Eingabedatei:

    ifstream ifs;
    ifs.open( infile);
    if (! ifs) {
        cerr << "cannot open file " << infile << " for input\n";
        exit(1);
    }

Die Eingabedaten werden hier also durch den Inputfilestream ifs vom Typ ifstream repräsentiert; durch die open() Anweisung erfolgt das Verbinden von Datei und Stream. Wenn eine Streamvariable in einem boolschen Ausdruck abgefragt wird, so liefert sie den Streamstatus zurück: true wenn die vorhergehende Operation erfolgreich war und false wenn es zu Fehlern gekommen ist oder das Dateiende erreicht wurde. Der Streamstatus unmittelbar nach ifs.open() gibt also Auskunft darüber, ob das Öffnen der Datei erfolgreich war, d.h. ob sie existiert.

Nun kann aus dem Inputfilestream gelesen werden. Erfolgt dies mit dem Eingabeoperator >> in eine Stringvariable hinein, so wird der Stream automatisch an Whitespaces (Leerzeichen, Tabulator, Zeilenwechsel) in einzelne Worte getrennt. Diese werden dann direkt an die Wortliste words angehängt:

    while (ifs >> word) {
        words.push_back( word);
    }

In der while Anweisung wird dabei jedesmal der Streamstatus getestet. Geht dieser auf false, so ist entweder das Dateiende erreicht oder ein Fehler aufgetreten. Diese Unterscheidung läßt sich mittels der Funktion eof() vornehmen:

    if (! ifs.eof()) {
        cerr << "error while reading from file " << infile << endl;
        exit(1);
    }

Der Inputfilestream wird nun nicht mehr benötigt und sollte geschlossen werden. Wird dies vergessen, so erfolgt das Schließen automatisch beim Verlassen des Blockes, in dem ifs gültig ist; hier also beim Verlassen von main().

    ifs.close();

Jetzt kann die Wortliste sortiert werden. Das verwendete list Template aus der STL hat zum Glück schon eine Sortierfunktion. Wenn in der Eingabedatei Wörter mehrfach vorkommen (wie im Beispiel "der" und "Hund"), dann stehen diese in der sortierten Liste unmittelbar hintereinander. Mittels der ebenfalls durch die STL zur Verfügung gestellten Funktion unique werden diese Mehrfachnennungen zusammengefaßt:

    words.sort();
    list<string>::iterator p = unique( words.begin(), words.end());
    words.erase(p, words.end());

Die so aufbereitete Wortliste kann nun ausgegeben werden. Das Öffnen der Ausgabedatei und die Fehlerabfrage unterscheiden sich nicht wesentlich von der Behandlung der Eingabedatei, nur daß jetzt ein Outputfilestream ofs vom Typ ofstream verwendet wird:

    ofstream ofs;
    ofs.open( outfile);
    if (! ofs) {
        cerr << "cannot open file " << outfile << " for output\n";
        exit(1);
    }

In den nun existieren Outputfilestream werden die Strings aus der Wortliste geschrieben. Zum Durchlaufen der Wortliste wird dabei ein Iterator verwendet. Die eigentliche Ausgabe erfolgt mit dem Ausgabeoperator <<. Mit endl wird ein Zeilenwechsel in den Stream geschrieben:

    list<string>::iterator iword;
    for (iword = words.begin(); iword != words.end(); ++iword) {
        ofs << *iword << endl;
    }

Zum Schluß wird noch der Outputfilestream geschlossen und überpüft, ob sein Status fehlerfrei ist:

    ofs.close();
    if (! ofs) {
        cerr << "error while writing to file " << outfile << endl;
        exit(1);
    }

    return 0;
}

Übungsaufgabe: Im Beispiel werden Wörter nur an Leerzeichen getrennt. Eventuell vorhandene Satzzeichen, wie Komma, Punkt und Semikolon werden dabei nicht berücksichtigt und an die Worte angehängt. Man erweitere das Programm so, daß die Satzzeichen mit berücksichtigt werden! (Hinweis: Die Eingabedatei kann mittels getline() zeilenweise gelesen werden. Die Positionen der Satzzeichen in der eingelesenen Zeile lassen sich dann zum Beispiel mit find_first_of() bestimmen.)