11. gyakorlat¶
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 a 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. Erre példa itt található.
std::set¶
Halmaz, 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.
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 |
|
Példa magyarázata¶
std::set<Kurzus> kurzus_halmaz;
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.
kurzus_halmaz.insert( k1 );
Egy elem beillesztése a halmazba. Természetesen nem rakhatunk bele más típust, csakis amit megadtunk.
kurzus_halmaz.size()
A halmazban található elemek számát adja meg.
kurzus_halmaz.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 : kurzus_halmaz )
A halmaz elemeit bejárhatjuk itertárotok használatával, begin()
és end()
, azonban í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áorolt elemre kapunk referenciát! Emlékezzünk, azok mindig cont elemek!
Helyesen for ( const Kurzus& kurzus : kurzus_halmaz )
-t kell használnunk!
for( auto& k : kurzus_halmaz )
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.
kurzus_halmaz.erase( torlendo );
A törlés-t az erase
metódussal tehetjük meg.
std::vector¶
A vector több elem tárolására alkalmas edott sorrendben. Egy elem többször is előfordulhat. Előző gyakorlatok során használtunk dinamikus tömböket, erre megvalósítás a vector.
Használatáoz include-olni kell a vector
header-t.
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 |
|
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!
std::map¶
A map kulcs été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 |
|
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ékeke típusa.
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 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.
FIRST -> kulcs
SECOND -> érték
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 elemet reprezentálja. A konkrét érték elérése érdekében az iterátort dereferálni kell!
Iterátorok hazsná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 |
|
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 |
|
Gyakorló kérdések¶
A set példájánál használható-e a következő kódrészlet? Miért? Miért nem?
kurzus_halmaz.insert( 5 ); // Kurzus halmazba egy intet szúrnánk be.
Gyakori hibák compile error logokkal¶
TODO