2016. szeptember 29., csütörtök

STL

      A Standard Template Library (STL) a C++ egyik standard könyvtára, amely az újrahasznosítható komponensek tág készletét kínálja fel. Az 1970-es években a komponensek struktúrákból és függvényekből álltak. Az 1980-as években a komponenseket a platform-függő könyvtárak osztályaival valósították meg, majd az 1990-es évek vége felé az STL megjelenésével a komponensek egyik új alkalmazása vált lehetővé, amely ezúttal platformtól független osztályokkal dolgozik.
      Az adatstruktúrák bizonyos szabályok alapján rendezett adatkollekciók vagy konténerek. Tegyük fel, hogy létrehozunk egy Array osztályt, amely int típusú objektumokból álló tömböt kezel. Osztálysablonnal az int típus Array<T> típusra bővíthető, így lehetőség van Array<double>, Array<char>, Array<Employee>, vagy bármilyen adattípusú tömböt alkotni. Hasonló módon lehet eljárni a verem típusú adatstruktúrák esetében is. Az STL nem más, mint osztálysablonokból felépített könyvtár, amely ezek mellett tartalmazza az adatstruktúrák felépítését is.
      A C-ben és a C++-ban a tömb elemei mutatókkal érhetőek el. A C++ STL-ben a konténerek elemeit iterátor objektumokkal érjük el, melyek olyanok mint a mutatók, viszont intelligensebbek. Az iterátor objektumok osztályai úgy vannak megtervezve, hogy általánosan használhatóak legyenek minden konténerben. A konténerek primitív műveleteket valósíthatnak meg, ám az STL algoritmusai a konténerektől függetlenek.
      A konténerek nem használnak öröklést és virtuális függvényeket a teljesítmény javítása érdekében, valamint nem használják a new és delete operátorokat.

Konténerek
      Az STL konténerek három típúsuak lehetnek:

  •  vector: dinamikus tömb, automata átméretezéssel az elemek beszúrásakor és kivételekor. Akár a statikus tömb esetén, az elemek memóriacímei egymásmelletiek, így egy mutatóval könnyen hivatkozhatunk bármelyikre.
  • list: duplán-láncolt lista, gyors beszúrással és törléssel. A beszúrás és törlés a lista bármely tagjánál lehetséges és mindkét irányba lehet lépkedni. A duplán-láncolt listák elemei egymástól független memóriahelyeken vannak tárolva, nem lehet a mutató növelésével a szomszédos elemre hivatkozni. Ellenben sokkal könnyebb az elemeket rendezgetni, cserélgetni, kivenni és hozzáadni, mert nem kell rendelkezésre álljon n egymás melletti üres memóriaterület. A hátrány az, hogy ezek a műveletek lassúbbak mint például a tömbök esetén, mert az n-edik elemig mindig el kell lépkedni egy ismert pozíciótól. Emellett plusz memóriát igényel a lista elemei közti kapcsolat fenntartása is.
  • forward_list: olyan mint a <list>, csak szimplán láncolt listát valósít meg, azaz lépkedni csak előre lehet benne.
  • deque: sor, melynek csak a végéről lehet kivenni és hozzáadni (mindkét végéről - két végű sor). Az elemek nem feltétlenül egymásutáni memóriacímeken vannak, ezért az előnyök és hátrányok hasonlóak a listáéhoz.
  •  map: egyeztetési lista kulcs-érték párok között. Egy az egyhez egyeztetés. A kulcs az elem egyedi azonosítója és általában rendezésre, gyorskeresésre használják, míg az érték az elem aktuális értéke, amihez a kulcs tartozik. Azért kell őket egyeztetni, mert típusuk különbözhet.
  • multimap: olyan mint a <map>, csak egy a többhöz egyeztetésre is képes, azaz egy kulcshoz több érték is tartozhat.
  • setolyan mint a <map>, csak nem tesz különbséget a kulcs és érték között, tehát nem kell egyeztni őket, mert a típusuk egyforma. Emiatt az értékük sem változtatható meg, minden eleme kötelezően konstans. Az elemek a konténerben mindig rendezve vannak.
  •  multiset: olyan mint a <set>, csak a kettős értékekkel is elbánik, azaz lehetnek duplák az elemek között.
  • stack:  LIFO (Last In First Out) verem. Ez olyan mint egy zsák, melyből mindig a legutoljára berakott elemet lehet elsőre kivenni.
  • queue: FIFO (First In First Out) verem. Ez olyan mint egy lyukas fenekű zsák, melyből a legelsőnek berakott elem esik ki legelőre.
  • priority_queue: sor, melyből mindig a legnagyobb prioritással rendelkező elemet lehet csak kivenni elsőre. A legnagyobb prioritással a legelső elem rendelkezik. Ez nem feltétlen a legelsőnek vagy legutolsónak betett elem, ugyanis nem veremről, hanem sorról beszélünk, melynek mindkét végére beszúrhatóak az elemek. Ez nagyon hasonló a halomhoz (heap), ahol a legelsőnek beszúrt elem a halom csúcsára kerül, a többi pedig lepereg róla jobb vagy bal oldalt.
A header fájlok tartalmai a namespace std részét képezik. A szekvenciális és asszociatív konténereket first-class konténereknek is nevezik. Az STL konténereknek számos közös tulajdonságuk van, például mind rendelkezik:
- implicit konstruktorral
- másoló konstruktorral
- destruktorral
- empty függvénnyel (amely true, ha van elem a konténerben, különben false)
- =, <, <=, >, >=, ==, != operátorokkal
- erase függvénnyel (amely kitöröl egy vagy több elemet a konténerből - csak a first-class konténerek esetén)
- clear függvénnyel (amely minden elemet kitöröl a konténerből - csak a first-class konténerek esetén)

      Amikor konténereket szeretnénk használni fontos megbizonyosodnunk arról, hogy az elemek adattípusa (ami például egy osztálytípus) rendelkezzen a minimális tulajdonságokkal. Amikor berakunk egy elemet a konténerbe, egy másolat keletkezik róla, ezért az adattípusnak rendelkeznie kell legalább egy másoló konstruktorral és egy = operátorral.

