Hoppa till innehållet

Programmera spel i C++ för nybörjare/Sprites och spelpjäser 8

Från Wikibooks


(Genomgången förutsätter att du har en fungerande installation av Microsoft Visual C++ 2010 Express. SFML 1.6 behövs egentligen inte på din dator bara du kommenterar bort alla include-satser som det står SFML i.)

Anta att vi har ett behov av att låta en klass ärva från mer än en klass. Hur går det till?

Man tar helt enkelt och skriver till den andra klassen efter ett komma. Anta att vi har en grundklass som heter fordon. Ur den har vi skapat klassen lastbil och klassen pansarvagn, och ur de två vill vi skapa klassen pansarbil:

class pansarbil : public lastbil, public stridsvagn
{
//kod här
}

Vi skulle kunna ange fler klasser som vi ärver från, men observera at vi inte ärver från grundklassen fordon, bara från de klasser som så att säga finns rakt ovanför i hierarkin.

Multipelt arv är inget man stöter på särskilt ofta, det räknas som en avancerad teknik och passar bara i vissa situationer, men det är bra att veta hur det går till att ärva från flera moderklasser. Vill man göra det lätt för sig är det enklare att utforma en klass steg för steg och låta arvet utvecklas från klass till klass:

Fordon – transportfordon – bepansrade transportfordon – bepansrade transportfordon med kanon – pansarbil.

Men det man vinner på att inte tänka så mycket själv förlorar man i större mängder kod.

Om vi tittar litet djupare på fordonet, så vad kan man säga om det? Vad har ett fordon för ”minsta gemensamma nämnare”? En x,y plats att börja på, en hastighet, en viss mängd bensin (vi struntar i cyklar och fordon utan motorer), och ett visst antal poäng som fordonet tål innan det slås ut, vi kallar det för pansar.

Fordonsklassen

[redigera]
Class fordon
{
public:
//Konstruktordeklaration, definition utanför klassdeklarationen
fordon(int  hastighet, double spelare_x, double spelare_y, int bensin, int pansar);//startvärden

//Destruktion
~fordon(){};

int  hastighet; //Hur snabb är den
double spelare_x; // var är den i sidled i programmet
double spelare_y; //var är den i höjdled i programmet
int bensin; //Hur långt kan den köra
int pansar; //Hur mycket tål den
//sf::Sprite sprite; //Används senare i grafiskt läge
};

Vi behöver egentligen aldrig fundera över vad som händer när vårt fordon skapas, eftersom vi troligtvis aldrig kommer att skapa ett ”fordon”, bara en underklass av fordon. Men för sakens skull ser en funktion ut så här:

//Konstruktionsdeklaration, fordon--------------------------------------------------
fordon::fordon (int  ut_hastighet, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_pansar) 
{
      hastighet=ut_hastighet;
      spelare_x=ut_spelare_x;
      spelare_y=ut_spelare_y;
      bensin = ut_bensin;
      pansar = ut_pansar;
//Också vill vi se att det verkligen skapas ett fordon när underklasserna skapas
std::cout << "Ett fordon rullar ut från fabriken!" << std::endl;
}

Arv lastbil

[redigera]

Sedan skall vi låta fordonet bilda en lastbil. Med en lastbil följer några nya värden. Vi vill främst veta ”lastkapaciteten”, dvs hur mycket kan lastbilen frakta. I just det här fallet väljer jag att tänka mig att lastbilen fraktar soldater, och med en Int/heltal vet jag hur många som får plats i lastbilen. Det finns ju inte delar av soldater, i alla fall inte innan de kommer till fronten. Att det finns en förare antar vi alltid. Dessutom vill jag så klart veta hur många som redan finns i lastbilen, och det kallar vi passagerare, även det en int.

class lastbil : public fordon
{
public:
Int lastkapacitet; //Avgör antalet människor som får plats
Int passagerare; // Avgör hur många som finns i lastbilen förutom chauffören.
lastbil(int  ut_hastighet, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_pansar)
:fordon(ut_hastighet, ut_spelare_x, ut_spelare_y, ut_bensin, ut_pansar)
{
lastkapacitet=11;
passagerare = 0;
}

//Destruktion
~lastbil(){};

//Funktioner
void ilastning()
 {
 std::cout << "Lastar i ” <<  lastkapacitet << ” soldater!” << std::endl;
 }

Void urlastning()
 {
 std::cout << "Lastar ur ” <<  lastkapacitet << ” soldater!” << std::endl;
 }


};

