istream vs. scanf


  • Mod

    Hallo LordZsar1,

    auf den ersten Blick sehen die C++-Methoden hier unterlegen aus und das ist auch richtig beobachtet. Die wahre Stärke von C++ ist jedoch der leichter als in C erreichbare höhere Abstraktionsgrad. Du brauchst deinen Lesecode schließlich nur ein einziges Mal zu schreiben, dann kannst du ihn immer wieder benutzen. Im Gegensatz zu sscanf, wo du jedes Mal angeben musst, wie das Format aussieht.

    Eine übliche Art und Weise dies zu erreichen, wäre, dass du dir eine passende Klasse definierst und für diesen den Operator>> überlädst, dann kannst du später einfach Code a la

    vector<Parametertyp> parameters;
    for (Parametertyp parameter; cin >> parameter; parameters.push_back(parameter));
    

    schreiben. Wie das geht findest du in unzähligen Forenbeiträgen (such mal nach Nutzer "Werner Salomon") und jedem guten C++-Buch. Das ist quasi die Standardmethode, Daten in C++ zu lesen.

    Es gibt auch viele Alternativen und fertige Bibliotheksfunktionen, um formatierte Daten zu lesen. Dann muss man nicht alles selber schreiben. Beliebt und sehr verbreitet ist zum Beispiel die Boost-Bibliothek. Deren Unterbibliotheken boost::tokenizer, boost::lexical_cast, boost::regex, und boost::string_algorithms kommen alle in Frage, um Formatierungsprobleme dieser Art einfach und elegant zu lösen (jeweils mit Betonung anderer Aspekte).

    Aber wir sind nicht auf diese Methoden beschränkt: Für Parameterdateien ist dies vermutlich auch nicht unbedingt das, was man möchte, da man vermutlich gemischte Datentypen vorliegen hat. Aber auch hier ist es einfach, den Lesevorgang und die ganze Datenverwaltung zu abstrahieren und man bekommt eine Schreibfunktionalität fast geschenkt mit dazu. Ein sehr, sehr einfacher Vorschlag, an dem man noch leicht sehr viel verbessern kann und der bloß zeigen soll, wie leicht man etwas konstruieren kann, was in der Benutzung noch deutlich angenehmer als scanf ist:

    (Ich erlaube mir mal, dein Datenformat als

    bild =   1366x768
    aufl =   800x600
    fps=     30
    faeden = 2
    

    anzunehmen (d.h. mit irgendeiner Art Whitespace zwischen Schlüssel und '='. Dein Format wäre auch nicht viel schwieriger, aber 2-3 Zeilen mehr Code)

    #include <iostream>
    #include <map>
    #include <string>
    #include <sstream>
    #include <stdexcept>
    
    class ParameterFile
    {
      std::map<std::string, std::string> dataset;
    
    public:
      void read(std::istream &in)
      {
        for(std::string line; std::getline(in, line);)
          {
            if (line.size() && line[0] != '#')    // Ich erlaube mal Kommentarzeilen mit '#' am Anfang
              {
                std::string key, value;
                char equals;
                std::stringstream lineparser(line);
                lineparser >> key >> equals >> value;
                if (!lineparser or equals != '=')
                  throw std::runtime_error("Parameter file line:\n" + line + "\nDoes not match the format\nKey = Value");
                dataset[key] = value;
              }
          }
      }
    
      template <typename ValueType> void get_parameter(const std::string &key, ValueType& value)
      {
        std::map<std::string, std::string>::iterator key_it = dataset.find(key);
        if (key_it == dataset.end())
          throw std::runtime_error("Key \"" + key + "\" does not exist in ParameterFile.");
        std::stringstream valueparser(key_it->second);
        valueparser >> value;
        if (!valueparser)
          throw std::runtime_error("Value \"" + key_it->second + "\" for key \"" + key + "\" is not of the expected format.");
      }
    
      template <typename ValueType> void add_parameter(const std::string &key, ValueType const& value)
      {
        std::stringstream valueparser;
        valueparser << value;
        dataset[key] = valueparser.str();
      }
    
      void remove_parameter(const std::string &key)
      {    
        std::map<std::string, std::string>::iterator key_it = dataset.find(key);
        if (key_it == dataset.end())
          throw std::runtime_error("Key \"" + key + "\" does not exist in ParameterFile.");
        dataset.erase(key_it);
      }
    
      void write(std::ostream &out)
      {
        for (std::map<std::string, std::string>::iterator key_it = dataset.begin(); key_it != dataset.end(); ++key_it)
          out << key_it->first << "\t=\t" << key_it->second << '\n';
      }
    
      ParameterFile() {}
      ParameterFile(std::istream &in) {read(in);}
    };
    

    Beispielhafter Anwendungscode sieht dann so aus:

    int main()
    {
      std::stringstream example_data(
                                     "foo = 123\n"
                                     "foobar = Hallo_Welt"
                                     );
    
      ParameterFile params(example_data);
    
      int foo;
      std::string foobar;
      params.get_parameter("foobar", foobar);  // Reihenfolge ist egal
      params.get_parameter("foo", foo);
      std::cout << "Gelesen:\nfoo = " << foo << "\nfoobar = " << foobar << '\n';
      params.add_parameter("bar", 5.7);
      params.remove_parameter("foobar");
      std::cout << "Geänderte Parameterdatei:\n";
      params.write(std::cout);
    }
    

    Das ist doch schon dem sehr ähnlich, was du wolltest. Merke: Das Ding ist viel mächtiger als scanf alleine, sondern ist bereits ein sehr solider Ansatz für eine komplette Rundumlösung zur Parameterverwaltung in einem Programm, die du immer wieder in allen deinen Programmen benutzen kannst. Der Ersatz für deine Zeilen 11-16 und 37-41 alleine wären bloß meine Zeilen 12-38, die auch wiederum mehr können, da sie Kommentare erlauben, aussagekräftige Fehlermeldungen generieren, mit allen denkbaren (auch benutzerdefinierten!) Datentypen zurecht kommen und bei fehlerhafter Benutzung zur Compilezeit Fehlermeldungen erzeugen anstatt zur Laufzeit abzustürzen. Defaultparameter bei fehlenden Daten sind trivial hinzuzufügen, lass einfach die Fehlerbehandlung weg und führe einen entsprechenden Parameter ein.

    Das alles ist auch kein sonderlich komplexer Code, sondern größtenteils Boilerplatecode, den ich in 10-15 Minuten runterschreiben konnte, begrenzt vor allem durch meine langsame Tippgeschwindigkeit.



  • Hallo Zsar (vorher LordZsar1),

    die wichtigsten Argumente sind schon genannt worden.
    Abschließend kann man noch sagen: "Lerne in C++ zu denken"
    Du hast einen Code, den Du oben vorgestellt hast und versuchst diesen jetzt in eine 'andere' Programmiersprache zu übersetzen. Das ist schon der erste Fehler - besser Du hast ein Problem (Einlesen einer INI-Datei) und die Frage wäre: "wie geht das in C++?"
    Und die Lösung ist eben genau nicht, den C-Code, den Du bereits hast, in C++ zu portieren.

    SeppJ hat Dir schon eine Lösung gepostet und auch ich habe hier im Forum bereits gezeigt, wie man einen INI-Datei mit einfachen Mitteln einliest - ist praktisch die gleiche Lösung wie die von SeppJ.

    Die Nachteile Deiner Lösung sind u.a.:
    - keine Vertauschung der Reihenfolge der Attribute möglich
    - kein Hinzufügen neuer Attribute möglich, die zwischen bereits bekannten stehen
    - Erweiterungen wie Kommentare und Blöcke sind an den Anwendercode gekoppelt.

    Besonders der letzte Punkt wiegt IMHO schwer. Mit C++ versucht man gerade bei Problemen, die immer wieder vorkommen, i.A. eine Lösung zu schaffen, die aus einem allgemeinen Teil und einem User-spezifischen Teil besteht. Letzterer sollte klein und easy-to-use sein.

    Mit den neuen Mitteln, die C++11 zur Verfügung stellt, lässt sich das ganze noch mal eleganter lösen:

    #include <iostream>
    #include <fstream>
    #include <map>
    #include <string>
    #include <functional> // function<>
    #include <limits> // numeric_limits<>
    
    // --   der INI-Reader, für jedes Attribut wird ein Funktor benötigt
    //      (allgemeiner Teil)
    template< typename C >
    struct ini_reader
    {
        explicit ini_reader( const C& reader_catalog )
            : reader_catalog_( &reader_catalog )
        {}
        friend std::istream& operator>>( std::istream& in, ini_reader ir )
        {
            for( std::string token; getline( in, token, '=' ); )
            {
                auto i = ir.reader_catalog_->find( token );
                if( i == end(*ir.reader_catalog_) )
                {   // unbekanntes Token überspringen (skipline)
                    in.ignore( std::numeric_limits< std::streamsize >::max(), '\n' );
                    continue;
                }
                i->second( in ); // <--- hier wird der Wert gleich mit dem 'richtigen' Typ gelesen
                if( in.eof() || (in >> std::ws).eof() ) // diese Konstruktion sorgt dafür, dass 'in' bei EOF nicht nach fail geht!
                    break;
            }
            return in;
        }
    private:
        const C* reader_catalog_;
    };
    template< typename C >
    ini_reader< C > ini( const C& cat )
    {
        return ini_reader< C >( cat );
    }
    
    // --   Beispiel für eine User-defined Klasse/struct
    struct Size
    {
        Size( float x, float y )
            : x_(x), y_(y)
        {}
        float x_, y_;
    };
    std::istream& operator>>( std::istream& in, Size& sz )
    {
        return (in >> sz.x_).ignore(1) >> sz.y_; // Alternative: in >> sz.x_ >> Char<'x'> >> sz.y_; // (s.u.)
    }
    
    int main()
    {
        using namespace std;
        // --   Attribute mit ihrer Defaultbelegung
        Size bildGroesse( 1024.f, 768.f );
        Size aufloesung( 800.f, 600.f );
        double fps = 25.;
        int faeden = 1;
        {
            string filename = "./Spiel.ini";
            ifstream file( filename.c_str() );
            if( !file.is_open() )
            {
                cerr << "Fehler beim Oeffnen der Datei '" << filename << "'" << endl;
                return -2;
            }
            // --   einlesen
            map< string, function< void( istream& ) > > reader;
            //      folgende 4 Zeilen sind der User-spezifische Teil des INI-Lesens
            reader["bild"] = [&bildGroesse]( istream& in ) { in >> bildGroesse; };
            reader["aufl"] = [&aufloesung]( istream& in ) { in >> aufloesung; };
            reader["fps"] = [&fps]( istream& in ) { if( in >> fps && fps < 12. ) in.setstate( std::ios_base::failbit ); }; // Beispiel für eine Bereichsüberprüfung
            reader["faeden"] = [&faeden]( istream& in ) { in >> faeden; };
            if( (file >> ini( reader )).fail() )
            {
                cerr << "Fehler beim Lesen der ini-Datei '" << filename << "'" << endl;
                return -3;
            }
        }
        // --   Start der Applikation
        cout << "Alle ok" << endl;
        // usw. 
    
        return 0;
    }
    

    Allgemeiner Teil und User-spezifischer Teil sind hier getrennt. Wenn man ein weiteres Attribut lesen will, muss lediglich die Liste ab Zeile 73 erweitert werden. Sollten neue Funktionalitäten wie Kommentare überlesen hinzukommen, so ist 'nur' die struct ini_reader anzupassen.
    Da sind dann auch so Dinge möglich, wie Werte gleich beim Einlesen zu überprüfen (s. Zeile 75: fps darf nicht kleiner werden als 12.).
    Zu der in Zeile 51 genannten Alternative siehe Char<''> (Helferlein zum Überlesen eines Zeichens).

    Auf die Gefahr hin, dass Du sagst: "alles kryptisch und undurchsichtig" ... sei Dir geantwortet: genau wie bei SeppJ ist das für mich Boilerplatecode. Das ist alles Gewohnheitssache, wenn man gelernt hat "in C++ zu denken".

    Gruß
    Werner



  • Ach! Was sind das für Eskapaden!

    Zsars Problemstellung schrieb:

    lese genau diesen Ausdruck und dann lese dieses Stückchen davon aus

    Format: [Teilmuster] Wert { [Teilmuster] Wert } [Teilmuster]
    Natürlich wäre ein Beispiel betont einfach gehalten. Zum Beispiel, oh, "Teilmuster Wert Teilmuster Wert"?
    Ebenso natürlich löst scanf das ganze Problem ebenso wenig - hiermit hätte es Probleme:

    Param123.5f <- 23.5
    Param1145.3f <- 45.3
    Param213 <- 13
    

    Und natürlich zum Dritten ist das nun ein Fall, den zu Missachten wahrhaftig getrost getan werden kann.

    Ich ziehe es vor, in Problemstellungen zu denken als in Lösungswerkzeugen. Denke in Hämmern, denke in Zangen!
    Ist es wahrlich so C++-spezifisches Vorgehen, eine Verallgemeinerung des Problems allgemein und so auf ewig zu lösen? Wäre es untypisch, wenn ich so in Java vorginge? Man denke nur - jedesmal, wenn ich in C eine Funktion definiere, die nicht ein reines Formatierungsmittel darstellt - welch entartetes Gedankengut ich da in Quelltext gieße!

    Man möge mir den Spott verzeihen, wie man einem Zwerg verziehe, dass er auf die Anrede "na, Großer" mit Unwillen reagierte.

    Und ist nicht doch genehm, dass ich mich bedankte?
    Doch ist es wohl: Meine Frage ist beantwortet und wiewohl das hier Präsentierte nicht die, allerdings aber eine Antwort ist, mag es mir doch nützlich werden, wie ein Bus die Füße schont: Wenn er einmal da ist, warum ihn nicht nutzen!
    Mein Dank soll also in der Tat an beide Beitragenden gehen - und der gleiche Satz mir um Verzeihung bei allen Dreien heischen, muss es doch einmal mehr geschehen sein, dass ich eine verständliche Fragestellung zu erschaffen nicht fertiggebracht hatte.



  • Du solltest einfach Dichter oder Schriftsteller werden. Für die deutsche Sprache hast du zu viel Talent um es für C++ zu verschwenden.

    Aber auch für mich ist dieser "Boilerplatecode" so "undurchsichtig und kryptisch", dass ich lieber eine Zeile sscanf schreibe. Das hat mir bisher auch noch nicht geschadet.



  • haha, bei mir ists umgekehrt.

    ich versteh kein wort von dem gedicht

    aber ich versteh den c++ code und genau da merke ich dann, dass ich doch kaum c++ kan oO

    mal so eine frage an euch beide (seppJ und Werner), wie lernt man sowas?
    ich meine, probleme und lösungen gut und schön. ich hab nur dann einfach oft das problem, komplexere denkweisen/lösungsansätze/sprachkonstrukte schonmal gesehen zu haben, aber ich erinnere mich in dem moment wo mans brauchen könnte kaum daran(als beispiel will ich mal anführen, dass ich vor längerer zeit den scott meyers gelesen habe und kurz danach trotzdem versucht habe, eine virtuelle methode im konstruktor der basisklasse aufzurufen oO)

    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...


  • Mod

    Ich habe bei anderen abgeguckt 🙂 . Die Streambufsachen aus anderen Threads von Werner. Das konkrete Beispiel in diesem Thread ist stark inspiriert von einer ganz ähnlichen Klasse, die in grauen Vortagen ein mystischer Arbeitsplatzvorgänger von mir ein einziges Mal geschrieben hat und die seither unverändert von mir und meinen Kollegen für alle möglichen Konfigurationsaufgaben benutzt wird. Womit man auch sieht: Abstraktion und Wiederverwendbarkeit sind kein Selbstzweck sondern wirklich nützlich und das funktioniert tatsächlich.

    Aber das sind vor allem Techniken und konkrete Ideen. Für allgemeine Problemlösungskompetenz hilft vermutlich nur Übung. Sowohl selber machen, als auch Ideen von anderen angucken. Dieses Forum ist vermutlich schon ganz gut geeignet. Wenn mal eine schwerere Frage kommt, entwickelt man selber eine Lösung und guckt sich hinterher auch an, was andere geschrieben haben.



  • Skym0sh0 schrieb:

    mal so eine frage an euch beide (seppJ und Werner), wie lernt man sowas?

    Hallo Skym0sh0,

    durch jahrelange Übung, durch ständige Beschäftigung mit dem Thema und mit dem Ehrgeiz stets die bessere Lösung zu finden. Auch wenn es manchmal religiöse Züge annimmt, um zu werten, was ist gut und was ist besser. D.h. am Ende hat jeder auch seinen Stil. Am deutlichsten wird das vielleicht bei den Threads, wo meine Wenigkeit mit Volkard diskutiert.

    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...

    Das tut mir Leid. Das ist wahrlich nicht die Intention. Wobei 'groß' ist hier relativ - ich find' sie eher klein.
    Vielleicht 'erschlage' ich den einen oder anderen mit meinen Programmbeispielen. Sie werden auch geboren aus einer Lust heraus, mit der Sprache und ihren Mitteln zu spielen. O-Ton Bjarne Stroustrup: "I find using C++ more enjoyable than ever. C++'s support for design and programming has improved dramatically over the years, and lots of new helpful techniques have been developed for its use."

    Und dann kommen auch Fragen wie diese:

    TheodorN. schrieb:

    habe eine Textdatei die so oder so ähnlich aussieht:

    R50=20
    R30=195
    X=R30
    I=R30+R50
    

    die Textdatei soll nach dem Durchlauf meines C++-Programms aber so aussehen:

    R50=20
    R30=195
    X=195
    I=215

    @Skym0sh0 - was erwartest Du darauf jetzt für eine Antwort? Wie viele Zeilen Code - und welche Komplexität - brauchst es Deiner Meinung nach, dieses Problem zu lösen?

    Schlussendlich würde ich mir auch mehr Diskussionen mit Leuten wünschen, die nicht so viel Erfahrung bzw. nicht das Know How haben.
    Also ruhig mal ganz blöd nachfragen, was das alles soll. Und ruhig mal einen konkreten Gegenvorschlag, auch wenn dann einen Antwort kommt, die Deinen Vorschlag aus einander nimmt, dann hast Du zumindest etwas gelernt. 😉

    In der Vergangenheit habe ich am meisten aus schlechtem Code gelernt. Code, der auf Grund seiner bloßen Existenz Riesenprobleme verursacht hat. Und dann kam automatisch die Frage: wie macht man es denn besser? Dabei habe ich natürlich auch einige Irrwegen beschritten, vielleicht bin ich jetzt auch wieder auf einem Irrweg. Aber wenn Du mich überzeugen willst, so musst Du mir auch eine Alternative bieten.

    Gruß
    Werner



  • Hallo Zsar,

    Zsar schrieb:

    Ich ziehe es vor, in Problemstellungen zu denken als in Lösungswerkzeugen. Denke in Hämmern, denke in Zangen!

    Und damit triffst Du den Kern des Problems ziemlich genau. Es ist doch so: die Problemstellung ist 'ini-Datei lesen' - der Hammer ist scanf. Der ist aber nur bedingt geeignet. Neben den bereits genannten Argumenten versagt scanf (bzw. schon das Vorgehen: lese "Teilmuster Wert Teilmuster Wert") insbesondere dann, wenn zwei Attribute mit einem gleichen Buchstaben beginnen, und das erste Attribut fehlt.

    Zieht man sich auf die Problemstellung zurück 'ini-Datei lesen', dann gibt mir C++ die Mittel, mir das passende Werkzeug für diese Problemstellung zu schnitzen. Und heraus kommt dann ein 'struct ini_reader' oder 'class ParameterFile' (s. SeppJs oder mein Posting oben). Und genau das meine ich mit "Denken in C++".

    Zsar schrieb:

    Ist es wahrlich so C++-spezifisches Vorgehen, eine Verallgemeinerung des Problems allgemein und so auf ewig zu lösen? Wäre es untypisch, wenn ich so in Java vorginge?

    Ich weiß nicht, wie man in Java vorgeht. Ich sehe hinter dem Ansinnen, das Allgemeine vom Spezifischen trennen zu wollen, keine Eigenschaft einer Programmiersprache, sondern ein Prinzip eines Programmdesigns. In wie weit man es im konkreten Einzelfall auch tut, ist immer auch eine Designentscheidung, und somit diskutierbar. Hier in diesem Fall bietet es sich an.

    Zsars Problemstellung schrieb:

    Ach! Was sind das für Eskapaden!

    Zsars Problemstellung schrieb:

    lese genau diesen Ausdruck und dann lese dieses Stückchen davon aus

    Format: [Teilmuster] Wert { [Teilmuster] Wert } [Teilmuster]
    Natürlich wäre ein Beispiel betont einfach gehalten. Zum Beispiel, oh, "Teilmuster Wert Teilmuster Wert"?

    OK - um auf die eigentliche konkrete Frage zurück zu kommen. Man kann sich in C++ natürlich auch ein scanf schnitzen, welches einen std::istream als ersten Parameter aufnimmt.
    Eine Zwischenlösung - welche einfacher zu realisieren ist könnte auch so aussehen:

    // Format: "bild=" x-Wert "x" y-Wert
        ifstream file( filename.c_str() );
        file >> muster("bild=") >> parameter.x >> muster("x") >> parameter.y;
    

    Beides (scanf mit istream + muster("..")) gibt es nicht 'von der Stange'. Und ich habe es auch noch nie gebraucht.

    Einen Lösungsvorschlag gibt es diesmal ganz bewusst keinen. Vielleicht hat mal jemand anders Lust, hier eine Implementierung für muster zu veröffentlichen.

    Gruß
    Werner



  • Werner Salomon schrieb:

    Das tut mir Leid. Das ist wahrlich nicht die Intention. Wobei 'groß' ist hier relativ - ich find' sie eher klein.
    Vielleicht 'erschlage' ich den einen oder anderen mit meinen Programmbeispielen. Sie werden auch geboren aus einer Lust heraus, mit der Sprache und ihren Mitteln zu spielen. O-Ton Bjarne Stroustrup: "I find using C++ more enjoyable than ever. C++'s support for design and programming has improved dramatically over the years, and lots of new helpful techniques have been developed for its use."

    aber genau das ist es ja warum viele C++ lieben (behaupte ich mal)
    ich hab mit 16 angefangen die sprache zu lernen, nicht um probleme zu lösen, sondern eher der sprache wegen. und nun bin ich eigentlich soweit, dass ich dieses gesammelte wissen auch nutzen kann um probleme zu lösen.

    das ding ist nur, ich mache das jetzt seit etwa 2007, ich habe ein paar projekte mit c++ geschrieben fürs studium und so (war nie was wirklich großes, nur wenn überhaupt 10-15.000 zeilen). und wenn ich meine kommilitonen angucke oder auch viele leute hier im forum oder leute, die das in ihrem beruf gelernt haben, dann denke ich mir, dass ich vom wissensstand her, C++ gut bis sehr gut kann.

    und am nächste abend gucke ich hier ins forum, laufe lese einem volkard, einem SeppJ oder einem Sone über den weg und fühle mich so dumm, dass ich denke, selbst für pommes wenden in der friteuse vom MCDonalds bin ich zu doof

    versteht mich nicht falsch, alle oben genannten (und noch viele mehr, so wie du werner) haben es aus meiner sicht drauf (ok, auch wenn sich viele für das lob an Sone aufregen dürften).

    und klar, man macht viel durch übung besser, guckt sich tricks ab und alles.

    traurig schaue ich der zukunft entgegen, wo es dann heisst "javascript ist die zukunft", "alles wird webbasiert", totale vernetzung und so. dann schaue ich in stellenanzeigen von den firmen und gesucht wird java, C#, web krams von oben bis unten und wieder zurück und manchmal noch delphi oder sowas.
    oder wenns dann mal C/C++ ist, dann aber so richtig tief spezialisiert, dass ich als informatiker bis jetzt nichtmals was von dem oberthema gehört habe.

    natürlich würde ich manchmal gerne eine antwort oder ein codeschnipsel posten, aber wie du schon sagtest, dann kommen leute die den standard beten (oder die msvc nicht mögen oder oder oder) und lösungen werden auseinander gerissen. oftmals schreibt man ja schon dabei "ist nicht perfekt, läuft aber", "ist ungetestet" usw., sprich man würde es selber vielleicht nicht so machen wenn es drauf ankommt, aber man wird zerrissen wie die gladiotoren in der arena im alten Rom(read-only-memory höhöhö 🤡 ).

    aber werner, du bist ein cooler typ, mal sehen ob ich mal auf einen deiner posts eine passende antwort habe, wenn dein beispiel mal wieder länger ist als die projekte mancher leute... 🙂



  • 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


Anmelden zum Antworten