Iterátorok
      Az iterátoroknak sok közös tulajdonságuk van a mutatókkal és arra jók, hogy a first-class konténerek elmeire mutassanak. Az iterátor egy olyan objektum, amely lehetővé teszi a konténer végigjárását annak megvalósításától függetlenül. Vannak olyan iterátorok is, melyek bizonyos konténerekre vannak szabva. A * hivatkozó operátor alkalmazható egy iterátoron, hogy elérjük azt az elemet, amire az iterátor mutat. Ha a ++ operátort alkalmazzuk egy iterátoron, akkor az a konténer következő elemére ugrik.
      Az iterátorokat rendezésre, kivevésre és beszúrásra használják. Az alábbi példa az istream_iterator iterátort használja az adatszekvenciák bevitelére és az ostream_iterator-t az adatszekvenciák kiolvasására. A program a billentyűzetről olvas be egész számokat és kiírja azok összegét.

#include <iostream>
using std::cout;
using std::cin;
using std::endl;
#include <iterator>

int main()
{
     cout << "Írj be két egész számot: ";
     std::istream_iterator<int> inputInt(cin); //iterátor deklarálása
     int number1, number2;
     number1 = *inputInt; //első szám beolvasasa
     ++inputInt; //iterátor növelése a következő beolvasáshoz
     number2 = *inputInt; //második szám beolvasása
    
     cout << "Összeg: ";
     std::ostream_iterator<int> outputInt(cout);
     *outputInt = number1 + number2; //az összeg kiírása

     return 0;
}

A kimenet:
Írj be két egész számot: 3 4
Összeg: 7

Az std::istream_iterator<int> inputInt(cin) utasítás egy istream_iterator típusú iterátort deklarál, amely int típusú értékek bevitelére képes a cin standard beviteli eszközről. Amint a number1 változóhoz hozzárendeljük az iterátor értékét, elindul az első beolvasási folyamat. A második beolvasás előtt az iterátort növelni kell. Az std::ostream_iterator<int> outputInt(cout) utasítás egy ostream_iterator típusú iterátort deklarál, amely int típusú értékek beszúrására képes a cout standard kimeneti eszközre. Ebben az esetben az iterátornak kell a kiírni kívánt értéket adni. Ha konténerben tárolt értékek megváltozhatnak a program futása alatt, akkor az iterator iterátort kell használni, viszont ha semmiképp se módosulhatnak, akkor a  const_iterator iterátor a megfelelő választás.

Az STL a következőképp kategorizálja az iterátorokat:
- input: elem kiolvasása a konténerből
- output: elem beszúrása a konténerbe
- forward: input és output képességek
- bidirectional: forward képesség mindkét irányban
- random-access: bidirectional képesség, a lépések véletlenszerű nagyságúak is lehetnek

Az iterátorokkal végzett műveletek a következők lehetnek:
- Minden iterátor:
- ++p: előnövelés
- p++: utónövelés
- input iterátorok:
                - *p: az iterátorra való hivatkozás mint rvalue
                - p = p1: értékátadás
                - p == p1: értékösszehasonlítás egyenlőségre
                - p != p1: értékösszehasonlítás egyenlőtlenségre
- output iterátorok:
                - *p: az iterátorra való hivatkozás mint lvalue
- forward iterátorok:
                - --p: előcsökkentés
                - p--: utócsökkentés
-random-access iterátorok:
                - p += i: a p iterátor növelése i lépéssel
                - p -= i: a p iterátor csökkentése i lépéssel
                - p + i: az iterátortól számított i lépés felfelé
                - p – i: az iterátortól számított i lépés lefelé
                - p[i]: az iterátortól i léplsre lévő elem referenciája
                - p < p1: igaz, ha p a p1 előtt van
                - p <= p1: igaz, ha p < p1, vagy igaz, ha p ugyanabban a pozícióban van mint p1
                - p > p1: igaz, ha p a p1 után van
                - p >= p1: igaz, ha p > p1, vagy igaz, ha p ugyanabban a pozícióban van mint p1

      Az STL egyik fontos aspektusa, hogy olyan algoritmusokat tartalmaz, amelyek általánosak minden konténerre: beszúrás, törlés, keresés, rendezés, stb. Nagyjából 70 standard algoritmus létezik, melyek az iterátorokat használva közvetett módon bánnak az elemekkel. Mivel az STL bővíthető, könnyen hozzáadhatók újabb algoritmusok anélkül, hogy a konténeren változtatni kéne. Az algoritmusokat pointer típusú tömbökön is lehet alkalmazni. Két alapvető algoritmus fajta létezik: a mutating-sequence algoritmusok megváltoztathatják a konténer tartalmát, míg a non-mutating-sequence algoritmusok nem változtatnak a konténer tartalmán. Néhány példa a <numeric> headerből: copy(), fill(), replace(), swap(), count(), find(), search().

Szekvenciális konténerek

Vector
      Egy vektor egy olyan adatstruktúra, melyben az adatok egymás utáni memóriacímeken vannak tárolva. Ez az elrendezés lehetővé teszi az elemek közvetlen elérését a [] operátorral. Amikor a vector típusú objektum memóriája már nem elegendő, akkor egy nagyobb memóriaterület foglalódik le az adott objektumnak, és az elemek átmásolódnak oda, felszabadítván az előző területet. A konténer egyik legfontosabb jellemzője, hogy milyen típusú iterátorral képes működni. A vector konténer a random-access iterátorokat támogatja, azaz a korábban felsorolt összes operátort ismrei. Minden STL algoritmus képes vector konténerrel dolgozni. A vector iterátorai a vektor elemeinek mutatóiként vannak megvalósítva. Ha egy algoritmusnak egy bizonyos típusú iterátorra van szüksége, hogy működjön, akkor az alkalmazott konténernek is támogatnia kell azt az iterátort.
      A következő példa a vector osztálysablon néhány funkcióját mutatja be, melyek közül párat minden first-class STL konténer is használat.

