Kann ich überhaupt programmieren lernen?

Ich weiß zwar nicht worauf du hinaus willst, aber gut.
... na hierauf:
iUebergabe hatte übrigens nur das Gesamtergebnis des letzten Spielers ausgespuckt, weil sich cout << iUebergabe nicht in der Schleife befand..
ich versuche dich die fehler auf diese art selber finden zu lassen ...
Zweiter Schleifendurchlauf:

i hat den wert 2.
Es erfolgt die Ausgabe "Spieler 2 wirft:" und i erhält den Wert 3.
iDurchgang1Spieler() wird aufgerufen und gibt (laut annahme) den wert 3 zurück.
iUebergabe erhält diesen rückgabewert.

iUebergabe erhält einen anderen Wert als iDurchgang1Spieler().
wenn du rein routinemäßig für jeden abschnitt deines codes so eine "ablaufverfolgung" im kopf hast, oder sie anfangs evtl runterschreibst, fallen dir solche dinge sehr schnell auf...
Wird das noch relevant? Momentan ist das doch eigentlich wurscht?!
naja ... du wolltest eine bestenliste die alle ergebnisse bis zum programmende im speicher behält ... dafür wären alle werte relevant ...

der einfachste weg wäre nun, die liste zu füllen wenn iDurchgang1Spieler() den wert an die main() zurück geliefert hat ...

aus prozeduraler sicht gehört diese aufgabe aber nicht in die main() ... dem liegt folgender gedankengang zugrunde:

iWurf() führt würfe durch ... würfe gibts nur im kontext eines spielers, und ein spieler führt alle seine würfe am stück durch ... -> die prezedur, die alle würfe eines spielers durchführt, ist aktuell die einzige prozedur die iWurf() aufruft ...

einträge auf die bestenliste gibt es nur, wenn auch ein spieler kontext vorhanden ist ... die prozedur die alle würfe eines spielers durchführt, behandelt aktuell den gesammten kontext eines spielers ... wenn ein spieler seine würfe durchgeführt hat, wird er auch auf die bestenliste eingetragen -> um dies sicherzustellen muss der eintrag auf die bestenliste am ende dieser prozedur stehen ... (hintergrund ist: sollte ein programm mal erweitert werden, und abgesehen von der main noch eine andere prozedur iDurchgang1Spieler() benutzen, steht der teil für die bestenliste schon, und du vermeidest doppelten code zu schreiben)

im endeffekt führen viele wege nach rom, und dieses konzept stellt nur eine mögliche auslegung dar ...

wichtig ist an dieser stelle erstmal nur, dass man das ganze entsprechend einer gewissen logik in einzelne prozeduren zerlegt, und die abhänigkeiten dieser prozeduren beachtet ... wenn man dabei kein henne-ei problem baut, hat man mit dieser art überlegung eigentlich sehr schnell ein grobes strickmuster zur hand, dass dem prozeduralen ansatz folgt, und sich zu einer lösung ausbauen lässt ... nun kann es anfangs schwer fallen zu entscheiden ob etwas eine eigene prozedur ist, oder nicht ... dafür gibt es, meiner erfahrung nach, keine allgemeingültige antwort ... wenn du allerdings code schreibst, der eine andere aufgabe erfüllt als der umgebende, solltest du darüber nachdenken ...


wie solls weiter gehen? einfach oder doch eher richtung prozeduraler ansatz?

einfach:

einführung der bestenliste als array in der main ...
befüllen des arrays während der schleife in der main...

prozedural:

einführung der bestenliste als array in der main
iDurchgang1Spieler() bekommt 2 neue parameter, eine referenz auf die bestenliste, und die nummer des aktuellen spielers (identifizierendes merkmal des kontextes)
einführung einer neuen prozedur, die in die bestenliste einträgt
aufruf dieser prozedur am ende von iDurchgang1Spieler()

oder widmen wir uns einer anderen baustelle?;)
 
Andere Baustelle? Gibt´s denn sonst noch welche?

Ich bereue es zwar eigentlich schon jetzt, da ich langsam mal mit dieser blöden Bowling Simulation abschliessen will, aber ich würde gerne mit der prozeduralen Methode weitermachen. Wie du schon gesagt hast, hilft mir das vielleicht den Programmablauf im einzelnen besser zu verstehen.

