12. gyakorlat¶
Algoritmusok¶
Az előző anyagban a standard template library (stl) adatszerkezeteinek egy részét és az azokon definiált algoritmusokat vettük végig. Ebben az anyagrészben az stl algoritmusai közül veszünk sorra párat. A függvények, melyek egy - egy algoritmust valósítanak meg, gyakran iterátorokkal, fix értékekkel esetleg olyan függvényekkel együtt használhatók, mleyek egy döntést határoznak meg. Mivel iterátorok használatakor gyakran első és utolsó elemet határozunk meg, fontos, hogy milyen intervallumon dolgoznak az algoritmusok. Általában igaz az, hogy első elemként megadott iterátort figyelembe veszik, míg utolsó elemként megadott iterátort nem használják fel, csupán a bejárás végének meghatározásához. Másként: az első elem zárt intervallum határ '[' az utolsó elem nyílt intervallum határ ')'. Az algoritmusok az
Iterátorok 2¶
Az iterátorokat már ismerjük az előző anyagrészből, azonban eddig csak annyit tudtunk, hogy az adatszerkezet egy elemét érhetjük el velük és képesek a teljes adaton végighaladni. Az algoritmusok használata során az iterátorok közt különbséget is kell tennünk. Lehetséges, hogy bizonyos iterátorok csupán az előre léptetést (következő elem / ++ operator) támogatják. Ennél többet tudnak azok az iterátorok, melyek már az előző elemet is képesek elérni (visszalépés / -- operator). Ennél többet tudnak azok az iterátorok, melyek nemcsak egy elemmel képesek léptetni az aktuális mutatott elemet, hanem tetszőleges számmal (random access / indexer operator / (it + n) ).
Az iterátorok típusai és néhány jellemzőjük
-
input iterator: képes a ++ operátorral lépni a következő elemre, azonban a már elhagyott elemek nem tekinthetőek érvényesnek. Ha egy algoritmus egyszer kell végigmenjen az adaton, akkor ez is elegendő.
-
forward iterator: képes a ++ operátorral lépni a következő elemre (akárcsak az input iterator) és a már elhagyott elemek érvényesek maradnak, újrafelhasználhatók. Akkor használatos, ha többször kell végighaladni az adaton.
-
bidirectional iterator: biztosítja a forward iterátor lehetőségeit és azonfelül képes elérni az előző elemet a -- operátorral. Támogatja az adat többszöri bejárását és akkor hasznos, ha szükséges az adott elem előtti elemek elérése.
-
random access iterator: biztosít mindent amit az előző iterátorok és képes egy tetszőleges elem elérésére is (indexer operátor és + operátor). Akkor hasznos, ha az algoritmus nemcsak végigjárja az adathalmazt, hanem indexeli is azt.
Minden olyan típus, mely valamelyik iterátor típus műveleteit kielégíti tekinthető iterátor típusnak és használható az stl-es algoritmusok használatakor.
find¶
A find
algoritmus megkeresi azt az elemet amit megadunk neki. Az első két paramétere egy InputIt típust vár, tehát olyan iterátort mely képes előre lépni, de csak egyszer fog az algoritmus végighaladni az adaton. A harmadik paraméter olyan típusú kell legyen ami található a tárolóban és az az érték amit keresünk. Visszatérési értékként szintén iterátort kapunk, mely a megtalált elemre mutat az adathalmazban. Ha nem találta meg, akkor az adathalmaz végét adja vissza. Ha egy elem többször is szerepelhet a tárolóban, a bejárás szerinti első elemet adja vissza.
Használata:
1 2 3 4 5 6 7 8 9 10 11 |
|
Használata olyan iterátorral, mely kielégíti az input iterátor elvárásait (pointer):
1 2 3 4 5 6 7 8 9 10 11 |
|
find_if¶
A find_if
hasonló a find
-hoz, azonban itt nem egy fix értéket adunk meg, hanem egy műveletet ami eldönti, hogy az adathalmaz elemei közül, melyik az, amelyik megfelel az elvártaknak. Tehát érték helyett valamilyen logikai feltételhez kötjük az érték keresését. Ez a feltétel több minden is lehet, most használjunk függvényeket.
A find_if
paraméterezése hasonló a find
paraméterezéséhez, azonban a 3. paraméter egy UnaryPredicate
. Egy elem akkor tartozik ehhez a típushoz, ha függvényként meghívható, egy paramétert vár abból a típusból amit az adathalmaz tárol és egy bool értéket ad vissza (jelezvén, hogy a kapott elemre igaz vagy hamis a predikátum.)
Használata:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Magyarázat:
A harmadik paraméter egy függvény neve. Az algoritmus tudja, hogy azt a függvényt kell meghívnia minden elemre miközben bejárja a tárolót. Az 1, 2 értékekre hamisat ad vissza, majd az 5-re igazat. Ekkor leáll a keresés, megtalálható az elem. Látható, hogy több elem is megfelel a feltételnek, de a bejárás szerinti első elemet kapjuk vissza.
count és count_if¶
A count
és count_if
közti kapcsolat hasonló a find
és find_if
közti kapcsolatra, csupán a végrehajtott algoritmus más. A feltételnek (érték vagy predikátum) megfelelő elemek darabszámát adja vissza.
Használata:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
remove és remove_if¶
Az elemek törlésére alkalmas. Szintén megtalálható az értékhez és predikátumhoz kötött verzió.
Nagyon fontos: a remove nem törli ki az elemeket! A remove a törlendő elemeket a tároló végére mozgatja és visszaadja azt a pozíciót (iterátort) ahonnan a "töröltnek" titulált elemek találhatók. Ezután kézzel kell törölni az adott itertátortól kezdve az elemeket.
Használata:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Használata kézzel való törléssel együtt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Lambda kifejezés¶
Az algoritmusok használatakor bizonyos esetekben (predikátum / comparison) függvényt adtunk meg az algoritmusoknak. Sokszor azonban csak egy helyen kell használnunk az adott eljárást, így fölösleges egy teljes függvényt készítenünk, hogy azt átadhassuk; ezzel is szennyezve a scope-ot amiben dolgozunk. Erre megoldás a lambda kifejezés. Egy olyan kis kód elem, ami függvényként hívható.
Függvény objektum.¶
Azt már láttuk, hogy osztályoknak definálhatunk operátorokat, ezzel is egy különleges szintakiszt alakítva ki. Ilyen operátor pl. a ++, --, +=, *, /, [] de akár maga a vessző is. Ezek mellett az operátorok mellett felüldefiniálható a függvény hívás operátor is operator()
. Ha egy típusra definiáljuk ezt az operátort, annak a típusnak az objektumai "függvény szerűen" meghívhatók.
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 |
|
Ha egy osztály példánya függvényként hívható, az algoritmusoknak függvények helyett osztály példányokat is átadhatunk.
Példa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Lambda megvalósítás¶
Lambda kifejezés a fent említett függvény objektumok rövidített írásmódja, mikor nincsen szükség az adott típusra öbb helyen, csupán egy, rövid feladata van.
Lambda kifejezés írásakor egy osztály készül, melynek csupán a hasznos részeit készíti el a fordító, pl. a függvényhívás operátor.
Lambda kifejezés: [](){}
.
Részletezése:
- a [] részben adhatunk meg változókat, melyek az eredeti scope-ban voltak elérhetők, pl. változók. Ez az úgynevezett 'capture'.
- () részben a lambda / függvény paramétereit adjuk meg.
- {} részben a lambda törzsét adjuk meg, hogy mi fusson meghívásakor.
Példa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Ha egy lambda kifejezésben szeretnénk használni változókat, akkor azt a [] jelek közt kell magadnunk, hogy az adott változót hazsnálni szeretnénk. Ekkor a változót átadhatjuk másolat szerint esetleg referencia szerint is. Erre 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 |
|