#include<iostream>
using std::cout;
using std::cin;
using std::endl;
#include <vector>

int main()
{
     const int SIZE = 6;
     int a[SIZE] = {1, 2, 3, 4, 5, 6};
     std::vector<int> v;
    
     cout << "A vektor kezdeti mérete: " << v.size() << "\nA vektornak szánt tárhely: " << v.capacity();
    
     v.push_back(2); //elem beszúrása a vektor végére
     v.push_back(3);
     v.push_back(4);
    
     cout << "\nA vektor új mérete: " << v.size() << "\nA vektornak szánt tárhely: " << v.capacity();
    
     cout << "\n\nA tömb tartalma mutatók segítsegevel: ";
     for(int *ptr = a; ptr != a + SIZE; ++ptr)
           cout << *ptr << ' ';
          
     cout << "\nA vektor tartalma iterátor segítségével: ";
     std::vector<int>::const_iterator p1;
     for(p1 = v.begin(); p1 != v.end(); p1++)
           cout << *p1 << ' ';
          
     cout << "\nA vektor tartalma visszafele: ";
     std::vector<int>::reverse_iterator p2;
     for(p2 = v.rbegin(); p2 != v.rend(); ++p2)
           cout << *p2 << ' ';

     return 0;
}

A kimenet:
A vektor kezdeti mérete: 0
A vektornak szánt tárhely: 0
A vektor új mérete: 3
A vektornak szánt tárhely: 4

A tömb tartalma mutatók segítségével: 1 2 3 4 5 6
A vektor tartalma iterator segítségével: 2 3 4
A vektor tartalma visszafele: 4 3 2

Az std::vector<int> v utasítás létrehozza a v objektumot a vector osztályból, és int értékeket tárolhat. A deklarálás révén létrejön egy nulla elemű és kapacitású vektor. A kapacitás nem a vektor méretét, hanem a neki fenntartott memória nagyságát jelenti. Ezt a két értéket a v.size() és a v.capacity() téríti vissza. A push_back függvény beszúr egy értéket a konténerbe. Ha már nincs hely, a vektor mérete és kapacitása automatikusan megnő.
Az std::vector<int>::const_iterator p1 utasítás létrehozza a p1 iterátort a vector konténernek. Mivel ezzel az iterátorral nem változtatunk a konténer tartalmán, deklarálhatjuk konstansnak (const_iterator). A v.begin() függvény visszatéríti a v első elemének a const_iterator-át, a v.end() pedig az utolsót. A p1++ parancs növeli az iterátort egy lépéssel. A konténer elemére az iterátoron keresztül hivatkozunk (*p1).
A visszafelé való kiíráshoz a std::vector<int>::reverse_iterator p2 deklarálást használjuk, amely esetében az rbegin() függvény jelenti a kezdetet és a rend() függvény a véget.

      A következő példában is egy vector típusú objektumot manipulálunk különböző módszerekkel:

#include <iostream>
using std::cout;
using std::endl;
#include <iterator>
#include <vector>
#include <algorithm>
#include <exception>

int main()
{
     const int SIZE = 6;
     int a[SIZE] = {1, 2, 3, 4, 5, 6};
     std::vector<int> v(a, a+SIZE);
    
     std::ostream_iterator<int> output(cout, " ");
     cout << "A vektor tartalma: ";
     std::copy(v.begin(), v.end(), output); //mindent a kimenetre
     cout << "\nA vektor első eleme: " << v.front() << "\nA vektor utolsó eleme: " << v.back();
    
     v[0] = 7;                       // első elem = 7 (1 helyett)
     v.at(2) = 10;                   // harmadik elem = 10 (3 helyett)
     v.insert(v.begin()+1, 22); // az első után beszúrja a 22-t (7 után)
     cout << "\nA vektor tartalma a módosítások után: ";
     std::copy(v.begin(), v.end(), output);
    
     try
     {
           v.at(100) = 725; //Hiba: a vektor csak 6 elemű
     }
     catch(std::exception& e)
     {
           cout << "\nKivétel: " << e.what();
     }
    
     v.erase(v.begin()); // kitörli a legelső elemet
     cout << "\nA vektor tartalma a törles után: ";
     std::copy(v.begin(), v.end(), output);
    
     v.erase(v.begin(), v.end()); // kitöröl mindent
     cout << "\nA törlés után a vektor " << (v.empty() ? "" : "továbbra sem ") << "üres.";
    
     v.insert(v.begin(), a, a+SIZE); // a vektort újra feltölti a tömb elemeivel
     cout << "\nA vektor tartalma a törlés előtt: ";
     std::copy(v.begin(), v.end(), output);
    
     v.clear(); // kitöröl mindent
     cout << "\nA törlés után a vektor " << (v.empty() ? "" : "továbbra sem ") << "üres.";
    
     return 0;
}

A kimenet:
A vektor tartalma: 1 2 3 4 5 6
A vektor első eleme: 1
A vektor utolsó eleme: 6
A vektor tartalma a módosítások után: 7 22 2 10 4 5 6
Kivétel: vector::_M_range_check: __n (which is 100) >= this->size() (which is 7)

A vektor tartalma a törlés után: 22 2 10 4 5 6
A törlés után a vektor üres.
A vektor tartalma a törlés előtt: 1 2 3 4 5 6
A törlés után a vektor üres.

