Zeichenkette einlesen und mittels Zeigerarithmetik die Anzahl der Zeichen ausgeben?



  • Hallo liebe Community, ich habe wieder folgende Aufgabe zu bewältigen:

    "Schreiben Sie eine C-Programm, dass eine Zeichenkette der maximalen Länge 128 einliest
    und die tatsächliche Länge dieser Zeichenkette mittels Zeigerarithmetik ausgibt."

    Allerdings habe ich keinen Ansatz, wie man das mit der Zeigerarithmetik lösen kann. Ich weiß nur, dass man über fgets die Zeile als solches einlesen und wieder ausgeben kann. Jetzt wäre die Frage, wie ich rein logisch an so eine Problematik als erstes vor gehe. Per Sizeof darf ich ja auch nicht vorgehen


  • Mod

    sizeof würde ja auch gar nicht funktionieren. Ich hoffe, dir ist das klar und du meintest strlen. Das ist auch genau das Stichwort: Du sollst strlen nachprogrammieren. strlen weiß ja auch nicht magisch, wie lang eine Zeichenkette ist, sondern berechnet das irgendwie. Das Geheimnis dahinter: In C markiert das Nullzeichen '\0' das Ende einer Zeichenkette. Das heißt, dein fgets schreibt nicht nur die Eingabe in den Speicher, sondern dahinter auch ein '\0'. Und dieses '\0' musst du jetzt suchen und dabei mitzählen, an wie vielen Zeichen du dabei vorbei gekommen ist. Und da du mit einem Zeiger auch char startest, bleibt dir auch nichts groß übrig, außer das mit Zeigerarithmetik zu machen.



  • Man muss nicht wirklich mitzählen. Man kann es auch so machen dass man sich den Zeiger in eine 2. Variable kopiert, und diese 2. Variable dann Zeichen für Zeichen weiterschiebt. Und wenn man die 0 gefunden hat, kann man den Abstand zwischen den beiden Zeigern berechnen (Anfang und Ende des Strings). Dann hat man 2x Zeigerarithmetik: 1x beim Weiterschieben und 1x am Schluss beim Abstand Berechnen 🙂



  • @Ravensouth

    Du zeigst leider nicht deinen Fortschritt (dein Code), da kann man dir schwer unter die Arme greifen. Hast du es überhaupt irgendwie schon versucht? Dann kann man dir sagen was du falsch machst. Probleme mit Zeigerarithmetik? Vielleicht hilft dir dieses Beispiel etwas auf die Sprünge:

    int main(){
        const char* s = "Hello World";
        printf("%c", *s); // Zeiger zeigt auf das erste Zeichen 'H'
        s += 1; // mittels Zeigerarithmetik den Zeiger inkrementieren
        printf("%c", *s); // Zeiger zeigt nun auf das nächste Zeichen 'e'
    }
    

    Also for-Schleife bauen, wie gezeigt drüber iterieren und aufhören, sobald *s '\0' ist (das Gleiche wie den numerischen Wert 0) soll die Bedingung sein, um die Schleife abzubrechen. Nullterminierung ist dir hoffentlich ein Begriff. Wie @SeppJ bereits erwähnt markiert das '\0' das Ende des Strings.

    Edit: Oder du machst es so wie @hustbear erwähnt. Das ist kürzer und schicker. Würde dann aber glaub ich dann auch in ein std::abs() packen.



  • This post is deleted!


  • @Luks-Nuke sagte in Zeichenkette einlesen und mittels Zeigerarithmetik die Anzahl der Zeichen ausgeben?:

    Würde dann aber glaub ich dann auch in ein std::abs() packen.

    Wozu? Ende-Anfang ist der positive Wert. Das nochmal durch abs zu jagen stiftet bloss Verwirrung.



  • @hustbaer sagte in Zeichenkette einlesen und mittels Zeigerarithmetik die Anzahl der Zeichen ausgeben?:

    Wozu? Ende-Anfang ist der positive Wert. Das nochmal durch abs zu jagen stiftet bloss Verwirrung.

    Dem stimme ich zu. Aber ich glaube ich hatte Mal bei anderen Datenstrukturen negative Ergebnisse bekommen, vermutlich weil der Speicher nicht zwingend so ausgelegt ist, dass begin auch wirklich vor end im Speicher vorliegt. Aber davon hab ich herzlich wenig Ahnung.



  • @Luks-Nuke sagte in Zeichenkette einlesen und mittels Zeigerarithmetik die Anzahl der Zeichen ausgeben?:

    Aber ich glaube ich hatte Mal bei anderen Datenstrukturen negative Ergebnisse bekommen, vermutlich weil der Speicher nicht zwingend so ausgelegt ist, dass begin auch wirklich vor end im Speicher vorliegt. Aber davon hab ich herzlich wenig Ahnung.

    Wenn man mit Zeigern arbeitet und mit ++ zum nächsten Element kommt, dann funktioniert Ende-Anfang. Wenn man mit Iteratoren arbeitet und mit ++ zum nächsten Element kommt, dann funktioniert Ende-Anfang ebenso, vorausgesetzt Ende und Anfang sind halt Iteratoren und diese unterstützen operator - (z.B. Random-Access Iteratoren).

    Und wenn keiner der beiden Fälle vorliegt, dann kann man überhaupt nicht davon ausgehen dass Ende-Anfang irgendwas brauchbares ergibt - mit oder ohne abs.

    Also angenommen man hätte eine Deque-artige Datenstruktur mit einem einzigen Array wo der freie Bereich irgendwo in der Mitt liegen kann, und man wenn man über das Ende rausläuft am Anfang weiter macht... (Pacman Style).
    Dann kommt bei abs(ZeigerAufRangeEnde-ZeigerAufRangeAnfang) nicht unbedingt was sinnvolles raus. Da kann dann entweder die Grösse der Range rauskommen oder die Capacity minus Grösse der Range.

    Mir fällt auf jeden Fall nichts ein wo man abs brauchen und damit zu einem sinnvollen Ergebnis kommen würde.

    Falls es so einen Fall gibt, lasste es mich gerne wissen.



  • @hustbaer

    Ich kann mit meiner wagen Erinnerung daran jetzt leider kein Beispiel-Code liefern.

    Ich meine nur irgendwo aufgegriffen zu haben, dass der Speicher das nicht zwangsweise von links nach rechts abspeichert.

    Aber nach weiteren Gedanken macht das Sinn, denn wenn du einen Iterator inkrementierst zeigt er ja wohl auf die nachfolgende Adresse.



  • @Luks-Nuke sagte in Zeichenkette einlesen und mittels Zeigerarithmetik die Anzahl der Zeichen ausgeben?:

    Ich meine nur irgendwo aufgegriffen zu haben, dass der Speicher das nicht zwangsweise von links nach rechts abspeichert.

    Der Satz macht für mich keinen Sinn. Also vergessen wir mal Endianness, denn das würde alles nur unnötig kompliziert machen, und ich denke auch nicht dass du das meinst.

    Der Speicher speichert das was du ihm gibst, an der Adresse die du verwendest. An der selben Adresse kannst du den Wert danach wieder lesen. Und das war's dann auch schon. Wenn du was an Adresse X schreibst, dann steht das halt dort. Wenn du was an Adresse X+1 schreibst, dann steht das halt an Adresse X+1. Links und rechts/vorne-hinten/Anfang-Ende etc. sind Konzepte die wir da reininterpretieren, haben aber technisch aus Sicht des Speichers keine Bedeutung.

    Wo sie eine Bedeutung gewinnen ist wenn man anfängt Datenstrukturen zu implementieren. Da geht's dann aber darum wie die Datenstruktur aufgebaut ist, und was der Code den man schreibt um diese umzusetzen macht. Und nicht darum dass der Speicher irgendwie selbst irgendwas andersrum abspeichert.

    Aber nach weiteren Gedanken macht das Sinn, denn wenn du einen Iterator inkrementierst zeigt er ja wohl auf die nachfolgende Adresse.

    Er. Nö 🙂

    Der Iterator zeigt auf das nächste Element. Das muss nicht unbedingt an der nachfolgenden Adresse liegen. Das kann auch davort oder überhaupt ganz woanders liegen. Allerdings abstrahiert der Iterator das ganze soweit, dass bei IteratorAufEnde-IteratorAufAnfang wieder das richtige rauskommt. Bei (&*IteratorAufEnde)-(&*IteratorAufAnfang) könnte allerdings beliebiger Unfug rauskommen.

    Also. Wenn du mit ++ Zeiger weiterschiebst, dann darfst du danach auch die Zeiger subtrahieren. Und wenn du mit ++ Iteratoren weiterschiebst, dann darfst du danach Iteratoren subtrahieren. Was du nichst darfst ist mit ++ Iteratoren weiterschieben, und danach Zeiger auf die Elemente subtrahieren.

    Und da es in diesem Thread um char Strings ging die mit Standard Library Funktionen eingelesen werden: diese liegen immer "vorwärts" im Speicher, d.h. ein Weiterspringen zum nächsten Zeichen in Leserichtung entspricht einem Weiterspringen im Speicher zum Byte mit der nächst höheren Adresse. (OK, genaugenommen könnten es multi-byte Strings sein, also sowas wie UTF-8. Dann wäre es nicht das nächste Zeichen sondern die nächste Code-Unit.)



  • @hustbaer

    Ok stimmt, gibt ja vielerlei Iteratoren. Ich hatte mich mit der Aussage jetzt auf String Iteratoren bezogen.

    Danke jedenfalls für die Erläuterung, für mich allerdings nicht die einfachste Lektüre.



  • Erstmal danke für die vielen Antworten. Immerhin wusste ich schon einmal, dass ich strlen simulieren muss. Ich bin in C noch nicht so fit und sehe noch nicht wirklich den Sinn diese Sprache zu lernen, da ich es im Berufsleben niemals brauchen werde. Aber ich versuche mir noch das C-Buch von Bradley L. Jones zu besorgen. Das soll zum lernen ganz gut sein.

    Bisher sieht mein geänderter Code so aus. Nur irgendwie habe ich gerade einen Denkfehler, warum mein programm nur eine Zahl einliest:

    #include <stdlib.h>
    #include <string.h>
    #include <stdio.h>
    #define MAX 128
    
    int main(){
    
    
        char *p;
        int i = 0;
        char z[MAX];
    
    
        printf("Gebe maximal 128 Zeichen in einer Zeile ein: ");
    
    
        gets(z);                        /*ganze Zeile einlesen*/
    
        while(*(p+i)!='\0'){            /*Zähle bis zum Ende der Zeichenkette*/
        p = &z[i];                      /*Adresse vom Zeichen ermitteln*/
        p = z + i;                      /*Wert vom Zeichen ermitteln*/
        printf("Test: %c",*p);          /*Ermittelter Wert zu testzwecken ausgeben*/
        i++;                            /*das Array um eins erhöhen*/
        return i;
        }
    
        printf("\nAnzahl:%d\n",i);      /*gib die Anzahl aus*/
    
    }
    
    

  • Mod

    Du wiederholst 3x die gleiche Rechnung, die du nur einmal brauchst: (p+i), p = &z[i], p = z + i. Das sind drei Beispiele für Zeigerarithmetik, die alle ungefähr das gleiche machen und wahrscheinlich so in deinem Lehrbuch stehen. Aber es ist nicht Sinn der Aufgabe, die alle irgendwo anzubringen. Du musst verstehen was das jeweils macht und mit einem Plan an die Aufgabe gehen.

    Bestes Beispiel die Zeilen 20 und 21. Was hast du dir dabei gedacht? Würdest du folgendes für sinnvoll erachten?

    i = 7;
    i = 15;
    

    Das ist quasi, was deine Zeilen 20-21 machen.



  • Das Problem ist allerdings das return i;, das dafür sorgt, dass direkt aus main gesprungen wird und damit das Programm beendet wird.
    printf("\nAnzahl:%d\n",i); wird niemals aufgerufen, wenn die Länge von p größer 0 ist.

    Edit: Ich sehe gerade, dass du gets verwendest, mach das nicht. gets ist mindestens deprecated (imho seit C99, in C2011 ist es komplett rausgeflogen), nimm stattdessen fgets.



  • @Ravensouth

    Dein Code sieht leider nicht so gut aus. Keine Ahnung wo ich da jetzt mit meckern anfangen soll... 🙂

    Versuch doch mal nach bereits existierenden strlen() Implementationen zu googeln. Da findest du durchaus hilfreiche Beispiele, wie man das macht.

    Schau dir beispielsweise das hier an:
    https://en.cppreference.com/w/cpp/string/byte/strlen

    Das verfolgt auch @hustbaer 's erwähnten Ansatz.



  • So ich melde mich nochmal erneut. Ich habe mich nochmal erneut daran versucht und habe das mit der nullten Stelle soweit kapiert. Im Internet hat man kaum etwas dazu gefunden, deshalb kam ich irgendwann auch so auf die Idee: Nur irgendwie denke ich, man hätte es noch sauberer lösen können und mehr die Zeigerarithmetik verwenden können. Hier der Code:

    #include <stdlib.h>
    #include <string.h>
    #include <stdio.h>
    #define MAX 130                         /*1 Byte für Zeichenkette = '\0' | 1 Byte extra, da wir ab i = 0 zählen*/
    
    int main(){
    
    
        int zahl = 0, i;
        char z[MAX];                        /*Array MAX groß*/
    
        printf("Gebe maximal 128 Zeichen in einer Zeile ein: ");
    
        fgets(z, MAX, stdin);                  /*ließt die ganze Zeile ein bis "MAX" Zeichen erreicht sind*/
    
        for(i=0; *(z+i) != 0; i++)             /*Zähle bis zum Ende der Zeichenkette*/
        {
        putchar(*(z+i));                       /*Kontrolle: Eingabe = Ausgabe*/
        zahl++;
        }
        zahl = zahl - 1;                       /*Minderung wegen der Versetzung um ein Byte*/
        printf("Anzahl:%d",zahl);              /*Ausgabe Anzahl*/
    
    return 0;
    }
    
    

  • Mod

    Probierst du deine Programme nicht aus?

    PS:

    1 Byte extra, da wir ab i = 0 zählen

    Ein Array int i[2]; hat zwei Elemente. i[0] und i[1].



  • @SeppJ sagte in Zeichenkette einlesen und mittels Zeigerarithmetik die Anzahl der Zeichen ausgeben?:

    Probierst du deine Programme nicht aus?

    Natürlich probiere ich meine Programme aus. Funktioniert doch. Das ein Array bei 0 anfängt weiß ich 🙂



  • @Ravensouth
    Teste dein Progamm mal mit nem Textfile als Input wo z.B. "test" (ohne folgenden Zeilenumbruch) drinsteht.
    Output:

    Gebe maximal 128 Zeichen in einer Zeile ein: testAnzahl:3
    


  • fgets speichert auch das Newline-Zeichen \n (im Gegensatz zu gets oder scanf("%s") - darum das Dekrementieren.

    Ein Möglichkeit ist mittels dem PP den Format-String für scanf zusammenzusetzen:

    #define STR(X) STRINGIFY(X)
    #define STRINGIFY(X) #X
    

    und dann

    scanf("%" STR(MAX) "s", z);
    

    s.a. Specifying the maximum string length to scanf dynamically in C (like "%*s" in printf)
    sowie der abgeänderte Ideone-Code.


Log in to reply