Interaktion mit dem Benutzer



  • Eine FSM ist in diesem speziellen Fall vielleicht "Overkill", aber eine nette Übung. Außerdem möchte ich mich nur aus dem C99 Standard bedienen.



  • char s[100];
    while( 1==scanf("%99s",s) )
    {
      ...
    }
    

  • Mod

    Beispielsweise so, falls Zeilenumbrüche eine Rolle spielen:
    https://ideone.com/2SD0ou

    Oder noch einfacher, wenn sie es nicht tun (nichts in deinem Format deutet darauf hin):
    https://ideone.com/AJprCr



  • Danke, das schaut gut aus!



  • Tomahawk schrieb:

    Beispiel:

    [...]
    
      while ((token = GetToken()) != NULL) {
        switch (state) {
        case 0:
          if (strcmp("A", token) == 0) {
    
    [...]
    

    Wenn ich Deine GetToken - Routine richtig verstehe, ist nicht sichergestellt, dass token nullterminiert ist - dann sollte strcmp doch nicht das richtige (erwartete) Ergebnis liefern, oder?



  • Belli schrieb:

    Tomahawk schrieb:

    Beispiel:

    [...]
    
      while ((token = GetToken()) != NULL) {
        switch (state) {
        case 0:
          if (strcmp("A", token) == 0) {
    
    [...]
    

    Wenn ich Deine GetToken - Routine richtig verstehe, ist nicht sichergestellt, dass token nullterminiert ist - dann sollte strcmp doch nicht das richtige (erwartete) Ergebnis liefern, oder?

    Und was ist mit Zeile 38?


  • Mod

    Tomahawk schrieb:

    Und was ist mit Zeile 38?

    Passt schon. Ich hoffe nur, dir ist klar, dass deine Funktion wirklich nur eine Kopie von strtok ist, alle Nachteile inbegriffen, die du eigentlich loswerden wolltest:

    strtok() kommt nicht in Frage, da nicht threadsafe.

    Deines erst recht.



  • Tomahawk schrieb:

    Und was ist mit Zeile 38?

    Jo, hatt ich übersehen ...



  • SeppJ schrieb:

    Tomahawk schrieb:

    Und was ist mit Zeile 38?

    Passt schon. Ich hoffe nur, dir ist klar, dass deine Funktion wirklich nur eine Kopie von strtok ist, alle Nachteile inbegriffen, die du eigentlich loswerden wolltest:

    strtok() kommt nicht in Frage, da nicht threadsafe.

    Deines erst recht.

    Ja, das stimmt.

    Lässt sich aber schnell ändern:

    struct {
      char* current;
      char buffer[SIZE];
    }
    

    Und dann als Referenz übergeben.

    Aber der Ansatz mit scanf() gefällt mir am besten. Einlesen und Tokenizen in Einem. Ich schätze mal da gibts keinen Haken?


  • Mod

    Tomahawk schrieb:

    Aber der Ansatz mit scanf() gefällt mir am besten. Einlesen und Tokenizen in Einem. Ich schätze mal da gibts keinen Haken?

    Es gibt nun eine maximale Tokenlänge (die Größe des an scanf übergebenen Feldes). Dieser Nachteil wiegt sicher nicht schwer, aber ist erwähnenswert.



  • Folgender Code ist zwar nicht ganz threadsafe (es wird auf stdin operiert), sollte aber soweit ganz gut zum einfachen Tokenizen geeignet sein.

    #include <ctype.h>  // isspace
    #include <stdio.h>  // fgets
    #include <string.h>  // strlen
    
    class Input {
    public:
      Input() {
        m_current = m_buffer;
      }
    
      // Returns true if successful
      bool GetInput() {
        m_current = m_buffer;
    
        if (fgets(m_buffer, SIZE, stdin) == NULL) {
          return false;  // End-of-file or error
        }
    
        // Remove newline
        m_buffer[strlen(m_buffer) - 1] = 0;
    
        return true;
      }
    
      // Returns a pointer to the next token or NULL
      const char* GetToken() {
        while (isspace(*m_current) != 0) {
          ++m_current;
        }
    
        if (*m_current == 0) {
          return NULL;
        }
    
        const char* token = m_current;
    
        while (isspace(*m_current) == 0) {
          ++m_current;
        }
    
        *m_current++ = 0;
    
        return token;
      }
    
    private:
      enum {
        SIZE = 1 << 16
      };
    
      char m_buffer[SIZE];  // 64K
      char* m_current;
    };
    

  • Mod

    Was soll das denn sein? Einerseits programmiert wie in C, aber andererseits ein paar Syntaxmittel benutzt, die es nur in C++ gibt. Für wen soll das denn gut sein?

    Ein paar Sachen, über die ich nicht glücklich bin:
    -Umgang mit Dateien, die nicht auf newline enden
    -Warum muss eigentlich eine Datenzerlegungsklasse selber lesen? Trennung von Zuständigkeiten!



  • SeppJ schrieb:

    Ein paar Sachen, über die ich nicht glücklich bin:
    -Umgang mit Dateien, die nicht auf newline enden
    -Warum muss eigentlich eine Datenzerlegungsklasse selber lesen? Trennung von Zuständigkeiten!

    Stimmt Beides - kein Einspruch!

    SeppJ schrieb:

    Was soll das denn sein? Einerseits programmiert wie in C, aber andererseits ein paar Syntaxmittel benutzt, die es nur in C++ gibt. Für wen soll das denn gut sein?

    Ich verwende isspace und char-array (lassen wir fgets außen vor wegen Trennung der Zuständigkeiten).

    Was ist daran nicht OK?



  • Tomahawk schrieb:

    Ich verwende isspace und char-array (lassen wir fgets außen vor wegen Trennung der Zuständigkeiten).

    Was ist daran nicht OK?

    Das class.

    Wenn du C machst, kompiliere doch bitte auch mit einem C-Compiler.



  • Nachfolgende Variante eines Tokenizer ersetzt alle spaces in buffer durch 0 und trägt die Anfangsadressen von jedem Token in buffer in die Token List ein.

    #include <assert.h>
    #include <ctype.h>  // isspace
    #include <stdio.h>  // NULL
    
    struct TokenInfo {
      char* token;
    };
    
    // Returns a pointer to the last token + 1
    TokenInfo* Tokenize(char* buffer, TokenInfo* token_list) {
      assert(buffer != NULL);
      assert(token_list != NULL);
    
      bool found_token = false;
    
      for (char* current = buffer; *current != 0; ++current) {
        if (found_token) {
          if (isspace(*current)) {
            *current = 0;
            found_token = false;
          }
        } else {
          if (isspace(*current)) {
            *current = 0;
          } else {
            token_list++->token = current;
            found_token = true;
          }
        }
      }  // for
    
      return token_list;
    }
    

    Die Interaktion in der Konsole oder einer Pipe sieht dann ihn etwa wie folgt aus:

    #include <stdio.h>  // fgets, stdin, NULL
    
    void Parse() {
      char buffer[1024];
    
      while (fgets(buffer, 1024, stdin) != NULL) {
        TokenInfo token_list[1024];  // HACK
        TokenInfo* end = Tokenize(buffer, token_list);
    
        for (TokenInfo* current = token_list; current < end; ++current) {
          const char* token = current->token;
    
          // Do something with "token"
        }
      }  // while
    
      // End-of-file or error
    }
    

    Der Datentyp TokenInfo soll nur verhindern, dass man direkt mit Doppelzeigern arbeiten muss (tut man so zwar auch, aber nicht so offensichtlich).

    Im Grunde ist dieser Algorithmus einen Ticken weniger performant als der Vorgänger, da man zusätzlich noch eine Token List benötigt.


Anmelden zum Antworten