Az std::vector<int> v(a, a+SIZE) utasítás egy int típusú v vektort deklarál. Meghívódik a vector osztálysablon túlterhelt konstruktora, mely két argumentumot kap. Mivel egy tömb mutatói iterátorként viselkednek, ez a deklaráció a v vektort az a tömb tartalmával inicializálja a-tól a+SIZE címig kizárólag.
Az std::ostream_iterator<int> output(cout, " ") utasítás definiálja az output iterátort, mely ostream_iterator típusú és felhasználható a cout -nál ha egész számokat kell kiírni szóközzel elválasztva. A copy() függvény az output iterátorra irányítja a begin()és end() közti tartalmát v-nek. Az első két paraméter a konténerből kiolvasott iterátorok értékei, a harmadik pedig egy kimenő iterátor. Hogy az STL függvények működjenek, a program elejére hozzá kell adni az <algorithm> headert. A v[0] = 7 és a v.at(2) = 10 utasítás két példa a vector objektum elemeinek eléréséhez. A [] operátorral ellentétben az at() függvény leellenőrzi, hogy az adott index benne van-e a vektor tartományában. Ha nincs, akkor kivétel keletkezik, amit kezelni lehet a try catch blokkok segítségével, amihez szükség van az <exception> headerre is. A v.insert(v.begin()+1, 22) utasítás az insert() függvényt használja, hogy betegye a 22 értéket az első paraméternek megadott pozíció után. A beszúrást követően minden más elem pozíciója eltolódik egy lépést. A v.insert(v.begin(), a, a+SIZE) utasítás is az insert() függvényt használja, de ezúttal három paraméterrel, ami azt jelenti, hogy a beszúrt elem egy másik konténerből származhat, melynek elejét és végét is meg kell adni. Ebben az esetben ez egy tömb, ám ez kezelhető konténerként is, mert a mutatói iterátoroknak felelnek meg. A v.erase(v.begin()) és a v.erase(v.begin(), v.end()) utasítások meghatározzák, hogy melyik elem vagy elemkollekció kerülhet törlésre. A v.clear() függvény mindet kitöröl v-ből.

List
      Egy láncolt lista egy olyan adatstruktúra, amelyben az elemek csomópontoknak nevezett lineáris objektumkollekciókból állnak, melyeket mutatók kötnek össze egymással. A lista az első csomópontra mutató mutatóval érhető el. Az utána következő csomópontok sorba ugrálva érhetők el, az utolsó csomópont a NULL-ba mutat. A duplán láncolt lista esetén minden csomópontnak két mutatója van: egy az előző, egy a következő csomóponthoz. A list szekvenciális konténer egy duplán láncolt listát valósít meg. Az iterátorai bidirectional típusúak azaz mindkét irányban bejárhatóvá teszik a konténert. Ez azt jelenti, hogy az input, output, forward és bidirectional típusú iterátorokat megkövetelő algoritmusok használhatóak ezzel a konténerrel. Az alapértelmezett tagfüggvények mellett a list osztály a következőket is tartalmazza: splice, push_front, pop_front, remove, unique, merge, reverse és sort. A következő program ezekből használ fel néhányat. Hogy a list olsztály működjön deklarálni kell a <list> header fájlt.

#include <iostream>
using std::cout;
using std::endl;
#include <list>
#include <algorithm>
#include <iterator>
template <class T>
void printList(const std::list<T> &listRef);

int main()
{
     const int SIZE = 4;
     int a[SIZE] = {2, 4, 6, 8};
     std::list<int> lista, otherlista;
    
     lista.push_front(1);
     lista.push_front(2);
     lista.push_back(4);
     lista.push_back(3);
     cout << "A lista tartalma: ";
     printList(lista);
    
     lista.sort();
     cout << "\nA rendezés után a lista tartalma: ";
     printList(lista);
    
     otherlista.insert(otherlista.begin(), a, a+SIZE);
     cout << "\nA beszúrás után az otherlista tartalma: ";
     printList(otherlista);
    
     lista.splice(lista.end(), otherlista);
     cout << "\nA splice után a lista tartalma: ";
     printList(lista);
    
     lista.sort();
     cout << "\nA rendezés után a lista tartalma: ";
     printList(lista);
    
     otherlista.insert(otherlista.begin(), a, a+SIZE);
     cout << "\nAz otherlista tartalma: ";
     printList(otherlista);
    
     lista.merge(otherlista);
     cout << "\nA merge: után a lista tartalma: ";
     printList(lista);
    
     cout << "\nAz otherlista tartalma: ";
     printList(otherlista);
    
     lista.pop_front();
     lista.pop_back();
     cout << "\nA pop_front és pop_back után a lista tartalma: ";
     printList(lista);
    
     lista.unique();
     cout << "\nAz unique után a lista tartalma: ";
     printList(lista);
    
     //a swap-ot minden konténer alkalmazhatja
     lista.swap(otherlista);
     cout << "\nA swap után a lista tartalma: ";
     printList(lista);
    
     cout << "\nAz otherlista tartalma: ";
     printList(otherlista);
    
     lista.assign(otherlista.begin(), otherlista.end());
     cout << "\nAz assign után a lista tartalma: ";
     printList(lista);
    
     lista.merge(otherlista);
     cout << "\nA merge után a lista tartalma: ";
     printList(lista);
    
     lista.remove(4);
     cout << "\nA remove után a lista tartalma: ";
     printList(lista);
    
     return 0;
}

template <class T>
void printList(const std::list<T> &listRef)
{
     if(listRef.empty())
           cout << "Üres!";
     else
     {
           std::ostream_iterator<T> output(cout, " ");
           std::copy(listRef.begin(), listRef.end(), output);
     }
}

