vector, set, map
Adatszerkezetek¶
Gyakran a problémák megoldása során különféle adatszerkezetekre van szükségünk annak érdekében, hogy hatékonyan, könnyen, kevés kódolással megoldhassuk a problémát. Ilyen adatszerkezetek közül mutatunk be párat a leggyakoribbak közül.
A bemutatott adatszerkezetek osztályokként vannak megvalósítva. Mindegyikre igaz, hogy az általuk tárolt/kezelt típust előre meg kell adnunk. Vagyis ha int
-et vagy Kurzus
-t szeretnénk használni, azt előre meg kell adnunk.
Ezek a megvalósítások úgynevezett template használatával készültek, mely nem része a gyakorlat anyagának, de jobb tudni róla.
Számunkra ami fontos, a template hasonló a Java generikushoz és ha hibázunk hosszú fordítási üzenetet képes generálni a rendszer, melyből nehezebb kideríteni a problémát az eddig megszokottnál.
Halmaz (std::set)¶
A halmazban egy elem csakis egyszer fordulhat elő. A tartalmazott elemek nem módosíthatók (const-ként tároltak), de törölhetők.
Minden elem rendezve kerül tárolásra egy fában, így a rendezést biztosítanunk kell. A rendezés megvalósításához szükségünk van a operator<
-re. Amennyiben egy adattípusra nem definiált a rendezés reláció, és az adattípust setben szeretnénk tárolni, fordítási hibát kapunk.
A halmaz használatához include-olni kell a set
headert.
Példa létrehozására és használatára:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
|
Kimenet
1
2
3
3
A kurzus letezik, id: 1
A kurzus letezik, id: 2
Az eddig meglevo kurzusok:
- 1
- 2
- 3
Kurzusok torles utan:
- 1
- 2
Példa magyarázata¶
std::set<Kurzus> kurzusHalmaz;
A halmaz egy osztály, így példányosítanunk kell. Ami érdekes, hogy a típus megadásánál <> jelek között egy másik típus található. Így tudjuk megadni, hogy milyen elemeket tervezünk a halmazba rakni.
kurzusHalmaz.insert( k1 );
Egy elem beillesztése a halmazba. Természetesen nem rakhatunk bele más típust, csakis amit megadtunk.
kurzusHalmaz.size()
A halmazban található elemek számát adja meg.
kurzusHalmaz.insert( k12 );
Habár insert-et hívunk, a méret nem változik, hiszen ilyen elemet már tároltunk, nem adódik újra hozzá.
kurzusok.contains( kurzus );
C++20 óta lehetőségünk van a contains metódus használatára, mely egy olyan típusú elemet vár, mint amilyet a halmazban tárolunk és megmondja, hogy az adott elem, már megtalálható-e a halmazban. Bool értékkel tér vissza.
kurzusok.find( kurzus ) != kurzusok.end();
C++20 előtt a find metódust használhattuk. Ez nem bool értékkel tér vissza, hanem egy olyan elemmel, mely megadja, hol található a keresett elem a halmazon belül (iterátor). Ha található ilyen elem, akkor egy valid iterátort kapunk, ha nem, akkor egy iterátort kapunk, mely a halmaz végét jelenti. Ezt a vég elemet az end
metódus hívással érhetjük el. Tehát find
metódussal lekérjük az elem pozícióját és ha az nem a végelem, akkor megtalálható a halmazban.
for( Kurzus k : kurzusHalmaz )
A halmaz elemeit bejárhatnánk itertárotok használatával is, begin()
és end()
metódusok segítenek a kezdő és végelemek kezelésében, ugyanakkor az iterátorral rendelkező adattárolók bejárása egyszerűbben is megadható az itt megadott bejárással. Így a halmaz által definiált bejárással az összes elemet iterálhatjuk kevesebb kód írásával.
Ekkor egy másolatot kapunk a tárolt elemről. Ha a forciklusban nem Kurzus
-t, hanem Kurzus&
-t írnánk, akkor nem másolatot kapnánk a tárolt elemről, hanem egy referenciát arra.
Fontos, hogy referencia használatakor a tárolt elemre kapunk referenciát! Emlékezzünk, azok mindig const elemek!
Ezért helyesen for ( const Kurzus& kurzus : kurzusHalmaz )
-t kell használnunk!
for( auto& k : kurzusHalmaz )
Ez a megvalósítás nagyon hasonlít az előbbihez, azonban itt nem kell kiírnunk a típust, hiszen az auto
használatával a fordító kikövetkezteti azt.
kurzusHalmaz.erase( torlendo );
A törlés-t az erase
metódussal tehetjük meg.
Vektor (std::vector)¶
A vector több elem tárolására alkalmas, adott sorrendben. Egy elem többször is előfordulhat.
Használatához include-olni kell a vector
headert.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Kimenet
3
Az egyes indexen levo kurzus ID-ja: 2
Kurzusok:
- 1
- 2
- 1
42
Példa magyarázata¶
std::vector<Kurzus> kurzusok;
A vector is egy osztály, ezt is példányosítani kell és itt is meg kell adni, hogy milyen típusú elemeket szeretnénk tárolni.
kurzusok.push_back( k1 );
A vector végére helyezi el a kapott elemet. Több beszúrási lehetőség is van, azonban a vector végére szúrni konstans időben lehet.
kurzusok[ 1 ].id
Akárcsak a megszokott tömböknél, itt is van lehetőségünk az elemeket index alapján elérni.
kurzusok.at( 0 ).id
Ezzel a hívással úgyanúgy indexelhetjük az elemeket, azonban ekkor a megadott index validálásra kerül.
Hibás indexeléskor std::out_of_range keletkezik.
for ( auto& kurzus : kurzusok )
Akárcsak a set esetében, itt is elérhetjük for ciklusban az elemeket. Akár másolattal, akár referenciával, azonban itt figyeljünk arra, hogy itt nem kell const-ként hivatkozni rájuk, akár módosíthatók is az eredeti elemek!
Leképezés (std::map)¶
A map kulcs érték tárolására alkalmas. Minden kulcs egyedi érték kell legyen!
Példa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
|
Kimenet
C++ eloado kurzusa: prog2
Java eloado kurzusa: prog1
Java eloado-nak van oraja, meghozza: prog1
Oktato: C++ eloado Ora: prog2
Oktato: Java eloado Ora: prog1
Oktato: C++ eloado Ora: prog2
Oktato: Java eloado Ora: prog1
Példa magyarázata¶
std::map<std::string, Kurzus> oktatok_kurzusa;
A map is egy osztály, így ezt is példányosítani kell, de most nem egy, hanem két extra típust is meg kell adnunk: az első a kulcsok típusa, a második a tárolt értékek típusa.
A kulcs típus elemei kell, hogy rendelkezzenek egy rendezési relációval. Primitív típusoknál ez nyilván adott, saját típusoknál nekünk kell erről gondoskodni.
oktatok_kurzusa[ prog2_eloado ] = prog2;
Az indexer operátorral egy kulcshoz egy adott értéket rendelhetünk.
Fontos, ha az adott kulcs nem létezett, akkor egy új elem kerül beszúrásra.
Kulcs tárolt értékének frissítésére is alkalmas.
Új érték beszúrásakor a default konstruktorra szükség van!
oktatok_kurzusa[ prog1_eloado ].nev
Az indexer operátor nemcsak beillesztésre / frissítésre alkalmas, lekérdezésre is használhatjuk.
Ezzel vigyázzunk, hiszen, ha olyan értéket kérünk le, mely nem létezik, a default konsturktorral alkotott érték beszúrásra kerül az új kulcshoz!
oktatok_kurzusa.at( prog2_eloado ).nev
Lekérdezés biztonságosabb módja. Így nem kerül új érték beszúrásra, azonban kivétel dobódhat.
Meghívása előtt érdemes ellenőrizni, hogy elérhető-e az adott elem vagy ennek hiányában kivételre felkészülni!
oktatok_kurzusa.find( prog1_eloado ) != oktatok_kurzusa.end()
Hasonlóan a set-hez itt is a find
egy iterátort ad vissza, és akkor található meg egy elem, ha az a map NEM utolsó utáni (vég) eleme.
for ( auto& element : oktatok_kurzusa )
Az elemek egy map-nél is bejárhatók, azonban ekkor két értéket kapunk, hiszen kulcs-érték párról beszélünk.
a kapott element
egy pair, aminek van first
és second
eleme. Erre példa a következő magyarázatban található.
element.first
és element.second.nev
Az iterálás során kapott objektum egy pair, melyben a first adattagban a map-ben tárolt KULCS található.
A second attribútumban található a VALUE.
for ( auto& [oktato, ora] : oktatok_kurzusa )
C++17 óta az előbbi iterációt egyszerűsíthetjük. Ekkor nem kell egy pair first és second attribútumát használnunk, automatikusan a kulcsot is és az értéket is egy objektumhoz rendelhetjük.
Jelen esetben az oktato
objektumba kerül az aktuális KULCS és az ora
objektumba az ÉRTÉK.
Iterátorok¶
A fentebbi példákban több példát is láthattunk arra, hogy for ciklussal be tudjuk járni az egyes tárolókat. Ezek a bejárások azt biztosítják, hogy minden elemre sor kerül. A háttérben iterátorok használata történik.
Iterátor: Olyan objektum, ami segítségével egy tároló elemein képesek vagyunk végigjárni. Az iterátor típusától függően támogathatja az előre, vissza irányba léptetést és a tetszőleges elemmel való léptetést. Az iterátor nem az egyes elemeket jelenti, csak az adott elemre mutat. A konkrét érték elérése érdekében az iterátort dereferálni kell, amiből most számunkra annyit kell érteni és tudni, hogy az iterátor neve elé kell tenni egy *-t, és így kapjuk meg az értéket, amire az iterátor hivatkozik!
Iterátorok használatakor vannak bizonyos elemek, melyeket megkülönböztetünk. Ilyen a bejárás során az első elem és az utolsó elem. Szokványos bejárás során az első elemet reprezentáló iterátort a begin
metódussal, a tároló végét reprezentáló iterátort az end
metódussal kérhetjük el.
1 2 3 4 5 6 7 8 9 10 |
|
Kimenet
1
77
4
-8
33
Az iterátorokkal való bejárás során változót kell létrehozni, amivel elérhetjük az elemeket. Ennek a változónak a típusa függ a használt tárolótól, az abban tárolt elemtől. Különféle tárolók iterátorai más - más műveletet engedhetnek meg, de a ++ operátort legtöbb esetben használhatjuk. Ennek segítségével a következő elemre referáló iterátort kapjuk meg.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Kimenet
1
4
-8
33
Létrehozva: 2020-11-16