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


(Genomgången förutsätter att du har en fungerande installation av Microsoft Visual C++ 2010 Express och SFML 1.6 på din dator.)

Konstanta medlemsfunktioner och statiska medlemsvariabler

redigera

Anta att vi har klassen stridsvagn:

 class stridsvagn 
{
public:
//Konstruktordeklaration, definition utanför klassdeklarationen
stridsvagn(int  hastighet, char typ, double spelare_x, double spelare_y);//startvärden
//Destruktion
~stridsvagn(){};

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
char typ;
sf::Sprite sprite;

};

//Konstruktionsdeklaration--------------------------------------------------
stridsvagn::stridsvagn (int  ut_hastighet, char ut_typ, double ut_spelare_x, double ut_spelare_y) 
{
 hastighet=ut_hastighet;
 typ=ut_typ;
 spelare_x=ut_spelare_x;
 spelare_y=ut_spelare_y;
 std::cout << "En stridsvagn är byggd!" << endl;
}

När man skapat sin klass och börjar att använda den finns det vissa saker man kan tänka på, bara för att vara extra säker.

Public och private (och protected)

redigera

Som nämnts tidigare är uppdelningen mellan public och private i en klass ett skydd för dig. Du skall inte kunna ändra saker av misstag. ”Det gör jag aldrig” kanske du säger, men faktum är att om du lagt ett projekt på hyllan en längre tid och sedan börjar med det igen är det lätt att glömma vad det var man hade för variabler i klassen som inte fick ändras i onödan. Grundregeln i klasser är att variablerna skall vara privata, medan funktionerna som kan ändra variablerna skall vara under public.

Protected

redigera

Protected är det som man som nybörjare i C++ världen har svårast att greppa. Protected är exakt samma sak som private i en klass som inte har någon form av arv vidare till nya klasser.

Ett problem, eller en fördel om man så vill, med klasser i C++ är att det som finns under private normalt sett inte ärvs vidare, bara det som står under public. Ibland vill man få med sig det som står under private också och för att komma runt det dilemmat har man infört varianten protected. En variabel eller funktion under protected är lika säker som en variabel som står som private i sin egen klass, men den variabeln eller funktionen kan ärvas vidare.

Här är ett litet exempel som inte kräver SFML. Vi för in en variabel som är public, protected och private. Sedan anropar vi samtliga vid starten av programmet. Som du ser (om du lägger in koden) fungerar det hur bra som helst.

#include "stdafx.h"
#include <iostream>
using namespace std;

	class testare
	{
	public:
	testare(int innummer); //Konstruktor
	int allmaentnummer; //En publik variabel
	void visa_siffrar(int siffra_att_visa) //En funktion
	{
	std::cout << "Siffran som skall visas =" << siffra_att_visa << endl;
	}


       protected:
       int skyddatnummer; //En protected/skyddad variabel

	private:
       int privatnummer; //En privat variabel
	};


	testare::testare(int utnummer)
	{
		//För att se skillnad på dem
	allmaentnummer = utnummer; //publikt nummer
	skyddatnummer = utnummer * 2; //protected blir dubbelt så stort
	privatnummer = utnummer * 4; //private blir 4 ggr större

	std::cout << "Allmaentnummer= " << allmaentnummer << endl; 
	std::cout << "Skyddatnummer= " << skyddatnummer << endl;
	std::cout << "Privatnummer= " << privatnummer << endl;

	visa_siffrar(allmaentnummer); //  Kommer åt publikt nummer
	visa_siffrar(skyddatnummer); // Kommer åt protected/skyddat nummer
	visa_siffrar(privatnummer); // Kommer åt private nummer
	}

     int main ()
     {
      testare(1); //Kontrollera om man kommer åt värdena
      return 0;
     };

Om vi sedan låter en annan klass ärva vår nyskapade klass kommer du att se att värdena ärvs, men man kan inte längre komma åt vare sig protected eller private längre. Du får ta bort // markeringarna för att se att det blir fel i koden just där:

