Funktion mit unbekanntem Rückgabetyp



  • CEinheit* randomClass(CKrieger *a, CBogenschütze *b, CMagier *c)
    {
      // Random bla bla bla
      return b; // Impliziter Upcast von CBogenschütze* nach CEinheit*
    }
    

    Dann musst du im nachhinein halt mit CEinheit weiter arbeiten, was kein Problem sein sollte, sofern du deinen Code sauber strukturierst.

    Übrigens ist die ungarische Notation out. 😉



  • Hm, aber irgendwie ist das doch blöd wenn ich extra Unterklassen mache und die dann direkt wieder zu einer Basisklasse werden. Ich möchte im Prinzip ja nur den Gegner auslosen und mit dessen Eigenschaften dann gegen den Spieler kämpfen. 🙂

    Der Gegner soll ja nicht plötzlich alle Attacken der Klasse CEinheit haben, sondern nur die, die für ihn bestimmt sind.



  • Schlechte Implementierung deinerseits. 🙂
    CEinheit sollte virtuelle Funktionen vorgeben, über die alle Einheiten gleich zu "bedienen" sind. Die Implementierungen deiner konkreten Klassen können gerne abweichen.



  • Ich glaub ich hab jetzt verstanden was du meinst. In etwa so?

    // C++ für Spieleprogrammierer
    // Listing 7.14
    // Virtuelle Funktionen
    //
    #include <iostream>
    
    using namespace std;
    
    // Klassen
    //
    
    // Basisklasse
    //
    class CRaumschiff
    {
    	protected:
    
    		// Membervariablen
    		int m_Farbe;
    		int m_Energie;
    
    	public:
    
    		// Memberfunktionen
    		CRaumschiff ()
    		{
    			m_Farbe = 0;
    			m_Energie = 1000;
    			cout << "Neues Raumschiff erstellt" << endl;
    		}
    
    		virtual ~CRaumschiff () {cout << "Destruktor Basisklasse\n";}
    
    		virtual void Starten () {cout << "Raumschiff startet!\n";}
    		virtual void Landen  () {cout << "Raumschiff landet!\n";}
    
    };
    
    // Abgeleitete Klasse für den Minenleger
    //
    class CMinenleger : public CRaumschiff
    {
    	private:
    
    		// Membervariablen
    		int m_Minen;
    
    	public:
    
    		// Memberfunktionen
    		CMinenleger ()
    		{
    			m_Minen = 10;
    			cout << "Neuer Minenleger erstellt" << endl;
    		};
    
    		~CMinenleger () {cout << "Destruktor Minenleger\n";}
    
    		void Starten () {cout << "Minenleger startet!\n";}
    		void Landen  () {cout << "Minenleger landet!\n";}
    
    };
    
    // Hauptprogramm
    //
    int main ()
    {
    	// Variablen
    	//
    	CRaumschiff *Schiff; // Ein Raumschiff
    
    	Schiff = new CMinenleger; // Ein Minenleger soll es sein
    
    	// Minenleger führt einige Aktionen aus
    	Schiff->Starten ();
    	Schiff->Landen ();
    
    	delete Schiff;  // Raumschiff zerstören
    
    	return 0;
    }
    

    ...und das ist der wichtige Teil den du meintest, oder?
    Schiff = new CMinenleger;

    ...und wenn mein Bogenschütze jetzt als CEinheit rückgegeben wird, ist er immernoch ein Bogenschütze, oder nicht?



  • Kay schrieb:

    ...und das ist der wichtige Teil den du meintest, oder?
    Schiff = new CMinenleger;

    ...und wenn mein Bogenschütze jetzt als CEinheit rückgegeben wird, ist er immernoch ein Bogenschütze, oder nicht?

    Ganz genau. Du kannst eben nicht mehr auf die Details vom Bogenschützen zugreifen, aber das sollte auch nicht nötig sein. Die als virtual markierten Funktionen die in Bogenschützen überschrieben wurden, werden auch nachwievor vom Bogenschützen und nicht von CEinheit aufgerufen.



  • Hier nochmal der code, ein bisschen angepasst an mein Beispiel:

    ich erhalte folgende Fehlermeldung:

    error C2664: 'randomClass': Konvertierung des Parameters 1 von 'CEinheit *' in 'CRitter *' nicht möglich

    Siehst du vielleicht woran das liegt?

    // C++ für Spieleprogrammierer
    // Listing 7.14
    // Virtuelle Funktionen
    //
    #include <iostream>
    
    using namespace std;
    
    class CEinheit
    {
    	protected:
    
    		int m_Farbe;
    		int m_Energie;
    
    	public:
    
    		CEinheit ()
    		{
    			m_Farbe = 0;
    			m_Energie = 1000;
    			cout << "Neues Raumschiff erstellt" << endl;
    		}
    
    		virtual ~CEinheit () {cout << "Destruktor Basisklasse\n";}
    
    		virtual void Starten () {cout << "Raumschiff startet!\n";}
    		virtual void Landen  () {cout << "Raumschiff landet!\n";}
    
    };
    
    class CBogenschütze : public CEinheit
    {
    	private:
    
    		int m_Minen;
    
    	public:
    
    		CBogenschütze ()
    		{
    			m_Minen = 10;
    			cout << "Neuer Bogenschütze erstellt" << endl;
    		};
    
    		~CBogenschütze () {cout << "Destruktor Bogenschützer\n";}
    
    		void Starten () {cout << "Bogenschütze startet!\n";}
    		void Landen  () {cout << "Bogenschütze landet!\n";}
    
    };
    
    class CRitter : public CEinheit
    {
    	private:
    		int m_Minen;
    
    	public:
    
    		CRitter ()
    		{
    			m_Minen = 10;
    			cout << "Neuer Ritter erstellt" << endl;
    		};
    
    		~CRitter () {cout << "Destruktor Minenleger\n";}
    
    		void Starten () {cout << "Ritter startet!\n";}
    		void Landen  () {cout << "Ritter landet!\n";}
    
    };
    
    CEinheit* randomClass(CRitter *a, CBogenschütze *b)
    {
    
      return a; // Impliziter Upcast von CBogenschütze* nach CEinheit*
    }
    
    int main ()
    {
    
    	CEinheit *Gegner1;
    	CEinheit *Gegner2;
    
    	Gegner1 = new CRitter; 
    	Gegner2 = new CBogenschütze;
    
    	randomClass(Gegner1, Gegner2); 
    
    	system("pause");
    	return 0;
    }
    


  • randomClass als Parameter auch nur CEinheit* nehmen lassen.
    Denn auch wenn du virtuelle Funktionen hast, heißt das noch nicht dass du einfach ein CEinheit* übergeben kannst, wo ein CRitter* erwartet wird.



  • Ah es funktioniert, danke euch. 🙂



  • Ich habe wieder ein kleines Problem... und zwar möchte ich, dass der Spieler zwischen drei verschiedenen Klassen wählen kann.

    Basisklasse:
    CEinheit

    abgeleitete Klassen:
    CKrieger
    CMagier
    CBogenschütze

    Wie kriege ich es jetzt hin, dass ich einen Spieler habe, der zur Laufzeit seine Klasse aussucht und mit dieser dann das ganze Spiel über spielen kann? Ich hab an eine Klasse CSpieler gedacht, die über eine Funktion GetClass oder so ihre Klasse annimmt, allerdings weiß ich nicht was ich für einen Parameter nehmen soll. Wie würdet ihr das lösen?



  • class Klasse
    {
    };
    
    class Ritter : public Klasse
    {
    };
    
    int main ()
    {
        Klasse* k = new Ritter;
    }
    


  • Dann habe ich aber ja das Problem, dass k keine speziellen Funktionen von Ritter aufrufen kann, sondern nur die von Klasse. Der Ritter soll ja Memberfunktionen haben, also Attacken, die ihn von den anderen unterscheiden.



  • Kay schrieb:

    Dann habe ich aber ja das Problem, dass k keine speziellen Funktionen von Ritter aufrufen kann, sondern nur die von Klasse. Der Ritter soll ja Memberfunktionen haben, also Attacken, die ihn von den anderen unterscheiden.

    Das ist ein Designfehler von dir. Überleg wie man es besser machen könnte, mit virtuellen Funktionen.



  • So... Hab jetzt mal pure virtuelle Funktionen eingebaut. Jetzt werden die richtigen Funktionen aufgerufen, aber ich habe immernoch keinen Zugriff auf die Member von CRitter zum Beispiel. Wie soll ich das da machen?

    #include <iostream>
    
    using namespace std;
    
    class CEinheit // pure virtuelle Basisklasse
    {
    	protected:
    		int m_Lebenspunkte; // hat jeder Charakter
    
    	public:
    
    		CEinheit ()
    		{
    			m_Lebenspunkte = 0;
    
    		}
    
    		virtual ~CEinheit () {cout << "Destruktor Basisklasse\n";}
    
    		virtual void Angriff1  () = 0;
    		virtual void Angriff2  () = 0;
    		virtual void Angriff3  () = 0;
    
    };
    
    class CBogenschütze : public CEinheit
    {
    	private:
    		int m_Power; // Haben nur Bogenschützen
    
    	public:
    		CBogenschütze ()
    		{
    			m_Power = 60;
    
    		};
    
    		~CBogenschütze () {cout << "Destruktor Bogenschützer\n";}
    
    		void Angriff1  () {cout << "Zing!\n";}
    		void Angriff2  () {cout << "Zang!\n";}
    		void Angriff3  () {cout << "Zong!\n";}
    
    };
    
    class CRitter : public CEinheit
    {
    	private:
    		int m_Kraft; // Haben nur Ritter
    
    	public:
    		CRitter ()
    		{
    			m_Kraft = 40;
    			cout << "Neuer Ritter erstellt" << endl;
    		};
    
    		~CRitter () {cout << "Destruktor Ritter\n";}
    
    		void Angriff1  () {cout << "Boom!\n";}
    		void Angriff2  () {cout << "Bamm!\n";}
    		void Angriff3  () {cout << "Bang!\n";}
    
    };
    
    int main ()
    {
    	CRitter *pRitter = NULL;
    	pRitter = new CRitter;
    	pRitter->Angriff1(); // funzt super
    
    	CBogenschütze *pBogenschütze = NULL;
    	pBogenschütze = new CBogenschütze;
    	pBogenschütze->Angriff2(); // funzt super
    
    	CEinheit *pSpieler = NULL;
    	pSpieler = new CRitter; // Spieler soll ein Ritter sein
    	pSpieler->Angriff1(); // funzt, ruft Attacke von Ritter auf
    
    	system("pause");
    	return 0;
    }
    


  • Wunderbar! Einfach soweit abstrahieren dass bei der Verwendung einer Einheit-Instanz nicht mehr von Belang ist, was das jetzt konkret ist.
    Anstelle von Angriff1, Angriff2, use kannst du dir überlegen, der Einheit noch eine Methode "int anzahlAngriffe()" zu geben, und die Angriff-Methode nach "void Angriff( int nummer )" umzubenennen.

    Du könntest aber auch mal ganz konkret beschreiben, warum du jetzt unbedingt Zugriff auf einen Ritter brauchst. Das würde wahrscheinlich mehr helfen das Problem zu lösen.



  • Also:

    Ich möchte ein Spiel schreiben, bei dem der Spieler gegen einen Computer Gegner kämpft. Wenn er den Ritter wählt, hat er ja andere Attacken als ein Magier zum Beispiel, deswegen ist es so wichtig, dass er ein Ritter bleibt, aber das geht ja jetzt so mit den puren virtuellen Funktionen anscheinend.

    Das Problem ist nur die Membervariablen, wie kann ich es anstellen, dass der Ritter auf seine Membervariable m_Kraft zugreifen kann, sprich pSpieler->m_Kraft (davon abgesehen dass es so eh nicht geht weil es private ist).

    Diese Membervariablen sind deswegen wichtig, weil ein Bogenschütze Power und nicht Kraft hat, sonst würde ich die ja einfach in die Basisklasse packen. Oder würde man einfach hingehen und anstatt Mana, Power, Kraft, einfach eine Variable namens Ressource machen? Die kann ich ja dann in die Basisklasse packen.



  • Ich verstehe den Unterschied zwischen Power und Kraft nicht 😃
    http://www.dict.cc/englisch-deutsch/power.html
    Ich würde wohl ein universelles Wort nehmen, denn sogesehen hat am Ende jede Klasse irgendwie eine "Energie".
    Außerdem finde ich Attacken in die Klassen Klasse zu schreiben für wenig sinnvoll, dafür ist eine Spell oder Skill Klasse gedacht.



  • Kay schrieb:

    Diese Membervariablen sind deswegen wichtig, weil ein Bogenschütze Power und nicht Kraft hat, sonst würde ich die ja einfach in die Basisklasse packen. Oder würde man einfach hingehen und anstatt Mana, Power, Kraft, einfach eine Variable namens Ressource machen? Die kann ich ja dann in die Basisklasse packen.

    Es sollte egal sein, was du vor dir hast! Es sollte dich nicht interessieren, ob das jetzt ein Bogenschütze mit "Power" oder ein Ritter mit "Kraft" ist.
    Deine virtuellen Funktionen kümmern sich darum, was im Hintergrund passiert.
    Du hast die Funktion "Einheit::attack(Einheit* opponent)", z.B.
    Oder von mir aus kapsele die einzelnen Arten von Attacken noch mal in eigenen Klassen.
    Es liegt jetzt an dir zu entscheiden, was ein Angriff dann bewirkt. Ein Schlag mit dem Schert auf den Kopf des Gegners könnte nun einen bestimmten Schaden zufügen. Oder gleich den Kopf abtrennen, wenn du keine adäquate Verteidung oder Rüstung aufweisen kannst. Das Ganze ist natürlich ETWAS komplexer als ein simples cout.

    Also löse dich von der Vorstellung, auf ein Objekt speziell reagieren zu wollen, sondern gib deiner CEinheit ein Interface, das im Allgemeinen Sinne die einzelnen Interaktionen zulässt.



  • Ok, ich programmier mal ein bisschen weiter und poste dann nochmal das Ergebnis, danke euch. 🙂


Anmelden zum Antworten