A kimenet:
A lista tartalma: 2 1 4 3
A rendezés után a lista tartalma: 1 2 3 4
A beszúrás után az otherlista tartalma: 2 4 6 8
A splice után a lista tartalma: 1 2 3 4 2 4 6 8
A rendezés után a lista tartalma: 1 2 2 3 4 4 6 8
Az otherlista tartalma: 2 4 6 8
A merge után a lista tartalma: 1 2 2 2 3 4 4 4 6 6 8 8
Az otherlista tartalma: Üres!
A pop_front és pop_back után a lista tartalma: 2 2 2 3 4 4 4 6 6 8
Az unique után a lista tartalma: 2 3 4 6 8
A swap után a lista tartalma: Üres!
Az otherlista tartalma: 2 3 4 6 8
Az assign után a lista tartalma: 2 3 4 6 8
A merge után a lista tartalma: 2 2 3 3 4 4 6 6 8 8
A remove után a lista tartalma: 2 2 3 3 6 6 8 8

Ebben a programban két int típusú lista van deklarálva, a lista és otherlista. A push_front a lista élére, a push_back a lista végére szúr be értékeket. A
lista.push_front(1);
     lista.push_front(2);
     lista.push_back(4);
     lista.push_back(3);
utasítások után a lista tartalma 2, 1, 4, 3 lesz. A sort tagfüggvény ezt növekvő sorrendbe rendezi. Az otherlista a tartalmát az a tömbből kapja az insert függvény segítségével. A lista.splice(lista.end(), otherlista) parancs kitöröl minden elemet az otherlista-ból és berakja az első paraméternek megadott iterátor után a lista-ban. Ebben az esetben ez az iterátor a lista.end(), ezért a lista az 1, 2, 3, 4, 2, 4, 6, 8 elemeket tartalmazza. Ez után egy újabb rendezés következik (1, 2, 2, 3, 4, 4, 6, 8), majd az otherlista újból feltelik a tömb értékeivel. A merge függény kitöröl minden elemet az otherlista-ból és beteszi a lista-ba. Ez csak akkor működik, ha mindkét lista ugyanúgy rendezve van. A lista új elemei: 1, 2, 2, 2, 3, 4, 4, 4, 6, 6, 8, 8. A pop_front és pop_back kitöröl egy-egy elemet a lista elejéről és végéről, így a lista elemei: 2, 2, 2, 3, 4, 4, 4, 6, 6, 8. Az unique függvény kitörli duplákat: 2, 3, 4, 6, 8. A függvény meghívása előtt érdemes rendezni a listát, hogy a kettős elemek egymás után legyenek, ezáltal hamarább lefut az algoritmus. A swap felcseréli a lista és az otherlista elemeit egymás között. Ettől a lista tartalma üres lesz és az otherlista felveszi a 2, 3, 4, 6, 8. Az assign átmásolja az otherlista elemeit a lista-ba. A merge ismét összemossa a két listát a lista-ba, majd a remove(4) kivesz minden 4-es értéket a listából. A lista végső értéke: 2, 2, 3, 3, 6, 6, 8, 8.

Asszociatív konténerek
      Az asszociatív konténerek arra vannak tervezve, hogy tárolják és elérjék az elemeket a kulcsok segítségével, melyeket keresési kulcsoknak is hívnak. Asszociatív konténer a multiset, set, multimap és map. Mindenik típusban a kulcsok rendezett sorrendben vannak tárolva és ez alapján járja végig őket az iterátor. A multiset és set olyan halmazokkal dolgozik melyekben az értékek azonosak a kulcsokkal. Ezeket egyeztett értékeknek hívjuk. A multiset és set közti egyetlen különbség, hogy a multisetben lehetnek duplák is. Ugyanez érvényes a map és multimap-ra is. A többi konténerhez képest, az asszociatív konténerek rendelkeznek a find, lower_bound, upper_bound és count függvényekkel is.

multiset
      Egy multiset konténer elemeinek rendezettségét egy összehasonlító objektum adja meg. Például létezhet olyan int típusú multiset konténer, melyben az elemek növekvő sorrendbe rendeződnek, ha a kulcsokat a less<int> összehasonlító objektummal rendezzük. Ebben az esetben a kulcsoknak ismerniük kell az operator< összehasonlítást. Egy multiset konténer bidirectional típusú iterátort használ. Az alábbi program egy  növekvő sorrendbe rendezett multiset konténerrel végez műveleteket. A multiset és set osztályoknak ugyanazok a tagfüggvényei, és a <set> headerfájlt kell deklarálni, hogy használni lehessen őket.

#include <iostream>
using std::cout;
using std::endl;
#include <iterator>
#include <set>
#include <algorithm>

int main()
{
     const int SIZE = 10;
     int a[SIZE] = {7,22,9,1,18,30,100,22,85,13};
     typedef std::multiset<int, std::less<int> > ims; //ims = integer multiset
     ims intMultiset; //multiset növekvő kulcsokkal
      
     cout << "Összesen " << intMultiset.count(15) << " darab 15-ös van a multisetben.\n";
     intMultiset.insert(15);
     intMultiset.insert(15);
     cout << "A beszúrás után " << intMultiset.count(15) << " darab 15-ös van a multisetben.\n";
    
     ims::const_iterator result;     //a result egy konstans iterátor
     result = intMultiset.find(15);  //a find értéket ad az iterátornak
     if(result != intMultiset.end()) //ha az iterátor nem a legutolsó elemre mutat
           cout << "Létezik a 15-ös szám\n";
     result = intMultiset.find(20);
     if(result == intMultiset.end()) //ha az iterátor a legutolsó elemre mutat
           cout << "Nem létezik a 20-as szám\n";
          
     intMultiset.insert(a, a+SIZE); //feltölti a multisetet az "a" elemeivel
     std::ostream_iterator<int> output(cout, " "); //kimeneti iterátor
     cout << "A feltöltes után a multiset tartalma:\n";
     std::copy(intMultiset.begin(), intMultiset.end(), output);
    
     cout << "\nA 22-es baloldali szomszédja : " << *(intMultiset.lower_bound(22));
     cout << "\nA 22-es jobboldali szomszédja : " << *(intMultiset.upper_bound(22));
    
     std::pair<ims::const_iterator, ims::const_iterator> p;
     p = intMultiset.equal_range(22);
     cout << "\nAz equal_range szerint a 22-es bal szomszédja: " << *(p.first) << ", jobb szomszédja: " << *(p.second);

     return 0;
}