Arv pansarvagnen

[redigera]

Så har vi den tredje klassen, pansarvagnen. Den har ingen lastkapacitet, men istället har den tung beväpning. Anta att den har en kanon den kan skjuta med och 100 skott:

class stridsvagn : public fordon
{
public:
Int ammunition; // Avgör hur många skott som finns i stridsvagnen.
stridsvagn(int  ut_hastighet, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_pansar)
:fordon(ut_hastighet, ut_spelare_x, ut_spelare_y, ut_bensin, ut_pansar)
{
ammunition=100;
}

//Destruktion
~stridsvagn(){};

//Funktioner
void skott()
 {
 std::cout << "Skott kommer!” << std::endl;
 }

};

Färdig kod, enkelt arv

[redigera]

För att kontrollera att koden fungerar prövar vi att skapa två stycken fordon, en lasbil och en pansarvagn som båda ärver från moderklassen fordon:

#include <iostream>
#include <SFML\System.hpp>
#include <SFML\Graphics.hpp>
#include <SFML\Window.hpp>


using namespace std;

//En moderklass för alla fordon i spelet
class fordon 
{
public:
//Konstruktordeklaration, definition utanför klassdeklarationen
fordon(int  hastighet, double spelare_x, double spelare_y, int bensin, int pansar);//startvärden

//Destruktion
~fordon(){};

int  hastighet; //Hur snabb är den
double spelare_x; // var är den i sidled i programmet
double spelare_y; //var är den i höjdled i programmet
int bensin; //Hur långt kan den köra
int pansar; //Hur mycket tål den
//sf::Sprite sprite; //Används senare i grafiskt läge
};


//Konstruktionsdeklaration, fordon--------------------------------------------------
fordon::fordon (int  ut_hastighet, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_pansar) 
{
     hastighet=ut_hastighet;
     spelare_x=ut_spelare_x;
     spelare_y=ut_spelare_y;
     bensin = ut_bensin;
     pansar = ut_pansar;
//Också vill vi se att det verkligen skapas ett fordon när underklasserna skapas
std::cout << "Ett fordon rullar ut från fabriken!" << std::endl;
}



//Skapa en klass som ärver fordon
class lastbil : public fordon 
{
public:
int lastkapacitet; //Avgör antalet människor som får plats
int passagerare; // Avgör hur många som finns i lastbilen förutom chauffören.
lastbil(int  ut_hastighet, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_pansar)
:fordon(ut_hastighet, ut_spelare_x, ut_spelare_y, ut_bensin, ut_pansar)
{
lastkapacitet=11;
passagerare = 0;
}

//Destruktion
~lastbil(){};

//Funktioner
void ilastning()
{
std::cout << "Lastar i " <<  lastkapacitet << " soldater!" << std::endl;
}

void urlastning()
{
std::cout << "Lastar ur " <<  lastkapacitet << " soldater!" << std::endl;
}

};



//Skapa en klass som ärver fordon
class stridsvagn : public fordon
{
public:
int ammunition; // Avgör hur många skott som finns i stridsvagnen.
stridsvagn(int  ut_hastighet, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_pansar):
fordon(ut_hastighet, ut_spelare_x, ut_spelare_y, ut_bensin, ut_pansar)
{
ammunition=100;
}

//Destruktion
~stridsvagn(){};

//Funktioner
void skott()
{
std::cout << "Skott kommer!" << std::endl;
}

};

/* 
//Skapa en klass som ärver bägge moderklasserna
class pansarbil : public lastbil, public stridsvagn
{
//Konstruktion
public:
pansarbil(); 
//Destruktion
~pansarbil(){};
};
*/

int main() 
   {  //Början av programkörningen

//Skapa först en stridsvagn
//stridsvagn(int ut_hastighet, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_pansar)
stridsvagn stridsvagn1(50, 100.0,100.0,100,100);
cout << "Pansarvagnen har " << stridsvagn1.ammunition << " skott" << endl ;

//Skapa sedan en lastbil
lastbil lastbil1(100, 200.0,200.0,200,10);
//Testa med ett funktionsanrop om den finns
lastbil1.ilastning();


return 0;
} //slut på programkörningen


