Funktion welche Zeichenstring zählt



  • Zuallererst: benutze geschwungene Klammern auch fuer Schleifen und Abfragen (if), die nur eine Anweisung enthalten:

    for(int i = 0; i < 15; ++i)
    {
        // ... so sollte das aussehen
    }
    

    Deine Frage zum char-array hat ja mit der eigentlichen Fragestellung nichts zu tun, ich komme gleich darauf zurueck.
    Du koenntest das aktuelle gelesene Zeichen ueberpruefen und wenn es ein bestimmtes Zeichen (z.B. ';') ist, die Schleife abbrechen. Generell ist deine Art, Zeichen zu lesen, nicht so knuelle. Das Problem ist, dass du ein char-array verwendest. Ich schaetze mal, ihr habt das halt so im Unterricht gemacht und von std::string habt ihr noch nie gehoert.
    Das laesst einen hin-und hergerissen, dir zu zeigen, wie man es mit einem char-array (halbwegs) sauber machen kann oder dich auf std::string zu verweisen. 😕
    Eigentlich bin ich davon ueberzeugt, dass es besser ist, es erst zu lernen wie man es richtig macht, um spaeter, wenn man mehr kann, die andere Variante kennenlernt.
    Ich zeige einfach mal beides.

    Die schoene Variante: std::string verwenden.

    #include <string> // string ist aus der standardbibliothek
    #include <iostream>
    
    using namespace std;
    
    int main()
    {
        cout << "Wie ist dein Name?\n";
        string antwort;
        cin >> antwort;
    
        cout <<  "Hallo " << antwort << '\n';
    }
    

    Bam. Einfacher gehts nicht. Beachte allerdings, dass die Eingabe nur bis zum ersten Whitespace (z.B. Leerzeichen oder Tabulator) liesst. Probiers aus!

    Die komplizierte Variante: char-arrays

    #include <iostream>
    
    using namespace std;
    
    int main()
    {
        char arr[15] = { 0 }; // das initialisiert alle Zellen mit 0
    
        // variante 1: die unsichere, niemals benutzen!
        cin >> arr; // liesst bis zum ersten Whitespace, aber was ist, wenn das Wort mehr als 14 Zeichen hat?
    
        // variante 2: mit get
        // liess solange Zeichen, bis ein gewuenschtes Endzeichen gelesen wurde oder der stream zu ende ist (eof)
        char ch;
        unsigned int i = 0;
        while(cin.get(ch))
        {
            arr[i++] = ch;
        }
    
        // variante 3: mit getline
        cin.getline(arr, 15);
    }
    

    Wie du siehst, ist das bis auf das erste Beispiel mit mehr Aufwand als mit der Verwendung von std::string verbunden. Die erste Variante ist aber so schlecht, dass sie einfach schon falsch ist.
    Die Benutzung von char* ist sehr fehleranfaellig und hat ein paar sehr spezielle Details, mit denen du dich nicht rumschlagen willst.

    Naja, auf zur eigentlichen Frage:
    Du sagst, du weisst wie ein Zeiger funktioniert. Weisst du auch, wie ein C-string funktioniert? Darum gehts hier. Ein c-string ist ein Zeiger auf den Anfang einer Zeichenkette im Speicher, also ein char*.
    Das wichtigste, was man beachten muss, ist dass ein c-string immer mit einer 0 abgeschlossen wird (die Zahl, nicht das Zeichen!).
    Wenn du also einen c-string mit dem Inhalt "Hallo" hast, sind fuer diesen String insgesamt 6(!) chars reserviert, das letzte enthaelt 0 (auch als '\0' geschrieben).

    Nimm einfach dieses Beispiel und teste damit deine Funktion:

    #include <iostream>
    
    using namespace std;
    
    int stringlen(char* pa)
    {
        // hier code einfuegen
    }
    
    int main() // void in leeren parameterlisten ist nicht noetig
    {
        const char* string = "Hallo Welt!";
    
        int length = stringlen(string);
        cout << "The length of the string '" << string << "' is " << length << '\n';
    }
    


  • Wo in der Aufgabenstellung kommt eine Eingabe vor?



  • Ich flippe gleich aus. Ich weiß nicht wie man die Zeichen zählt.
    Mir ist klar das Bei " Hallo " im Speicher [H][A][L][L][O][\0] belegt ist. Ich weiß auch, dass ich mit einer Schleife zählen muss, aber ich komme nicht auf den Wert, der die Schleife eingrenzt.

    *pa zeigt ja im Moment auf das "H", wenn ich das richtig verstanden habe. Und das "H" ist pa[0] ?



  • chippo schrieb:

    *pa zeigt ja im Moment auf das "H", wenn ich das richtig verstanden habe. Und das "H" ist pa[0] ?

    Ja, kann man so sagen auch wenn es verwirrt klingt...

    chippo schrieb:

    Ich weiß auch, dass ich mit einer Schleife zählen muss, aber ich komme nicht auf den Wert, der die Schleife eingrenzt.

    Vergiss einfach mal für ein moment die normale Schleife von 0 bis X, du weißt ja noch garnicht wie weit du zählen musst. Statt also bis zu einem festen Wert zu zählen zählst du von 0 an bis du pa[i] == '\0' erreicht hast.



  • #include <iostream>
    #include <string>
    
    using namespace std;
    
    int stringlen(char* pa)
    {
    	int i, summe;
    
    	for (i = 0; pa[i] == '\0'; i++)
    	{
    		summe = summe + i;
    	}
    
    	return summe-1 ;
    
    }
    
    int main()
    {
    	char* string = " Hallo ";
    
    	int length = stringlen(string);
    	cout << "The length of the string '" << string << "' is " << length << '\n';
    }
    

    So sieht es jetzt aus. Ich gehe jetzt schlafen, bevor ich noch meinen PC zeige, was eine Wasserkühlung mit dem Kärcher ist. Ohman wie ich es hasse, was nicht zu verstehen 👎
    Gute Nacht!



  • Nicht ganz, aber nah dran.

    #1: summe = summe + i macht keinen sinn, wenn dann wäre das + 1 für jedes Zeichen.
    #2: du hast bereits i was für dich zählt
    #3: Deine Bedingung ist falschrum. Du willst weiterzählen solange das Zeichen nicht '\0' ist

    Nach den 3 kleinen Verbesserungen sieht der Code dann so aus:

    int stringlen(char* pa) 
    { 
        int i;
        for (i = 0; pa[i] != '\0'; i++) {}
        //Leerer Schleifenbody. i wird bereits durch die Schleife erhöht und
        //das Zeichen wird durch die Bedingung geprüft
    
        return i;
    }
    


  • #1,5: summe ist nicht initialisiert. Du hast summe keinen Anfangswert gegeben.

    Und das sollte² dein Compiler als Warnung anmeckern. Und die musst du auch beachten.
    ²Wenn er das nuicht macht, musst du die Einstellungen für die Warnungen verändern.



  • Es funktioniert nun. Ich habe einfach viel zu viel an die normale Schleife mit Stoppwert x gedacht. Jetzt macht das alles auch Sinn 😃

    Vielen Dank an die fleißigen Helfer! 👍



  • Damit auch eine schöne Implementierung dasteht:

    #include <cstddef>
    
    std::size_t strlen( char const * str )
    {
    	char const * p = str;
    	while( *p++ );
    	return p - str - 1;
    }
    


  • Swordfish schrieb:

    Damit auch eine schöne Implementierung dasteht:

    könnte die überlaufen? Ist es garantiert, dass das letzte byte im addressraum nicht belegt werden kann?


  • Mod

    otze schrieb:

    Swordfish schrieb:

    Damit auch eine schöne Implementierung dasteht:

    könnte die überlaufen? Ist es garantiert, dass das letzte byte im addressraum nicht belegt werden kann?

    Da dachte ich auch sofort dran, aber ich meine, dass es erlaubt ist, um Eins hinter das Ende eines Arrays zu zählen. Ich habe aber gerade keine Lust, das im Standard nachzuschlagen und warte stattdessen auf einen Kommentar von den Leuten, die den Standard auswendig können.



  • SeppJ schrieb:

    otze schrieb:

    [
    könnte die überlaufen? Ist es garantiert, dass das letzte byte im addressraum nicht belegt werden kann?

    Da dachte ich auch sofort dran, aber ich meine, dass es erlaubt ist, um Eins hinter das Ende eines Arrays zu zählen. Ich habe aber gerade keine Lust, das im Standard nachzuschlagen und warte stattdessen auf einen Kommentar von den Leuten, die den Standard auswendig können.

    Es ist garantiert.

    §5.7/5 schrieb:

    [...]Moreover, if the expression P points to the last element of an array object, the expression (P)+1 points one past the last element of the array object, and if the expression Q points one past the last element of an array object, the expression (Q)-1 points to the last element of the array object. If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.



  • SeppJ schrieb:

    otze schrieb:

    Swordfish schrieb:

    Damit auch eine schöne Implementierung dasteht:

    könnte die überlaufen? Ist es garantiert, dass das letzte byte im addressraum nicht belegt werden kann?

    Da dachte ich auch sofort dran [...]

    Zählen darf ich, wohin ich will. Nur dereferenzieren nicht.

    // und unsigneds dürfen auch überlaufen soviel sie wollen. Wenn mir dagegen der Benutzer einen String gibt, der nicht Nullterminiert ist, ist er selber schuld.



  • Swordfish schrieb:

    SeppJ schrieb:

    otze schrieb:

    Swordfish schrieb:

    Damit auch eine schöne Implementierung dasteht:

    könnte die überlaufen? Ist es garantiert, dass das letzte byte im addressraum nicht belegt werden kann?

    Da dachte ich auch sofort dran [...]

    Zählen darf ich, wohin ich will. Nur dereferenzieren nich

    Nein, Pointerarithmetik ist nur innerhalb arraygrenzen erlaubt.



  • Swordfish schrieb:

    Zählen darf ich, wohin ich will. Nur dereferenzieren nicht.

    problem ist nur, wenn durch einen Überlauf p=0 ist. Dann gibt p - str - 1 nicht das richtige.

    //edit doch, klar. überlauf ist ja definiert.



  • otze schrieb:

    Swordfish schrieb:

    Zählen darf ich, wohin ich will. Nur dereferenzieren nicht.

    problem ist nur, wenn durch einen Überlauf p=0 ist. Dann gibt p - str - 1 nicht das richtige.

    //edit doch, klar. überlauf ist ja definiert.

    Üherlauf für Pointer ist nicht definiert, da er nie auftreten kann! Es ist legal, bis eins hinters Ende eines Arrays zu zählen, egal wo das Array ist.
    Weitere Pointerarithmetik ist dann UB.



  • Nathan schrieb:

    Üherlauf für Pointer ist nicht definiert, da er nie auftreten kann! Es ist legal, bis eins hinters Ende eines Arrays zu zählen, egal wo das Array ist.
    Weitere Pointerarithmetik ist dann UB.

    Ist mir bekannt, ich war in der lage deinen post zu lesen bevor ich geantwortet hatte. Das war auf Swordfish bezogen.


  • Mod

    Nathan schrieb:

    Weitere Pointerarithmetik ist dann UB.

    Was, ich darf nicht mehr zurück!?
    (~Edit: Wetten du hast die Scheiße schon erwartet?~ :p )



  • Also ich würde an der "schönen" Lösung eher bekritteln dass sie a) unnötig kompliziert ist und b) ne implizite signed -> unsigned Konvertierung enthält.
    Wieso nicht einfach

    std::size_t strlen(char const* str)
    {
        for (std::size_t i = 0; ; ++i)
            if (str[i] == '\0')
                return i;
    }
    

    ?


  • Mod

    hustbaer schrieb:

    Also ich würde an der "schönen" Lösung eher bekritteln dass sie a) unnötig kompliziert ist und b) ne implizite signed -> unsigned Konvertierung enthält.
    Wieso nicht einfach

    std::size_t strlen(char const* str)
    {
        for (std::size_t i = 0; ; ++i)
            if (str[i] == '\0')
                return i;
    }
    

    ?

    Diese Lösung ist aber komplizierter für den Computer, sofern der Compiler sie nicht zu Swordfishs Lösung optimiert, welche (für eine portable Version) bereits ziemlich optimal ist.

    Man kann natürlich noch darüber streiten, dass Swordfishs Version ab schon PTRDIF_MAX aussteigt (zumindest in einer strengen Standardauslegung, bei der die Differenz zweier Pointer jenseits dieses Werts nicht mehr definiert ist), deine erst ab SIZE_MAX. Aber soweit ich weiß verbietet der Standard sowieso nicht, dass nicht irgendein Objekt sogar größer sein darf als SIZE_MAX. Letztlich scheitert also beide Varianten bei theoretisch möglichen Szenarien, die aber praktisch nie vorkommen werden.


Anmelden zum Antworten