Programmera spel i C++ för nybörjare/C++ referenser online/pekare i C++ för nybörjare


Pekare på enkel svenska redigera

Pekare är ett oerhört abstrakt begrepp som är svårt att bemästra för blivande speltillverkare. Här nedan följer en någorlunda förenklad förklaring till vad pekare är och varför det är så viktigt att kunna hantera dem.

Tänk dig ett rutnät på 4x4 rutor. För att hitta en speciell ruta kan vi ange koordinater med olika bokstäver:

  A B C D
E|_|_|_|_|
F|_|_|_|_|
G|_|_|_|_|
H|_|_|_|_|


Varje ruta har en koordinat. Längst upp i vänstra hörnet finns t.e.x E,A. Nedre högra hörnet får heta H,D.

Om vi tänker oss att detta är insidan på ett dataminne kan vi säga att just den här biten minne har 16 olika adresser. Tänk dig att varje rruta egentligen är en öppen kartong.

Just för tillfället är samtliga kartonger oanvända, men även om kartongerna är tomma kan vi hitta rätt kartong med hjälp av adressen, t.ex. E,A.


Tänk dig nu att vi skapar tre variabler:

int x=1;
int y=2;
int z=3;

När man skapar en variabel i ett program, lagras också innehållet i en (eller flera) platser i minnet. För enkelhetens skull kan vi tänka oss att varje variabel får plats i varsin kartong. Tänk dig

  • att x hamnar i kartongen F,B
  • att y hamnar i kartongen F,C
  • och att z hamnar i kartongen F,D.

Vill man då hitta dessa variablers adresser, var kartongerna ligger i rutnätet, skriver man;

cout << "X finns i "<<&x << " Y finns i " << &y << " och Z finns i "<< &z <<  endl;

När koden skrivs ut står det:

X finns i F,B Y finns i F,C och Z finns i F,D

Detta är en förenkling, på riktigt står värdena i hexadecimala tal, men nu använder vi en förenkling av verkligheten.

Slutsats redigera

Genom att skriva &x får man alltså veta i vilken kartong variabeln x har lagt sig.

Fungerande kod:
#include "stdafx.h"
#include <iostream>

int _tmain(int argc, _TCHAR* argv[])
{

int x=1;
int y=2;
int z=3;
std::cout << "X finns i "<<&x << " Y finns i " << &y << " och Z finns i "<< &z <<  std::endl;

return 0;
}

Vi kan skapa en ny int

int annanint = 15;

Sedan skapar vi &EA som en ny plats i minnet, en ny kartong för just den variabeln, som egentligen redan har en kartong eftersom alla variabler måste ha en kartong att bo i någonstans i minnet. Då har innehållet i annanint kopierats till en annan plats i minnet, EA:s kartong:

int &EA = annanint;

Skriver vi c out << annanint << "" <<&EA; Blir svaret 15 EA.

Om vi däremot skapar ännu en int:

int ennuenint = 30;

och sedan skriver vi att

EA=ennuenint;
cout << annanint << " " <<&EA << " " << ennuenint;

Då blir svaret: 30 EA 30;

Slutsats: redigera

Eftersom &EA har den adress där annanint bor finns det också en variabel som heter EA som är kopplad till samma adress (&EA). Ändrar vi värdet på EA ändrar vi också värdet på annanint eftersom de värdena delar samma kartong. Dvs. alla ändringar som sker med EA sker också med annanint. Det omvända gäller också. De ändringar vi gör med annanint sker också med EA.

Fungerande kod:

#include "stdafx.h"
#include <iostream>

int _tmain(int argc, _TCHAR* argv[])
{

int annanint = 15;
int &EA = annanint;
int ennuenint = 30;
EA=ennuenint;
std::cout << annanint << " " <<&EA << " " << ennuenint << std::endl;
return 0;
}


Då har vi kommit till pekarna. Vi kan skapa tre pekare. De skulle kunna heta *Knatte, *Fnatte, och *Tjatte, men för enkelhetens skull döps de till *px, *py, *pz och tilldelas värdet 0. Pekare helt utan värde hittar på egna värden och är jättefarliga så ge alltid en pekare ett värde.

int *px=0;
int *py=0;
int *pz=0;

pekare har en * symbol kopplade till sig. Just nu är de "deklarerade" eller "skapade" på enklare svenska, men de pekar ingenstans. Tänk dig att du skapat tre blinda små möss som inte har en aning om vart de skall ta vägen någonstans. Innan en pekare gör någon nytta måste vi koppla dem till någon variabel (eller liknande). Försöker vi att skriva ut en tom pekare:

cout << *px;

kan du kompilera koden, men du kan inte köra programmet. Skriver du istället:

cout << px << py << pz;

får du ut en räcka nollor. Pekarna har ju ingenting att visa upp ännu.


För att koppla något värde till en pekare är man tvungen att koppla pekaren till en minnesadress, en kartong.

px=&x;
py=&y;

Skriver du

cout << px << py << pz;

ser du att px har fått X adress F,B och py har fått Y adress F,C. Vi har alltså kopplat pekaren till en kartong i minnet som innehåller någonting. Vad innehåller det då?

cout << *px << *py;

Får vi ut värdena för variablerna x och y: 1 och 2 Programmet kraschade när vi försökte skriva ut samma sorts pekare innan, det var för att de saknade koppling. Det vi gjort nu, att läsa av pekaren vilket värde den har, kallas på fint programmeringsspråk att man "derefererar" eller avläser pekaren.


