Einlesen aus Textdatei mittels istream_iterator



  • Hallo,
    ich habe eine Textdatei in der Form:

    #725454 Schenker Rudolf DE-30159 Hannover
    Shirt 135.95 1
    Tie 89.59 1
    #987654 Orbison Roy US-TN37075 Hendersonville
    Mug 1.49 3
    T-Shirt 14.90 1
    ..

    In folgendem Format:

    <customer_id> <last_name> <first_name> <postal_code> <city>
    <article> <price> <quantity>

    Wobei es beliebig viele <article> <price> <quantity> - Zeilen geben kann, und ein neue Customer-Zeile durch das # - Zeichen vor der customer_id markiert wird. Ist hoffe ich verständlich.

    Diese Textdatei sollen wir halt einlesen und die einzelnen Werte jeweils in die richtige Attributen unserer Klasse einspeichern.

    Das Problem ist jetzt, das in der Aufgabe gefordert wird, das wir einen istream_iterator dafür verwenden sollen. Verstehe nicht wie man das möglichst einfach lösen soll? Ich hätte das jetzt irgendwie so in der Art angestellt (so halb Pseudocode-mäßig):

    while(true) {
            char c;
            infile.get(c);
            if(c != '#') {
                cout<<"Formatfehler!"<<endl;
                return -1;
            } else {
                int kundennummer;
                string vorname, nachname, plz, stadt, artikel;
                double preis;
                int anzahl;
    
                infile >> kundennummer >> vorname >> nachname >> plz >> stadt
                        >> artikel >> preis >> anzahl;
            } 
        }
    

    Mit einem istream_iterator würde mir nur so etwas seltsames einfallen:

    vector<string> ii_vector;
        istream_iterator<string> ii (infile); 
        istream_iterator<string> eof;
    
        while(ii != eof)
            ii_vector.push_back(*(ii++));
    

    Dann hab ich praktisch jedes Wort der Textdatei in dem vector<string> und dann würde ich das kompliziert in die jeweiligen Typen double (für den Preis), int (für Kundennummer, Anzahl er Artikel) umwandeln, aber das kann es ja irgendwie nicht sein.

    Jemand irgendwelche Vorschläge? Wie könnte man möglichst einfach und übersichtlich die Textdatei mit den verschiedenen Werten typgerecht mittels istream_iterator in jeweilige Variablen speichern? Das heißt Kundennummer int int speichern, vor-und nachname in string etc.

    mfg



  • Der Iterator wird ja nicht einfach so vom Himmel gefallen sein. Was habt ihr vorher damit gemacht?



  • Also die Folien basieren auf den Büchern von stroustrup und ein Beispielprogramm wäre das Folgende:

    int main() 
    { 
    string from, to; 
    cin >> from >> to; // lies die Namen der Quell- und Zieldatei ein 
    ifstream is(from.c_str()); // öffne Eingabestream 
    ofstream os(to.c_str()); // öffne Ausgabestream 
    istream_iterator<string> ii(is); // erstelle Eingabe-Iterator für Stream 
    istream_iterator<string> eos; // Eingabe-Wächter 
    ostream_iterator<string> oo(os,"\n"); // erstelle Ausgabe-Iterator für Stream 
    vector<string> b(ii,eos); // b ist ein Vektor, der mit Daten aus der // Eingabe initialisiert wird 
    sort(b.begin() ,b.end()); // sortiere den Puffer 
    copy(b.begin() ,b.end() ,oo); // kopiere Puffer in Ausgabe 
    }
    
    Stroustrup, Bjarne. Einführung in die Programmierung mit C++ (Pearson Studium - IT) (German Edition) (Seite767). Pearson Deutschland. Kindle-Version.
    

    Und das ist das einzige was mir mit dem istream_iterator gemacht haben und in dem Stroustrup Buch wird der istream_iterator auch nicht sonderlich anders mehr verwendet. Also der istream iterator wird in dem Buch nicht verwendet um speziell formatierte Textdateien in Klassenobjekte einzulesen. Stattdessen verwendet Stroustrup dafür das Überladen des >> Operators in den jeweiligen Klassen.


  • Mod

    Der istream_iterator ist eine praktische Art und Weise, wie man einer Funktion sagen kann, dass sie immer wieder Operator >> für einen bestimmten Datentyp aufrufen soll. Dafür muss aber auch eine sinnvolle Definition des Operators >> für diesen Typ existieren. Der Typ ist hier aber nicht String, sondern dein Klassentyp. Das heißt, du solltest zuerst einmal einen funktionierenden Operator >> für deine Klasse schreiben. Und dann benutzt du den in einem istream_iterator. Schematisch so:

    class DeineKlasse
    {
      // ...
    };
    
    istream& operator>>(istream& lhs, DeineKlasse& rhs)
    {
      // ...
    }
    
    int main()
    {
      ifstream in("some_file");
      istream_iterator<DeineKlasse> ii(in), eos;
      vector<DeineKlasse> values(ii, eos);  // Ruft wiederholt operator>> für deine Klasse auf die Datei auf und initialisiert mit dem Ergebnis den Vector.
    }
    


  • Vllt. hilft dir Writing/reading data structure to a file using C++ (den Code aus der Antwort), d.h. erstelle dir eine Klasse für einen Customer und benutze dann istream_iterator<Customer>.

    Edit: Und im Textmodus lesen, nicht im Binärmodus.



  • Hallo,
    ja danke so wird das gemeint sein. Ich hab jetzt die Eingabeoperatoren geschrieben (habe insgesamt drei Klassen, also drei Eingabeoperatoren). Aber wenn ich das jetzt so schreibe:

    istream_iterator<Order> ii (in);
        istream_iterator<Order> eos;
        vectOrd (ii, eos);
    

    Wobei vectOrd das hier ist:

    vector <Order> vectOrd;
    

    bekomme ich als Fehlermeldung:

    main.cpp:38:21: error: no match for call to '(std::vector<Order>) (std::istream_iterator<Order>&, std::istream_iterator<Order>&)'
         vectOrd (ii, eos);
    

    Wenn ich das hingegen so schreibe:

    Order order;
    
        while (in>>order)
            vectOrd.push_back(order);
    

    Funktioniert es ohne Probleme. Auch die Ausgabe mittels

    for(int i=0; i<vectOrd.size() ; ++i) 
            cout << vectOrd[i];
    

    liefert das gewünschte Ergebnis (hab die Ausgabeoperatoren der Klassen auch geschrieben).

    Jemand ne Ahnung was da los ist?



  • In SeppJs Beispiel wird ein Constructor von std::vector benutzt (std::vector::vector (3)). Fall Dein Vector schon existiert, wenn Du die Daten einfügen willst, möchtest Du vielleicht std::vector::insert (3) verwenden.



  • Danke, mit insert funktionierts.

    vectOrd.insert(vectOrd.begin(), ii, eos);
    

    Ich hab jetzt folgendes Problem. Ich hab nun den vector VectOrd und eine liste namens listOrd, in beiden Container stehen jeweils die Daten aus den Textdateien drin. In vectOrd die Daten von einer Textdatei und in listOrd die Daten einer anderen Textdatei. Nun will ich beide container zusammenführen mittels der std::merge Funktion.

    vector<Order> vectOrd;
        list<Order> listOrd;
    
        einlesen_cpo1 (vectOrd);
        einlesen_cpo2 (listOrd);
    
        vector<Order> vectOrd_merge;
    
        merge(vectOrd.begin() , vectOrd.end() , listOrd.begin() , listOrd.end() , 
                vectOrd_merge.begin());
    

    Allerdings funktioniert das nicht. Ich bekomme einen segmantation fault. Weiß wer was da falsch ist? Beide container sind mittels sort() sortiert. Wobei bei der Liste mit listOrd.sort() sortiert wurde und bei dem vektor mit std::sort().



  • Du musst entweder vorher Speicher für den resultierenden vector reservieren (du weißt ja, wie groß er sein muss) oder du verwendest einen std::back_inserter, der mit allen Containern funktioniert, die push_back anbieten (wie eben ein vector).

    Bei diesem Codebeispiel (unten) wird merge und back_inserter verwendet.



  • Jop danke.

    Ich hätte noch eine Frage. Eine Aufgabenstellung ist die folgende:

    Gehen Sie von Ihren Containern listOrd und vectOrd aus Aufgabe 1 aus und überprüfen Sie mit
    geeigneten, automatisierten Positivtests, ob die Anzahl der Elemente in den beiden Containern
    den Daten aus cpo1.txt und cpo2.txt entspricht. Lösen Sie im Fehlerfall eine Ausnahme aus,
    die das Programm kontrolliert beendet.
    

    cpo1.txt und cpo2.txt sind jeweils in dem Format wie in Post 1 beschrieben. Ich hab alles ordnungsgemäß in die beiden container eingelesen. Aber wie sollte man sinnvollerweise mit "automatisierten Positivtests" testen ob die Anzahl der Elemente stimmt? Ich hab einfach nochmal die cpo1.txt und cpo2.txt geöffnet und Zeichen für Zeichen eingelesen und einen Zähler hochgezählt sobald ein '#' Zeichen vorgekommen ist und dann am Ende geschaut ob der Zähler mit .size() der Container übereinstimmt. Das ganze dann in ein try, catch, throw eingebunden. Denn ein '#' bedeutet ja, das ein neuer Customer kommt.

    Hat wer noch ne andere Vorstellung, was mit der Aufgabe gemeint sein könnte?

    Eine zweite Frage:
    Ich hab noch eine Aufgabe gemacht wo ich strukturiert eine Datei einlesen sollte. Ich hab also alles in die Klasse eingelesen mit ihren Attributen. Aber in dieser Klasse musste ich den Kopierkonstruktor, den netbeans beim erstellen einer neuer Klasse anfangs automatisch erstellt, löschen, weil mit dem Kopierkonstruktor der mir ansonsten nur Standardwerte in den vector speichern würde. Weiß wer wieso ich den Kopierkonstruktor löschen musste? Ich hatte gedacht man müsste nicht selber einen Kopierkonstruktur schreiben sofern man nicht mit Zeigern in der besagten Klasse arbeitet.


Log in to reply