C++ programozás 14. – Tömbök

Tömbök, avagy de sokan vagytok!

A tömb, mint adattípus az összetett adattípusok közé tartozik. A tömb valójában egy sorszámozott egyforma típusú elemeket tartalmazó halmaz. Halmaz alatt csak annyit értek, hogy több elemet tartalmaz, nem matematikai értelemben használjuk, itt ugyanis egy érték többször is előfordulhat a tömbben. A tömbök mérete (hogy hány elemet tartalmaz) középiskolai szinten lényegtelen, akkora tömbökkel nem dolgozunk, ami a túl nagy méret miatt problémát okozna. Ami viszont fontos: a tömb mérete csak egyszer adható meg, amikor deklaráljuk a tömböt. Vagyis ha megadtam, hogy ez egy 10 elemet tartalmazó tömb, akkor ezen később nem változtathatok. Különösen figyelni kell erre akkor, amikor nem tudod, hogy hány elemet szeretnél tárolni, akkor kénytelen vagy az elméleti maximális méretet beállítani, amit a feladat ad meg.

Tömb elemei

Egy tömb, mint már említettem egy sorszámozott egyforma típusú elemeket tartalmazó halmaz. A sorszámozásnak fontos szerepe van, mert az elemek sorrendje – amíg meg nem változtatjuk – kötött. Mindenkinek megvan a saját azonosítója, amivel a tömbben elfoglalt pontos helyére hivatkozunk. Ezt nevezzünk indexnek. A sorszám annyiban nem a legpontosabb elnevezés, hogy itt a sorszámozás – amit innentől nevezzünk indexelésnek – 0-val kezdődik.

Mivel minden tömbelem helye fix, és a helyét az elem indexe adja meg, ezért lehet egy tetszőleges elemre hivatkozni a következő módon:

tomb[index]

Ez az adott indexű helyen tárolt elem konkrét értékét adja vissza, és amíg direkt nem cserélgetjük össze az elemeket, vagy nem változtatjuk meg az értéküket, mindig ugyanazt az értéket adja. Így lehet például a tömb feltöltése közben az adott helyen lévő “tárolóban” értéket elhelyezni. Értelemszerűen az index legkisebb értéke 0 lehet (ez az első elem), a legnagyobb pedig tömbméret-1 (ez az utolsó).

Az indexnek minden esetben egy nem negatív egész számot kell megadni, de akkor honnan tudjuk, hogy melyik valóban az utolsó elem? Hogyan tudjuk a tömbméretet megkapni? Univerzális megoldás az alap c++ tömbök méretének meghatározására nincs. A tömb méretét neked külön kell tárolnod, és használni, amikor arra szükség van. A későbbi példákból egyértelmű lesz, hogy hogyan. A tömb elemei közül vannak olyanok, melyeket jó, ha általános formában ismersz.

tomb[0]       // mindig ez az első elem
tomb[meret-1] // mindig ez az utolsó elem

Fontos, hogy a tömbméret nem a tömbben általunk eltárolt elemek számát adja meg, mert az lehet kevesebb is, mint a tömb mérete. Például tudom, hogy legfeljebb 20 értéket akarok tárolni, akkor egy 20 elemű tömbre van szükségem. És ha csak 15-öt tároltam el? Akkor az utolsó 5 üres lesz. Van ilyen. Illetve lesz benne valami, de hogy mi az tőlem független. A tömbméret tehát azt a darabszámot adja meg, amennyi elem maximálisan elfér a tömbben és nem a már eltárolt elemek számát. Ha nem használtuk ki a tömb teljes méretét, akkor nekünk kell külön nyilvántartani, hogy melyik az utolsó elem, amit mi helyeztünk el a tömbben. Hiszen utána is vannak elemek a tömbben, melyeknek még egyszer hangsúlyozom, nem tudjuk, mik az értékei.

Az alábbi példában egy 10 elemű tömböt hoztam létre, de nem töltöttem fel teljesen. A tömb mérete egyértelműen 10, de nekem nyilván kell tartanom az 5-ös értéket is, mert 5 elem van benne. Ezek közül a tomb[4] az utolsó, hiszen az indexelés 0-tól kezdődik. Így a tárolt 5-ös értékkel a tömb összes feltöltött eleme elérhető, így nem fogok továbbszaladni a kérdőjellel jelölt elemekre, melyek értéke ismeretlen, és nem is egyformák.

tomb

A lényeg: saját változóban tárold azt hogy hány elemet helyeztél el a tömbben. Legyen ez a változó mondjuk db nevű változó.

tomb[db-1]    // ez az utolsó általam elhelyezett elem
tomb[meret-1] // ez pedig a tömb utolsó eleme, ami ismeretlen kezdőérték