A kimenet:
Összesen 0 darab 15-ös van a multisetben.
A beszúrás után 2 darab 15-ös van a multisetben.
Létezik a 15-ös szam
Nem létezik a 20-as szám
A feltöltés után a multiset tartalma:
1 7 9 13 15 15 18 22 22 30 85 100
A 22-es baloldali szomszédja : 22
A 22-es jobboldali szomszédja : 30
Az equal_range szerint a 22-es bal szomszédja: 22, jobb szomszédja: 30

A count függvényt mindenik asszociatív konténer ismeri. A paraméterként megadott érték előfordulási számát téríti vissza. Az insert új elemet ad a konténerhez. Az elemek keresését a find függvény végzi, ami egy iterator-t vagy egy const_iterator-t térít vissza, amely az első megtalált értékre mutat. Ha semmit sem talált, akkor az ugyanazt az iterátort téríti vissza, amit az end függvény is eredményezne, így vele is ellenőrizhető. Az insert egész tömbök beszúrására is képes, ahogyan a korábbi példákban tette. A lower_bound és az upper_bound a paraméternek megadott érték szomszédaira mutató iterátort téríti vissza. Az std::pair<ims::const_iterator, ims::const_iterator> p utasítás létrehozza a p objektumot a pair osztályból. A pair objektumai arra jók, hogy értékpárokat kapcsoljanak össze. Itt például a p objektumnak két const_iterator iterátora van, melyeknek típusa ims. Azért van szükség a pair típusú objektumra, hogy tárolni lehessen az equal_range  függvény eredményét, ugyanis ez két iterátort térít vissza, a lower- és upper_bound iterátorokat. A két visszatérített iterátor a pair osztály publikus tagfüggvénye, a first és a second.

map
      A map konténert a kulcs-érték egyeztetések tárolására és keresésére használjuk. A kettős kulcsok nem elfogadottak, így minden kulcshoz csak egy érték tartozhat. A kereséshez a [] operátort használjuk, melybe a kulcsot írjuk, akár egy indexet. A set és multiset konténer számos függvénye használható a map és multimap konténerrel is.

#include<iostream>
using std::cout;
using std::endl;
#include <map>

int main()
{
     typedef std::map<int, double, std::less<int> > mid;
     mid pairs;
    
     pairs.insert( mid::value_type(15, 2.7) );
     pairs.insert( mid::value_type(30, 111.11) );
     pairs.insert( mid::value_type(5, 1010.1) );
     pairs.insert( mid::value_type(10, 22.22) );
     pairs.insert( mid::value_type(25, 33.333) );
     pairs.insert( mid::value_type(5, 77.54) );//kettős kulcs
     pairs.insert( mid::value_type(20, 9.345) );
     pairs.insert( mid::value_type(15, 99.3) );//kettős kulcs
    
     cout << "a map tartalma:\nKulcs\tÉrték\n";
     mid::const_iterator iter;
     for(iter = pairs.begin(); iter != pairs.end(); ++iter)
           cout << iter->first << '\t' << iter->second << '\n';
          
     pairs[25] = 9999.99;//a 25-ös kulcshoz tartozó érték
     pairs[40] = 8765.43;//a 40-es kulcshoz tartozó érték
     cout << "\nA műveletek után a map tartalma:" << "\nKulcs\tÉrték\n";
     for(iter = pairs.begin(); iter != pairs.end(); ++iter)
           cout << iter->first << '\t' << iter->second << '\n';
    
     return 0;
}

A kimenet:
A map tartalma:
Kulcs   Érték
5       1010.1
10      22.22
15      2.7
20      9.345
25      33.333
30      111.11

A műveletek után a map tartalma:
Kulcs   Érték
5       1010.1
10      22.22
15      2.7
20      9.345
25      9999.99
30      111.11
40      8765.43

A typedef std::map<int, double, std::less<int> > mid utasításban a typedef a mid álnevet adja a map konténernek, melyben a kulcsok int típusúak, az értékek double típusúak és az elemek növekvő sorrendben vannak rendezve. A mid pairs utasítás létrehozza mid típsú pairs objektumot. Az elemek hozzáadási ismét az insert függvénnyel történik, ám a paraméterbe egyszerre kell szerepeljen a kulcs és az érték is: mid::value_type(15, 2.7), ahol 15 a kulcs, 2.7 az érték. A kettős kulcsok esetén a második változat nem kerül be a konténerbe. A mid::const_iterator iter egy konstans iterátort deklarál, amellyel végigjárható a map. Ez az iterátor egyformán hordozza a kulcs (first) és érték (second) információt is. A [] operátor lehetőséget ad, hogy a kulcs segítségével hivatkozzunk az értékre. Ha a kulcs nem létezik, akkor a [] operátor berakja a konténerbe az értékkel együtt.

Adapterek
      Az STL három adaptert bocsát rendelkezésre: stack, queue és priority_queue. Ezek nem first-class konténerek, mert önmagukban nem alkotnak adatstruktúrát, ahol az adatokat tárolni lehet és nem is tudnak iterátorokkal dolgozni. A programozónak kell kiválasztania az adatstruktúrát, amit az adapterhez csatol. Mindenik adapter ismeri a push is pop függvényeket, melyek a beszúró és törlő műveleteket végzik.

stack
      A stack osztály a hozzá csatolt adatstruktúra egyik végéről enged csak beszúrni és kivenni (LIFO verem). A stack objektuma bármilyen szekvenciális konténerrel használható (vector, list, deque).

#include <iostream>
using std::cout;
using std::endl;
#include <stack>
#include <vector>
#include <list>
template <class T>
void popElements(T &s);