#include "stdafx.h"
#include <iostream>
using namespace std;

	class testare
	{
	public:
	testare(int innummer); //Konstruktor
	int allmaentnummer; //En publik variabel
	void visa_siffra(int siffra_att_visa) //En funktion
	{
	std::cout << "Siffran som skall visas =" << siffra_att_visa << endl;
	}

	protected:
       int skyddatnummer; //En protected/skyddad variabel

	private:
       int privatnummer; //En privat variabel
	};

	testare::testare(int utnummer)
	{
	//För att se skillnad på dem
	allmaentnummer = utnummer; //publikt nummer
	skyddatnummer = utnummer * 2; //protected blir dubbelt så stort
	privatnummer = utnummer * 4; //private blir 4 ggr större

	std::cout << "Allmaentnummer= " << allmaentnummer << endl; 
	std::cout << "Skyddatnummer= " << skyddatnummer << endl;
	std::cout << "Privatnummer= " << privatnummer << endl;

	visa_siffra(allmaentnummer); // Kommer åt publikt nummer
	visa_siffra(skyddatnummer); // Kommer åt protected/skyddat nummer
	visa_siffra(privatnummer); // Kommer åt private nummer
	}

	//En ärvd klass
	class aervd_testare : public  testare
	{
	public:
	aervd_testare(int  ut_siffra): testare(ut_siffra){};
	};


       int main () 
       {
	testare testare1(1); //skapa testare 1, Skriver ut alla 3 så vi ser att de finns

	testare1.visa_siffra(testare1.allmaentnummer); // Kommer åt publikt nummer
	//testare1.visa_siffra(testare1.skyddatnummer); // Kommer inte åt protected/skyddat nummer
	//testare1.visa_siffra(testare1.privatnummer); // Kommer inte åt private nummer

	//En ärvd testare-----------------------------------------------------------------------------------
	aervd_testare testare2(2); //Skapa testare 2, Skriver ut alla 3 så vi ser att de finns

	testare2.visa_siffra(testare2.allmaentnummer); // Kommer åt publikt nummer
	//testare2.visa_siffra(testare2.skyddatnummer); // Kommer inte åt protected/skyddat nummer
	//testare2.visa_siffra(testare2.privatnummer); // Kommer inte åt private nummer

       return 0;
       };

Konstant medlemsfunktion

redigera

Om man verkligen vill köra med både hängslen och livrem kan man lägga till ordet "const" bakom en funktion som är kopplad till en klass. Gör man så ser man till så att även om man ändrar funktionen senare kommer den aldrig att kunna ändra en variabel inuti en klass. Anta att man har en funktion som skall kontrollera hur mycket bränsle det finns i en stridsvagn.

int kolla_bensin();

Funktionen finns där för att man skall få en siffra tillbaka som visar bränslemängden. Anta att jag senare ändrar den funktionen vid något förvirrat ögonblick så att om bränslet == 0 (tom tank) får tanken 5 liter extra. Helt plötsligt får sedan varken stridsvagnen eller någon annan class som ärvt stridsvagnen slut på bensin. För att förhindra att funktionen ändrar någon variabel i klassen, vare sig den är private eller public, skriver vi så här:

int kolla_bensin() const;

Funktionen har blivit en konstant funktion som inte kan förändra ett värde, bara läsa av det.

Statisk medlemsvariabel

redigera

Det finns andra finesser med "static" förutom att skydda variabler. Anta att du vill veta exakt hur många pansarvagnar som byggts i ett spel. Då kan du naturligtvis skapa en lista där du sparar någon form av identitetsnummer för varje stridsvagn, då kan du läsa av hur många poster som finns i listan för att se hur många som tillverkats. Eller så skapar du en ”statisk medlemsfunktion” tillsammans med en ”statisk medlemsvariabel” (eller ”static data member” som den heter på engelska) dvs. en medlemsfunktion som gäller samtliga instanser av en klass som läser en variabel som är gemensam för alla instanser av klassen. Du har klassen stridsvagn som har identiteten 0, varje stridsvagn som skapas, som kopia eller med kommandot ”new” från den, kan ha ett nummer som automatiskt räknas upp för varje ny, skapad stridsvagn.