Tömbök deklarálása

A tömböt logikailag ugyanúgy kell deklarálni, mint egy egyszerű változót, azzal a különbséggel, hogy a típusán és nevén kívül a tömb méretét is meg kell adni kötelező jelleggel.

int tomb[5];

A tömb deklarálás formailag ettől nem térhet el!

Tömb inicializálása

Előfordulhat, hogy a tömbnek már előre tudjuk a fix értékeit, és ezeket rögtön oda is adjuk neki kezdőértékként. Lássuk ezt hogyan lehet megtenni:

int tomb[5] = {1,2,3,4,5};

A fent megadott tömbnek megadtuk – kötelező jelleggel – hogy 5 eleme lesz, majd ezeket kezdőértékként a kötött formában felsoroljuk. Fontos, hogy több elemet nem adhatunk meg kezdőértékként, mint amekkora a tömb mérete. Ezt még a c++ is hibának veszi, pedig sok hibát lenyel. Kevesebbet viszont adhatunk! Lássuk:

int tomb[5] = {1,2};

Itt az 5 elemű tömbnek csak két értéket adtunk oda kezdőértékként. Ilyenkor az első két elem értéke a felsorolásnak megfelelő lesz, vagyis 1 és 2, a többi elem értéke pedig 0 lesz.

Alaphelyzetben, ha a tömbnek nem adunk kezdőértékeket, a c++ akkor is kiír valami értéket tömbelemként, amikor semmit nem adtunk meg neki. Nézzük az alábbi példát:

int tomb[3];

cout << tomb[0] << endl;
cout << tomb[1] << endl;
cout << tomb[2] << endl;

Ez a következő kimenetet adta:

4309664
2686868
4309758

A tömb még nem kapott kezdőértékeket, de valamit mutat, amelyek természetesen nem valós értékek, de hibát sem produkálnak! Hogyan lehetne akkor megadni, hogy a tömb legyen “üres”, vagyis ne az előzőekhez hasonló ismeretlen értékek legyenek benne, hanem mondjuk mindegyik 0 legyen. Gyakorlatilag ugyanúgy, mint amikor nem adjuk meg a méretének megfelelő összes kezdőértéket. Itt csak az elsőt adjuk meg, és az is 0 legyen:

int tomb[5] = {0};

Ebben a tömbben minden tömbelem 0 lesz, így talán átláthatóbb lesz, hogy melyik elemhez rendeltünk már hozzá értéket, és melyikhez nem. Természetesen itt ügyelni kell arra, hogy ha nem töltjük fel teljesen a tömböt, akkor nyilván kell tartani, hogy éppen hány elem van benne. Vegyük a következő példát:

int tomb[5] = {0}; // minden elem 0 kezdőértékkel rendelkezik

tomb[0] = 3;
tomb[1] = -5;
tomb[2] = 0;

cout << tomb[0] << " ";
cout << tomb[1] << " ";
cout << tomb[2] << " ";
cout << tomb[3] << " ";
cout << tomb[4] << " ";

Kimenetként a következőt láthatjuk:

3 -5 0 0 0 

A gond az, hogy az alap 0 értékek és az általunk beállított 0 érték gyakorlatilag megkülönböztethetetlen. Akkor kezelhetjük ezt a problémát, ha tudjuk, hogy az 5 elemű tömbbe elhelyeztünk 3 elemet, mondjuk 3, -5, 0-át, és megjegyeztük, hogy jelenleg 3 elem van a tömbben. Így a 0-ák közül tudjuk, hogy az első általunk feltöltött elem, a többi pedig a tömb alapértékei.

Műveletek tömbökkel

Tömb feltöltése

A feladat a következő: Töltsünk fel egy 10 elemű egészeket tartalmazó tömböt az [1;100] intervallumból és tároljuk el a kisorsolt értékeket.

Amikor egy tömbbel dolgozunk, szinte mindig ciklusra van szükség. Hogy a ciklus milyen típusú (elöl tesztelő, hátul tesztelő vagy növekményes, esetleg speciális foreach), azt mindig az adott feladattípus dönti el. Amikor például egy tömböt feltöltünk értékekkel vagy ki akarjuk íratni a tartalmát, akkor úgyis végig kell nézni az egészet, minden eleméhez hozzá kell férni.

Mivel a ciklusokat már feltételezem, hogy ismeri az olvasó, így lássuk a megoldásokat alap feladatokra, egyelőre nem teljes programban. A lenti kódban a már ismertetett módon használom a számsorsolást:

int meret = 10;
int tomb[meret] = {0};
for( int i = 0; i < meret; i++ )
{
  tomb[i] = rand() % 100 + 1;
}