I detta med deklarering och dereferering ligger ett av pekarnas största problem. Koden för att skapa en pekare *px ser exakt likadan ut som när man derefererar den *px trots att de egentligen inte har någonting alls med varandra att göra. Lär dig att förstå skillnaden så blir pekare mycket mindre mystiska redan här.

Slutsats redigera

Genom att koppla pekaren px till adressen för x dvs. &x, har vi en koppling mellan pekaren och variabeln x genom att de delar samma minneskartong. När vi har den kopplingen kan vi dereferera pekaren och både läsa av- och ändra värdet i adressen/minneskartongen. Genom att ändra värdet i minneskartongen kan vi samtidigt ändra värdet på alla andra variabler som delar på samma minnesplats/kartong. I det här exemplet gäller det variabeln x.

Fungerande kod:

#include "stdafx.h"
#include <iostream>

int _tmain(int argc, _TCHAR* argv[])
{

int *px=0;
int *py=0;
int *pz=0;

int x=1;
int y=2;
int z=3;

px=&x;
py=&y;

std::cout << px << py << pz << std::endl;
//pz har ingen adress, blir 00000000

// std::cout << *px << *py << *pz; ger error
// *pz har ingen adress associerad

std::cout << *px << *py << std::endl;

return 0;
}



Vad händer då om man ändrar x?

x = 100;
cout << *px; 

Blir100. Kopplingen fungerar. Ändrar vi x ändrar vi också pekaren *px. vad händer om vi ändrar *px?

*px = 500;
cout << x; 

Blir 500.

Slutsats redigera

Så länge *px och x delar samma minnesadress (&x) kommer alla förändringar som sker med x också att ske med *px;



Varför är det så spännande? Jo för det innebär att om vi har olika variabler i programmet kan vi ändra variablerna med hjälp av pekare, oavsett var pekarna finns. Vanligvis används detta i funktioner. En funktion kan bara ge ett enda värde tillbaka (som main har return 0 som svar). Med hjälp av pekare sker samma förändringar inne i funktionen som med variabler utanför funktionen. Om man däremot skapar nya variabler inne i funktionen ändras de bara inom funktionen.

Exempel

Vi skapar en funktion som gör att två variabler byter värde med varandra. Deklarera pekaren före main:

void skiftavaerden(int *pekarex, int *pekarey);

Funktionen returnerar ingenting, så egentligen borde ingenting hända när den körs eftersom allt sker inuti funktionen. Skriv in själva funktionen efter programslutet.

void skiftavaerden(int *pekarex, int *pekarey)
{
   int z=0;
   z= *pekarex;
   *pekarex = *pekarey;
   *pekarey= z;
}

Om du sedan skriver in följande i programmet:

x=200;
y=400;
z=800;

skiftavaerden(px,py);

Kommer du att se att x blivit 400 och y blivit 200, men z är fortfarande 800. varför? z fick ju värdet 200 inuti funktionen? Svaret är att det z som användes i funktionen bara råkade ha samma namn som den variabeln z som finns utanför funktionen. De två olika z-variablerna har egentligen ingenting alls med varandra att göra mer än att de råkar dela namn.

Slutsats redigera

variabler som deklarerats inuti en funktion förändras inte utanför funktionen även om de har samma namn. För att kunna ha ett värde som förändras både inuti- och utanför en funktion måste de dela samma minneskartong, och det är just vad pekare gör.

Fungerande kod:

#include "stdafx.h"
#include <iostream>

void skiftavaerden(int *pekarex, int *pekarey);

int _tmain(int argc, _TCHAR* argv[])
{

int *px=0;
int *py=0;
int *pz=0;

int x=200;
int y=400;
int z=800;

px=&x;
py=&y;
pz=&z;

std::cout << "x=" << x << "y=" << y << "z=" << z << std::endl; 

skiftavaerden(px,py);

std::cout << "x=" << x << "y=" << y << "z=" << z << std::endl; 

return 0;
}

void skiftavaerden(int *pekarex, int *pekarey)
{
  int z=0;
  z= *pekarex;
  std::cout << "z=" << z << std::endl;
   //z blir 200, men enbart det z som finns inuti funktionen
  *pekarex = *pekarey;
  *pekarey= z;
}



Slutligen, om vi nu inte deklarerat pekare, kan man inte använda sig av minneshantering i funktionerna ändå? Vi såg ju hur man kunde koppla två värden till samma minneskartong i början av den här genomgången utan att det fanns några pekare.


Gör så här: Skriv om koden så att z och EA får nya värden:

annanint=10;
z=200;
cout << &annanint << " " << &z << " " << annanint << " " << z << endl;

Då ser vi att annanint har en adress (vart den nu hamnat) och z har en egen adress. För att få funktionen att fungera utan pekare måste vi tilldela den minnesadreserna istället:

skiftavaerden(&annanint,&z);

Skriv sedan:

cout << &annanint << " " << &z << " " << annanint << " " << z << endl;

Då ser du att minnesadresserna, de två kartongerna, fortfarande är desamma, men värdena har skiftats, dvs. innehållet i kartongerna har bytt plats.

Slutsats redigera

det går att ändra värden utan att skapa pekare om man bara vill ändra värden i funktioner, bara man:

  • har pekare i funktionsdeklarationen = void skiftavaerden(int *pekarex, int *pekarey);
  • men adresser som tilldelas dem = skiftavaerden(&annanint,&z);

Det visar också att värdet i pekaren px hanteras exakt på samma sätt som värdet i adressen &x om man inte kopplat en pekare till den.