Zerlegen von Eingabe mit cin



  • Kann ich etwas mit cin einlesen und direkt im Stream zerlegen? Sonst müsste ich nämlich mit getline in einen String lesen und da zerstückeln.

    In der Konsole kann zB Folgendes eingegeben werden:

    add 23
    find 23
    quit
    

    Dabei will ich anhand des ersten Wortes dispatchen und gegebenenfalls die nachfolgende Nummer anschauen. Ich hätte operator>> genommen, leider muss ich bei der Eingabe von nur "find" (ohne Nummer) warten, bis wieder etwas eingegeben wird...



  • .



  • Edit: Ich hab das mit getline übersehen und es vorgeschlagen.


  • Mod

    Klar geht das und zwar so, wie du es mit dem string machen würdest (den du dann ja sicherlich in einen stringstream geladen hättest). Für mehr Details müsstest du genauer beschreiben wie dein Format aussieht und was mit den Daten passieren soll, derzeit würde ich es in allereinfachster Variante so machen

    for(string keyword; cin >> keyword; )
    {
     if (keyword == "add")
      {
        int zahl;
        cin >> zahl;
        // Wasauchimmerdamitpassierensoll
      }
     if (keyword == "find")
      {
        int zahl;
        cin >> zahl;
        // Wasauchimmerdamitpassierensoll
      }
     if (keyword == "quit")
      {
        break;
      }
    }
    

    Natürlich kann man das noch um zig Schichten abstrahieren, wenn man mehr als drei Schlüsselworte oder kompliziertere Reaktionen hat.



  • Ja, aber dann ist das Problem eben, dass bei einer eingabe von "find" ohne zahl auf die zahl gewartet wird, und der benutzer eine 2. Eingabe machen muss. Wie kann ich diesen fehler abfangen?



  • Spinne auf dem Transistor schrieb:

    Ja, aber dann ist das Problem eben, dass bei einer eingabe von "find" ohne zahl auf die zahl gewartet wird, und der benutzer eine 2. Eingabe machen muss. Wie kann ich diesen fehler abfangen?

    Abfangen? Eine dreckige Möglichkeit wäre mit cin.rdbuf()->in_avail() zu prüfen, ob noch weitere Zeichen vorhanden sind bevor man die Zahl einliest, aber das ist IMO sehr unschön und fehleranfällig.


  • Mod

    Spinne auf dem Transistor schrieb:

    Ja, aber dann ist das Problem eben, dass bei einer eingabe von "find" ohne zahl auf die zahl gewartet wird, und der benutzer eine 2. Eingabe machen muss. Wie kann ich diesen fehler abfangen?

    😕 Also folgt die Zahl auf die Eingabe von find, wo ist das Problem?

    Sone schrieb:

    Abfangen? Eine dreckige Möglichkeit wäre mit cin.rdbuf()->in_avail() zu prüfen, ob noch weitere Zeichen vorhanden sind bevor man die Zahl einliest, aber das ist IMO sehr unschön und fehleranfällig.

    Nein, es ist schlichtweg falsch. Das wurde hier auch schon tausendemale diskutiert, das kann doch nicht alles an dir vorüber gegangen sein.



  • SeppJ schrieb:

    Sone schrieb:

    Abfangen? Eine dreckige Möglichkeit wäre mit cin.rdbuf()->in_avail() zu prüfen, ob noch weitere Zeichen vorhanden sind bevor man die Zahl einliest, aber das ist IMO sehr unschön und fehleranfällig.

    Nein, es ist schlichtweg falsch. Das wurde hier auch schon tausendemale diskutiert, das kann doch nicht alles an dir vorüber gegangen sein.

    Wo das?



  • SeppJ schrieb:

    😕 Also folgt die Zahl auf die Eingabe von find, wo ist das Problem?

    Es verwirrt den Benutzer. Wenn er Return drückt, soll die Eingabe entweder akzeptiert oder abgelehnt werden, aber nicht "halb akzeptiert". Danach kommt die nächste, unabhängige Eingabe.

    Unglaublich, dass schon wieder 2/3 der Beiträge dieses Threads von Sone mit Müll zugespammt wurden 🙄



  • Spinne auf dem Transistor schrieb:

    Es verwirrt den Benutzer.

    Wenn du willst, dass sich der Benutzer auf deinem Konsoleninterface wohlfühlt, würde ich nicht std::cin verwenden.

    Ich verwende dafür z.B. GNU readline (der Wikipedia-Artikel ist etwas übersichtlicher). Wobei es dort auch darauf hinausläuft, den string zu zerstückeln (kannst du mit einem stringstream machen).

    Trotz allem ist es möglich, wenn man sich in die Tiefen der IO-Streams begibt. Hier ein Beispiel, wie das aussehen könnte:

    struct special_newline_ctype : std::ctype<char>
    {
      mask table[table_size];
    public:
      special_newline_ctype(size_t refs = 0) : std::ctype<char>(table, false, refs) {
        std::copy(classic_table(), classic_table()+table_size, table);
        table['\n'] = (mask)cntrl;
      }
    };
    
    class line_guard {
      std::istream& os;
      std::locale l;
    public:
      line_guard(std::istream& os = std::cin) : os(os), l(os.getloc())
      { os.imbue(std::locale(l, new special_newline_ctype)); }
    
      bool eol() { os >> std::skipws; return os.peek() == '\n'; }
    
      ~line_guard() {
        os.clear(); os.imbue(l);
        os.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
      }
    };
    
    int main()
    {
      std::string s;
      while (std::cin >> s) {
        line_guard g; int nr;
        if (s == "find") {
          if (std::cin >> nr && g.eol()) std::cout << "found " << nr << '\n';
          else std::cout << "usage: find <number>\n";
        } else if (s == "add") {
          if (std::cin >> nr && g.eol()) std::cout << "added " << nr << '\n';      
          else std::cout << "usage: add <number>\n";
        } else if (s == "quit" && g.eol()) {
          return 0;
        } else {
          std::cout << "usage: quit|((find|add) <number>)\n";
        }
      }
    }
    

    Idee ist, dem Stream zu sagen, dass '\n' kein Whitespace ist. Das macht in Zeile 7. Sobald ein Newline eingelesen wurde, stoppt der Stream mit einlesen und setzt ein Errorbit. Das muss der Guard natürlich löschen.

    line_guard ist ein schöner Wrapper um das. Man kann bis zu einem newline lesen (dann wird ein failbit gesetzt). Wenn der Destructor aufgerufen wird und das newline noch nicht erreicht wurde, wird der Rest ignoriert. Das lässt sich vorher jedoch mit eol abfragen.

    Das Programm ist ziemlich robust. Hier eine Beispiel-Session:

    find 1
    found 1
    add a
    usage: add <number>
    add 2 b 
    usage: add <number>
    quit c
    usage: quit|((find|add) <number>)
    quit
    

  • Mod

    Spinne auf dem Transistor schrieb:

    SeppJ schrieb:

    😕 Also folgt die Zahl auf die Eingabe von find, wo ist das Problem?

    Es verwirrt den Benutzer. Wenn er Return drückt, soll die Eingabe entweder akzeptiert oder abgelehnt werden, aber nicht "halb akzeptiert". Danach kommt die nächste, unabhängige Eingabe.

    Ok, wenn das Format zeilenbasiert ist, dann spricht auch nichts grundlegendes dagegen, es zeilenweise zu parsen. Entweder mit den bekannten Methoden (du könntest zum Beispiel in meinem Beispielprogramm einfach ein getline in die if-Blöcke machen) oder mit fertigen Bibliotheken, wie mont es zeigt. Die Methode von mont hat den Vorteil, dass diese biblitoheken von vielen Programmen benutzt werden, so dass man mit wenig Aufwand das gleiche Look&Feel wie in anderen bewährten Programmen bekommt. Ein Nachteil wäre, dass man nichts über das Parsen von Nutzereingaben lernt. Musst du wissen, was bei deinem Projekt im Vordergrund steht.



  • Ok, vielen Dank, besonders an mont für die ausführliche Erklärung über Stream-Interna. Ich höre hier immer wieder, wie mächtig Streams eigentlich seien, aber wenn man intern alles umbiegen muss, um das gleiche wie eine getline-Lösung zu erhalten, muss man sich schon überlegen, ob sich das lohnt...

    Wahrscheinlich werde ich es beim einfachen Fall belassen (operator>>), und dann auf etwas Komfort verzichten. Der Code wird nämlich von anderen angeschaut und sollte nicht zu kompliziert sein.


Log in to reply