Table of Contents

Laboratorul 11: STL

In cadrul acestui laborator vom aprofunda cunostintele legate de componentele parametrizate, functii clase template, si vom vorbi despre STL (Standard Template Library). Aceasta biblioteca generalizata ne va permite sa folosim algoritmi, structuri de date deja implementate.

Ca referinte externe, recomandam urmatorul capitol din Absolute C++:

1. Introducere

STL (Standard Template Library) face parte din C++ Standard Library si este un set de clase template ce implementeaza structuri de date cunoscute si metode asociate lor.

STL contine trei componente cheie:

  • Clase Container
  • Iteratori
  • Algoritmi

Containerul reprezinta un obiect ce contine o colectie de alte obiecte. Clasele container implementeaza structuri de date clasice sub forma template, astfel ofera flexibilitate in utilizare. De asemenea, se ocupa de managementul spatiului de memorie pentru elemente si pun la dispozitie functii membre pentru accesul la obiecte.

Iteratorii permit accesul la elementele dintr-un container sau ajuta la parcurgerea acestora, independent de modul in care sunt stocate. Iteratorii reprezinta o generalizare a pointerilor, fiind obiecte ce indica alte obiecte (elementele din container).

Fiecare clasa container are definiti iteratorii proprii.

STL puna la dispozitie algoritmi pentru procesarea elementelor din colectii (cautare, partitionare, ordonare, lucru cu multimi, gasire minim/maxim, etc.). Algoritmii au ca parametrii iteratori si reprezinta functii template ce nu sunt metode ale claselor container.

2. Containere

Exista mai multe tipuri de containere:

Structura de date implementata Nume STL #include
Tablou dinamic vector <vector>
Lista dublu inlantuita list <list>
Stiva stack <stack>
Coada cu doua capete queue <queue>
Arbore binar/Multime set <set>
Multime (se pot repeta valorile) multiset <set>
Hash table map <map>
Hash table (se pot repeta valorile) multimap <map>
Max-heap priority_queue <queue>

Toate containerele sunt template.

Exemplu: vector<int> vect;

Toate containerele au iteratori asociati, iar tipul lui e specificat prin:

container<T>::iterator

Toate containerele au urmatoarele functii membre:

Functie membra Rezultat
int size() returneaza numarul de elemente din container
iterator begin() returneaza iterator la primul element
iterator end() returneaza iterator la nodul santinela (ultimul element al containerului)
bool empty() returneaza true daca obiectul container este gol

2.1. Vector

Clasa vector permite realizarea unui vector alocat dinamic, ce poate contine elemente de orice tip.

Clasa permite acces usor la orice element prin operatorul de indexare sau prin iteratori.

Implementarea din STL pune la dispozitie constructori, operator=, operator[], diferite metode pentru manipularea obiectelor din vector, realocarea spatiului, etc.

Exemplu apel constructori:
#include <vector>
using namespace std;
 
vector<int> a;           //Vector gol cu elemente de tip int
vector<double> b(10);    //Vector cu 10 elemente de tip double
vector<int> c(5,9);      //Vector cu 5 elemente de tip int initializate cu valoarea 9
vector<int> d(a);        //Copia vectorului a

Cateva metode din clasa vector:
push_back() – adauga un element la finalul vectorului si creste dimensiunea acestuia cu 1
pop_back() – scoate ultimul element din vector si reduce dimensiunea acestuia cu 1
clear() – scoate toate elementele din vector si seteaza dimensiunea acestuia la 0
empty() – returneaza true daca vectorul este gol (false altfel)
resize() – modifica dimensiunea vectorului
capacity() – returneaza capacitatea setata prin constructor sau cu functia resize
insert() – insereaza un element pe pozitia specificata; creste dimensiunea
erase() – scoate elementul de pe pozitia specificata; scade dimensiunea

Pentru toate metodele puse la dispozitie de clasa vector, recomandam urmatoarea documentatie:

http://www.cplusplus.com/reference/vector/vector/

2.2. List

Containerul List implementeaza o lista dublu inlantuita, ale carei elemente sunt imprastiate prin memorie si conectate prin pointeri. Lista poate fi parcursa in ambele sensuri, atat inainte, cat si inapoi.

Putem adauga sau elimina elemente atat de la inceputul listei, cat si de la finalul acesteia folosind metodele: push_back, push_front, pop_back, pop_front. Complexitatea acestor operatii este O(1).

Pentru a adauga sau sterge elemente de pe anumite pozitii se folosesc iteratori.

Principalul dezavantaj al listelor este ca NU se pot accesa imediat valori de pe o anumita pozitie.

Inserarea, mutarea si stergea unui element se face cu costuri mici, indiferent de pozitie.

Exemplu:
#include <iostream>
#include <list>
#include <iterator>
using namespace std;
 
//Functie pentru afisarea elementelor din container
void showlist(list<int> g) 
{ 
    //Iterator lista
    list<int>::iterator it; 
 
    //Parcurgere container
    for(it = g.begin(); it != g.end(); ++it) {
	cout << *it << ' ';
    }
    cout << endl; 
} 
 