int main()
{
     std::stack<int> intDequeStack;                      //deque-hez csatolt stack
     std::stack<int, std::vector<int> > intVectorStack;  //vector-hoz csatolt stack
     std::stack<int, std::list<int> > intListStack;      //list-hez csatolt stack

     for(int i=0; i<10; ++i) // mindeniket feltölti 1-től 10-ig
     {
           intDequeStack.push(i);
           intVectorStack.push(i);
           intListStack.push(i);
     }

     cout << "intDequeStack: ";
     popElements(intDequeStack);
     cout << "\nintVectorStack: ";
     popElements(intVectorStack);
     cout << "\nintListStack: ";
     popElements(intListStack);

     return 0;
}

template <class T>
void popElements(T &s)
{
     while(!s.empty())
     {
           cout << s.top() << ' ';
           s.pop();
     }
}

A kimenet:
intDequeStack: 9 8 7 6 5 4 3 2 1 0
intVectorStack: 9 8 7 6 5 4 3 2 1 0
intListStack: 9 8 7 6 5 4 3 2 1 0

Ha nem csatolunk semmilyen adatstruktúrát az adapterhez, akkor alapból a deque konténer csatolódik hozzá. A pop függvény nem térít vissza semmit, egyszerűen csak törli a legfelső elemet. A legfelső elemet a top téríti vissza anélkül hogy törölné.

queue, priority_queue
      A queue osztály a hozzácsatolt adatstruktúra végére szúr be és az elejéről töröl ki. A queue objektuma list és deque adatstruktúrákkal működik. Ha semmi nincs hozzácsatolva, akkor alapból a deque-t használja. A push a push_back, a pop a pop_front függvényt hívja meg.
      A priority_queue osztály rendezni is tudja a beszúrt elemeket, és az objektuma vector és deque adatstruktúrákkal működik. Alapból növekvő sorrendbe rendeződnek az elemek (less<T>), de ezen lehet módosítani.

#include <iostream>
using std::cout;
using std::endl;
#include <queue>

int main()
{
     std::queue<double> values;
     std::priority_queue<double> priorities;
    
     values.push(3.2);
     values.push(9.8);
     values.push(5.4);
     cout << "Pop a queue-ből: ";
     while(!values.empty())
     {
           cout << values.front() << ' '; //kiírja
           values.pop(); //törli
     }
    
     priorities.push(3.2);
     priorities.push(9.8);
     priorities.push(5.4);
     cout << "\nPop a priority_queue-ből: ";
     while(!priorities.empty())
     {
           cout << priorities.top() << ' '; //kiírja
           priorities.pop(); //törli
     }

     return 0;
}

A kimenet:
Pop a queue-ből: 3.2 9.8 5.4
Pop a priority_queue-ből: 9.8 5.4 3.2

A values és priorities két vermet valósít meg. Az egyik queue, a másik priority_queue típusú. A queue típus a front, a priority queue a top függvényt használja a verem első elemének kiolvasásához.

Algoritmusok
      Az STL bevezetése előtt az osztálykönyvtárak és a különböző fejlesztők algoritmusai nem voltak kompatibilisek egymással. Az algoritmusok az osztályok funkcióiként szolgáltak. Az STL az iterátorok segítségével elválasztotta ezt a kettőt, így sokkal egyszerűbb az algoritmusok használata és bővítése. Az algoritmusok nem függenek a konténerek felépítésének részleteiből, és amíg eleget tesznek az algoritmusok feltételeinek, addig bármilyen típusú adatstruktúrákkal alkalmazhatóak. Jelenleg közel 100 STL algoritmus létezik, melyek közül néhány a következő programokban alkalmazva van:

A fill, fill_n, generate és generate_n algoritmusok
   A fill és fill_n függvények feltöltik a konténer elemeit vagy elemcsoportját egy értékkel. A generate és generate_n függvények is ugyanezt teszik, csak minden elemet vagy elemcsoportot más értékkel töltenek fel. A generátorfüggvényeknek nincsenek argumentumai, csupán visszatérítenek egy értéket amit aztán a konténer elemének lehet átadni.

#include <iostream>
using std::cout;
#include <algorithm>
#include <vector>
#include <iterator>

char nextLetter();

int main()
{
     std::vector<char> chars(10);
     std::ostream_iterator<char> output(cout, " ");
    
     std::fill(chars.begin(), chars.end(), '5'); //eléjetől végéig 5-tel tölti fel
     cout << "A chars vektor tartalam a fill(5) után:    ";
     std::copy(chars.begin(), chars.end(), output);
    
     std::fill_n(chars.begin(), 5, 'A'); //elejétől az első 5 elemet A-val tölti fel
     cout << "\nA chars vektor tartalma a fill_n(5,A) után:";
     std::copy(chars.begin(), chars.end(), output);
    
     std::generate(chars.begin(), chars.end(), nextLetter); //elejétől végéig feltölti A-val kezdve
     cout << "\nA chars vektor tartalma a generate(nextLetter) után:\t";
     std::copy(chars.begin(), chars.end(), output); //mivel 10 elem van, a J-ig jut el
    
     std::generate_n(chars.begin(), 5, nextLetter); //elejétől az első 5 elemet K-val kezdve tölti fel
     cout << "\nA chars vektor tartalma a generate_n(5,nextLetter) után:";
     std::copy(chars.begin(), chars.end(), output); //mivel 10 elem van, az O-ig jut el
    
     return 0;
}

char nextLetter()
{
     static char letter = 'A';
     return letter++;
}

A kimenet:
A chars vektor tartalma a fill(5) után:    5 5 5 5 5 5 5 5 5 5
A chars vektor tartalma a fill_n(5,A) után:A A A A A 5 5 5 5 5
A chars vektor tartalma a generate(nextLetter) után:    A B C D E F G H I J
A chars vektor tartalma a generate_n(5,nextLetter) után:K L M N O F G H I J

A fill argumentumai megkövetelik a az első és utolsó pozíciót is amit fel kell tölteni, a fill_n pedig a kezdeti pozíciót és az attól számított elemszámot kéri a feltöltéshez. A generate függvények minden elemre meghívják a nextLetter függvényt.

