Vergleich auf Steuerzeichen



  • Hallo zusammen,

    ich bin zur Zeit dabei das Buch "C++ Primer" durchzuarbeiten, da ich von C# auf C++ umsteigen möchte. Eigentlich stellen die Grundlagen keine große Herausforderung dar, doch nun hab ich leider ein Problem bei einer Übungsaufgabe aus dem Buch. Wahrscheinlich steh ich einfach nur auf dem Schlauch 😕

    Es geht um folgendes: Von der Konsole einlesen und bestimmte Buchstaben zählen. Darunter sind unter anderem auch blank, tab und new line.
    Bei Buchstaben stellt das ganze kein Problem dar, nur bei den Steuerzeichen.
    Am besten einfach mal mein Code:

    while(cin >> str)
    {
        for(string::size_type ix = 0; ix != str.size(); ++ix)
        {
            switch(str[ix])
            {
                case 'a':
                case 'A':
                    ++aCnt;
                    break;
                // und so weiter
                // aber jetzt der Problembereich
    
                case '\t':
                    ++tabCnt;
                    break;
                case '\n':
                    ++newLineCnt;
                    break;
            }
        }   
    }
    

    Ich habe es schon mit folgendem Probiert, aber es hat nie funktioniert wie gewollt:

    case ' ':
    case '\20':
    

    bzw. für tab und new line auch

    case '\t':
    

    usw.

    Vielen Dank!



  • Hallöle,

    was genau funktioniert denn nicht? Gibt es einen Compiler-Fehler, oder stimmt das Ergebnis mit dem von dir erwarteten nicht überein?

    Suchst du vielleicht std::getline? der operator >> für string überspringt nämlich alle Whitespaces (Tabs, Leerzeichen, Newlines, etc.), daher wirst du diese nie zählen können.

    Grüßle



  • oh sorry hab das genaue Problem vergessen.

    Die Steuerzeichen werden einfach nicht gezählt. Egal welchen String ich eingebe es sind immer 0.
    Compiler bringt weder Fehler noch Warnungen.



  • Da der operator>> für std::string solche Zeichen als Trennzeichen interpretiert, wirst du sie in deinen Strings nicht finden.


  • Mod

    Das ist an sich schon richtig, der Fehler liegt beim einlesen: Der Operator>> trennt die Eingabe an Whitespaces auf. Sprich: Alle deine Steuerzeichen werden bewusst überlesen. Du suchst vermutlich get, read, readsome oder eventuell auch getline. Schlag diese in einer Referenz nach und schau dir die Unterschiede an.

    Du programmierst gerade übrigens umständlich eine (Hash-)map nach. Mit einer std::map<char, int> kannst du dir die ganze switch-Kaskade sparen.



  • "aber es hat nie funktioniert wie gewollt: "
    ist eine schlechte Fehlerbeschreibung.

    cin >> ... überliest standardmäßig alle Whitespace-Zeichen. Probier mal:

    cin >> noskipws >> str



  • OK ich versteh schon wo der Fehler liegt. Damit hat sich die ganze Sache schon erledigt.

    Vielen Dank 👍


  • Mod

    Belli schrieb:

    "aber es hat nie funktioniert wie gewollt: "
    ist eine schlechte Fehlerbeschreibung.

    cin >> ... überliest standardmäßig alle Whitespace-Zeichen. Probier mal:

    cin >> noskipws >> str

    Das macht den Effekt nur noch schlimmer. operator>>(istream&, string&) stoppt beim ersten Whitespace. Mit noskipws überspringt er aber den führenden Whitespace (der dort nach der letzten Leseaktion noch steht) nicht. Es wird daher gar nichts mehr gelesen:

    http://ideone.com/N7opN

    Denk dir, dass der Stream vor einer formatierten Leseaktion (also Operator>>) automatisch bis zum nächsten Nicht-Whitespace vorspult. Dann wird die normale Logik der Ausleseaktion ausgeführt. Mit noskipws entfällt dieses Vorspulen. So richtig Sinn macht noskipws daher nur, wenn die Ausleselogik nicht (wie bei string) bei Whitespaces anhält, zum Beispiel wenn man einzelne chars liest.



  • MrBrownwait, ich hoff' mal, dass du den ziemlich versteckten Beitrag von Seppj

    Seppj schrieb:

    Du programmierst gerade übrigens umständlich eine (Hash-)map nach. Mit einer std::map<char, int> kannst du dir die ganze switch-Kaskade sparen.

    nicht übersiehst. Das geht so zb. viel eleganter:

    #include <map>
    #include <string>
    #include <iostream>
    
    int main()
    {
        using namespace std;
        map<char, unsigned> zeichen;
        string eingabe;
        getline(cin, eingabe);
        for(auto iter = eingabe.begin(); iter != eingabe.end(); ++iter)
            ++zeichen[*iter];
        for(auto iter = zeichen.begin(); iter != zeichen.end(); ++iter)
            cout << iter->first << " kommt " << iter->second << " mal vor.\n";
        return 0;
    }
    


  • Incocnito ich hab den Beitrag gesehen und auch verstanden wie das ganze funktioniert, aber für den Anfang werd ich mich mal daran orientieren was im C++ Primer so vorkommt damit der Lerneffekt da ist.
    Als ich die Aufgabe gelesen hab "... count the number of blank spaces, tabs, and new lines read." hab ich gedacht das ist ja "Kindergarten", aber jetzt weiß ich was der Lerneffekt sein soll und der "Aha-Effekt" war auch da 😉


  • Mod

    @Incognito: Dann nimm doch auch range-based loops, wenn du C++11 machst:

    for(auto c : eingabe)
            ++zeichen[c];
        for(auto elem : zeichen)
            cout << elem.first << " kommt " << elem.second << " mal vor.\n";
    


  • std::map ist keine Hash-Map, sondern üblicherweise ein RB-Baum. Ihr denkt an std::unordered_map.

    Beides halte ich in diesem Fall aber nicht für richtig sinnvoll, weil char auf gängigen Plattformen nicht allzu viele Werte annimmt und man locker eine Table ohne Hash ausbreiten kann, ohne in Speicherprobleme zu laufen. Ich denke mir das etwa so:

    #include <cstddef>
    #include <iostream>
    #include <limits>
    
    template<typename T, std::size_t N>
    std::size_t array_size(T (&)[N]) { return N; }
    
    int main() {
      // So ziemlich überall ist 1 << std::numeric_limits<char>::digits == 256
      unsigned counters[1 << std::numeric_limits<char>::digits] = { 0 };
      char c;
    
      while(std::cin.get(c)) {
        ++counters[static_cast<unsigned char>(c)];
      }
    
      for(std::size_t i = 0; i < array_size(counters); ++i) {
        if(counters[i] != 0) {
          std::cout << static_cast<char>(static_cast<unsigned char>(i))
                    << " kommt " << counters[i] << " mal vor.\n";
        }
      }
    }
    

    Die Casterei ist nicht besonders hübsch, zugegeben, aber dafür ist die Adressierung schön schnell.


  • Mod

    seldon schrieb:

    std::map ist keine Hash-Map, sondern üblicherweise ein RB-Baum. Ihr denkt an std::unordered_map.

    Das habe ich nicht gemeint. Der Threadersteller programmiert hier effektiv eine Hashmap (mit der Identität als Hashfunktion). Eine normale map reicht hier auf jeden Fall. deine Methode ist auch das gleiche wie beim Threadersteller aber ohne Switch-Kaskade und somit genau so elegant wie die map. (und dazu noch flotter 👍 )



  • Warum castest du da zweimal?


Anmelden zum Antworten