Skapa en funktion som är ”inline”, dvs. helt och hållet befinner sig inuti klassen, under public: så att man når den utifrån. Under variablerna skriver man:

Static int antal_stridsvagnar;  //Statisk medlemsvariabel eller static data member

Under funktionerna skriver man:

static int visa_antal_stridsvagnar();
{
return  antal_stridsvagnar;
}

Lägg in så att antal_stridsvagnar räknas upp när man skapar en ny med

++antal_stridsvagnar 

i konstruktionsdeklarationen.

Nollställ räknaren med en funktion alldeles före main.

Int  stridsvagn::antal_stridsvagnar = 0; 

Vill jag läsa av hur många stridsvagnar som skapats när jag är inuti main, skriver jag bara

cout << ”antal byggda= ” << stridsvagn:: antal_stridsvagnar() << endl;

Det som är finurligt är att jag dessutom kan komma åt det värdet i vilken som helst instans av klassen. Anta att jag skapat tre stridsvagnar: stridsvagn1, stridsvagn2, stridsvagn3.

Då kan jag lika gärna skriva:

cout << ”antal byggda= ” << stridsvagn1.antal_stridsvagnar << endl;
cout << ”antal byggda= ” << stridsvagn2.antal_stridsvagnar << endl;
cout << ”antal byggda= ” << stridsvagn3.antal_stridsvagnar << endl;

Om bara tre byggts ger samtliga cout värdet 3 även om jag avläser värdet i olika instanser.

Koden ser ut så här:

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

#define SFML_STATIC //Se till så att det inte behövs extra DLL-filer 

class stridsvagn 
{
public:
//Konstruktordeklaration, definition utanför klassdeklarationen
stridsvagn(int  hastighet, char typ, double spelare_x, double spelare_y);//startvärden
//Destruktion
~stridsvagn(){};

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
char typ;
sf::Sprite sprite;

static int antal_stridsvagnar;  //Statisk medlemsvariabel eller static data member

static int visa_antal_stridsvagnar() //Används för att visa antalet i moderklassen
{
return  antal_stridsvagnar;
}

};

//Konstruktionsdeklaration--------------------------------------------------
stridsvagn::stridsvagn (int  ut_hastighet, char ut_typ, double ut_spelare_x, double ut_spelare_y)
{
 hastighet=ut_hastighet;
 typ=ut_typ;
 spelare_x=ut_spelare_x;
 spelare_y=ut_spelare_y;
 ++antal_stridsvagnar;
 std::cout << "En stridsvagn är byggd!" << std::endl;
}

int stridsvagn::antal_stridsvagnar = 0; //Definition utanför klassdeklarationen

int main (int argc, char *argv)//Startar programmet, allt syns enbart i konsollfönstret
 { //main startar
 stridsvagn stridsvagn1(50,'s',100,100);
 stridsvagn stridsvagn2(50,'s',100,100);
 stridsvagn stridsvagn3(50,'s',100,100);

 std::cout << "antal byggda(1)= " << stridsvagn1.antal_stridsvagnar << std::endl; 
 //Värdet i en instans
 std::cout << "antal byggda(2)= " << stridsvagn2.antal_stridsvagnar << std::endl;
 std::cout << "antal byggda(3)= " << stridsvagn3.antal_stridsvagnar << std::endl;

 std::cout << stridsvagn::visa_antal_stridsvagnar() << std::endl; 
 //Eller med inbyggd funktion i moderklassen

 return 0;
}//Main slutar

Varför fungerar det här egentligen? Jo, genom att "antal_stridsvagnar" är definierad som en statisk variabel nollställs den inte mellan funktionskörningarna utan behåller sitt värde. Detta kan man också göra med vanliga variabler i ett program som inte innehåller klasser