Matematikai algoritmusok
   Ide tartoznak például a random_shuffle, count, count_if, min_element, max_element,
accumulate, for_each és a transform függvények.

#include <iostream>
using std::cout;
#include <algorithm>
#include <numeric> //accumulate
#include <vector>
#include <iterator>

bool nagyobbmint9(int);
void negyzet(int);
int kob(int);

int main()
{
     const int SIZE = 10;
     int a1[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
     int a2[] = {100, 2, 8, 1, 50, 3, 8, 8, 9, 10};
     int result;
     std::vector<int> v(a1, a1+SIZE);  //v egy vektor a1 tartalmával
     std::vector<int> v2(a2, a2+SIZE); //v2 egy vektor a2 tartalmával
     std::vector<int> cubes(SIZE);     //cubes egy 10 elemű üres vektor
     std::ostream_iterator<int> output(cout, " "); //output egy kiíró operátor

     cout << "A v vektor tartalma: ";
     std::copy(v.begin(), v.end(), output);
     std::random_shuffle(v.begin(), v.end());
     cout << "\nA v vektor tartalma a random_shuffle után: ";
     std::copy(v.begin(), v.end(), output);

     cout << "\n\nA v2 vektor tartalma: ";
     std::copy(v2.begin(), v2.end(), output);
    
     result = std::count(v2.begin(), v2.end(), 8);
     std::cout << "\nA 8-asok száma: " << result;
    
     result = std::count_if(v2.begin(), v2.end(), nagyobbmint9);
     std::cout << "\nA 9-nél nagyobb elemek száma: " << result;
     std::cout << "\nA legkisebb elem: " << *(std::min_element(v2.begin(), v2.end()));
     std::cout << "\nA legnagyobb elem: " << *(std::max_element(v2.begin(), v2.end()));
     std::cout << "\nAz elemek összege: " << std::accumulate(v.begin(), v.end(), 0);
    
     std::cout << "\n\nA v elemei négyzetre emelve: ";
     std::for_each(v.begin(), v.end(), negyzet);
    
     std::transform(v.begin(), v.end(), cubes.begin(),kob);
     cout << "\nA v elemei a köbre emelve: ";
     std::copy(cubes.begin(), cubes.end(), output);

     return 0;
}

bool nagyobbmint9(int value) {return value > 9;}
void negyzet(int value) {cout << value * value << ' ';}
int kob(int value) {return value * value * value;}

A kimenet:
A v vektor tartalma: 1 2 3 4 5 6 7 8 9 10
A v vektor tartalma a random_shuffle után: 9 2 10 3 1 6 8 4 5 7

A v2 vektor tartalma: 100 2 8 1 50 3 8 8 9 10
A 8-asok száma: 3
A 9-nél nagyobb elemek száma: 3
A legkisebb elem: 1
A legnagyobb elem: 100
Az elemek összege: 55

A v elemei négyzetre emelve: 81 4 100 9 1 36 64 16 25 49
A v elemei a köbre emelve: 729 8 1000 27 1 216 512 64 125 343

Az std::random_shuffle(v.begin(), v.end()) véletlenszerűen rendezi a verem elemeit a megadott pozíciók között. Az argumentumban megadott iterátorok random-access típusúak. Az std::count(v2.begin(), v2.end(), 8) utasítás megszámolja a nyolcasokat a megadott pozíciók között. Az std::count_if(v2.begin(), v2.end(), nagyobbmint9) utasítás megszámolja hányszor térít vissza true-t a nagyobbmint9 függvény a megadott pozíciókon belül. Az a std::min_element(v2.begin(), v2.end()) legkisebb elemre mutató iterátort téríti vissza a megadott pozíciók között. Az iterátor által mutatott érték a * operátorral írható ki. Az std::accumulate(v.begin(), v.end(), 0) sablonja a <numeric> header fájlban vann deklarálva és feladata, hogy összeadja a két pozíció közti értékeket. Az std::for_each(v.begin(), v.end(), negyzet) művelet a pozíciók által meghatározott elemek mindenikére meghívja a harmadik paraméterként megadott függvényt. Ehhez hasonlít az std::transform(v.begin(), v.end(), cubes.begin(),kob) művelet, viszont az eredményt egy másik vektorba teszi. Sem a transform sem a for_each nem változtat a vektor eredeti tartalmán.

A következő példa egy beolvasott 5 elemű vektorról állapítja meg, hogy palindrom-e vagy sem:

#include<iostream>
using std::cout;
using std::cin;
#include<vector>
#include<iterator>
template <class T>
bool palindrom(T &s);

int main()
{
     std::vector<int> v;
     int ertek;
    
     cout << "Kerem a vektor tartalmát: ";
     std::istream_iterator<int> beolvas(cin);
    
     for(int i=1; i<=5; ++i)
     {
           ertek = *beolvas;
           v.push_back(ertek);
           if (i<5) ++beolvas;
     }
    
     cout << "A vektor tartalma: ";
     std::ostream_iterator<int> kiir(cout, " ");
     std::copy(v.begin(), v.end(), kiir);
    
     if(palindrom(v))
           cout << "\nA vektor palindrom";
     else
           cout << "\nA vektor nem palindrom";
    
     return 0;
}

template <class T>
bool palindrom(T &s)
{   
     std::vector<int>::const_iterator i;
     std::vector<int>::reverse_iterator r=s.rbegin();
     bool flag = 1; //palindrom

     for(i=s.begin(); i!=s.end(); ++i)
     {
           if (*r!=*i) flag = 0;
           ++r;
     }
    
     if(flag==1) // ha még mindig palindrom
           return 1;
     else
           return 0;
}

A kimenet:
Kérem a vektor tartalmát: 1 2 3 2 1
A vektor tartalma: 1 2 3 2 1
A vektor palindrom