9. fejezet: Mutatók és tömbök
Minden komolyabb programozási nyelvben vannak tömbök, amelyek gondos kezekben komoly fegyvert jelenthetnek. Először is tanuljunk meg tömböt deklarálni!
---------------------------------------------
//Tömbök használata
#include <iostream>
using namespace std;
#define SPACE " "
int main()
{
cout <<"Ez o:t szamot ir ki...\n ";
int tomb [5] = {1,2,3,4,5};
for (int szam = 0; szam<5; szam++)
{
cout <<tomb[szam] <<SPACE;
}
cout <<endl;
}
---------------------------------------------
A fenti egyszerű példából is jól látható, hogy maga a deklaráció semmi gondot nem okoz, mivel egyszerűen beírjuk a tömb típusát (jelen esetben: int), majd elnevezését (most: tomb), végül az egyes elemek számát (most: 5). Fontos, hogy a C++ esetén minden tömb a 0-ás index-szel kezdődik. Tehát a tömb egyes elemei: tomb[0], tomb[1], ...
Persze a deklaráció történhet külön-külön is. Erre példa a következő:
---------------------------------------------
//Tömbök használata
#include <iostream>
using namespace std;
#define SPACE " "
int main()
{
int tomb[5];
cout <<"Ez o:t szamot ir ki...\n ";
for (int szam = 0; szam<5; szam++)
{
tomb[szam] = szam*10;
cout <<tomb[szam] <<SPACE;
}
cout <<endl;
}
---------------------------------------------
Itt jól látható, hogy a tömb deklarációja már a main() függvény elején megtörtént, ám a konkrét értékeket csak menet közben adjuk meg (miután 10-zel megszoroztuk az index-et). Ezek, mint látható, csupán egydimenziós tömbök, azaz vektorok. Természetesen kellenek többdimenziósok is, azaz mátrixok.
Lássuk példaként erre egy igen egyszerű tömböt, egy 10x10-es szorzótáblát!
---------------------------------------------
//Tömbök használata: szorzótábla
#include <iostream>
using namespace std;
#define SPACE " "
int main()
{
int tabla[11][11]; //Azért 11-es mindkét tömb indexe, mert így 1-től 10-ig lehet futtatni mindkét irányt
cout <<"Ez egy 10x10-es szorzotablat ir ki:...\n";
for (int xindex = 1; xindex<11; xindex++)
{
for (int yindex=1; yindex<11; yindex++)
{
tabla[xindex][yindex] = xindex*yindex; // Ez a tulajdonképpeni szorzás
cout <<xindex <<"x" <<yindex <<"="; // Mit is akarunk összeszroozni?
if (tabla[xindex][yindex]<10) cout <<SPACE; // Ha túl nagy lenne az eredmény, tesz eléje egy SPACE-t
cout <<tabla[xindex][yindex] <<SPACE; // Kiírja a szorzás eredményét
}
cout << endl; // Sorok végét le kell zárni
}
cout <<endl;
}
---------------------------------------------
Igen, tisztában vagyok azzal, hogy ezt simán meg lehet oldani tömbök nélkül is, de ez véleményem szerint egy tökéletes példa a tömbök használatára.
Fontos, túl sok index használata veszélyes lehet, mert igen gyorsan túlterheli a memóriát. Vegyük például a következő tömböt: char century [100][365][24][60][60];
Mérete: 100*365*24*60*60 = 3 153 600 000 byte!
Érdekesség, hogy a vektorokat az egyes alprogramok is elfogadják paraméterként.
Lássuk például a következőt:
---------------------------------------------
// Vektor, mint paraméter
#include <iostream>
using namespace std;
void tombkiir (int arg[], int length) {
for (int n=0; n<length; n++) // Számláló ciklus 0-tól a ciklus hosszáig
cout << arg[n] << " "; // A konkrét kiíratás
cout << "\n";
}
int main ()
{
int tomb1[] = {5, 10, 15}; // Első tömb deklarálása
int tomb2[] = {2, 4, 6, 8, 10}; // Második tömb deklarálása
tombkiir (tomb1,3); // Első tömb kiíratása
tombkiir (tomb2,5); // Második tömb deklarálása
return 0;
}
---------------------------------------------
Kicsit másképpen kell kezelni a mutatókat és egészen másképp kell értelmezni!
Már láttuk, hogy a változókat, illetve az állandókat többféleképpen is el lehet érni a memóriában. A pointerek (mutatók) esetében nem kell azzal törődni, hogy hol is vannak a memóriában, hanem egyszerűen használhatjuk az azonosítójukat, ha rá akarunk utalni.
Magát a számítógép memóriáját úgy is el lehet képzelni, mint egymás utáni cellák sorozatát, ha egy cella mérete 1-1 byte. Így minden egyes cella könnyedén azonosítható, mert van egyedi azonosító száma, melyet a következő azonosító számot tartalmazó cella követ. Például, ha az 1777-es cellát keressük, akkor pontosan tudhatjuk, hogy az 1776-os és az 1778-as között lesz megtalálható.
10. fejezet: Hivatkozási operátor (&)
Amint egy változót deklaráltunk, a szükséges memória címét azonnal hozzárendeljük. Szerencsére nem nekünk kell gondoskodni arról, hogy fizikailag hol is helyezkedjen el magában a memórián belül, mivel ezt a rendszer automatikusan elvégzi helyettünk. A cím, amely meghatározza egy változó memórián belüli címét, ez lesz a hivatkozási (referencia) operátor. Ezt változóra való hivatkozást meg kell előzni egy változó előtti jellel (&), ami az angol nyelvű és rövidített jele. Például: ted = &andy;
A pointereket három különféleképpen lehet deklarálni, mégpedig a következő típus szerint: típus * name;
Ezek konkrét megvalósítása:
int * number;
char * character;
float * greatnumber;
Ezek sorban: egész, karakter, illetve float típusú változó. Szeretném kihangsúlyozni, hogy a csillag-jel (*), amit a deklarációhoz használunk, csupán azt jelenti, hogy egy pointer-típusú változó következik, semmi mást! Most jöjjön mintaképpen egy kód-részlet:
andy = 25; // Itt értéket adunk az andy változónak. Ez legyen 25. Az andy memória-beli helye legyen: 1776. (Ezt a gép adja!)
fred = andy; // Ezzel felveszi a fred nevű változó az andy értékét, azaz a 25-öt.
ted = &andy; // A ted nevű változó viszont az andy memóriahelyét mutatja, ami lehet valami egész más, pl.: 1776.
Teljes kóddal:
---------------------------------------------
// Pointer
#include <iostream>
using namespace std;
int main ()
{
int andy, fred;
int * ted;
andy = 25; // Itt értéket adunk az andy változónak. Ez legyen 25. Az andy memória-beli helye legyen: 1776. (Ezt a gép adja!)
fred = andy; // Ezzel felveszi a fred nevű változó az andy értékét, azaz a 25-öt.
ted = &andy; // A ted nevű változó viszont az andy memóriahelyét mutatja, ami lehet valami egész más, pl.: 1776.
cout <<andy <<endl;
cout <<fred <<endl;
cout <<ted;
return 0;
}
---------------------------------------------
Egy másik lehetőséges pointer-műveletet tesz lehetővé a csillag (*). Nézzük a következőt: beth = *ted;
Ezt a következőképpen kell olvasni: beth egyenlő a ted-re mutató pointerrel értékével. Íme egy egyszerű eset:
beth = ted; // beth egyenlő a ted-del ( 1776 )
beth = *ted; // beth egyenlő a ted-re mutató értékkel ( 25 )
Szeretném felhívni a figyelmet a két különböző fenti lehetőség közti különbségre:
&: egy hivatkozási operátor és úgy kell olvasni, hogy "a ..... címe"
*: vissza-hivatkozási operátor és úgy kell olvasni, hogy "a ..... pointer értéke"
Így talán jobban láthatók, hogy ezek gyakorlatilag egymás ellentétei.
Legyen most egy újabb kódrészlet - érdemes lefuttatni!
---------------------------------------------
//További pointerek
#include <iostream>
using namespace std;
int main ()
{
int elsoertek = 5, masodikertek = 15;
int * p1, * p2; // pointerek deklarálása
p1 = &elsoertek; // p1 = az elsoertek címe
p2 = &masodikertek;// p2 = a masodikertek címe
cout <<"A p1 jelenlegi erteke: " <<p1 <<endl;
cout <<"A p2 jelenlegi erteke: " <<p2 <<endl;
*p1 = 10; // p1 pointer értéke legyen
*p2 = *p1; // A p2 pointer értéke legyen a p1 pointer értéke
p1 = p2; // p1 értéke legyen p2
*p1 = 20; // A p1 pointer értéke legyen 20
cout << "Az elso: ertek : " << elsoertek << endl;
cout << "A masodik ertek: " << masodikertek << endl;
return 0;
}
---------------------------------------------
Mivel a dolog még zavaros lehet, ezért jöjjön még egy példa:
---------------------------------------------
// További pointerek
#include <iostream>
using namespace std;
int main ()
{
int numbers[5]; // Inicializáljuk a numbers vektort
int * p; // Inicializáljuk a p pointert
p = numbers; *p = 10; // A p értéke legyen azonos a numbers-szel. A p pointer értéke legyen 10.
p++; *p = 20; // Növeljük meg a p-t eggyel. A p pointer értéke legyen 20.
p = &numbers[2]; *p = 30; // A p legyen a numbers[2] címe, a p pointer értéke pedig legyen 30.
p = numbers + 3; *p = 40;
p = numbers; *(p+4) = 50;
for (int n=0; n<5; n++)
cout << numbers[n] << ", "; // Ez pedig a kiírás
return 0;
}
---------------------------------------------
A kiírás a következő lesz: 10, 20, 30, 40, 50
Még valami: tapasztalatból tudom, hogy a pointerek sokaknak zavarosak. Igen, ez így szokott lenni. Az emelt szintű felvételi legtöbb feladatsorát meg lehetett ezen rész nélkül is írni, de persze jóval könnyebb ezzel. Érdemes rajta gondolkozni!