home | ### Übung 2
| Diese Übung war ein wenig schwierig zusammenzustellen, da das reine Anwenden des Unterrichtsstoffes langweilig sein kann. Vielleicht ist das Schreiben und Lesen einer Datei ein wenig Anreiz um zu experimentieren. Ihr könnt Euch auch gerne eine andere Aufgabe selber stellen, Hauptsache Ihr übt ! Diese Übung wird die IOStream Klassen verwenden, wobei wir ausnutzen, dass die fstream-Klassen von den stream-Klassen abgeleitet sind.
| Hole die Lösung der CD-Liste vom letzten mal, oder verwende die Lösung, die ich Euch im Unterricht gezeigt habe. Versuche die CD-Klasse ein wenig zu erweitern, so dass Ihr vernünftige Daten einfügen könnt, oder ergänzt das Programm so, dass der Benutzer einigermassen bequem CD's einfügen kann. Ergänze die Klasse CD mit zwei Funktionen zum Schreiben in eine istream& und Lesen aus einer ostream& ! Diese Funktionen sollen nachher verwendet werden um die Daten in eine Datei zu speichern. Ergänze die Klasse CDListe ebenfalls um eine Schreiben-Funktion. Diese Funktion beginnt bei der ersten CD ruft bei dieser die Schreiben-Funktion auf (siehe nachher auch noch das Beispiel), und holt sich danach die nächste CD und schreibt auch diese, usw. bis alle CD's geschrieben wurden (siehe unten). Ganz schön wäre auch eine Lesen-Funktion, in der eine noch leere Liste selber CD's herstellt ! Oder in einer bereits bestehenden Liste CD's aus einer Datei einfügt. Einige Tips zur Lösung sind unten noch grob skizziert. Vor allem gibt es beim Lesen der Daten aus einer Datei gewisse Details zu beachten. Falls Du noch nicht genug hast kannst Du noch versuchen den <<-operator so zu überschreiben, dass man Code wie diesen schreiben kann :
    int main()
    {
        CDListe liste;
        cout << liste;
        ofstream fout("cd.txt");
        fout << liste;
        // etc.
        return 0;
    }
Auch dazu gibt es weiter unten ein wenig Hilfe.
### Tips und Hilfe |
| Also hier ein wenig Hilfe. Als erstes die Lesen und Schreiben-Funktionen für die CD-Klasse:
aus CD.H |
    class CD
    {
        public:
            CD();
            void writeToStream(ostream& out);
            void readFromStream(istream& in);

        private:
            string m_title;
            double m_length;
            string m_interpret;
    };
aus CD.CPP |
    void CD::writeToStream(ostream& out)
    {
        out << m_title << "\t";
        out << m_interpret << "\t";
        out << m_length << endl;
    }

    void CD::readFromStream(istream& in)
    {
        getline(in, m_title, '\t');
        getline(in, m_interpret, '\t');
        in >> m_length;
    }
Beim Schreiben fügen wir nach dem Titel einen Tabulator-Zeichen ein ("\t"). Auf diese Art sind die ist der die Daten von einer CD sauber getrennt. Die Datei sind dann ungefähr so aus.
    test    Fred Feuerstein    35.77
    hallo   Fred Feuerstein    35.88