Slutledning, vi har skapat en basklass som heter fordon, ur den skapar vi två nya klasser som två grenar i ett träd. Anta nu att vi skall låta dessa två grenar böja sig mot varandra och skapa en tredje klass som ärver både stridsvagnens pansar och kanon, men också lastbilens möjlighet att lasta in människor. Möt pansarbilen.

Multipelt arv, pansarbil

[redigera]
class pansarbil : public lastbil, public stridsvagn
{
//Konstruktion
Pansarbil();
//Destruktion
~pansarbil(){};
};

”Ja’ ba’ va!?” kanske du säger när du ser klassen, men den är logisk. Allt du vill ha i form av funktioner och variabler finns redan i de två moderklasserna. Allt ok? Inget mer att fundera över? FEL!

Om du försöker att kompilera den här koden för en pansarbil kommer du att få flera röda streck och koden går inte igenom kompilatorn. Varför? Allt ser ju ut att vara kopplat som det skall? Problemet som uppstår är att pansarbilen ärver från två olika moderklasser (pansarvagn och lastbil) som bägge har samma moderklass (fordon). Det gör kompilatorn rätt förvirrad. Koden kallas för "diamond of doom" och liknande pga. sin diamantform om man ritar upp strukturen i ett träd.

Hur "Diamantproblemet" uppstår.
  • A = klassen fordon
  • B = lastbil
  • C = pansarvagn
  • D = pansarbil

Om en funktion ärvs från fordon till lastbil och pansarvagn, ändras med en override men behåller namnet, kommer kompilatorn att drabbas av problem när pansarbilen vill använda samma, ärvda funktion. Vilken variant av funktionen skall den använda? Alla har samma namn men hanteras olika. Som du ser har diagrammet formen av en diamant, därav sitt namn "diamond of doom".

Det finns flera sätt att komma runt problemet, men för att över huvud taget kunna kompilera koden måste B och C ange sina arv från A som "virtuella" så att D verkar ärva direkt från A.

Virtuellt arv

[redigera]

Om du provkör kod och får felkoden

error C2385: ambiguous access of 'fordon'

eller något liknande ambiguous innebär det troligtvis att det kommer arv av samma sak från två olika håll och kompilatorn inte vet vad den skall välja. Anta att du ärvde pansarbil från två olika pansarvagnsklasser och båda har funktionen "skjut", men den ena klassen har gjort en override, ungefär som tigertanken gjorde i föregående exempel. Vilket skott skall du skjuta då?

Ett sätt att undvika detta är att deklarera arven som moderklasserna får som virtuella. Istället för att skriva:

class lastbil : public fordon

skriver du

class lastbil : public virtual fordon

och sedan gör du likadant på pansarvagnen. Du kommer fortfarande att kunna skapa kopior av klasserna, men när pansarbilsklassen skall kompileras kommer den att förstå i vilken ordning de olika klasserna och deras funktioner skall kompileras.

Din pansarbilsklass måste också deklareras med alla sina arv:

//Skapa en klass som ärver bägge moderklasserna
class pansarbil : public lastbil, public stridsvagn
{
//Konstruktion
public:
pansarbil(int  ut_hastighet, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_pansar):
	  stridsvagn(ut_hastighet, ut_spelare_x, ut_spelare_y, ut_bensin, ut_pansar), 
		  lastbil(ut_hastighet, ut_spelare_x, ut_spelare_y, ut_bensin, ut_pansar), 
		  fordon(ut_hastighet, ut_spelare_x, ut_spelare_y, ut_bensin, ut_pansar){};

//Destruktion
~pansarbil(){};
};

och sedan, i main, kan du skapa en pansarbil och verkligen kontrollera att den fått med sig funktionerna från både pansarvagnen och lastbilen:

 pansarbil pansarbil1(100, 300.0, 300.0 ,200, 80);
 cout << "Kollar ärvda funktioner" << endl;
 cout << "Vi har " << pansarbil1.ammunition <<" skott!" << endl;
 pansarbil1.skott();
 pansarbil1.ilastning();