Ez a programrész a meret változóban megadott méretű tömböt feltölt az adott intervallumból kisorsolt elemekkel. Lássuk a soronkénti magyarázatot:

  • 1 – megadom a tömb kívánt méretét
  • 2 – létrehozok egy akkora méretű egészeket tartalmazó tömböt 0 kezdőértékekkel
  • 3 – indítok egy növekményes ciklust, ami egy ciklusváltozót (i) elindít 0-tól és addig megy amíg kisebb, mint a tomb nevű tömb mérete. Vagyis mi lesz az i utolsó értéke? meret-1. Ismerős? Ez az utolsó elem indexe. Vagyis ez a ciklus végiglépteti az i változót a tömb összes lehetséges indexén. Akármekkora is a tömb. Ezért jegyeztük meg a méretét, hogy itt használhassuk, mert lényegtelen, hogy a program elején mekkora tömbméretet adtunk meg.
  • 5 – a léptetett indexeket felhasználva a tömb minden elemére hivatkozva egy kisorsolt véletlen számot adunk meg értékként az intervallumból .

Tömb elemeinek kiírása

Mi van akkor, ha ellenőrizni akarjuk, hogy a tömböt tényleg megfelelően töltöttük-e fel? Hátha elszúrtuk az intervallumot.

for( int i = 0; i < meret; i++ )
{
    cout << tomb[i] << " ";
}

Ezt a programrészt már nem is kell nagyon magyarázni. Az 1. sor ciklusa segítségével végigmegyünk a tömb összes indexén. A 3. sorban pedig mindig kiíratjuk a tömb aktuális indexű (vagyis mindegyik) elemét úgy, hogy egy szóközt is hagyunk utána, hogy az elemek elkülönüljenek egymástól.

Hogy néz ki ez az egész egyben?

#include <iostream>
#include <cstdlib> // srand(), rand()
#include <ctime>   // time(0)

using namespace std;

int main()
{
// véletlenszám generátor megkeverése
    srand(time(0));

    int meret = 10;
// tömb deklarálása és méretének megadása
    int tomb[meret] = {0};

// tömb feltöltése
    for( int i = 0; i < meret; i++ )
    {
        tomb[i] = rand() % 100 + 1;
    }

// tömb elemeinek kiíratása
    for( int i = 0; i < meret; i++ )
    {
        cout << tomb[i] << " ";
    }

// extra sordobás
    cout << endl;

    return 0;
}

Talán a 29. sor nem világos mit keres ott. Mivel a tömb elemeit egymás mellé írjuk le, ezért az utolsó elem után – vagyis a ciklus befejeztével – illik egy új sort kezdeni. Hátha jön még más feladat is, ami jó lenne ha elválna a kiíratástól.

Következő lecke: Alap algoritmusok

4 Replies to “C++ programozás 14. – Tömbök”

  1. A DevC++ program segítségével lefuttattam a példafeladatot, de a 14.sornál hibát jelez.
    A 14. sor:
    int tomb[meret] = {0};
    A hibajelzés:
    variable-sized object `tomb’ may not be initialized
    Mi lehet a probléma?

    • DevC++ gond lehet, az érettségin aktuálisan használható CodeBlocks 17.12-vel működik.

      Nálad úgy lehetne megoldani, hogy kézzel beírod a meret változó helyére a tömb méretét.

  2. Ez a világ legrosszabb nyelve (tanulási szempontból). Ezzel próbáltak felkészíteni az emelt infó érettségire… Full fapad Codeblocks környezet. Saját erőből váltottam C#-ra, és megjavult az életem (és az érettségi is jól sikerült)
    Elképesztő, hogy mennyivel könnyebben boldogultam azzal a nyelvvel.
    Csak egy példa: tömb hossza C#-ban: tomb.Length()
    Ugyanez C++-ban: sehogy! meg lehet csinálni sizeof(tomb)/sizeof(tomb[0]) kifejezéssel, ami valami egészen gusztustalan.

    A C++-tól ma is kiráz a hideg.

    • Ez a sizeof(tomb)/sizeof(tomb[0]) csak akkor működik, ha ugyanabban a függvényben deklaráltuk a tömböt. Ha másik függvénynek átadjuk paraméterként a tömböt, és ott akarjuk ezzel a sizeof(tomb)/sizeof(tomb[0]) kifejezéssel megkapni a méretét, már nem a helyes eredményt kapjuk. Ezért szoktam mindig eltároltatni egy változóba a tömbméretet, annak mindig ugyanaz lesz az értéke.

Vélemény, hozzászólás?

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük

*

Ez az oldal az Akismet szolgáltatást használja a spam csökkentésére. Ismerje meg a hozzászólás adatainak feldolgozását .