istream vs. scanf



  • Skym0sh0 schrieb:

    und grad deine lösungen werner salomon, die du gerne mal postest, wenn wer irgendwelche ein/ausgabe fragen hat, das sind allein schon von der größe und der genutzten sprachkonstrukte so große beispiele, da schwindet echt die lust am programmieren...

    Bei mir ist es genau umgekehrt. Ich les mir solche Posts dann immer genau durch bis ich verstanden habe, was er da tatsächlich macht. Und dann freue ich mich einfach über die Eleganz und bekomme direkt Lust, bereits vorhandenen Code von mir mit dem neu gelernten zu verbessern. 🙂



  • Einfachst C++:

    #include <iostream>
    #include <fstream>
    
    int main()
    {
    	char c;
    	int x;
    	int y;
    	std::string text;
    	unsigned char fehlgeschlagen = 0x1 + 0x2 + 0x4 + 0x8;
    
    	std::ifstream parameter ("Spiel.ini"); // Pfade ergänzen?
    
    	if(parameter)
    	{
    		unsigned int wert[2];
    		parameter >> text >> x >> c >> y;
    		parameter >> text >> text;  // nur zur Demo - hier nicht so gefordert
    	}
    	std::cout << "x: " << x << "  y: " << y << std::endl;
    	std::cout << text << std::endl; // nur zur Demo - hier nicht so gefordert
    }
    

    Hast du dir die Mustervariante so vorgestellt Werner? Ist allerdings ohne: me.c_str() 😉



  • f.-th. schrieb:

    Hast du dir die Mustervariante so vorgestellt Werner? Ist allerdings ohne: me.c_str() 😉

    Nein - so nicht.

    Ich habe es vielleicht zu ungenau beschrieben.
    Der Benutzer von muster soll programmieren können:

    int wert;
        istream in;
        if( in >> muster("bild=") >> wert )
            // 'wert' wurde gelesen
    

    wobei mit muster("bild=") die Zeichenfolge 'bild=' im Stream überlesen wird. Sobald ein Zeichen davon abweicht, soll der Stream in den Zustand fail gehen, und gleichzeitig keine weiteren Zeichen mehr einlesen - was er dann sowieso tut.

    Angenommen der Input-Stream enthält "bild=42", dann soll '>> muster("bild=")' bis zur '4' von 42 alles überlesen. Das anschließende '>> wert' liest dann den Integer. Enthält der Stream etwas anderes - z.B. "Farbe=rot", so soll die Lese-Funktion istream >> muster("bild=") schon beim Buchstaben 'F' anhalten und das Fehlerbit setzen. Letzteres verhindert dann, das weitere Zeichen konsumiert werden.

    Folglich muss der der Ausdruck muster("bild=") etwas liefern, für das ein Streaming-Operator existiert. Und innerhalb dieser Streaming-Funktion geschieht das Einlesen und Überprüfen der Zeichen.
    Was muster("bild=") genau liefert, bleibt Dir überlassen. Da gibt es auch mehrere Lösungswege.

    Gruß
    Werner



  • So vielleicht?

    struct muster {
    
            public:
    
                    muster(std::string str) : m_muster(str) {}
    
                    friend std::istream& operator>>(std::istream& in, muster const& m) {
                            auto it = m.m_muster.begin();
                            for(char c; it != m.m_muster.end() && (c = in.get()); ++it) {
                                    if(c != *it) {
                                            in.setstate(std::ios::failbit);
                                            return in;
                                    }
                            }
                            return in;
                    }
    
            private:
    
                    std::string m_muster;
    };
    


  • #include <sstream>
    #include <fstream>
    #include <string>
    #include <iostream>
    
    int main()
    {
    	std::stringstream example_data(
    									"foo = 123\n"
    									"foobar = Hallo_Welt"
                                     );
    	std::string key, value;
    	while ( std::getline(example_data, key, '=') && std::getline(example_data, value) )
    	{
    		std::cout << "'" << key << "' = '" << value << "'" << std::endl;
    	}
    
    	return 0;
    }
    


  • pyhax schrieb:

    So vielleicht?

    Ja - der Ansatz sieht gut aus - dann jage es doch mal durch meinen Test (s.u.) und feile noch mal an den Details.

    Viel Spaß 😉

    #include <iostream>
    #include <sstream>
    #include <cassert>
    
    // .. Deine muster-Implementierung
    
    int main()
    {
        using namespace std;
        // -- muster - test
        int wert;
    
        // --   Gutfälle
        {
            istringstream in("hallo 7");
            in >> muster("hallo") >> wert;
            assert( in );
            assert( wert == 7 );
        }
        {
            istringstream in("hal lo42");
            in >> muster("hal lo") >> wert;
            assert( in );
            assert( wert == 42 );
        }
        {
            istringstream in(" \ntoken101");
            in >> muster("token10") >> wert;
            assert( in );
            assert( wert == 1 );
        }
    
        // --   Fehlerfälle
        {
            istringstream in("hallo 7");
            in >> muster("Hallo") >> wert;
            assert( in.fail() );
        }
        {
            istringstream in(" hallo 7");
            in >> noskipws >> muster("hallo") >> wert;
            assert( in.fail() );
        }
        {
            istringstream in("  hal");
            in >> muster("hallo") >> wert;
            assert( in.eof() );
            assert( in.fail() );
        }
        {   // --   mit Wieder-Aufsetzen
            istringstream in("hallo 17");
            in >> muster("Hallo") >> wert;
            assert( in.fail() );
            in.clear();
            in >> muster("hallo") >> wert;
            assert( in );
            assert( wert == 17 );
        }
    
        return 0;
    }
    

    Gruß
    Werner



  • struct muster {
    
    	public:
    
    		muster(std::string str) : m_muster(str) {}
    
    		friend std::istream& operator>>(std::istream& in, muster const& m) {
    			std::istream::sentry _(in);
    			auto it = m.m_muster.begin();
    			for(char c; it != m.m_muster.end() && (c = in.get()); ++it) {
    				if(c == std::istream::traits_type::eof()) {
    					in.setstate(std::ios::failbit & std::ios::eofbit); // Oder setzt istream::get schon das EOF und FAIL bit? 
    					break;
    				}
    				if(c != *it) {
    					in.putback(c);
    					for(--it;it != m.m_muster.begin() - 1; --it) in.putback(*it); // Sollen passende Zeichen bei einem nicht
    					                                                              // ganz korrektem muster wieder zurück
    					                                                              // geschrieben werden?
    					in.setstate(std::ios::failbit);
    					break;
    				}
    			}
    			return in;
    		}
    
    	private:
    
    		std::string m_muster;
    };
    

    Alle tests bestanden 😃 Aber ich dachte, in.putback() muss nicht immer funktionieren (laut Standard)? Bin mir aber da nicht ganz sicher.



  • Hallo pyhax,

    pyhax schrieb:

    Alle tests bestanden 😃

    Gratuliere 👍

    pyhax schrieb:

    Aber ich dachte, in.putback() muss nicht immer funktionieren (laut Standard)? Bin mir aber da nicht ganz sicher.

    nun, bei einem istringstream funktioniert es eigentlich immer.

    Vielleicht denke ich mir noch ein paar härtere Testbedingungen aus 😉
    .. aber jetzt ist es schon spät.

    Gruß
    Werner



  • Werner und Pyhax ihr zeigt das sehr gut.

    Aber ich befürchte nur wenige haben C++ so drauf wie ihr. Der Beitragsersteller hat wahrscheinlich schon kapituliert und wird weiter sein "C mit cout" - Stil folgen. Selbst in "C mit cout" erschien mir sein Quelltext noch ein wenig zu umständlich 😕

    Kann man da einen C++ Einsteiger freundlicheren Weg finden? Oder passt das nicht zum Stil dieses Forums? Okay, das sollte in jedem C++ Einsteiger-Buch auch erklärt werden. Aber die Liste der C++ Bücher, die mehr schaden als nutzen wird ja auch nicht kürzer.

    Werner und Pyhax macht weiter so - ihr zeigt das, was nur in wenigen anderen C++ Beispielen zu finden ist.



  • Ich denke je öfter und regelmäßiger man z.B. mit solchen IO-Sachen zu tun hat, desto mehr wird man solch elegante Lösungen zu schätzen wissen.

    Wenn man aber nur selten damit zu tun hat, kommt sicher eher eine Lösung mit den Sprachmitteln heraus, mit denen man hauptsächlich arbeitet. Was aber nicht schlecht sein muss. Wenn man eigenen Code nach ein paar Jahren wieder ändern muss, fällt die Arbeit am Eigengewächs sicher leichter, als die an einer "Perle" die man mal im Netz gefunden hat, aber später nicht mehr versteht.

    Übrigens neigen Programmierer in anderen Sprachen anscheinend auch zu komplexen Konstrukten zum Einlesen von INI-Dateien:

    Dictionary<string, Dictionary<string, string>> InIFile
    = ( from Match m in Regex.Matches( data, pattern, RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline )
     select new
     {
      Section = m.Groups["Section"].Value,
    
      kvps = ( from cpKey in m.Groups["Key"].Captures.Cast<Capture>().Select( ( a, i ) => new { a.Value, i } )
         join cpValue in m.Groups["Value"].Captures.Cast<Capture>().Select( ( b, i ) => new { b.Value, i } ) on cpKey.i equals cpValue.i
         select new KeyValuePair<string, string>( cpKey.Value, cpValue.Value ) ).ToDictionary( kvp => kvp.Key, kvp => kvp.Value )
    
      } ).ToDictionary( itm => itm.Section, itm => itm.kvps );
    


  • f.-th. schrieb:

    .. Aber ich befürchte nur wenige haben C++ so drauf wie ihr. Der Beitragsersteller hat wahrscheinlich schon kapituliert und wird weiter sein "C mit cout" - Stil folgen. Selbst in "C mit cout" erschien mir sein Quelltext noch ein wenig zu umständlich 😕

    Kann man da einen C++ Einsteiger freundlicheren Weg finden?

    Kann man sicher, in diesem Fall sollte es kein Problem sein. Angenommen Du kannst die Basics, string & vector und ein wenig IO. Dann kann man sicher eine Funktion schreiben, mit folgender Signatur:

    bool check_muster( std::istream& in, const std::string& muster_text );
    

    Dazu muss man noch wissen, dass sowohl std::cin, als auch ifstream beides std::istream's sind, auch wenn std::ifstream nochmal abgeleitet ist. Das wissen viele Anfänger nicht, aber viele Anfänger wissen schon dass man mit std::istream::get() Zeichen einlesen kann.
    Die obige Funktion soll jetzt die Zeichen aus 'muster_text' aus dem istream lesen und dann ein true liefern, wenn alle Zeichen passen. Ansonsten soll die Funktion false zurückgeben und mit dem Lesen aufhören, wenn ein Zeichen nicht übereinstimmt.

    bool check_muster( std::istream& in, const std::string& muster_text )
    {
        for( unsigned i = 0; i < muster_text.size(); ++i )
        {
            char c = in.get();
            if( c != muster_text[i] )
            {
                return false; // keine Übereinstimmung -> Fehler
            }
        }
        return true; // jedes Zeichen des inputs stimmte mit 'muster_text' überein
    }
    

    ist das schon schwer - nee oder?

    Wenn man diesen ersten Entwurf jetzt auf meinen Test loslassen würde - ich weiß, es geht nicht, Schnittstelle passt nicht, aber nur mal für Spaß angenommen - dann knallt es in Zeile 29. Das liegt daran, dass der erste Entwurf keine führenden Leerzeichen überliest. Das sollte er tun, denn die Funktion soll sich ähnlich verhalten, wie wenn man z.B. ein Wort oder eine Zahl einliest.

    Das kann auch jeder ausprobieren, wenn er hinter der Funktion check_muster einfach noch dies einfügt:

    // -- Black Magic - nicht angucken ;-)
    struct muster { explicit muster( const std::string& t ) : t_(t) {} std::string t_; };
    std::istream& operator>>( std::istream& in, muster m ) { if( !check_muster( in, m.t_ ) ) in.setstate( std::ios_base::failbit ); return in; }
    

    .. und nicht vergessen, die notwendigen includes (die aus meinem Test).
    Zu dem 'black magic' komme ich heute nicht - im Augenblick dient das nur dazu, die geforderte Schnittstelle herzustellen. man könnte natürlich auch den Anwender-Code (z.B. den Test) so ändern, dass er nur mit der Funktion oben arbeiten kann.

    Überlesen von Leerzeichen geht ganz einfach mit std::ws. Einfach hinzufügen:

    bool check_muster( std::istream& in, const std::string& muster_text )
    {
        in >> std::ws; // führende white space character überlesen
        for( unsigned i = 0; i < muster_text.size(); ++i )
        {
            char c = in.get();
            if( c != muster_text[i] )
            {
                return false; // keine Übereinstimmung -> Fehler
            }
        }
        return true; // jedes Zeichen des inputs stimmte mit 'muster_text' überein
    }
    

    phyax hat das in seinem Code ganz geschickt mit dem std::istream::sentry-Objekt (Zeile 😎 gemacht; würde ich aber schon als fortgeschritten bezeichnen.

    Über die obige Hürde kommen wir drüber, dummerweise knallt es nun in Zeile 56 des Tests. Das Wieder-Aufsetzen funktioniert nicht.
    Wenn ein Zeichen gelesen wurde, so ist es natürlich für spätere Leser nicht mehr zugänglich, auch wenn der erste damit nichts anfangen kann. Das Zeichen sollte also wieder zurück in den Stream - das geht mit std::istream::putback. Ich hatte mich ja an anderer Stelle schon mal über putback ausgelassen, finde aber gerade den Thread nicht wieder.

    Man kann putback nicht bedenkenlos aufrufen, aber man kann in 99% aller Fälle putback genau einmal rufen, wenn man vorher genau ein Zeichen gelesen hat. Man stelle sich vor, dass das Input-Device eine Serielle-RS232-Schnittstelle ist (die älteren unter uns kenne das noch 🕶 ) Die Hardware diese Device kann i.A. genau 1 Zeichen halten. Mit dem Lesen dieses Zeichens erkläre ich nur das aktuelle Zeichen als ungültig - also bereits gelesen, aber man muss nicht schon das nächste Zeichen empfangen. Mit dem einmaligen(!) putback geschieht jetzt nichts anderes als diese Zeichen wieder als 'ungelesen' zu markieren. Damit steht es dem nächsten Leser wieder zur Verfügung.

    Kommt ein falsches Zeichen, so put'te man es 'back' und verlasse die Funktion mit false .

    bool check_muster( std::istream& in, const std::string& muster_text )
    {
        in >> std::ws; // führende white space character überlesen
        for( unsigned i = 0; i < muster_text.size(); ++i )
        {
            char c = in.get();
            if( c != muster_text[i] )
            {
                in.putback( c ); // das erste nicht übereinstimmende Zeichen zurück
                return false; // keine Übereinstimmung -> Fehler
            }
        }
        return true; // jedes Zeichen des inputs stimmte mit 'muster_text' überein
    }
    

    .. und schon läuft zumindest der Test durch.

    Es gibt aber noch zwei Punkte zu diskutieren. Der erste Punkt betrifft die führenden Leerzechen. std::ws liest immer(!) die Leerzeichen. Ein Benutzer hat aber die Möglichkeit, das skipws-Flag im Stream rückzusetzen, d.h. er möchte die führenden Leerzeichen gar nicht überlesen, vielleicht weil sie Teil der Muster-Textes sind.
    Das ist einfach, wenn man weiß, wie man dieses Flag abfragt.

    if( in.flags() & std::ios_base::skipws )
            in >> std::ws; // führende white space character nur überlesen, wenn 'skipws' gesetzt
    

    Der zweite Punkt ist wesentlich subtiler, und betrifft den Fall, wenn das Einlesen des Zeichens mit in.get() schief geht. Natürlich wird ein Character zurück gegeben und da das ein char(EOF) (in der Praxis meist ein char(-1)) ist, wird das in den seltensten Fällen mit dem Mustertext übereinstimmen. Zu Problemen kommt es erst dann, wenn das Zeichen zufällig mit den letzten Zeichen in Mustertext übereinstimmt. So ein Fehler tritt gegebenenfalls sehr spät auf, und kann sehr ärgerlich werden.
    Nun die Lösung ist einfach - man fragt den Stream ab, ob noch alles ok ist. Das geht genauso wie nach dem Einlesen von einer Zahl oder sonstwas. Schreibt man dann:

    int zahl;
        if( in >> zahl ) { // alles gut
    

    so fügt man hier nur die entsprechende Abfrage ein:

    bool check_muster( std::istream& in, const std::string& muster_text )
    {
        if( in.flags() & std::ios_base::skipws )
            in >> std::ws; // führende white space character überlesen, wenn 'skipws' gesetzt
        for( unsigned i = 0; i < muster_text.size(); ++i )
        {
            char c = in.get();
            if( !in )
                return false; // EOF oder Fehler
            if( c != muster_text[i] )
            {
                in.putback( c ); // das erste nicht übereinstimmende Zeichen zurück
                return false; // keine Übereinstimmung -> Fehler
            }
        }
        return true; // jedes Zeichen des inputs stimmte mit 'muster_text' überein
    }
    

    und gut ist!

    f.-th. schrieb:

    Oder passt das nicht zum Stil dieses Forums?

    es sollte sicher nicht 'Stil des Forums' sein, hier nur 'Hi-Tech'-Lösungen zu posten, oder noch schlimmer posten zu dürfen. Ich find's auch nicht gut, wenn mancher, der hier mutig (oder naiv) seine ersten Versuche postet, gleich zum Volltrottel erklärt wird. 😉
    Letztlich vergrault man sich dadurch den Nachwuchs - und jeder hat mal klein angefangen.

    Gruß
    Werner



  • respekt werner und dickes +1



  • Werner, kann man in deiner Version nicht statt get() + putback() einfach peek() + ignore() verwenden? 🙂
    Fände ich persönlich schöner.



  • Sone schrieb:

    kann man in deiner Version nicht statt get() + putback() einfach peek() + ignore() verwenden? 🙂

    Hallo Sone,

    ja - kannst Du machen. Zu beachten ist, dass peek den istream nicht auf fail sondern nur auf EOF setzt, falls EOF erreicht wird. Die anschließende Abfrage sollte also sein

    char c = in.peek();
        if( !in.good() ) // ist in.eof()==true, dann ist in.good()==false
            return false;
    

    Dieses Vorgehen ist auf streambuf-Ebene sogar das übliche (also sgetc() und anschließend snextc()), auf Stream-Ebene eher nicht, was ich bisher gesehen habe.

    Gruß
    Werner


Anmelden zum Antworten