Mein erstes Programm



  • Hallo Forum,

    ich hoffe mein Posting ist hier richtig, wenn nicht bitte verschieben. Danke!

    Bisher habe ich immer etwas Abstand von C genommen aber endlich habe ich mal die Zeit gefunden um ein kleines Programm zu schreiben. Eigentlich keine große Sache und diente für mich als Übung und für meine Sohn als Training 😉

    Das war meine Idee:
    Um meinen Sohn etwas Mathe-Üben zu lassen und dies mit einer kleinen Belohnung zu versehen hatte ich folgenden Einfall, ein Programm zu schreiben das Plus, Minus, Geteilt und Multiplizieren kann, mit einfachen Zahlen. Wenn er X Aufgaben richtig gemacht hat soll Minecraft als Belohnung starten.
    Soweit so gut, nur ich habe ein paar kleine Probleme die ich nicht gelöst bekomme und ich hoffe ihr könnt mir dabei helfen diese Probleme zu lösen.

    Problem 1: Gibt man einen Buchstaben statt Zahl ein, werden alle nachkommende Aufgaben automatisch als Falsch gedeutet ohne überhaupt eine Eingabe zu tätigen. Das ist natürlich ungeschickt.

    Frage: Wie würdet ihr dieses Problem Lösen? oder wo ist genau mein Problem? Hab schon vieles Probiert aber bekomme den Fehler nicht raus. Ich ging bisher immer davon aus das ein Buchstabe, z.B. A als Zahl 65 bekommt und somit das Ergebnis einfach als Falsch gedeutet wird. Demnach ist das nicht so, warum? und wie kann ich eine Abhilfe schaffen?

    Problem 2: Ich lasse mir Zufallszahlen generieren, leider sind die Zahlen ohne ein Sleep alles andere als Zufall sondern immer gleich. Aber eine Sekunde warten zu müssen ist auch nicht unbedingt praktisch aber funktioniert erst einmal als Workaround.

    Frage: Was mache ich bei der Generierung der Zufallszahlen falsch? In einer For-Schleife schien es zu funktionieren. Als Funktion ausgelagert und einfach zwei mal ausführen (für zwei Zahlen) scheint das nicht mehr zu gehen.

    Ich würde mich freuen wenn mich jemand über diese Dinge aufklären könnte und ggf. meinen ersten Spagetti-Code unter die Lupe nimmt und mir ggf. Tipps geben könnte, was ich falsch mache und begründet warum dies falsch ist. Ein Link wo mir weiterhilft das Grundlegende Problem zu verstehen würde mir natürlich auch weiterhelfen.

    Mals sehen ob ich mein Code irgendwie anhängen kann.

    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include <unistd.h>
    
    int getrandom(int mi, int ma) {
        int random_number, i;
        srand(time(NULL));
        sleep(1);
        srand(time(0));
        for(i=mi; i < ma; i++) {
            random_number = rand() % ma;
        }
        return random_number;
    }
    
    int divides(int z1, int z2) {
        return z1 / z2;
    }
    
    int sub(int z1, int z2) {
        return z1 - z2;
    }
    
    int add(int z1, int z2) {
        return z1 + z2;
    }
    
    int multi(int z1, int z2) {
        return z1 * z2;
    }
    
    void gut(int z, int z1) {
        if(z == z1) {
            printf("Gut gemacht!\n");
        } else {
            printf("Leider falsch, es wäre %d gewesen.\n",z);
        }
    }
    
    int get_task() {
        int z1 = getrandom(1, 100);
        int z2 = getrandom(1, 100);
        int rt = getrandom(1, 4);
        int erg, eingabe;
        int result;
        switch(rt) {
            case 0: // 0 = Add
                erg = add(z1,z2);
                printf("%d + %d = ", z1, z2);
                scanf("%d",&eingabe);
    	    printf("%d\n",eingabe);
                gut(erg, eingabe);
                if(erg ==  eingabe) {
                    result = 0;
                } else {
                    result = 1;
                }
    	    break;
            case 1: // 1 = Minus
                // die größere Zahl muss immer vorne Stehen.
                if(z1 > z2 ) {
                    erg = sub(z1,z2);
                    printf("%d - %d = ", z1, z2);
                } else {
                    erg = sub(z2,z1);
                    printf("%d - %d = ", z2, z1);
                }
                scanf("%d",&eingabe);
                gut(erg, eingabe);
                if(erg == eingabe) {
                    result = 0;
                } else {
                    result = 1;
                }
    	    break;
            case 2: // 2 = geteilt
                if(z1 > z2 ) {
                    erg = divides(z1,z2);
                    printf("%d / %d = ", z1, z2);
                } else {
                    erg = divides(z2,z1);
                    printf("%d / %d = ", z2, z1);
                }
                scanf("%d",&eingabe);
                gut(erg, eingabe);
                if(erg == eingabe) {
                    result = 0;
                } else {
                    result = 1;
                }
    	    break;
            case 3: // 3 = Mal
    	    z1 = getrandom(1,9);
                z2 = getrandom(1, 10);
    	    if(z1 > z2) {
    	      erg = multi(z1, z2);
    	      printf("%d * %d = ", z1, z2);
    	    } else {
    	      erg = multi(z2,z1);
    	      printf("%d * %d = ", z2, z1);
    	    }
                scanf("%d",&eingabe);
                gut(erg, eingabe);
                if(erg == eingabe) {
                    result = 0;
                } else {
                    result = 1;
                }
    	    break;
            default:
                z1 = getrandom(1,9);
                z2 = getrandom(1, 10);
                erg = multi(z1, z2);
                printf("%d * %d = ", z1, z2);
                scanf("%d",&eingabe);
                gut(erg, eingabe);
                if(erg == eingabe) {
                    result = 0;
                } else {
                    result = 1;
                }
    	    break;
        }
        return result;
    }
    
    int main() {
        int count=0; // counter für Richtige ergebnise, solange soll eine schleife durchlaufen.
        int max_count=20; // counter für Anzahl richitger aufgaben die gemacht werden soll.
    
        while(count !=  max_count) { // while schleife bis 10 aufgaben richtig gemacht wurden.
            if( get_task()  == 0) {
                count++;
                if(count > 5)
                    printf("Du hast schon %d aufgaben richtig beantwortet, weiter so!\n",count);
            }
        }
    
        system("java -jar ~/.minecraft/launcher.jar");
        return (EXIT_SUCCESS);
    }
    

  • Mod

    Sei nicht so geizig mit Buchstaben bei der Benennung. Mische nicht Deutsch und Englisch bei der Benennung - das "gut" wirkt unfreiwillig komisch beim Lesen - nimm nur Englisch.

    Programmlogik habe ich mir nicht angesehen, weil mir die ganzen z1, z2, usw. zu entwirren zu anstrengend war.

    PS: Nur einmal srand im Programm benutzen. Dein komischer Workaround mit dem sleep sollte Hinweis genug sein, dass es derzeit falsch benutzt wird.

    PPS: Zu Frage 1: Werte die Rückgabewerte von scanf aus, um Fehler zu erkennen. Tritt ein Fehler auf, so sind die fehlerverursachenden Eingaben noch vorhanden und müssen noch anderweitig verarbeitet werden (also zum Beispiel verworfen werden), sonst werden weitere Versuche, diese zu lesen, natürlich weiterhin fehl schlagen.



  • knasan schrieb:

    Ich ging bisher immer davon aus das ein Buchstabe, z.B. A als Zahl 65 bekommt und somit das Ergebnis einfach als Falsch gedeutet wird. Demnach ist das nicht so, warum?

    scanf wertet Zeichen aus. Dadurch kann es Buchstaben durchaus von Ziffern (ASCII 48-37) unterscheiden.
    Da ein Buchstabe nicht zu einer Ganzzahl passt, wird das einlesen abgebrochen und das falsch Zeichen in den Eingabestrom zurückgestellt.

    Du kannst den Rest der Zeile z.B damit überlesen: https://www.c-plusplus.net/forum/p1146014#1146014

    Was soll die Schleife bei den Zufallszahlen?
    Du erzeugst damit 99 Zufallszahlen und nimmst die Letzte.
    Das macht das nicht zufälliger.



  • - srand(time(0)) nur 1x im Prozess aufrufen, z.B. am Beginn von main()
    - getrandom entschlacken wie beschrieben
    - da es immer wieder vorkommt, einen simplen int inklusive Fehlerbehandlung einzulesen und sich die Leute - wie du - immer wieder wundern, warum scanf das nicht alles inkl. stdin-Leerung auch bei fehlerhaften Eingabe macht, hier mal eine Variante, wie sowas aussehen kann, womit du deine scanf-Aufrufe ersetzen kannst:

    /* liest solange von stdin, bis gültiges int eingegeben wurde;
    ENTER (\n) dient als Eingabeende */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <limits.h>
    
    int checkInt(long *l, const char *s)
    {
    	char *e;
    	if (*s == '\n') return 0;
    	errno = 0;
    	*l = strtol(s, &e, 10);
    	return (!*e || *e == '\n') && errno != ERANGE && *l >=INT_MIN && *l <= INT_MAX;
    }
    
    int getInt()
    {
    	char str[1000];
    	long longvar;
    	while (fgets(str, 1000, stdin), !checkInt(&longvar, str));
    	return (int)longvar;
    }
    


  • Ich möchte mich bei allen bedanken, jeder Beitrag habe ich zu Herzen genommen und bin eure Ratschläge nachgegangen.

    1. @SeppJ, du hast natürlich recht, das mischen von deutsch und englisch ist keine gute Idee und ich habe jetzt jede Funktion einen passenden Namen gegeben.

    2. Wie @Wutz mir geraten habe ich jetzt nur noch einmal srand in mein Quellcode, es reichte in meinem Fall auch sogar time(NULL) aus. Habe die Funktion getrandom entschlankt und sogar die For-Schleife entfernt und den Fehler wo ich sonst immer hatte, habe ich jetzt nicht mehr. Mein Fehler war, das ich das Programm immer durch eine bash for-Schleife gejagt habe und somit kam immer die gleiche Zufallszahl raus. Im C Programm selbst, funktioniert die Zufallszahl wie gewünscht und ich habe es einfach Falsch Interpretiert und dachte ich habe einen Fehler.

    3. @DirkB, danke für den Link - fflush hab ich schon mal gelesen und muss dies wohl nochmal tun ... 😉

    Danke schon mal an alle bis hier her.

    EDIT:
    So wie es jetzt aussieht habe ich meine Probleme gelöst. Vielen Dank an alle.

    int getrandom(int ma) {
        int random_number;
        random_number = rand() % ma;
        return random_number;
    }
    /* getInt läuft solang bis eine Zahl eingegeben wurde */
    int getInt() {
        int user_result, check;
        while(check != 1) {
            check = scanf("%d", &user_result);
            getchar();
            if(check != 1) {
                printf("Bitte geben Sie nur Zahlen ein. Probiere es noch einmal.\n");
            }
        }
        return user_result;
    }
    

    Der Code ist mit Sicherheit nicht Perfekt, aber es läuft erst mal und ich habe einen guten ersten Einblick bekommen. Als nächstes werde ich versuchen die Ergebnisse in einer Textdatei zu speichern, als eine Art Highscore ... aber jetzt kann erst mal Neujahr kommen und danach packe ich es an.

    Danke für eure Hilfe und Tipps und wünsche alle einen guten Rutsch ....



  • knasan schrieb:

    fflush hab ich schon mal gelesen und muss dies wohl nochmal tun ... 😉

    Vergiß fflush für stdin .

    knasan schrieb:

    Der Code ist mit Sicherheit nicht Perfekt, aber es läuft erst mal

    Tut er nicht.

    Baachte die Warnungen vom Compiler!
    (Wenn du kein bekommst, erhöhe den Warn Level)

    Welchen Wert hat check in Zeile 9?

    Da die Eingabe erst mit der Entertaste übernommen wird, können durchaus mehr als ein fehlerhaftes Zeichen eingegeben worden sein.
    Da kommst du mit einem getchar nicht weit.

    Fehlertolerante Usereingaben sind eine komplexe Angelegenheit und daher für Anfänger eher ungeeignet.



  • Danke für den Hinweis. Ich gebe dir recht, diese Materie ist wirklich nicht einfach und es ist mit Sicherheit nicht falsch wenn man sich damit etwas beschäftigt.
    Mal sehen ob ich dazu Heute noch komme.

    Kann mir jemand ein Howoto nennen wo darauf näher eingeht?
    Meine Abfang Methode habe ich aus dem OpenBook http://openbook.rheinwerk-verlag.de/c_von_a_bis_z/

    Ich werde mein Status wieder auf offen Stellen und nicht mehr so selbstsicher schnell als gelöst makieren.

    Gruß



  • Gut, dass du gleich sagst, wo du dein Unwissen her hast.

    Der Autor hat hier keinen guten Ruf, da das Buch (speziell die Online-Ausgabe) voller Fehler ist.

    Wutz hat schon eine Möglichkeit gepostet.

    Die Frage ist immer, was als gültige Eingabe erkannt werden soll.
    Ist 123xy noch gültig (bis zur 3) oder schon falsch ....



  • Hallo DirkB,

    ich wollte herausfinden ob eine gültige Zahl eingegeben worden ist. Meine jetzige Lösung hat natürlich zwei mir bekannte Probleme

    1. 2a wird noch als gültig erkannt.
    2. Gibt man zwei Zahlen an, ist meine Methode auch Fehleranfällig.

    Ich werde mir die Lösung von Wutz ansehen und melde mich dann nochmal.

    Wegen http://openbook.rheinwerk-verlag.de/c_von_a_bis_z/ hat dieser Syntax oder logische Fehler? Kannst du mir das bitte genauer erläutern? Ich habe hier noch ein Buch von diesem Autor hier "Grundkurs C", sollte ich hiervon auch die Finger lassen oder hilft mir dieses Buch bei C weiter? Ich möchte ja nichts gleich Falsch Lernen, davon habe ich auch nichts.

    Danke


  • Mod

    knasan schrieb:

    Wegen http://openbook.rheinwerk-verlag.de/c_von_a_bis_z/ hat dieser Syntax oder logische Fehler? Kannst du mir das bitte genauer erläutern? Ich habe hier noch ein Buch von diesem Autor hier "Grundkurs C", sollte ich hiervon auch die Finger lassen oder hilft mir dieses Buch bei C weiter? Ich möchte ja nichts gleich Falsch Lernen, davon habe ich auch nichts.

    Beides. Und noch allerlei mehr Arten von Fehlern. Der Autor hat einfach keine tiefgehende Ahnung vom Thema. Das merkt man schon an seinem Portfolio, dass er über alles mögliche Bücher geschrieben hat. Niemand kann ein solch vielseitiger Experte sein. Er ist nur auf einem Gebiet Experte, und zwar da drin, schöne Bücher zu schreiben, die sich gut verkaufen. In denen verbreitet er dann jede Menge gefährliches Halbwissen, dass er sich mal eben schnell irgendwo zusammen gesucht hat.

    Der ganze Verlag hat übrigens den Ruf, hauptsächlich Bücher dieser Art zu verlegen. Schlecht recherchiert, aber gut geschrieben, und dann erfolgreich unter Titeln verkauft, die hauptsächlich Ahnungslose ansprechen, die nicht erkennen können, ob der Inhalt stimmt oder nicht.

    Und ja: Es ist besser, nicht nach diesen Büchern zu lernen, denn du lernst viel falsches, was du hinterher nur mühsam los wirst. Oder du gibst frustriert auf, weil nichts richtig funktioniert.



  • Hi,

    ich habe den Code von Wutz eingefügt und dieser Funktioniert ohne Probleme. Leider verstehe ich den Code noch nicht zu 100%. Ich werde mal versuchen soweit den Code zu erklären so weit ich komme, damit Ihr wisst wo mein Fehler oder Unwissenheit ist. Vielen Dank für eure Mühen.

    int checkInt(long *l, const char *s) {
        char *e;
        if (*s == '\n' ) return 0;
        errno = 0;
        *l = strtol(s, &e, 10);
        return (!*e || *e == '\n') && errno != ERANGE && *l >=INT_MIN && *l <= INT_MAX;
    }
    
    int getInt() {
        char str[1000];
        long longvar;
        while (fgets(str, 1000, stdin), !checkInt(&longvar, str));
        return (int)longvar;
    

    Beides sind Funktionen, checkInt nimmt zwei Parameter an die vom Typ Pointer sind. Der Parameter s ist sogar eine Constante und kann zur Laufzeit nicht verändert werden.

    Es wird wein Pointer auf e erstellt. Ich hoffe das ich es bis hier her alles richtig verstanden habe.

    if (*s == '\n' ) return 0 wenn der Pointer ein Newline ist, dann soll der Ausdruck sofort ein true zurückliefern.

    errno = 0, ich kenne das noch nicht ist eine Variable Zuweisung die Variable wird in errno.h deklariert.

    *l = strtol(s, &e, 10); darauf kann ich mir nichts zusammenreimen und verstehe ich bisher noch nicht, da mir strtol völlig unbekannt ist.

    return (!*e || *e == '\n') && errno != ERANGE && *l >=INT_MIN && *l <= INT_MAX;

    Wenn mir jemand diesen Ausdruck erklären könnte, wäre auch super!

    bei getInt
    Wenn ich es richtig verstehe, wird ein Array erstellt das Maximal 1000 Zeichen beinhalten kann. Eine Variable longvar die als Rückgabe Zeiger von checkInt dient. Am ende wird der Zeiger als Int Rückgabe konveniert.

    PS: Sie getInt Methode habe ich soweit geändert das man zwei Zahlen und ein Zeichen übergeben kann, somit kann ich in der While Schleife immer sagen welche Aufgabe es sich gehandelt hat.
    Wegen die Bücher, ich habe etwas in Web gefunden, kann ich danach gefahrlos Lernen? https://de.wikibooks.org/wiki/C-Programmierung

    Danke für Eure Antworten und Hilfe



  • knasan schrieb:

    Beides sind Funktionen,

    Ja

    knasan schrieb:

    checkInt nimmt zwei Parameter an die vom Typ Pointer sind.

    Fast. Der Typ ist Pointer auf long bzw. Pointer auf char

    knasan schrieb:

    Der Parameter s ist sogar eine Constante und kann zur Laufzeit nicht verändert werden.

    Der Pointer kann verändert werden, aber nicht das, worauf er zeigt. (bzw wird dieses zugesichert und de Compile kann reagieren)

    knasan schrieb:

    Es wird wein Pointer auf e erstellt.

    Es wird ein Pointer auf char mit dem Namen e erstellt.

    knasan schrieb:

    Ich hoffe das ich es bis hier her alles richtig verstanden habe.

    Entscheide selber

    knasan schrieb:

    if (*s == '\n' ) return 0 wenn der Pointer ein Newline ist, dann soll der Ausdruck sofort ein true zurückliefern.

    Wenn de rPointer auf ein Newline zeigt, dann wird ein false zurück gegeben

    knasan schrieb:

    errno = 0, ich kenne das noch nicht ist eine Variable Zuweisung die Variable wird in errno.h deklariert.

    Wenn du weißt, dass sie errno.h deklariert ist, solltest du auch wissen, wozu sie da ist.

    knasan schrieb:

    *l = strtol(s, &e, 10); darauf kann ich mir nichts zusammenreimen und verstehe ich bisher noch nicht, da mir strtol völlig unbekannt ist.

    Die Standard Library von C ist so klein, dass man durchaus alle Funktionen kennen kann. Wenn nicht, hilft ein Blick in eien Referenz oder Online Hilfe.
    http://www.cplusplus.com/reference/cstdlib/strtol/?kw=strtol

    knasan schrieb:

    return (!*e || *e == '\n') && errno != ERANGE && *l >=INT_MIN && *l <= INT_MAX;

    Wenn mir jemand diesen Ausdruck erklären könnte, wäre auch super!

    Wenn du die Referenz zu strtol durchgelesen und verstanden hast, sollt dir dieser Ausdruck auch klar werden.
    Es wird geprüft ob das Zeichen nach der Zahl das Stringende oder ein Newline ist und ob kein Fehler bei der Wandlung aufgetreten ist.

    knasan schrieb:

    PS: Sie getInt Methode habe ich soweit geändert das man zwei Zahlen und ein Zeichen übergeben kann, somit kann ich in der While Schleife immer sagen welche Aufgabe es sich gehandelt hat.

    Du hättest sie auch zweimal aufrufen können.

    knasan schrieb:

    Wegen die Bücher, ich habe etwas in Web gefunden, kann ich danach gefahrlos Lernen? https://de.wikibooks.org/wiki/C-Programmierung

    Es bleibt der Versuch eines Online-Tutorials.



  • Jetzt ist mir einiges klarer.
    Ich lag leider manchmal leicht daneben ...
    Danke für den Link - diesen kannte ich noch nicht.


Anmelden zum Antworten