Fullständig kod

[redigera]
#include <iostream>
#include <SFML\System.hpp>
#include <SFML\Graphics.hpp>
#include <SFML\Window.hpp>


using namespace std;

//En moderklass för alla fordon i spelet
class fordon 
{
public:
//Konstruktordeklaration, definition utanför klassdeklarationen
fordon(int  hastighet, double spelare_x, double spelare_y, int bensin, int pansar);//startvärden

//Destruktion
~fordon(){};

int  hastighet; //Hur snabb är den
double spelare_x; // var är den i sidled i programmet
double spelare_y; //var är den i höjdled i programmet
int bensin; //Hur långt kan den köra
int pansar; //Hur mycket tål den
//sf::Sprite sprite; //Används senare i grafiskt läge
};


//Konstruktionsdeklaration, fordon--------------------------------------------------
fordon::fordon (int  ut_hastighet, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_pansar) 
{
     hastighet=ut_hastighet;
     spelare_x=ut_spelare_x;
     spelare_y=ut_spelare_y;
     bensin = ut_bensin;
     pansar = ut_pansar;
//Också vill vi se att det verkligen skapas ett fordon när underklasserna skapas
std::cout << "Ett fordon rullar ut från fabriken!" << std::endl;
}



//Skapa en klass som ärver fordon, observera tilläget "virtual"
class lastbil : public virtual fordon
{
public:
int lastkapacitet; //Avgör antalet människor som får plats
int passagerare; // Avgör hur många som finns i lastbilen förutom chauffören.
lastbil(int  ut_hastighet, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_pansar)
:fordon(ut_hastighet, ut_spelare_x, ut_spelare_y, ut_bensin, ut_pansar)
{
lastkapacitet=11;
passagerare = 0;
}

//Destruktion
~lastbil(){};

//Funktioner
void ilastning()
{
std::cout << "Lastar i " <<  lastkapacitet << " soldater!" << std::endl;
}

void urlastning()
{
std::cout << "Lastar ur " <<  lastkapacitet << " soldater!" << std::endl;
}

};



//Skapa en klass som ärver fordon, observera tilläget "virtual"
class stridsvagn : public virtual fordon
{
public:
int ammunition; // Avgör hur många skott som finns i stridsvagnen.
stridsvagn(int  ut_hastighet, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_pansar):
fordon(ut_hastighet, ut_spelare_x, ut_spelare_y, ut_bensin, ut_pansar)
{
ammunition=100;
}

//Destruktion
~stridsvagn(){};

//Funktioner
void skott()
{
std::cout << "Skott kommer!" << std::endl;
}

};

//Skapa en klass som ärver bägge moderklasserna
class pansarbil : public lastbil, public stridsvagn
{
//Konstruktion
public:
pansarbil(int  ut_hastighet, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_pansar):
	  stridsvagn(ut_hastighet, ut_spelare_x, ut_spelare_y, ut_bensin, ut_pansar), 
		  lastbil(ut_hastighet, ut_spelare_x, ut_spelare_y, ut_bensin, ut_pansar), 
		  fordon(ut_hastighet, ut_spelare_x, ut_spelare_y, ut_bensin, ut_pansar){};

//Destruktion
~pansarbil(){};
};
int main() 
   {  //Början av programkörningen

//Skapa först en stridsvagn
//stridsvagn(int ut_hastighet, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_pansar)
stridsvagn stridsvagn1(50, 100.0,100.0,100,100);
cout << "Pansarvagnen har " << stridsvagn1.ammunition << " skott" << endl ;

//Skapa sedan en lastbil
lastbil lastbil1(100, 200.0,200.0,200,10);
//Testa med ett funktionsanrop om den finns
lastbil1.ilastning();

 //Skapa en pansarbil som ärver från båda
 pansarbil pansarbil1(100, 300.0, 300.0 ,200, 80);
 cout << "Kollar ärvda funktioner" << endl;
 cout << "Vi har " << pansarbil1.ammunition <<" skott!" << endl;
 pansarbil1.skott();
 pansarbil1.ilastning();
return 0;
} //slut på programkörningen