int main() {
    list<int> myList;
 
    myList.push_back(5);	//Adauga elementul 5 la finalul listei
    myList.push_front(3);	//Adauga elementul 3 la inceputul listei
    myList.push_back(10);	//Adauga elementul 10 la finalul listei
    myList.push_front(1);	//Adauga elementul 1 la inceputul listei
 
    showlist(myList);	//Afiseaza 1 3 5 10
 
    return 0;
}

Pentru toate metodele puse la dispozitie de clasa list, recomandam urmatoarea documentatie:

http://www.cplusplus.com/reference/list/list/

2.3. Stack si Queue (Stiva si Coada)

Containerul stack implementeaza o stiva, adica o lista simplu inlantuita de tip LIFO (Last In First Out). Varful stivei este singurul obiect al containerului care poate fi accesat.

Containerul queue implementeaza o coada, adica o lista simplu inlantuita de tip FIFO (First In First Out). Avem acces atat la inceputul cozii, cat si la finalul acesteia.

In ambele containere o sa se adauge elemente folosind metoda push si vor fi eliminate elemente folosind metoda pop.

Exemplu:
#include <iostream>
#include <queue>
#include <stack>
using namespace std;
 
int main() {
    //Stiva cu elementele de tip char
    stack<char> s;
    //Coada cu elementele de tip int
    queue<int> q;
 
    //Adaugare elemente in varful stivei folosind push
    s.push('a');
    s.push('b');
    s.push('c');
 
    //Adaugare elemente la sfarsitul cozii folosind push
    q.push(0);
    q.push(1);
    q.push(2);
 
    //Cat timp stiva nu este goala
    while (!s.empty()) {
        cout << s.top() << ' ';	//Afisam elementul din varful stivei
	s.pop();    //Eliminam elementul din varful stivei
    }
    cout << endl;
    //Se va afisa:
    //c b a
 
    //Cat timp coada nu este goala
    while (!q.empty()) {
        cout << q.front() << ' ';    //Afisam elementul de la inceputul cozii
	q.pop();    //Eliminam elementul de la inceputul cozii
    }
    //Se va afisa:
    //0 1 2
 
    return 0;
}

Stivele si cozile NU au iteratori disponibili!

Pentru toate metodele puse la dispozitie de clasele stack si queue, recomandam urmatoarele documentatii:

http://www.cplusplus.com/reference/stack/stack/ http://www.cplusplus.com/reference/queue/queue/

2.4. Priority Queue (Heap)

Containerul priority_queue implementeaza o coada cu prioritati, adica o structura de tip max-heap, in care cea mai mare valoare este intotdeauna in varf.

Avand in vedere faptul ca se fac comparatii intre valorile tipurilor de date prezente in container, trebuie sa definim mereu operator< pentru tipul respectiv de date.

Exemplu:
#include <iostream>
#include <queue>
using namespace std;
 
int main() {
    //Container cu elemente de tip int
    priority_queue<int> pq;
 
    //Adaugam elemente in coada cu prioritati
    pq.push(10);
    pq.push(20);
    pq.push(5);
    //Eliminam elementul din varf, adica elementul 20 (cu cea mai mare prioritate)
    pq.pop();
 
    cout << pq.top() << endl;    //Se va afisa 10
 
    return 0;
}

Nici coada cu prioritati NU are iteratori disponibili!

Pentru toate metodele puse la dispozitie de clasa priority_queue, recomandam urmatoarea documentatie:

http://www.cplusplus.com/reference/queue/priority_queue/

2.5. Set (Arbori Binari)

Containerele de tip set realizeaza un arbore binar balansat (de cautare), deci cautarea unui element va fi eficienta.

Pentru a se crea o multime, tipul de date trebuie sa aiba operator< definit.

Toate elementele din containerul set sunt distincte (in caz contrar se foloseste containerul multiset).

Obiectele din container sunt ordonate si nu mai pot fi modificate, acestea pot fi doar sterse sau se pot adauga noi obiecte.

Operatii de baza pentru containerul set:

  • insert(element)
  • erase(iterator)
  • erase(valoare element)
  • iterator find(valoare element)

Exemplu:
#include <iostream>
#include <set>
using namespace std;
 
int main() {
    //Multime cu elemente de tip int
    set<int> mySet;
 
    //Adaugam elemente in containerul set
    mySet.insert(1); 
    mySet.insert(4); 
    mySet.insert(2); 
    mySet.insert(5); 
    mySet.insert(3); 
 
    //Iterator pentru parcurgere set
    set<int>::iterator it;
 
    //Parcurgem containerul set
    for (it = mySet.begin(); it != mySet.end(); it++) {
    	cout << *it <<' ';    //Afisare element
    }
    cout << endl;
    //Se va afisa:
    //1 2 3 4 5
 
    return 0;
}

Pentru toate metodele puse la dispozitie de clasa set, recomandam urmatoarea documentatie:

http://www.cplusplus.com/reference/set/set/

