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!