Soweit habe ich schon mal alles vorbereitet: :D

Code:
int iDurchgang1Spieler(int Spielernummer, int BezugBestenliste)
{
    ...
Code:
int main(){

    int iBestenliste[100][2];
    ...
Und nun?


Ich editier nochmal:

Die einfache Variante habe ich gerade selber lösen können. Juhu! Endlich.

Code:
	//	main.cpp	//

#include <iostream>
#include <conio.h>
#include <time.h>
using namespace std;


int iWurf(){

	int iPunkte = 0;

	iPunkte = rand()%11;	//Ein Wurf eines Spielers.
	cout << iPunkte << endl;
	if (iPunkte == 10){
		cout << iPunkte << endl;	//Im Fall eines Strikes.
		iPunkte = iPunkte + iPunkte;
	}

	return iPunkte;
}


int iDurchgang1Spieler(int Spielernummer, int BezugBestenliste){
	
	int iDurchgangSpieler = 0;
	for (int i = 0; i < 10;i++){	//Alle Würfe EINES Spielers.
	iDurchgangSpieler = iDurchgangSpieler + iWurf();
	}

	return iDurchgangSpieler;
}


int main(){

	int iBestenliste[100];
	int iSpielernr = 0;
	int iBezugBestenl = 0;
	int iUebergabe;
	int iPlayer;
	
	cout << "Wieviele Spieler sollen am Turnier teilnehmen?" << endl;
	cin >> iPlayer;
	cout << "Es nehmen " << iPlayer << " Spieler am Turnier teil." << endl;
	
	srand ((unsigned)time(NULL));
	for (int i = 1; i <= iPlayer;){			
	cout << "Spieler " << i++ << " wirft:" << endl;	// i++ zählt in dieser Zeile einfach nur die Zahlen hinter "Spieler " hoch.
	iUebergabe = iDurchgang1Spieler(iSpielernr, iBezugBestenl);	// Alle Würfe aller Spieler.
	cout << endl << "Gesamtergebnis: " << iUebergabe << endl << endl;
	iBestenliste[i] = iUebergabe;
	}

	cout << endl << "	Bestenliste: " << endl << endl;

	for (int j = 1; j <= iPlayer; ){
		cout << "Spieler " << j++ << " hat " << iBestenliste[j] << " Punkte!" << endl;	
	}
	
	_getch();
}
 
Zuletzt bearbeitet:
Andere Baustelle? Gibt´s denn sonst noch welche?
joa ... nach belieben viele ...
Soweit habe ich schon mal alles vorbereitet: :D

Code:
int iDurchgang1Spieler(int Spielernummer, int BezugBestenliste)
{
    ...
Code:
int main(){

    int iBestenliste[100][2];
    ...
soweit so gut ... nur mit dem typ von BezugBestenliste hätte ich so meine bedenken ...

um zu verstehen wie ein array unter c/c++ funktioniert kommt man leider nicht darum herum sich anzuschauen wie es im speicher aussieht ...

zur veranschaulichung nehmen wir mal das c typische array of char (landläufig auch als null-terminierter string bekannt)

angenommen wir haben ein solches array of char, mit dem namen txt, welches den Text "Hallo Welt!" beinhaltet, dann könnte das im speicher z.B. so aussehen:

0x0abc00: 'H'
0x0abc01: 'a'
0x0abc02: 'l'
0x0abc03: 'l'
0x0abc04: 'o'
0x0abc05: ' '
0x0abc06: 'W'
0x0abc07: 'e'
0x0abc08: 'l'
0x0abc09: 't'
0x0abc0a: '!'
0x0abc0b: NULL

mit txt[0] würde man ein 'H' bekommen...
mit &txt[0] würde man 0x0abc00 bekommen ... (falls nicht bekannt, & ist der adress operator, der dir die speicheradresse statt dem eigentlichen wert einer variable liefert)

schöne heile 1D welt ... alles steht der reihe nach im speicher, und txt ist eigentlich nichts anderes als ein pointer auf das aller erste element ...

aber wie sieht ein 2D array aus? eigentlich genauso wie die 1D version, nur dass die elemente wiederum 1D arrays sind... angenommen wir haben ein 2D int array ...

int z[3][2]

das füllen wir wie folgt mit zahlen:

z[0][0]=0
z[0][1]=1
z[1][0]=2
z[1][1]=3
z[2][0]=4
z[2][1]=5


und schauen uns wieder an wie das im speicher liegen könnte ...

0x0abc00: 0x0abc03
0x0abc01: 0x0abc05
0x0abc02: 0x0abc07
0x0abc03: 0
0x0abc04: 1
0x0abc05: 2
0x0abc06: 3
0x0abc07: 4
0x0abc08: 5

&z[0] (also z) liefert wieder 0x0abc00 ...
z[0] liefert 0x0abc03 also die startadresse des ersten der 3 arrays der zweiten dimension

die array variable, in diesem beispiel z oder im vorherigen txt, ist immer ein pointer ... im eindimensionalen fall ein pointer auf das erste element des arrays ... im zweidimensionalen ein pointer auf einen pointer auf das erste element des arrays ... im dreidimensionalen haben wir dann einen pointer auf einen pointer auf einen pointer auf das erste element des arrays ... etc ...

warum breite ich das hier so aus? ... wenn wir nun ein 2d array als parameter übergeben wollen, ist unsere referenz auf dieses array ein int** (also ein pointer auf einen int pointer)

wie du ja bereits weißt sind die parameter einer funktion immer kopien der jeweiligen originalwerte ... da in diesem fall aber der parameter ein pointer ist (unser pointer auf einen int pointer; int**) stört das nicht weiter ... von eben diesem pointer wird eine kopie angelegt und an die funktion gegeben ... diese kopie des pointers zeigt immernoch auf die gleiche speicheradresse, und änderungen die wir an dieser stelle im speicher vornehmen werden dort auch vom originalpointer "gesehen" ... wenn eine funktion einen pointer übergeben bekommt, arbeitet sie also nicht mit einer kopie der daten (nur mit einer kopie des pointers) ... in der literatur findet man häufig aussagen dass parameter "by value" oder "by reference" übergeben werden ... dahinter steckt exakt der unterschied, ob der wert (value) oder eine referenz auf ihn (also ein pointer) übergeben wird ... im ersten fall sind alle änderungen die eine funktion am wert des parameters macht nicht sichtbar für die aufrufende funktion, im 2. fall sind sie sichtbar...

programmiert man objektorientiert und der funktionsparameter ist ein objekt, oder nutzt man arrays, so ist implizit "by reference" gemeint


so ... mal wieder genug der vielen worte ...

nachdem du ja die einfache lösung bereits hinbekommen hast, wäre einer der nächsten schritte zur prozeduralen lösung die einführung einer prozedur, die einträge auf die bestenliste machen kann ...

wenn wir uns mal zurück entsinnen, muss eine solche prozedur wissen in welchem spielerkontext der eintrag stehen soll ... sie muss wissen auf welche bestenliste sie schreiben soll ... und es wäre mit sicherheit nicht verkehrt, wenn sie auch noch weiß was sie eintragen soll ...


so, und um nun zu deiner ersten frage in diesem Thread zu kommen, die antwort lautet offensichtlich "ja" :D
 
Ich hab mal einen Tag Programmierpause eingelegt. Nun also weiter.

Mit dem Thema wie ein Array im Speicher aussieht, willst du mich schon ein bißchen quälen, oder?! :wink:

GrafZahl:
nachdem du ja die einfache lösung bereits hinbekommen hast, wäre einer der nächsten schritte zur prozeduralen lösung die einführung einer prozedur, die einträge auf die bestenliste machen kann ...

wenn wir uns mal zurück entsinnen, muss eine solche prozedur wissen in welchem spielerkontext der eintrag stehen soll ... sie muss wissen auf welche bestenliste sie schreiben soll ... und es wäre mit sicherheit nicht verkehrt, wenn sie auch noch weiß was sie eintragen soll ...
Ich hab mich zumindest gedanklich gestern schon ein wenig mit der Problematik auseinander gesetzt und versuch jetzt gerade irgendwie zu realisieren, was du geschieben hast.

Ich hab jetzt die beiden tollen Parameter int iDurchgang1Spieler(int iSpielernummer, int iBezugBestenliste), aber weiß nur theoretisch was ich mit den anfangen kann.
Außerdem fehlt mir auch jegliche Idee was ich in die neue Prozedur, die Einträge in die Bestenliste vornehmen soll, konkret hineinschreiben soll und wie ich sie die Einträge dürchführen lassen könnte.

Leider finde ich überhaupt keinen Ansatz..
 
wenn ich dich ärgern wollte hätte ich dir das array mit pointerarithmetik erklärt ... :P
(also mit den worten ... wofür indexoperatoren? arr[5][5] sieht hässlich aus ... schreibt sich doch so viel schöner: *((*(arr+5))+5) ...
oder wie wäre es mit der itteration durch ein array? for(int i=0;i<5;i++) cout << *arr++ << endl;
)

also was fehlt dir für den ansatz? du weißt nicht so recht wie du mit den parametern arbeiten kannst ...


hilft dir das hier weiter?
Code:
int main()
{

    int arr[5][4];
    
    for(int i=0;i<5;i++)
        for(int j=0;j<4;j++)
        {
            funkt(i,j,arr);
        }

    for(int i=0;i<5;i++)
    {
        for(int j=0;j<4;j++)
        {
            cout << arr[i][j] << " ";
        }
        cout << endl;
    }
}

void funkt(int i, int j, int **arr)
{
    if(0 == j % 2)
        arr[i][j]=i*j;
    else
        arr[i][j]=i+j;
}
wie ein bestenlisten eintrag aussehen kann, habe ich dir vor ein paar tagen hier schonmal skizziert ... wenn du diese skizze nun mit dem aufbau von arrays vergleichst und dir mal die frage stellst, was nötig ist um soetwas zu schreiben, solltest du ein stück weiter kommen ...

Code:
void bestenListenEintragSchreiben(int spielerNr, int punktzahl, int **bestenListe)
{
    bestenListe[spielerNr][0]=spielerNr;
    bestenListe[spielerNr][1]=punktzahl;
}
zu anfang werden alle spieler mit ihren punkten auf den eintrag ihrer spielernummer geschrieben ... die liste ist dann noch nicht sortiert ... damit beim sortieren spielernummer und punktzahl nicht ihre zusammengehörigkeit verlieren, steht die spielernummer zusammen mit der punktzahl in der 2. array dimension...

wenn du dir nun den aufbau von arrays im speicher ansiehst, kannst du erkennen, dass die erste dimension pointer auf die arrays der 2. dimension enthält ... wenn man nun die erste dimension des arrays anhand des punktwerts in der 2. dimension sortiert (also ldegiglich die pointer in der ersten dimension in eine neue reihenfolge bringt), kann man die liste sortieren, ohne dass die punktzahl von der spielernummer getrennt wird...
 
Zuletzt bearbeitet:
Hilft mir vielleicht ein bißchen weiter, aber leider nicht weit genug.

Ich glaube die prozedurale Methode übersteigt meine momanten Fähigkeiten sehr.

GrafZahl:
void funkt(int i, int j, int **arr)
Warum sind da zwei ** vor arr? Ich dachte einen Pointer weist man mit nur einem * aus?!

Außerdem habe ich den Rückgabewert von Funktionen immer noch nicht richtig verstanden, glaube ich zumindest.

Code:
int iWurf(){

    int iPunkte = 0;

    iPunkte = rand()%11;    //Ein Wurf eines Spielers.
    cout << iPunkte << endl;
    if (iPunkte == 10){
        cout << iPunkte << endl;    //Im Fall eines Strikes.
        iPunkte = iPunkte + iPunkte;
    }

    return iPunkte;
}


int iEintragBestenliste(int iSpielernummer, int iPunktzahl, int **iBezugBestenliste){

    iPunktzahl = iWurf();
    iBezugBestenliste[iSpielernummer][iPunktzahl];
    
    return iBezugBestenliste[iSpielernummer][iPunktzahl];

}


int iDurchgang1Spieler(int iSpielernummer, int iBezugBestenliste){
    
    int iDurchgangSpieler = 0;
    for (int i = 0; i < 10;i++){    //Alle Würfe EINES Spielers.
        iDurchgangSpieler = iDurchgangSpieler + iWurf();
    }

    return iDurchgangSpieler;    //Übergibt alle Würfe eines Spielers.
}


int main(){
  ...
Die Funktionen haben einen Rückgabewert, müssen sie ja auch, sonst zeigt Visual Studio Fehler beim Compilieren an. Wenn ich die Rückgabewerte auskommentiere und Visual Studio die Fehler beim compilieren einfach überspringen lasse, läuft das Programm trotzdem wie vorgesehen ab.

Meine Frage ist jetzt natürlich, warum brauchen die Funktionen zwingend einen Rückgabewert, wenn doch gar nichts zurückgegeben wird, bzw. das Programm auch ohne die Werte auskommt?
Und wohin werden die Werte durch return übergeben? Von der Funktion an die main oder von der main an die Funktion.

Genauso ist´s eigentlich auch bei der main funktion. Es gehört zum guten Ton diese den wert 0 zurückgeben zu lassen (return 0), wenn sie funktionieren sollte. Aber main funktioniert komplett ohne return, es tauchen dabei weder Fehlermeldungen noch Warnungen auf.

Und nun zur meinem letzten Unverständnis. Wie fülle ich die Parameter einer Funktion? Bezogen auf meinen Code baumeln die beiden Parameter int iDurchgang1Spieler(int iSpielernummer, int iBezugBestenliste) so rum. Sie haben keinen wert, sind immer noch total überflüssig und machen gar nix.

Ach ja. Bei dem in diesem Post geposteten Codeteil, ist die Funktion int iEintragBestenliste(int iSpielernummer, int iPunktzahl, int **iBezugBestenliste), der klägliche Versuch Einträge in die Bestenliste vorzunehmen und sinnvoll mit Parametern und Rückgabewerten umzugehen.
 
Warum sind da zwei ** vor arr? Ich dachte einen Pointer weist man mit nur einem * aus?!
ein pointer auf einen int schreibt man als
Code:
int *ptr;
ein pointer auf einen pointer auf einen int schreibt man als
Code:
int **ptr;
Außerdem habe ich den Rückgabewert von Funktionen immer noch nicht richtig verstanden, glaube ich zumindest.

Code:
int iWurf(){

    int iPunkte = 0;

    iPunkte = rand()%11;    //Ein Wurf eines Spielers.
    cout << iPunkte << endl;
    if (iPunkte == 10){
        cout << iPunkte << endl;    //Im Fall eines Strikes.
        iPunkte = iPunkte + iPunkte;
    }

    return iPunkte;
}


int iEintragBestenliste(int iSpielernummer, int iPunktzahl, int **iBezugBestenliste){

    iPunktzahl = iWurf();
    iBezugBestenliste[iSpielernummer][iPunktzahl];
    
    return iBezugBestenliste[iSpielernummer][iPunktzahl];

}


int iDurchgang1Spieler(int iSpielernummer, int iBezugBestenliste){
    
    int iDurchgangSpieler = 0;
    for (int i = 0; i < 10;i++){    //Alle Würfe EINES Spielers.
        iDurchgangSpieler = iDurchgangSpieler + iWurf();
    }

    return iDurchgangSpieler;    //Übergibt alle Würfe eines Spielers.
}


int main(){
  ...
Die Funktionen haben einen Rückgabewert, müssen sie ja auch, sonst zeigt Visual Studio Fehler beim Compilieren an. Wenn ich die Rückgabewerte auskommentiere und Visual Studio die Fehler beim compilieren einfach überspringen lasse, läuft das Programm trotzdem wie vorgesehen ab.
nein... wenn dein compiler einen Fehler meldet, wird der compiler-lauf abgebrochen, sprich es wird keine ausführbare datei erzeugt ... wenn du in visual studio dennoch startest, wird die letzte funktionierende version gestartet... schau mal ob du einen menüpunkt findest der "build clean" heißt ... der beseitigt vor dem bauen alle überreste vorheriger builds ...
Meine Frage ist jetzt natürlich, warum brauchen die Funktionen zwingend einen Rückgabewert, wenn doch gar nichts zurückgegeben wird, bzw. das Programm auch ohne die Werte auskommt?
Und wohin werden die Werte durch return übergeben? Von der Funktion an die main oder von der main an die Funktion.
streng formal reden wir hier über methoden ... eine methode heißt funktion wenn sie einen wert zurück liefert ... im sprachgebrauch wird dieser unterschied sehr häufig nicht gemacht ... da heißt einfach alles funktion

liefert eine methode also keinen wert zurück, wird sie in c/c++ als void deklariert:
Code:
void foo()
{
//some magic here
}
liefert sie einen wert, muss der typ den sie liefert deklariert werden ... liefert sie beispielsweise einen int:
Code:
int bar()
{
return 42;
}
der maßgebliche unterschied ist folgender:

Code:
int main()
{
int blubb=bar(); // hier wird bar() aufgerufen ... bar() liefert 42 zurück, und blubb hat danach den wert 42
foo(); // ruft die methode foo() auf ... der code dort würde an dieser stelle ausgeführt ...

int grosseskacka=foo(); // das geht gnadenlos schief ... der compiler weiß, dass foo() keinen wert, und erst recht keinen int liefert ...

bar(); // dieser aufruf ist legal ... hier würde dann der code in bar ausgeführt, und auch der wert 42 zurück geliefert ... aber dieser aufruf tut nichts mit dem resultat von bar() ... der wert wird einfach verworfen ...
}
Genauso ist´s eigentlich auch bei der main funktion. Es gehört zum guten Ton diese den wert 0 zurückgeben zu lassen (return 0), wenn sie funktionieren sollte. Aber main funktioniert komplett ohne return, es tauchen dabei weder Fehlermeldungen noch Warnungen auf.
das ist ein überbleibsel aus dem c99 standard ... wenn die main kein return statement besitzt, so wird es als implizit angenommen ... der compiler tut so als stünde dort ein return 0 (im fall von c++ ... c99 gibt in diesem fall den letzten register wert zurück, der in dem register steht, das typischerweise den rückgabewert enthält ... ) der ordnung halber, damit man als mensch auch lesen kann was wann zurückgegeben wird, sollte man es ausschreiben
Und nun zur meinem letzten Unverständnis. Wie fülle ich die Parameter einer Funktion? Bezogen auf meinen Code baumeln die beiden Parameter int iDurchgang1Spieler(int iSpielernummer, int iBezugBestenliste) so rum. Sie haben keinen wert, sind immer noch total überflüssig und machen gar nix.
schau dir mal meine prozedur (siehe vorheriger post, spoiler tag) an ... da siehst du wie auf 2 array elemente geschrieben wird ... damit dies geschehen kann muss die prozedur mit entsprechenden parametern aufgerufen werden ... wo dieser aufruf geschehen sollte, wird dir vermutlich klar, wenn du mal schaust an welcher stelle im programm alle werte für einen eintrag in die bestenliste bekannt sind ...
in iDurchgang1Spieler(...) sind alle parameter bekannt ... (nachdem du den bezug zur bestenliste zu einem int** gemacht hast)

Code:
void bestenListenEintragSchreiben(int spielerNr, int punktzahl, int **bestenListe)
{
    bestenListe[spielerNr][0]=spielerNr;
    bestenListe[spielerNr][1]=punktzahl;
}
int iDurchgang1Spieler(int iSpielernummer, int **iBezugBestenliste){
    
    int iDurchgangSpieler = 0;
    for (int i = 0; i < 10;i++){    //Alle Würfe EINES Spielers.
        iDurchgangSpieler = iDurchgangSpieler + iWurf();
    }
    bestenListenEintragSchreiben(iSpielernummer,iDurchgangSpieler,iBezugBestenliste);
    return iDurchgangSpieler;    //Übergibt alle Würfe eines Spielers.
}
 
Als ich damals (vor einem Jahr) mit Programmieren(auch in C++) angefangen habe, hatte ich auch solche Zweifel(v.a wegen Mathe). Mittlerweile bin ich allein durch das Programmieren ziemlich gut in Mathe geworden... Also ich glaube, das kommt dann automatisch von alleine.
 
Zurück
Oben