2.6. Map (Hash Table)

Un container de tip map ne permite sa cautam intr-o maniera rapida un obiect folosind o cheie unica (spre exemplu cautam o persoana dupa id-ul acesteia).

Un astfel de container foloseste o pereche de tipuri template:

map<K, T>

K reprezinta tipul de date al cheii, iar T reprezinta tipul de date pe care vrem sa le stocam.

Containerul map va contine elementele indexate cu indecsi de tipul K al cheii.

Pentru tipul de date K al cheii este nevoie ca operator< sa fie definit, altfel se va folosi un container de tip unordered_map.

Elementele containerului sunt mereu sortate in functie de cheie (similar cu un arbore binar de cautare).

Functia insert pentru containerul map asteapta un argument de tip pair.

Pair este o structura template care are atributele first si second.

map<int, char> myMap;
 
pair<int, char> obj(10, 'x');
myMap.insert(obj);

Se poate folosi functia operator[] pentru inserarea in map.

map<char, double> myMap;
 
myMap['a'] = 20.189;
myMap['b'] = 3.1415;

Exemplu:
#include <iostream>
#include <map>
using namespace std;
 
int main() {
    //Map cu tipurile de date char, double
    map<char, double> myMap;
    //Iterator pentru map
    map<char, double>::iterator it;
 
    //Adaugare element folosind operator[]
    myMap['a'] = 18.91;
 
    //Adaugare element folosind insert() si pair
    pair<char, double> obj('b', 3.1415);
    myMap.insert(obj);
 
    it = myMap.find('a');	//Cautam elementul cu cheia 'a'
    cout << it->second << endl;	//Se va afisa 18.91
 
    it = myMap.find('b');	//Cautam elementul cu cheia 'b'
    cout << it->second << endl;	//Se va afisa 3.1415
 
    return 0;
}

Pentru toate metodele puse la dispozitie de clasa map, recomandam urmatoarea documentatie:

http://www.cplusplus.com/reference/map/map/

2.7. Ce si cand folosim?

  • Daca dorim sa cautam in mod rapid date pe baza unei chei, o sa folosim map.
  • Elementele sunt unice, au operator< definit si sunt de interes cautari rapide, utilizam set.
  • Daca este nevoie in principal de elementul cu cea mai mare valoare, folosim priority_queue.
  • Daca este de interes adaugarea rapide de elemente si nu sunt accesate des, o sa utilizam list.
  • Daca depindem de accesul rapid la elemente stocate pe o anumita pozitie, recomandat este vector.

3. Iteratori

Urmatoarele operatii de pointeri sunt definite pentru aproape toti iteratorii:

  • it++ //muta iteratorul it cu o pozitie inainte
  • it-- //muta iteratorul it cu o pozitie inapoi
  • it1 = it2 //atribuie iteratorului it1 iteratorul it2
  • it1 == it2 si it1 != it2 //compara pozitiile dintre iteratorii it1 si it2

In clasa vector iteratorul poate fi mutat cu mai multe pozitii intainte sau inapoi.

it = it + 4;

sau

it = it - 3;

Toate containerele STL contin metodele begin() si end() care returneaza iteratori.

Metoda begin() pointeaza la primul element al containerului.

Metoda end() pointeaza la nodul santinela (care nu contine niciun element, se afla imediat dupa ultimul element al containerului).

Cand o functie de cautare NU gaseste elementul pe care il cauta returneaza iteratorul end().

Pentru mai multe informatii privind iteratorii, recomandam referinta externa:

http://www.cplusplus.com/reference/iterator/

4. Biblioteca <algorithm>

Contine o serie de functii care sunt utile pentru lucrul cu elementele din containere:

De obicei, functiile primesc ca parametri iteratori pentru pozitiile de inceput si final ale intervalului unde vrem sa realizam operatia.

NU sunt functii membre ale claselor!

Exemplu:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
 
int main() {
    //Container vector cu elemente de tip int
    vector<int> vect;
 
    //Adaugam elemente
    vect.push_back(10);
    vect.push_back(30);
    vect.push_back(15);
    vect.push_back(20);
    vect.push_back(10);
 
    //Iterator pentru parcurgere vector
    vector<int>::iterator it;
 
    for (it = vect.begin(); it != vect.end(); it++) {
    	cout << *it << ' ';	//Afisam elementele
    }
    cout << endl;
    //Se va afisa:
    //10 30 15 20 10
 
    //Sortam vectorul folosind functia sort
    sort(vect.begin(), vect.end());
 
    for (it = vect.begin(); it != vect.end(); it++) {
    	cout << *it << ' ';    //Afisam elementele
    }
    cout << endl;
    //Se va afisa:
    //10 10 15 20 30
 
    cout << count(vect.begin(), vect.end(), 10) << endl; 
    //Se va afisa 2 (numarul de aparitii al lui 10)
 
    return 0;
}

Pentru mai multe informatii privind biblioteca <algorithm>, recomandam referinta externa:

http://www.cplusplus.com/reference/algorithm/