Lottospiel wirft schwierigkeiten auf



  • Ich weiß, dass das thema schon häufiger mal zur sprache kam, aber es ist ja doch jedesmal anders.

    gefordert ist ein lottoprogramm, was den spieler zuerst auffordert die anzahl seiner gewünschten tips einzugeben (z.B. 2). Nun soll er für seine tips die zahlen eingeben (6 aus 49). Das Programm soll doppelt eingegebene zahlen abfangen und den benutzer dazu auffordern erneut die zahl einzugeben. Die Tips sollen ausgegeben werden und die Zahlen sollen gezogen werden.

    so: die gezogenen zahlen stellen kein problem dar. 1. leider gibt mir mein programm keine matrix mit den getippten werten aus. das hätte ich aber gerne zur visualisierung. 2. bricht die schleife zur erneuten eingabe eines tips nicht ab. mein programm gibt mir unendlcih oft "geben sie eine neue zahl ein" aus.

    Nun mein frage: was läuft schief??? Ich starre nun schon stunden drauf und komme einfach nicht weiter...

    #include <iostream>
    #include <ctime> // Bibliothek, die Zufallszahlen ermöglicht
    using namespace std;
    
    int main (){
    
    	int Tips; // Anzahl der Tips die abgegeben werden soll
    
    	cout << "Geben sie ein, wieviele Tips sie abgeben wollen" << endl;
    	cin >> Tips;
    
    	double **matrix = new double * [Tips]; // Erstellen einer dynamischen Matrix mit Tips-Zeilen und 6 Spalten
    	for (int i = 0; i < Tips; i++){
    		matrix[i] = new double [6];
    	}
    
    	int Zahl;// eingegebene Zahlen für die Tips
    	bool doppelteEingabe = false;
    	for (int i = 0; i < Tips; i++){
    		for (int j = 0; j < 6; j++){
    			cout << "Bitte geben sie fuer den " << i+1 << "ten Tip die " << j+1 << "te Zahl ein" << endl;
    			cin >> Zahl;
    
    			do {
    				doppelteEingabe = false;
    				matrix [i][j] = Zahl;
    
    				for(int p = 0; p<j; p++){
    					if (matrix[i][p] == Zahl){
    						doppelteEingabe = true;
    					cout << "geben sie eine neue zahl ein" << endl;
    					}
    
    				}
    			}
    			while (doppelteEingabe == true);
    			cout << matrix [i][j] << " ";
    		}
    		cout << endl;
    
    	}
    
    	int lottoZahlen[6]; // String oder 1D-Array
        bool doppelteZahl = false; // zum Abfangen von doppelt gezogenen Zahlen
    
        srand((int)time(0));
    
        for(int j=0;j<6;j++){ // Einträge werden gezogen
            do{
                doppelteZahl = false; // also, wenn es keine doppelte Zahl gibt
                lottoZahlen[j]=1 + rand()%48;
    
    			for(int i=0;i<j;i++){
                    if(lottoZahlen[j] == lottoZahlen[i]){ // wenn es doppelte Zahl gibt dann 
                        doppelteZahl = true;
                    }
    
                }
    		}
    		while (doppelteZahl == true);
    
    		cout << lottoZahlen[j] << endl;
    	}
    }
    

    Ps: Ich bin blutiger anfänger, also entschuldigt etwaige "dumme" fehler...


  • Mod

    Ich habe jetzt nicht das ganze Programm durchgesehen, weil deine Beschreibung nach einem bestimmten Anfängerfehler klang, den ich dann auch prompt gefunden habe. Das hier:

    double **matrix
    

    Das ist KEIN zweidimensionales Array. Daher geht ieser Zugriff schief und es kommt zu undefiniertem Verhalten:

    matrix [i][j]
    

    Dieses Problem haben viele Leute. Sebastian Pizer hat sich in diesem Thread mal die Mühe gemacht, das Problem anhand eines Bildchens zu erklären:
    http://www.c-plusplus.net/forum/viewtopic-var-t-is-249196-and-view-is-previous.html



  • leider habe ich noch nicht ganz verstanden, wieso mein array nicht 2 dim ist. kannst du es vlt noch einmal kurz erläutern, bitte?



  • StehtAufDemC++Schlauch schrieb:

    leider habe ich noch nicht ganz verstanden, wieso mein array nicht 2 dim ist. kannst du es vlt noch einmal kurz erläutern, bitte?

    Ich kann's erläutern 🙂 . Du hast einen 2-dimensionalen Zeiger weil dein 2-facher Zeiger auf 2 verschiedene Speicheradressen zeigt. Dereferenzierung:
    *matrix -> zeigt auf Speicheradresse 1
    **matrix -> zeigt auf Speicheradresse 2

    Für jeden Zeiger kannste nun neuen Speicher reservieren.

    Ansonsten:
    2 dimensional bedeutet einfach nur das ein weiteres Array mit angehängt wird:
    int a[2][2];
    // ´ `
    // das hier ist die "2. Dimension"


  • Mod

    Ok, nochmal ein Bildchen (Warnung die folgende Darstellung ist vereinfacht. Es geht hier um eine VERSTÄNDLICHE Erklärung):

    Ein zweidimensionales Array [2][3] kann man sich so vorstellen:
    
        +--------+--------+--------+
        | [0][0] | [0][1] | [0][2] |
        +--------+--------+--------+
        | [1][0] | [1][1] | [1][2] |
        +--------+--------+--------+
    
    Konkret im Speicher liegen die Werte aber einer nach dem anderen. Es handelt sich um ein Array von einem Array:
    
                   +--------+--------+--------+--------+--------+--------+
                   | [0][0] | [0][1] | [0][2] | [1][0] | [1][1] | [1][2] |
                   +--------+--------+--------+--------+--------+--------+
    Speicheradresse:   X        X+1     X+2       X+3      X+4      X+5
    
    Dabei ist X die Adresse des Arrays.
    
    Wenn man den Zugriffsoperator [] anwendet macht der einfach folgendes:
    Array[i] = Adresse des Arrays + i * Breite eines Elements
    
    Hat man ein Array von einem Array, so gibt wird das im Prinzip genauso gemacht. Die Breite eines Elements des äußeren Arrays ist dabei aber einfach die Breite der inneren Arrays:
    Array[i][j] = Adresse des Arrays + i * Breite inneres Array + j * Breite eines Elements
    
    Nehmen wir mal an, die Breite eines Elements in dem obigen Beispiel sei 1. Dann ist die Breite des inneren Arrays die Breite von 3 Elementen nacheinander, also 3. Somit kann ein Zugriff zum Beispiel so aussehen:
    
    Array[1][2] = X + 3 * 1 + 2 = X+5
    Wie du siehst, wird die Speicherzelle korrekt ausgerechnet.
    
    ______________________________________________________________
    Nun ein Blick auf das, was du gemacht hast:
    Matrix ist ein Zeiger auf einen Zeiger. Zuerst hast du matrix = new double * [Tips] gemacht. Nehmen wir mal an, Tips wäre 2:
    
                   +--------+--------+
                   |  [0]   |  [1]   |
                   +--------+--------+
    Speicheradresse:   M        M+1   
    
    Dabei ist M die Adress auf die Matrix zeigt. Die Elemente Matrix[0] und Matrix[1] sind nun aber selber Zeiger. Entsprechend hast du ihnen mit matrix[i] = new double [6]; die Adressen von weiteren Arrays zugewiesen. Im weiteren nehme ich statt eines Arrays mit 6 Elementen, ein Array mit 3 Elementen, damit es übersichtlicher ist:
                   +--------+--------+--------+
                   |  [0]   |  [1]   |  [2]   |
                   +--------+--------+--------+
    Speicheradresse:   M0      M0+1     M0+2
    
                   +--------+--------+--------+
                   |  [0]   |  [1]   |  [2]   |
                   +--------+--------+--------+
    Speicheradresse:   M1      M1+1     M1+2
    
    Dabei ist M0 die Adresse auf die Matrix[0] zeigt und M1 die Adresse auf die Matrix[1] zeigt. Es gilt NICHT M1=M0+3. New reserviert den Speicher irgendwo, nicht zwangsweise am Stück.
    
    Insgesamt sieht das dann so aus:
    
                   +--------+--------+.....+--------+--------+--------+.....+--------+--------+--------+
                   |   M0   |   M1   |     |  [0]   |  [1]   |  [2]   |     |  [0]   |  [1]   |  [2]   |
                   +--------+--------+.....+--------+--------+--------+.....+--------+--------+--------+
    Speicheradresse:   M        M+1           M0       M0+1    M0+2           M1       M1+1    M1+2
    
    Wie gesagt schon gesagt, M, M0, und M1 können an ganz unterschiedlichen Stellen liegen.
    
    Wenn du jetzt den Elementzugriffsoperator auf Matrix anwendest:
    Matrix[i][j]
    Dann wird wieder gerechnet:
    Matrix[i][j] = M + i * Breite des inneren Elements + j * Breite eines Datenelements
    Du kannst hier schon sehen, dass dies überhaupt nichts mit M0 und M1 zu tun hat, wo die Elemente ja eigentlich liegen. Man greift irgendwo auf den Speicher hinter M zu.
    Hinzu kommt auch noch, dass ein inneres Element hier kein array ist, sondern ein Pointer und die Breite eines Pointers ist eine andere als die eines Arrays. Es hätte dich ja auch schließlich niemand daran gehindert, Matrix[0] auf ein Array mit 10 Elementen zeigen zu lassen und Matrix[1] auf ein Array mit 50 Elementen.
    

    Schlussfolgerung: Die ganze Zeigerarithmetik funktioniert nicht mehr wie man naiv erwarten würde, wenn man mit Zeigern auf Zeiger arbeitet.

    Mögliche Abhilfen:

    1. Bei geschachtelten STL-Containern verläuft der Zugriff so, wie man es erwartet
    2. Man nimmt Matrix nur als einfachen Zeiger und macht dann
    Matrix = new double[Länge*Breite];
    

    Und dann macht man sich seine Zeigerarithmetik eben selber. Das heißt statt Matrix[i][j] schreibt man Matrix[i*Länge + j]



  • Ich habe mal den Code so umgeschrieben, dass er funktioniert.

    #include <iostream>
    #include <ctime> // Bibliothek, die Zufallszahlen ermöglicht
    using namespace std;
    
    int main (){
    
        int Tips; // Anzahl der Tips die abgegeben werden soll
    
        cout << "Geben sie ein, wieviele Tips sie abgeben wollen" << endl;
        cin >> Tips;
    
        int **matrix = new int * [Tips]; // Erstellen einer dynamischen Matrix mit Tips-Zeilen und 6 Spalten
        for (int i = 0; i < Tips; i++){
            matrix[i] = new int [6];
        }
    
        int Zahl;// eingegebene Zahlen für die Tips
        bool doppelteEingabe = false;
        for (int i = 0; i < Tips; i++){
            for (int j = 0; j < 6; j++){
                cout << "Bitte geben sie fuer den " << i+1 << "ten Tip die " << j+1 << "te Zahl ein" << endl;           
    
                do {
    				cin >> Zahl; // <- Muss in die do-while-Schleife
    
                    doppelteEingabe = false;
                    matrix [i][j] = Zahl;
    
                    for(int p = 0; p<j; p++){
                        if (matrix[i][p] == Zahl){
                            doppelteEingabe = true;
                        cout << "Geben sie eine neue Zahl ein" << endl;
                        }                   
                    }
                }
                while (doppelteEingabe == true);
            }
    
        }
    
    	for (int i = 0; i != Tips; ++i)	{ // Seperate Ausgabe der Matrix
    		for (int j = 0; j != 6; ++j)
    			cout << matrix[i][j] << " ";
    		cout << endl;
    	}
    
        int lottoZahlen[6]; // String oder 1D-Array
        bool doppelteZahl = false; // zum Abfangen von doppelt gezogenen Zahlen
    
        srand((int)time(0));
    
        for(int j=0;j<6;j++){ // Einträge werden gezogen
            do{
                doppelteZahl = false; // also, wenn es keine doppelte Zahl gibt
                lottoZahlen[j]=1 + rand()%48;
    
                for(int i=0;i<j;i++){
                    if(lottoZahlen[j] == lottoZahlen[i]){ // wenn es doppelte Zahl gibt dann
                        doppelteZahl = true;
                    }
    
                }
            }
            while (doppelteZahl == true);
    
            cout << lottoZahlen[j] << " ";
        }
    }
    

    Der Fehler, dass du in eine Endlosschleife geraten bist, sobald du eine Zahl doppelt eingegeben hast, lag ganz einfach daran, dass du nur außerhalb der do-while-Schleife nach der Zahl gefragt hast.

    Die Ausgabe der eingetippten Werte habe ich von der eigentlichen Eingabe abgetrennt.

    Noch eine Frage zu den mehrdimensionalen Arrays:

    Die Erklärung von SeppJ ist logisch und schlüssig, aber wieso klappt es dann trotzdem? Ich verstehe die Arbeitsweise der Zeiger momentan folgendermaßen:

    int **m = new int * [3]
    for (int i = 0; i != 3; ++i)
       m[i] = new int [2]
    

    Wenn ich nun m dereferenziere, bekomme ich einen Zeiger, der selbst wiederrum auf den Anfang eines Arrays zeigt. Wenn ich diesen auch dereferenziere, bekomme ich den gespeicherten Wert. D.h. m[0][0] liefert den gewünschten Wert.
    Durch das inkrementieren des zweiten Zeigers m[0][1], zeigt dieser doch nur auf das zweite Element des Arrays, auf das er zeigt - er wird ja einfach nur um die Größe eines Ints verschoben.

    Das heißt, der einzige Unterschied zu einem richtigen mehrdimensionalen Array ist, dass sich die Daten nicht alle hintereinander im Speicher befinden müssen, aber kann das nicht egal sein (zumindest in diesem Fall)?

    MfG,
    ScRaT



  • ScRaT_GER schrieb:

    Das heißt, der einzige Unterschied zu einem richtigen mehrdimensionalen Array ist, dass sich die Daten nicht alle hintereinander im Speicher befinden müssen, aber kann das nicht egal sein (zumindest in diesem Fall)?

    Das sehe ich auch so. Zeigerarithmetik wird hier nirgendwo benutzt.


  • Mod

    life schrieb:

    ScRaT_GER schrieb:

    Das heißt, der einzige Unterschied zu einem richtigen mehrdimensionalen Array ist, dass sich die Daten nicht alle hintereinander im Speicher befinden müssen, aber kann das nicht egal sein (zumindest in diesem Fall)?

    Das sehe ich auch so. Zeigerarithmetik wird hier nirgendwo benutzt.

    Ihr habt natürlich recht, ich habe nicht an die Auswertungsreihenfolge des []-Operators gedacht. Probleme treten erst dann auf, wenn man Zeiger auf Zeiger und mehrdimensionale Arrays ineinander umwandeln will.



  • SeppJ schrieb:

    Ich habe jetzt nicht das ganze Programm durchgesehen, weil deine Beschreibung nach einem bestimmten Anfängerfehler klang, den ich dann auch prompt gefunden habe. Das hier:

    double **matrix
    

    Das ist KEIN zweidimensionales Array. Daher geht ieser Zugriff schief und es kommt zu undefiniertem Verhalten:

    matrix [i][j]
    

    Dieses Problem haben viele Leute. Sebastian Pizer hat sich in diesem Thread mal die Mühe gemacht, das Problem anhand eines Bildchens zu erklären:
    http://www.c-plusplus.net/forum/viewtopic-var-t-is-249196-and-view-is-previous.html

    Falsch. Bei einem T** t liefert t[x][y] -> *(*(t+x)+y) und das ist völlig korrekt so. Probleme gibt es nur, wenn man versucht, so eine Struktur zu behandeln, als wenn alles hintereinander im Speicher liegt.

    PS: Ich hätte bis zum Ende lesen sollen...


Anmelden zum Antworten