Globale Klassenvariable [Problem gelöst]



  • Hallo.

    OS: Win 10 Tech. Prev. Build 10041,
    IDE: Visual Studio 2013 Ultimate,
    keine veränderungen bei den Projekteinstellungen.

    Ich bin noch ein Neuling was programmieren angeht und ich bin gerade dabei ein Programm zu schreiben welches 13 Würfelobjekte erzeugt mit denen man dann auch würfeln kann (bis jetzt nur textbasiert).

    Ich möchte die Deklarationen und Definitionen der Würfelobjekte in einer/mehreren externen Datei/en machen, bekomme aber wie ich es auch versuche entweder den Error LNK2005 ("class Wuerfel * pWuerfel1" (?pWuerfel12@@3PAVWuerfel@@A) ist bereits in main.obj definiert.) oder meine Objekte pWuerfel1 haben den Fehler: Diese Deklaration hat keine Speicherklasse oder keinen Typspezifizierer.

    Ich werd langsahm wahnsinnig 😞

    Danke schon im vorab für jegliche Hilfe 🙂


  • Mod

    Code?



  • // wuerfelgen.hpp
    #ifndef _WUERFELGEN_
    #define _WUERFELGEN_
    
    #include "wuerfel.hpp"
    
    Wuerfel * pWuerfel1;
    
    #endif
    
    // wuerfelgen.cpp
    #include "wuerfelgen.hpp"
    
    pWuerfel1 = new Wuerfel;
    

  • Mod

    Wuerfel * pWuerfel1;
    

    Da hast du eine Variable definiert. Da ich mal annehme, dass der Header an mehreren Stellen eingebunden wird (wie bei Headern normal), wird die Variable daher mehrmals im Programm definiert. Aber man darf jedes Objekt nur einmal definieren (außer bei ein paar ganz bestimmten Ausnahmen).
    http://en.wikipedia.org/wiki/One_Definition_Rule

    Es hat zwar nicht direkt mit deinem Problem zu tun, aber dein Programmierstil ist bereits jetzt ziemlich mies. Vermutlich aufgrund schlechter Vorbilder. Du solltest frühzeitig Gegenmaßnahmen ergreifen, bevor du dir Schlechtes angewöhnst:

    • Ungarische Notation (Typkürzel bei Variablennamen) sind bei Sprachen, in denen nutzerdefinierte Typen eine große Rolle spielen, also z.B. C++, eher verpönt. Nutz einfach sprechende Bezeichner. Der genaue Typ einer Variable sollte eigentlich nie eine Rolle spielen. Und falls er doch mal nötig ist, können viele Entwicklungswerkzeuge diesen bei Bedarf ermitteln.
    • Globale Variablen sind fast immer ein No-Go.
    • Explizites new in C++ geht gar nicht. C++ ist nicht Java. Wieso überhaupt new? Und wenn schon new, dann hinter einer Ressourcenverwaltungsklasse weggekapselt.
    • Noch einmal das gleiche in anderen Worten: "Normale" Zeiger sollten niemals besitzend sein, sondern wirklich nur Verweise. Für besitzende Zeiger gibt es Extraklassen. Weiterhin ist unklar, wieso hier überhaupt ein Zeiger benutzt wird, noch dazu global.
    • Bezeichner, die mit einem Unterstrich gefolgt von einem Großbuchstaben beginnen sind reserviert und sollten nicht benutzt werden. Ebenso Bezeichner, die doppelte Unterstriche enthalten und Bezeichner, die auch bloß mit einem Unterstrich beginnen (egal was folgt). Es gibt zwar unterschiedliche Abstufungen, wo genau diese nicht erlaubt sind*, aber eine gute Faustregel ist, generell nie Unterstriche am Anfang oder doppelte Unterstriche zu nutzen.

    PS: Ich sollte vielleicht noch anfügen, dass man mittels extern Variablen deklarieren kann (also keine Definition). Auch wenn das hier eine Lösung wäre, ist das aus den oben erklärten Gründen ganz sicher keine empfehlenswerte Lösung. Die wahre Lösung wäre voraussichtlich eine, die gar keiner globalen Variablen bedarf. Diese Lösung können wir aber nicht ermitteln, weil wir nicht deine Problemstellung kennen.

    *: Hier benutzt du _WUERFELGEN_ im globalen Scope, wo Bezeichner dieser Art tatsächlich reserviert sind.



  • SeppJ schrieb:

    • Globale Variablen sind fast immer ein No-Go.

    Wieso das?

    SeppJ schrieb:

    • Explizites new in C++ geht gar nicht. C++ ist nicht Java. Wieso überhaupt new? Und wenn schon new, dann hinter einer Ressourcenverwaltungsklasse weggekapselt.

    Mit new kann man doch Speicher reservieren soweit ich weis. Also reserviere ich so den nötigen Speicher für das neue Objekt. Im Orginal hab ich 13 Würfel.
    Was heißt "Explizites new"?
    Was ist eine Ressourcenverwaltungsklasse?

    SeppJ schrieb:

    • Ungarische Notation (Typkürzel bei Variablennamen) sind bei Sprachen, in denen nutzerdefinierte Typen eine große Rolle spielen, also z.B. C++, eher verpönt.

    Das wusste ich nicht. Hab das halt mal so in nem YouTube Video gesehen und übernommen, genauso das mit dem new.

    SeppJ schrieb:

    • Bezeichner, die mit einem Unterstrich gefolgt von einem Großbuchstaben beginnen sind reserviert und sollten nicht benutzt werden. Ebenso Bezeichner, die doppelte Unterstriche enthalten und Bezeichner, die auch bloß mit einem Unterstrich beginnen (egal was folgt). Es gibt zwar unterschiedliche Abstufungen, wo genau diese nicht erlaubt sind*, aber eine gute Faustregel ist, generell nie Unterstriche am Anfang oder doppelte Unterstriche zu nutzen.

    Werd ich mir merken. 🙂

    SeppJ schrieb:

    Die wahre Lösung wäre voraussichtlich eine, die gar keiner globalen Variablen bedarf.

    Es ist mir eigentlich grad nicht so wichtig dass es Global ist. Ich möchte nur, dass die Deklaration und Definition nicht in meiner main.cpp sind.

    Oh je... ich glaube man merkt, dass ich ein Anfänger bin. 😃


  • Mod

    multipilz5 schrieb:

    SeppJ schrieb:

    • Globale Variablen sind fast immer ein No-Go.

    Wieso das?

    Weil dann nicht mehr nachvollzogen werden kann, wer wann wo warum Änderungen vornehmen kann oder sonstwie zugreift. Damit wird eventuelle Fehlersuche die Hölle.

    SeppJ schrieb:

    • Explizites new in C++ geht gar nicht. C++ ist nicht Java. Wieso überhaupt new? Und wenn schon new, dann hinter einer Ressourcenverwaltungsklasse weggekapselt.

    Mit new kann man doch Speicher reservieren soweit ich weis. Also reserviere ich so den nötigen Speicher für das neue Objekt. Im Orginal hab ich 13 Würfel.

    Warum nicht

    Wuerfel wuerfel;
    

    ? C++ ist nicht Java. Der Normalfall ist, dass man nicht new benutzt.

    Was heißt "Explizites new"?

    Ein new, das wirklich sichtbar im Anwendungscode steht. Kommt in C++ praktisch nie vor. Ist ein Alarmzeichen dafür, dass jemand versucht, Java in C++ zu machen.

    Was ist eine Ressourcenverwaltungsklasse?

    Eine Klasse, deren Objekte eine Ressource verwalten. In der Regel beinhaltet dies, dass sie bei Beginn ihrer Lebenszeit die Kontrolle über eine Ressource erhalten und diese Ressource bei Ende ihrer Lebenszeit automatisch freigeben. So kann es nie zu Ressourcenleaks kommen, obwohl (oder gerade weil) sich der Programmierer um gar nichts kümmern muss. RAII.

    SeppJ schrieb:

    • Ungarische Notation (Typkürzel bei Variablennamen) sind bei Sprachen, in denen nutzerdefinierte Typen eine große Rolle spielen, also z.B. C++, eher verpönt.

    Das wusste ich nicht. Hab das halt mal so in nem YouTube Video gesehen und übernommen, genauso das mit dem new.

    Die Expertise von Youtubevideos ist, genauso wie die von irgendwelchen Tutorials die wer weiß wer ins Netz gestellt haben könnte, suspekt.

    Es ist mir eigentlich grad nicht so wichtig dass es Global ist. Ich möchte nur, dass die Deklaration und Definition nicht in meiner main.cpp sind.

    Das sollte dir aber wichtig sein.



  • SeppJ schrieb:

    multipilz5 schrieb:

    Es ist mir eigentlich grad nicht so wichtig dass es Global ist. Ich möchte nur, dass die Deklaration und Definition nicht in meiner main.cpp sind.

    Das sollte dir aber wichtig sein.

    Damit meinte ich nicht, dass dass es mir egal ist ob global oder nicht. Ich wollte damit nur ausdrücken, dass ich nicht alles in eine Datei packen möchte(hab ich wo möglich etwas unklar ausgedrückt).

    Also, ich habe jetzt gelernt:

    • Niemals globale Variablen benutzen.
    • Ungarische Notation ist verpönt.
    • new nur bei Ressourcenverwalungsklassen benutzen.

    Ich glaube ich weis jetzt auch wie ich meine Frage besser Formuliere:
    Ich mache ein Programm in dem ich 13 Würfel habe und mit ihnen gleichzeitig würfeln kann.

    Was habe ich schon?:
    Ich habe die Funktion zum Würfeln, also zum eine Zufällige Zahl von 1 bis 6 zu generieren zum laufen gebracht.

    Was ist mein Problem?:
    Ich weis nicht so ganz wie ich meine 13 Würfel "elegant" Deklarieren soll.

    Und danke für die tollen Tipps die ich bekommen hab 🙂 .



  • "Würfel" doch einfach 13 mal. Was du mit den generierten Zahlen machst, hängt natürlich auch davon ab, was du vorhast. Beispielsweise könntest du die Zahlen speichern, das ginge in etwa so:

    std::vector<unsigned int> numbers;
    for(int i = 0; i < 13; ++i)
    {
        numbers.push_back(roll_dice());
    }
    // weitere Verarbeitung
    

    Eventuell möchtest du die Zahlen ja gar nicht speichern. Du kannst sie ja auch gleich verarbeiten, z.B. schrittweise die Summe bilden.



  • "Würfel" doch einfach 13 mal. Was du mit den generierten Zahlen machst, hängt natürlich auch davon ab, was du vorhast. Beispielsweise könntest du die Zahlen speichern, das ginge in etwa so:

    std::vector<unsigned int> numbers;
    for(int i = 0; i < 13; ++i)
    {
        numbers.push_back(roll_dice());
    }
    // weitere Verarbeitung
    

    Eventuell möchtest du die Zahlen ja gar nicht speichern. Du kannst sie ja auch gleich verarbeiten, z.B. schrittweise die Summe bilden.



  • Hyde++ schrieb:

    std::vector<unsigned int> numbers;
    for(int i = 0; i < 13; ++i)
    {
        numbers.push_back(roll_dice());
    }
    

    Also ich weis nicht was .push_back(roll_dice()); macht.
    (Ich bin aber natürlich dankbar wenn mir das jemand erklärt 😃 )

    Hyde++ schrieb:

    "Würfel" doch einfach 13 mal.

    Danke!!! Danke! Danke! Danke!
    😃 😃 😃

    Jetzt müsste es klappen. Mal sehen. 🙂



  • Danke Leute es funktioniert.

    Ich habe jetzt in der Klasse Wuerfel ein int array gemacht in dem abgespeichert wird was "gewürfelt" wurde.

    Das volgende ist für die, die es interessiert, wie mein ganzes Programm jetzt aussieht 😃 .

    // wuerfel.hpp
    #ifndef WUERFEL
    #define WUERFEL
    
    class Wuerfel
    {
    public:
    	void würfeln();
    	void reset();
    	void getSeite() const;
    private:
    	int seite[13];
    };
    
    #endif
    
    // wuerfel.cpp
    #include <Windows.h>
    #include <time.h>
    #include <iostream>
    using namespace std;
    
    #include "wuerfel.hpp"
    
    void Wuerfel::würfeln()
    {
    	for (int i = 0; i < 13; i++)
    	{
    		srand(time(0));
    
    		seite[i] = 1 + (rand() % 6);
    
    		if (seite[i] == 6)
    			seite[i] = 5;
    
    		Sleep(1000);
    	}
    }
    
    void Wuerfel::reset()
    {
    	for (int i = 0; i < 13; i++)
    	{
    		seite[i] = 0;
    	}
    }
    
    void Wuerfel::getSeite() const
    {
    	for (int i = 0; i < 13; i++)
    	{
    		cout << endl
    			<< "Wuerfel " << i + 1 << "\t: " << seite[i];
    	}
    }
    
    // main.cpp
    #include <iostream>
    using namespace std;
    
    #include "wuerfel.hpp"
    
    int main()
    {
    	Wuerfel a;
    	a.reset();
    	a.würfeln();
    	a.getSeite();
    
    	cin.get();
    	return 0;
    }
    

  • Mod

    Ein paar Kommentare, was man besser machen könnte/sollte:

    • Klare Zuweisung von Aufgaben für eine Klasse. Einen Würfel verbindet wohl niemand mit einem Objekt, das 13 Würfelwürfe enthält. Das hat da nichts zu suchen.
    • Gleiches gilt für Funktionen. Eine Funktion, die Wuerfel::getSeite heißt, verbindet wohl niemand damit, dass sie irgendetwas ausgibt. Ausgabe sollte mit Programmlogik nichts zu tun haben. Besser wäre wohl eine Funktion, die einen Wert zurück gibt, mit dem der Aufrufer dann machen kann, was er für richtig hält (was dann auch gerne eine Ausgabe des Wertes sein kann)
    • Deine Würfelfunktion sieht total falsch aus. Das sleep ist anscheinend dazu da, das dort falsch gesetzte srand auszubügeln. Die if-Abfrage verstehe ich gar nicht.


  • multipilz5 schrieb:

    Also ich weis nicht was .push_back(roll_dice()); macht.
    (Ich bin aber natürlich dankbar wenn mir das jemand erklärt 😃 )

    Das war nur ein Beispiel, in dem deine Funktion, die Zahlen zwischen 1 und 6 erzeugt, "roll_dice" heißt.
    push_back hängt ein Element (hier ein unsigend int) hinten an den vector an.

    Falls du vector nicht kennst: gutes Buch besorgen, oder je nach dem wie fit du bist hier nachlesen:
    http://en.cppreference.com/w/cpp/container/vector

    In deinem Code ist die direkte Verwendung der 13 auch nicht so schön. Was, wenn daraus mal 15 werden? Dann musst du das an mehreren Stellen ändern. Besser ist es, eine Konstante zu verwenden, in der du die Anzahl speicherst. Dann beziehst du dich überall, wo du 13 schreibst, stattdessen auf die Konstante und musst bei Bedarf nur eine Zeile ändern.
    Was du machst, ist unter dem Begriff "Magic Number" bekannt. Eine Zahl, die direkt im Code auftaucht (Hart gecoded). So etwas wie =0 oder + 1 ist ok, so etwas braucht man ab und an, aber 13 fällt definitv in diese Kategorie. Bei einem komplexeren fremden Code ist das sehr irritierend, da man sich die Frage stellt, warum ausgerechnet hier 13 steht und nicht 42 oder so.

    Mischen von Englisch und Deutsch ist auch nicht gerade schön. Bei getSeite rollen sich mir die Fußnägel hoch. Gewöhn dir lieber an, auf Englisch zu programmieren.



  • SeppJ schrieb:

    • Deine Würfelfunktion sieht total falsch aus. Das sleep ist anscheinend dazu da, das dort falsch gesetzte srand auszubügeln. Die if-Abfrage verstehe ich gar nicht.

    Ich habe im Internet nichts anderes zum Thema zufällige Zahlen generieren gefunden. Wo gehört srand() denn sonst hin, wenn es überhaupt da rein gehört?
    Die Auflösung der if-Abfrage: Am Ende möchte ich das Spiel Marswürfel umsetzen. Bei diesem Spiel muss man mit 13 Würfeln Punkte sammeln. Auf zwei der sechs Seiten ist das selbe Symbol.

    SeppJ schrieb:

    • Klare Zuweisung von Aufgaben für eine Klasse. Einen Würfel verbindet wohl niemand mit einem Objekt, das 13 Würfelwürfe enthält. Das hat da nichts zu suchen.

    Sollte ich dann lieber eine eigene Klasse wie vielleicht "Handle" oder so machen in die ich dann so Funktionen wie "wuerfeln()" rein mache?

    • Gleiches gilt für Funktionen. Eine Funktion, die Wuerfel::getSeite heißt, verbindet wohl niemand damit, dass sie irgendetwas ausgibt. Ausgabe sollte mit Programmlogik nichts zu tun haben. Besser wäre wohl eine Funktion, die einen Wert zurück gibt, mit dem der Aufrufer dann machen kann, was er für richtig hält (was dann auch gerne eine Ausgabe des Wertes sein kann)

    Das habe ich gerade nur deswegen so gemacht um kurz auszuprobieren, ob das jetzt gut funktioniert. Danke trotzdem für den Tipp. Ich hätte da aber noch eine Frage. Kann man auch ein ganzes array als Rückgabewert benutzen und kann ich dann mit [] oder so sagen welches Element des Arrays ich haben möchte oder wie könnte man das denn in meinem Fall geschickt machen? Einfach

    int[] Wuerfel::getSeite() const
    {
        return seite;
    }
    

    ?
    Geht das so?
    Oder macht man das anderst? 😕

    Falls du vector nicht kennst: gutes Buch besorgen
    

    Kenn ich tatsächlich noch nicht. Ich komme auf die Preferenzen noch nicht so klar. Aber bevor ich mir ein Buch über Vector besorge sollte ich vielleicht erst mein c++ Grundlagenbuch durchlesen und komplett verstanden haben 😃 .

    Hyde++ schrieb:

    Was du machst, ist unter dem Begriff "Magic Number" bekannt. Eine Zahl, die direkt im Code auftaucht (Hart gecoded). So etwas wie =0 oder + 1 ist ok, so etwas braucht man ab und an, aber 13 fällt definitv in diese Kategorie. Bei einem komplexeren fremden Code ist das sehr irritierend, da man sich die Frage stellt, warum ausgerechnet hier 13 steht und nicht 42 oder so.

    Da hast du natürlich recht. Muss ich mir noch abgewöhnen.

    Hyde++ schrieb:

    Gewöhn dir lieber an, auf Englisch zu programmieren.

    Okay, werd ich machen 😉 .

    Verzeiht bitte, dass mein code "stilistischer Müll" ist 😃 . Wie gesagt ich bin noch Anfänger und das ist die erste Situation in dem ich mich mit leuten über meinen code unterhalte die Ahnung von dem Thema haben. Aber ich bemühe mich eure Ratschläge zu beachten. Ich will ja schließlich hauptberuflich Programmierer werden, und da sollte man glaub gut verständlichen code von sich geben 😃 .
    Deswegen bin ich immer froh wenn ich hier was dazulernen kann 🙂 .



  • Wenn du es mit der Programmiererei ernst meinst, dann musst du ungedingt drauf achten, nicht irgendwelche grottigen Quellen zu benutzen. Vergiss alle Tutorials, Youtube-Videos und ähnliches. Die sind schrott. Es gibt leider auch einige schlechte Bücher, da muss man auch aufpassen. Hier im Forum gibts einen Eintrag mit Buchempfehlungen. Der ist leider etwas unübersichtlich, kannst ja trotzdem mal reinschauen.
    https://www.c-plusplus.net/forum/251551
    Kurzfassung:
    Oft wird der Primer empfohlen, der scheint gut für Einsteiger zu sein. Ich habe mit "C++ - Professionell Programmieren und Anwenden" (oder so) von Breymann angefangen, das hat mir gut gefallen. Danach habe ich mir noch "Die C++ Programmiersprache" von Stroustroup geholt. Das zweite würde ich aber erst lesen, wenn du schon einiges verstanden hast. Das ist auch so hart. Aber gut. Das öffnet die Augen 🙂
    Wenn du das hast, solltest du noch "Effective C++" von Scott Meyers lesen. Wenn du die anderen Bücher aufmerksam gelesen hast, solltest du einige Punkte ohnehin schon kennen.



  • @Hyde++ Danke für die Tipps. Das Erlenkötter Buch habe ich sogar schon in Besitz. Ich muss noch schauen welches Buch ich mir als nächstes kauf. 🙂



  • multipilz5 schrieb:

    Die Auflösung der if-Abfrage: Am Ende möchte ich das Spiel Marswürfel umsetzen. Bei diesem Spiel muss man mit 13 Würfeln Punkte sammeln. Auf zwei der sechs Seiten ist das selbe Symbol.

    Also eigentlich hast Du einen Würfel mit 5 Seiten, von denen eine doppelt so häufig fällt, wie die anderen.

    Das geht allerdings sehr viel besser in C++. Du kannst aus dem folgenden die ersten drei Funktionen einfach übernehmen.
    Selbst, wenn es erstmal kryptisch aussieht, gibt es IMHO keinen Grund, Dir das mit rand() selbst zusammenzudengeln.

    #include <iostream>
    #include <random>
    
    // Diese Funktion gibt irgendein Bitmuster zurueck (uniform random bit generator (URBG))
    // Uebernommen aus W. E. Browns "Random Number Generation is Not Simple!"
    // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3847.pdf
    std::default_random_engine& global_urbg() {
      static std::default_random_engine re;
      return re;
    }
    
    // Dies entspricht srand()
    void randomize() {
      static std::random_device rd;
      global_urbg().seed(rd());
    }
    
    // Ein Wuerfelwurf
    // Dieser Würfel hat 5 Seiten, von denen eine doppelt so haeufig
    // faellt wie die anderen
    int roll_dice() {
      static std::discrete_distribution<int> d{ 2, 1, 1, 1, 1 };
      return d(global_urbg());
    }
    
    int main() {
      // wir wollen "zufaelligen Zufall"
      randomize();
    
      // ein paarmal wuerfeln
      // und das ergebnis in einem Array merken
      // Ein Wuerfelwurf ist 0,1,2,3 oder 4, also nehmen wir ein int[5]
      // und erhoehen die Zahl an der gewuerfelten Position um eins.
      int results[5]={0};
      for(int i=0; i<10000; ++i){
        int ix = roll_dice();
        results[ix]++;
      }
    
      // der erste wert duerfte ungefaehr doppelt so hoch
      // sein, wie die anderen (die alle ca. gleichhoch sein sollten)
      for(auto i : results)
        std::cout << i << '\n';
    }
    

    Damit beantworte ich auch Deine Frage, wo denn srand() hinkommt (bzw. bei diesem code randomize() ): Einmal an den Anfang von main() oder gar nicht.

    Näher kommen wir dem Spiel, wenn wir die Würfelseiten - die bisher die schnöden Zahlen 0-4 sind - noch benamsen™:

    #include <iostream>
    #include <random>
    
    // Diese Funktion gibt irgendein Bitmuster zurueck (uniform random bit generator (URBG))
    // Uebernommen aus W. E. Browns "Random Number Generation is Not Simple!"
    // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3847.pdf
    std::default_random_engine& global_urbg() {
      static std::default_random_engine re;
      return re;
    }
    
    // Dies entspricht srand()
    void randomize() {
      static std::random_device rd;
      global_urbg().seed(rd());
    }
    
    // Moegliche Wuerfelergebnisse
    enum face { death_ray, tank, human, cow, chicken };
    
    // Benamsung der Ergebnisse
    const char* to_string(face f) {
      static const char* names[] = { "death ray", "tank", "human", "cow", "chicken" };
      return names[f];
    }
    
    // wuerfeln
    // diesmal keine Zahl als Ergebnis, sondern eine der
    // verschiedenen Wuerfelseiten
    face roll_dice() {
      static std::discrete_distribution<int> d{ 2, 1, 1, 1, 1 };
      return static_cast<face>(d(global_urbg()));
    }
    
    int main() {
      // wir wollen "zufaelligen Zufall"
      randomize();
    
      // ein paarmal wuerfeln und das ergebnis drucken
      for(int i=0; i<42; ++i)
        std::cout << to_string(roll_dice()) << std::endl;
    }
    

Log in to reply