Unerwünschte Textzeile beim auslesen löschen - str.erase() funktioniert nicht!?



  • Hallo, ich habe mir zu Übungszwecken eine Textdatei geschrieben und wollte sie auslesen. Dabei möchte ich unerwünschte Textzeilen wie z.B. in dem Fall die Zeile ********************* löschen. Ich poste mal die Textdatei. Hab sie in googledocs erstellt, wegen Formatierung und Co.

    https://drive.google.com/file/d/0B4sSbxPEmrOdZjkwc1UxeWU3azA/edit?usp=sharing

    Und hier meine Lösungsansätze:

    int main()
    {
    
    	string einlesen;
    	string name;
    	string matrikelnummer;
    	string note;
    	string ueberspringe;
    	fstream quelle("Noten.txt");
    
    	for (int i = 0; i < 4; i++)
    	{
    		getline(quelle, einlesen);
    	}
    
    	while (!quelle.eof())
    	{
    		int hilf;
    		hilf = einlesen.find("*");		
    		if (hilf != string::npos)
    		{
    		  einlesen.erase(hilf);		//Sollte eigentlich die Zeile   mit "****" löschen
    		}
    		getline(quelle, einlesen, ';');
    		name = einlesen;
    
    		quelle >> einlesen;
    		einlesen.erase(einlesen.size() - 1); // Erase um Semikolon wegzukriegen
    		matrikelnummer = einlesen;
    
    		quelle >> einlesen;
    		einlesen.erase(einlesen.size() - 1);
    		note = einlesen;
    
    		cout << name << endl;
    		cout << matrikelnummer << endl;
    		cout << note << endl;
    
    	}
    

    Was mache ich nur falsch? Ich komme einfach nicht auf die Lösung. Bitte helft mir, ich hab am Mittwoche eine Klausur. 😞


  • Mod

    Leider ist da so ziemlich absolut alles falsch. Ich kann nicht einmal nachvollziehen, wieso du das so versuchst, wie du es tust. Du scheinst die zur Verfügung stehenden Mittel nicht verstanden zu haben. Weiterhin hast du dir ein paar falsche Techniken aus fragwürdigen Quellen abgeschaut zu haben (ich nehme mal an, dass du dir Quatsch wie while(!quelle.eof()) nicht selber ausgedacht hast). Aber hauptsächlich scheint das Problem zu sein, dass du nicht weißt, was die von dir benutzten Funktionen überhaupt tun. Und zwar alle. Daher ist es schwer, dir weiter zu helfen, außer dir zu sagen, dass du wohl so ziemlich alles neu lernen musst.

    Es könnte auch helfen, wenn du lernst, wie man selber solche Probleme löst. Beispielsweise könntest du mit einem Debugger (oder notfalls mit ein paar Testausgaben) leicht rausfinden, was deine Zeilen 12-15 machen. Ich weiß nicht genau, was du dir von diesen versprichst, aber ich bin mir ziemlich sicher, dass das, was wirklich passiert nicht dem entspricht, was du denkst.

    Keines von beidem (Wiederholung des Gelernten oder Hilfe zur Selbsthilfe) wirst du bis Mittwoch auch nur ansatzweise lernen können. Das kommt davon, wenn man kurz vor knapp merkt, dass man nichts gelernt hat. Dann ist es zu spät.



  • Also die Zeilen 12-15 lesen die ersten 3 Zeilen der Textdatei in meinen string. da ich diese nicht verwenden möchte, tue ich nichts weiter damit. ist ja nur eine überschrift und keine Variablen. Also warum soll das falsch sein?

    while(!quelle.eof()) führt den Anweisungsblock aus solange Daten in der Textdatei existieren.

    Wenn ich ganz falsch liege, kannst du mich ja korrigieren aber das ist die Weise wie ich es gelernt bzw. verstanden hab.

    Btw: die Klausur ist erst nächste Woche Mittwoch. In 8 Tagen sollte ich vllt doch noch hinkriegen eine Textdatei auszulesen. Ich brauch nur noch den richtigen Ansatz. Dafür bin ich ja hier. 🙂


  • Mod

    Array schrieb:

    Also die Zeilen 12-15 lesen die ersten 3 Zeilen der Textdatei in meinen string. da ich diese nicht verwenden möchte, tue ich nichts weiter damit. ist ja nur eine überschrift und keine Variablen. Also warum soll das falsch sein?

    Dann teste deine Annahme doch einmal. Gib mal aus, was du da eingelesen hast.

    while(!quelle.eof()) führt den Anweisungsblock aus solange Daten in der Textdatei existieren.

    So? Wann wird denn das eof-Flag gesetzt?

    Wenn ich ganz falsch liege, kannst du mich ja korrigieren aber das ist die Weise wie ich es gelernt bzw. verstanden hab.

    Die Frage ist, ob du falsch verstanden hast oder ob es dir eventuell sogar falsch beigebracht wurde. Wurde dir das while(!eof) so beigebracht? Das ist ein beliebter Fehler von Lehrern, die vor vierzig Jahren mal PASCAL gelernt haben und die Unterschiede zu C-artigen Sprachen nie kapiert haben. Falls ja, ist auch alles andere aus deren Mund mit Vorsicht zu genießen.



  • Was hat das mit Unterschied zu C-artigen Sprachen zu tun?
    Die ganze "haben fertig" und "haben Fehler" Geschichte ist bei den iostreams nun wirklich etwas gewöhnungsbedürftig umgesetzt.

    @Array
    Lies dein Programm Zeile für Zeile, und dann geh anhand einer Referenz-Doku (wie z.B. http://www.cplusplus.com/reference/) alle Funktionen durch die du da aufrufst. Also lies dir durch was die machen. Was sie genau machen.

    Was die while (!quelle.eof()) Sache angeht... das ist doppelt falsch. Auf den einen Fehler kommst du selbst drauf wenn du dein Programm mal soweit hast dass es keinen kompletten Unfug mehr macht.

    Was den zweiten Fehler angeht, lies mal dahier:
    http://www.cplusplus.com/reference/ios/ios/good/
    http://www.cplusplus.com/reference/ios/ios/eof/
    http://www.cplusplus.com/reference/ios/ios/bad/
    http://www.cplusplus.com/reference/ios/ios/eof/
    http://www.cplusplus.com/reference/ios/ios/operator_bool/
    und
    http://www.cplusplus.com/reference/ios/ios/operator_not/


  • Mod

    hustbaer schrieb:

    Was hat das mit Unterschied zu C-artigen Sprachen zu tun?
    Die ganze "haben fertig" und "haben Fehler" Geschichte ist bei den iostreams nun wirklich etwas gewöhnungsbedürftig umgesetzt.

    Ob das wohl in C genau so ist? Und in Java? Und in C#?
    Eigentlich fällt mir keine moderne Profisprache ein, die das eof-Flag vorzeitig setzt. Das ist nur so bei Lehrsprachen wie Pascal oder Basic anders.



  • Das ist die Ausgabe:

    Texdatei Nr1
    Klausurnoten
    ********************************************************
    Drücken Sie eine beliebige Taste . . .

    Also ich habe gelernt, dass !eof() solange noch Daten gelesen werden können, bedeutet.

    Ich hab jetzt mal danach gesucht und da steht: Das eofbit wird gesetzt, nachdem
    eine Leseoperation das Ende der Eingabe erreicht hat.

    Trotzdem bringt mich das nicht groß weiter. Wenn das alles falsch ist, kannst du mir ja zeigen wie man's richtig macht. 😉



  • Danke hustbaer, ich werde mir das nochmal genauer durchlesen und mich dann nochmal melden. 🙂


  • Mod

    Array schrieb:

    Das ist die Ausgabe:

    Texdatei Nr1
    Klausurnoten
    ********************************************************
    Drücken Sie eine beliebige Taste . . .

    Von welchem Code? Und was folgerst du daraus?

    Geh mal deinen kompletten Code durch und lass dir an jeder Stelle des Programmverlaufs anzeigen (vorzugsweise mit einem Debugger, zur Not mit Ausgaben), welchen Wert welche Variablen haben. Vergleiche deine Ergebnisse mit deinen Erwartungen. Du wirst feststellen, dass das vorne und hinten nicht passt.

    Mit kleinen Korrekturen ist es jedoch in diesem Fall nicht getan. Wie hustbaer empfohlen hat, solltest du dich mal kundig machen, was die Funktionen, die du aufrufst, überhaupt genau tun (Der Operator >> ist auch eine dokumentierte Funktion!). Danach kannst du nachdenken, wie dein Programm tatsächlich aussehen muss.

    Also ich habe gelernt, dass !eof() solange noch Daten gelesen werden können, bedeutet.

    Ich hab jetzt mal danach gesucht und da steht: Das eofbit wird gesetzt, nachdem
    eine Leseoperation das Ende der Eingabe erreicht hat.

    Trotzdem bringt mich das nicht groß weiter.

    Das heißt, deine Schleife bricht dann ab, wenn bereits eine Leseoperation fehlgeschlagen ist. Da du innerhalb deiner Schleife aber fröhlich Leseoperationen durchführt und die Ergebnisse ungeprüft verarbeitest, wirst du beim letzten Durchlauf irgendwelchen Unsinn verarbeiten, weil das Lesen fehlschlug und du erst viel zu spät darauf reagierst.

    Wenn das alles falsch ist, kannst du mir ja zeigen wie man's richtig macht. 😉

    Typische Leseschleife in C-artigen Sprachen:

    while(erfolgreich(leseaktion(quelle, daten)))
      verarbeite(daten);
    

    Konkret für C++ geben die meisten (aber nicht alle! Referenz nutzen!) Leseaktionen ihre Quelle (den Stream) selbst zurück. Diese könntest du dann mit eof prüfen a la while(!(getline(quelle, ziel).eof())) , aber die Streams werden auch automatisch in solchen Kontexten auf ihre Fehlerflags geprüft (und zwar auf alle Fehlerflags, nicht nur eof!). Also üblicherweise etwas wie:

    while(getline(quelle, ziel))
      verarbeite(ziel);
    

    Oder vergleichbare Konstrukte.

    Das ist aber tendenziell eher ein Nebenschauplatz. Was die Lesefunktionen an sich genau machen und wie Zeichenketten funktionieren ist hier erst einmal ein viel wichtigeres Thema. Das mit dem eof hast du jetzt einmal gesehen und hoffentlich gelernt. Das andere ist viel mehr für dich zu lernen und du musst dann noch lernen, wie man alles richtig zusammen fügt (das mache ich auch bewusst nicht vor, weil du es sonst nicht lernen würdest). Ich wollte nur feststellen, ob das Problem eher bei dir oder deinem Lehrer liegt.



  • Hallo Array,

    Array schrieb:

    Also ich habe gelernt, dass !eof() solange noch Daten gelesen werden können, bedeutet.

    kannst Du uns noch mitteilen, von wem Du das gelernt hast.
    Sobald man zwischen Lesefehler (FAIL) und EOF unterscheiden kann, ist dies in jeder Programmiersprache falsch. Korrekt ist immer if(!FAIL) , aber natürlich bitteschön nach(!) dem Lesen prüfen.

    Ich weiß, dass Code-Beispiele mit while(!inputStream.eof()) 100-tausend-fach im I-Net zu finden sind. Dieser Hoax wird uns wohl noch alle überleben.

    Gruß
    Werner



  • Wurde mir so im Tutorium beigebracht. Hab es wie du schon sagst auch schon öfter auf Seiten im Netz gelesen, deswegen hab ich das auch nicht weiter angezweifelt.

    Also: Wenn ich das jetzt richtig verstanden hab, muss ich zuerst einen Test auf if(!quelle.good()) machen. Dieser gibt true zurück wenn eof, badbit oder failbit gesetzt werden.

    if(quelle.good())
    {
    getline(quelle, einlesen);
    
    }
    

    Wobei mir jetzt immer noch nicht klar ist warum ich kein getline benutzen soll. Getline liest doch eine Zeile bis zum '\n' ein und speichert sie in einlesen. Was ja mein Ziel ist, weil ich den Variablen "einlesen" übergebe.

    Wenn ich dann eine Zeile in meinem String habe, kann ich den ja mittels einlesen.find("") auf das Zeichen "" überprüfen. Die Methode liefert mir die Position des ersten aufkommens des Zeichens. Statt dem einlesen.erase() hab ich eine bessere Methode gefunden, nämlich das einlesen.clear(), was mir die ganze Zeile löscht. (Ich mache die Abfrgae, falls mehrere solcher Zeichen auftauchen sollten)

    Wenn einlesen nicht das Zeichen "*" hat, tue dies, andernfalls, lösche die Zeile;

    if (einlesen.find("*") == string::npos)
    			{
    				getline(quelle, einlesen, '\n');
    
    				getline(quelle, einlesen, ';');
    				name = einlesen;
    
    				getline(quelle, einlesen, ';');
    				matrikelnummer = einlesen;
    
    				getline(quelle, einlesen, ';');
    				note = einlesen;
    			}
    
    			else
    			{
    				einlesen.clear();
    			}
    

    http://www.cplusplus.com/reference/string/string/clear/
    Natürlich funktioniert es immer noch nicht. Ich werde mir das nochmal genauer ansehen aber kann mir mal nicht jemand genau sagen warum ich was nicht benutzt soll bzw. in dem Fall falsch ist? Das würde mich schon ziemlich weiterbringen.



  • Hallo Array,

    Array schrieb:

    Also: Wenn ich das jetzt richtig verstanden hab, muss ich zuerst einen Test auf if(!quelle.good()) machen. Dieser gibt true zurück wenn eof, badbit oder failbit gesetzt werden.

    if(quelle.good())
    {
    getline(quelle, einlesen);
    
    }
    

    Werner Salomon schrieb:

    Korrekt ist immer if(!FAIL) , aber natürlich bitteschön nach(!) dem Lesen prüfen.

    Also genau wie SeppJ schon ausgeführt hat:

    if( getline(quelle, einlesen) ) { // bzw. while( getline(quelle, einlesen) )
             // 'einlesen' bearbeiten
    

    getline gibt hier eine Referenz auf ' quelle ' zurück und die Abfrage ( if oder while ) liefert ein !quelle.fail() .. und natürlich nachdem(!) versucht wurde einen Zeile aus ' quelle ' zu lesen!
    Übrigens kann der Input Stream dann auch nach EOF gehen, d.h. ein quelle.eof() würde true liefern, nämlich genau dann, wenn die letzte Zeile nicht mit einem LF(Linefeed) abgeschlossen ist. Trotzdem ist das eine Zeile und die Lese-Operation war Ok.

    Array schrieb:

    Wurde mir so im Tutorium beigebracht.

    Schönen Gruß an Deinen Dozenten, dann kläre ihn mal auf 😉

    Array schrieb:

    Wobei mir jetzt immer noch nicht klar ist warum ich kein getline benutzen soll.

    Weil Du in Deinem Beispiel zumindest am Anfang getline zum Überspringen von Zeilen einsetzt, nicht zum Lesen. Hier wäre eine ignore angebracht.
    getline liest auch wirklich jede Zeile - also auch Leerzeilen - u.U. ist hier immer ein quelle >> std::ws vor dem getline angebracht, was alle Leerzeichen, Leerräume und Tabs überliest.
    Ich kann Deine Datei leider nicht öffnen, daher weiß ich nicht was Du lesen möchtest.

    Das erase in Zeile 23 löscht nur ein Zeichen in einlesen , aber nie die Zeile. erase ist hier eine Methode von std::string , da einlesen ein std::string ist.

    Gruß
    Werner



  • .. inzwischen konnte ich Deine Datei lesen. Ich habe Dir das hier mal skizziert, wie man diese Daten einliest. Wobei ich mich an Deinen Code angelehnt habe .. in produktivem Code würde man das wohl noch etwas anders machen, aber ich wollte das nicht überfrachten.

    Melde Dich doch bitte und sage uns, wie Du damit zurecht kommst.

    Gruß
    Werner

    #include <iostream>
    #include <fstream>
    #include <string>
    #include <limits> // numeric_limits<>
    
    int main()
    {
        using namespace std;
        const std::streamsize ALLES = numeric_limits< streamsize >::max();
    
        ifstream quelle("Noten.txt");   // [i]fstream reicht
        if( !quelle.is_open() ) // IMMER prüfen, in 80% aller Fehlerfälle kann die Datei nicht gefunden werden!
        {
            cerr << "Fehler beim Oeffnen der Datei" << endl;
            return -2;
        }
    
        for( int zeile=0; zeile<4; ++zeile )
            quelle.ignore( ALLES, '\n' );   // überspringe die Zeile
    
        string name;
        string matrikelnummer;
        double note;
        char c;
        while( quelle >> c ) // bricht ab bei Lesefehler
        {
            // auf '*' prüfen
            if( c == '*' )
            {
                quelle.ignore( ALLES, '\n' );   // Kommentar überspringen
                continue; // oder break; ??
            }
            quelle.putback( c ); // wieder zurück in den Stream, wir brauchen das Zeichen noch!
            getline( quelle, name, ';' );
            getline( quelle >> ws, matrikelnummer, ';' ); // das 'ws' überliest das Leerzeichen vor 'matrikelnummer'
            quelle >> note;
            quelle.ignore( ALLES, '\n' ); // und den Rest der Zeile wieder überspringen
    
            // jetzt erst prüfen, ob das alles ok war!
            if( quelle )
            {   // hier kannst Du jetzt sicher sein, dass alle Variablen korrekt eingelesen wurden
                cout << name << "; " << matrikelnummer << "; " << note << endl;
            }
            else
                break;  // Lesefehler, also Schleife beenden
        }
        return 0;
    }
    


  • Vielen Dank, Werner. Ich habe das Programm gleich mal getestet. Werde es aber mit dem Debugger nochmal durchgehen müssen und mir 1-2 Funktionen nochmal genauer anschauen um es richtig zu verstehen.



  • Hallo Werner, ich habe mich jetzt länger mit Dateiverarbeitung beschäftigt und Recht hattet ihr. Ich überprüfe jetzt immer erst ob auf if(!quelle.is_open()) und in der while-Schleife mache ich die Überprüfung auf while(quelle.good()) und in dieser dann erst die Prüfung auf while(!quelle.eof()). Das hat mir sehr viel Zeit und Fehler erspart. Vielen Dank erstmal für die hilfreichen Tipps. 🙂

    Nur habe ich das mit den limits nicht ganz verstanden. Könntest du mir das vielleicht noch erklären?

    #include <iostream>
    #include <fstream>
    #include <string>
    #include <limits> // numeric_limits<>

    using namespace std;
    const std::streamsize ALLES = numeric_limits< streamsize >::max();

    for( int zeile=0; zeile<4; ++zeile )
    quelle.ignore( ALLES, '\n' ); // überspringe die Zeile

    while( quelle >> c ) // bricht ab bei Lesefehler
    {
    // auf '' prüfen
    if( c == '
    ' )
    {
    quelle.ignore( ALLES, '\n' ); // Kommentar überspringen
    continue; // oder break; ??
    }

    Und dann evtl. noch diese Zeile?
    Warum wird das genau gemacht, was bewirkt es in diesem Fall konkret?

    quelle.putback( c ); // wieder zurück in den Stream, wir brauchen das Zeichen noch!

    }

    Vielen Dank schonmal. 🙂



  • Hallo Array,

    Array schrieb:

    Nur habe ich das mit den limits nicht ganz verstanden. Könntest du mir das vielleicht noch erklären?

    Es gibt die Methode ignore. Mit dem ersten Parameter gibst Du die Anzahl der Zeichen an, die Du maximal ignorieren bzw. überlesen möchtest. Für den Fall dass dort die größte für diesen Typ darzustellende Zahl steht, heißt das so viel wie 'unendlich'. D.h. dann ist die Anzahl nicht begrenzt und ignore arbeitet sich bis zu dem Zeichnen durch, welches gleich dem zweiten Parameter (hier '\n' bzw. EOL bzw. End-Of-Line) ist oder eben bis zum Ende der Datei.

    Da es in C++ keine eindeutige Festlegung gibt, wie groß so ein Typ werden kann, gibt es die Struktur std::numeric_limits. Dort kann man verschieden Eigenschaften arithmetischer Typen abfragen.
    Und std::numeric_limits<std::streamsize>::max() liefert eben die größte Zahl, die mit dem Typ std::streamsize darstellbar ist. Das ist bei einem 32Bit-System i.A. =2147483647 (=0x7FFFFFFF); kann bei einem 64Bit-System aber auch =9223372036854775807 (=0x7FFFFFFFFFFFFFFF) sein.

    Array schrieb:

    Und dann evtl. noch diese Zeile?
    Warum wird das genau gemacht, was bewirkt es in diesem Fall konkret?

    quelle.putback( c ); // wieder zurück in den Stream, wir brauchen das Zeichen noch!

    Schau hier nach: putback; oder lass die Zeile mal weg und schau Dir an, was dann passiert 😉 . putback stellt das Zeichen 'c' wieder zurück, so dass es noch einmal gelesen werden kann. Zu beachten ist, dass das nicht beliebig oft geht! Am zuverlässigsten funktioniert es, wenn Du vorher genau ein Zeichen mit ' quelle >> c ' gelesen hast; das ist hier der Fall.

    Array schrieb:

    .. Ich überprüfe jetzt immer erst ob auf if(!quelle.is_open()) und in der while-Schleife mache ich die Überprüfung auf while(quelle.good()) und in dieser dann erst die Prüfung auf while(!quelle.eof()).

    Hm! - noch mal zur Klarstellung: Im Prinzip sollte ein Code zum Lesen immer so aussehen:

    Datei öffnen
        prüfen mit is_open()
    
        Schleife // ggf. for(;;)
           Lesen eines vollständigen Eintrags (hier von name bis note)
           // und jetzt erst:
           if( quelle )   // bzw. if( !quelle.fail() ) // ist das gleiche
               Ok, eingelesene Daten verwenden
           else
               Einlesen beenden
        Ende der Schleife
        // Wenn eine Datei bis zum Ende gelesen werden soll, dann und nur dann
        if( quelle.eof() )
            Ok, Datei bis zum Enden gelesen
    

    Der Schleifenkopf inklusive Lesen plus prüfen, lässt sich in eine einzige Zeile zusammen fassen, wenn man Name, Matrikelnummer und die Note in einer Struktur 'Eintrag' zusammen fasst - etwa so

    Eintrag eintrag;
        while( quelle >> eintrag ) {
            .. eingelesene Daten 'eintrag' verwenden
    

    Gleichwertig ist:

    for( Eintrag eintrag; quelle >> eintrag; ) {
            .. eingelesene Daten 'eintrag' verwenden
    

    Gruß
    Werner



  • Vielen lieben Dank an alle und vor allem dir Werner! Das hat mich wirklich weiter gebracht. Jetzt muss ich nur noch das mit dem überladen des Ausgabeoperators lernen und dann kann die Klausur auch kommen. 🙂


Anmelden zum Antworten