| Das hilft sehr beim Einlesen der Daten, was man in der Funktion CD::readFromStream sieht. Die Funktion getline nimmt als ersten Parameter eine Referenz auf einen istream. Das zweite ist der string, der die Daten erhält. Das wichtigste ist der dritte Parameter '\t' ! Dieser sagt der getline-Funktion, dass sie alles einlesen muss bis zum TAB-Zeichen. Normalerweise liest die getline Funktion wie der Name schon sagt eine ganze Linie ein. Mit dem dritten Parameter gibt man ihr aber an bei welchem Zeichen sie aufhören soll. Man hätte genau so gut ein anderes Trennzeichen verwenden können, z.B. ein ":". Diese Daten können übrigens problemlos in eine Tabellenkalkulation (Excel) importiert werden. Solche Daten können auch mit Excel erzeugt werden !
| Jetzt noch ein kleines Beispiel zum Testen dieser Funktionen. Das komplette main.cpp, das auch gleich eine ganz kleine CD-Klasse zum Testen enthält kannst Du Dir auch downloaden.
main.cpp |
    const char* fileName = "c:\\cd.txt";

    int main()
    {
        CD cd1;
        CD cd2;

        // Ausgabe auf den Bildschirm
        cd1.writeToStream(cout);
        cd2.writeToStream(cout);

        // Ausgabe in eine Datei
        ofstream fout(fileName);
        cd1.writeToStream(fout);
        cd2.writeToStream(fout);

        // Die Datei muss geschlossen
        // werden, sonst kann sie
        // unten nicht mehr geöffnet
        // werden
        fout.close();

        // Aus der Datei wieder lesen
        ifstream in(fileName);
        cd1.readFromStream(in);
        cd2.readFromStream(in);

        return 0;
    }
Wie im Kommentar bereits angegeben, muss die Datei, die mit fout geöffnet wurde, explizit mit der Funktion close der Klasse fstream wieder geschlossen werden, denn sonst könnte man weiter unten keinen ifstream machen, der aus dieser Datei liest. Beachte auch, dass der Datei Name oben als const char* defniert wurde. So dass besteht nicht die Gefahr beim mehrmaliger Eingabe des Dateinamens einen Fehler zu machen. Auch muss man dann nur an einem Ort den Dateinamen ändern. Euer Programm könnte sogar den Benutzer um die Eingabe eines Dateinamens bitten.
| Die CDListe muss ungefähr so alle CD's schreiben.
    void CDList::writeToStream(ostream& out)
    {
        if(m_First == 0)
        {
            // die Liste ist leer
            // also einfach return
            return;
        }

        CD* actual = m_First;

        do
        {
            actual->writeToStream(out);
            actual = actual->GetNext();
        }while(actual != m_Last)
    }
Dieser Code ist nicht getestet ! Also überlegt Euch gut, ob Ihr ihn so übernehmen könnt.
    void CDList::readFromStream(istream& in)
    {
        // solange nicht das Dateiende
        // erreicht wurde
        while(in.eof() == false)
        {
            // neue CD erstellen
            CD* newCD = new CD;
            // Cd aus stream lesen
            newCD->readFromStream(in);
            // Cd in Liste einfügen
            AddNewCD(newCD);
        }
    }
Mit der Funktion eof() der Klasse istream wird überprüft ob das Dateiende erreicht wurde. Es wird in der Schleife eine CD erstellt. Diese neue CD liest danach selbst die Daten aus dem istream. Zuletzt wird diese neue CD in die Liste eingefügt.
Der << Operator |
| Was jetzt noch folgt ist nur für die Interessierten ! Es macht nichts wenn Du es nicht verstehst (wobei Du das natürlich könntest !) Im Grunde genommen können wir jetzt unsere CDListe in einen Stream schreiben indem wir schreiben :
        CDList eineListe;

        // viel Code um CD's einzufügen
        // ...
        // Jetzt noch Daten schreiben
        ofstream fout("c:\\test.txt");
        eineListe.writeToStream(fout);
Um Code wie diesen zu schreiben benötigen wir aber mehr:
       CDList eineListe;

        // viel Code um CD's einzufügen
        // ...
        // Jetzt noch Daten schreiben
        ofstream fout("c:\\test.txt");
        fout << einListe;
Dieser Code kompiliert noch nicht. Wir müssen noch den operator<< definieren :
    ostream& operator<<(ostream& out, CDListe& liste)
    {
        liste.writeToStream(out);
        return out;
    }
Beachte, dass der operator<< nicht zu einer Klasse gehört !