Documente Academic
Documente Profesional
Documente Cultură
- Introducere
Pe lng avantajele oferite de posilibatea de a menine i nelege mai uor
aplicaiile, programarea orientat pe obiecte l ofer pe acela al reutilizrii codului.
C++ standard include o bibliotec standard care cuprinde o colecie foarte
cuprinztoare de componente reutilizabile. Vom introduce n acest capitol Standard
Template Library (STL) i vom prezenta cele trei componente cheie ale acestei
biblioteci: containerii (structuri de date sub form de template-uri), iteratorii i
algoritmii. Deoarece STL cuprinde foarte multe clase, vom prezenta doar o parte
dintre acestea nsoite de cteva exemple.
n anii 70, componentele folosite n programe erau sub forma structurilor de
control i a funciilor. n anii 80, au nceput s se foloseasc componente sub form
de clase dintr-o gam larg de biblioteci dependente de platform. n ultima parte a
anilor 90, odat cu apariia STL se introduce un nou nivel de folosire a
componentelor prin clase independente de platform, fiind de ateptat ca n urmtorii
ani numrul acestor clase s creasc exponenial.
Structurile de date sunt colecii de date sau containeri organizate dup diverse
reguli. n programarea orientat pe obiecte, structurile de date sunt obiecte care
conin colecii de obiecte. S presupunem ca implementm o clas Array care ar
reprezenta un tablou de obiecte de tip int. Folosind template-uri, am putea extinde
acest tip de dat la Array<T> astfel nct s putem implementa Array<char>,
Array<double>, Array<Employee> sau, n general, tablouri de orice tip de dat.
n mod similar putem proceda pentru implementarea structurilor de date de tip stiv
sau pentru alte tipuri de structuri de date. STL este o bibliotec de clase template
care conine, ntre altele, i implementri ale structurilor de date.
n C i C++ obinuim s accesm elementele unui tablou folosind pointeri. n
C++ STL, accesm elementele containerilor prin obiecte iterator care arat ca
pointerii, dar se comport mai inteligent. Clasele iterator sunt proiectate s poat fi
folosite generic pentru orice container. Containerii implementeaz operaii primitive,
iar algoritmii STL sunt implementai independent de containeri.
Containerii STL sunt unul dintre cele mai bune exemple de reutilizare a codului
i de folosire a claselor template. Implementrile structurilor de date care fac
legturile ntre obiecte prin pointeri pot conduce la probleme serioare legate de
gestiunea dinamic a memoriei, erori care nu pot fi detectate la compilare i care
sunt uneori foarte dificil de rezolvat. Unul dintre marile avantaje ale containerilor STL
este c rezolv aceste neajunsuri.
STL evit folosirea motenirii i a funciilor virtuale din considerente de
performan. De asemenea, STL evit folosirea operatorilor new i delete n
favoarea alocatorilor pentru a permite o mai mare varietate de metode de control al
alocrii i dealocrii memoriei.
Containeri
Containerii STL sunt de trei tipuri: containeri secven, containeri asociativi i
adaptori container.
Containerii secven
- vector fiierul header <vector> implementarea unui tablou dinamic, cu
redimensionare automat la inserarea unui nou element sau la tergerea unui
element;
- list fiierul header <list> - list dublu nlnuit cu inserare i tergere
rapid;
- deque fiierul header <deque> - coad n care elementele pot fi adugate sau
terse din ambele capete; difer de coada obinuit prin faptul c acolo
adugarea i tergerea elementelor se face la un singur capt.
Containerii asociativi
- map fiierul header <map> pstreaz asocieri ntre perechi de valori i chei,
mapare unu-la-unu;
- multimap fiierul header <map> este similar cu map, dar accept duplicate,
mapare unu-la-mai multe;
- set fiierul header <set> cheile sunt chiar valorile pstrate;
- multiset fiierul header <set> este similar cu set, dar accept duplicate.
Adaptori container
- stack fiierul header <stack> implementeaz o stiv last-in-first-out
(LIFO);
- queue fiierul header <queue> implementeaz o coad first-in-first-out
(FIFO);
- priority_queue fiierul header <queue> coad n care elementul cu
prioritatea cea mai mare este ntotdeauna primul element extras.
Coninutul tuturor fiierelor header face parte din namespace std. Containerii
secven i cei asociativi sunt grupai generic sub denumirea first-class containers.
Containerii STL au fost proiectai n aa fel nct dispun de o serie de funcionaliti
comune. Iat cteva dintre acestea:
- constructor implicit;
- constructor de copiere;
- destructor;
- empty funcie care returneaz true dac sunt elemente n container i false
n caz contrar;
- operator=;
- operator<;
- operator<=;
- operator>;
- operator>=;
- operator==;
- operator!=;
- erase funcie care terge unul sau mai multe elemente din container (doar n
first-class containers);
2
- clear funcie care terge toate elementele din container (doar n first-class
containers).
Atunci cnd decidem s folosim un container, este important s ne asigurm c
tipul de dat elementelor care urmeaz a fi stocate n container dispune de un set
minim de funcionaliti. La inserarea unui element ntr-un container se face o copie a
acestuia, de aceea tipul de dat al elementului trebuie s dispun de un constructor
de copiere i de un operator de asignare.
Iteratori
Iteratorii au multe caracteristici comune cu pointerii i sunt folosii, printre altele,
pentru a pointa la elemente ale containerilor de tip first-class. Un iterator este un
obiect care permite programatorului s parcurg toate elementele unei colecii,
indiferent de implementarea acesteia. Exist iteratori STL a cror implementare este
adaptat unor anumii containeri. Sunt, ns, iteratori care pot opera asupra tuturor
containerilor. De exemplu, operatorul de derefereniere * se poate aplica unui iterator
pentru a folosi elementul asupra cruia pointeaz. Operatorul ++ aplicat unui iterator
mut poziia iteratorului asupra urmtorului element din container.
Folosim iteratorii mpreun cu secvenele. Secvenele pot fi organizate n
containeri sau pot fi secvene de intrare ori ieire. Programul de mai jos
demonstreaz citirea datelor folosind istream_iterator de la intrarea standard
care poate fi privit ca o secven de date de intrare n program. Programul
ilustreaz, de asemenea, tiprirea datelor folosind ostream_iterator la ieirea
standard care poate fi privit ca o secven de date de ieire din program. Programul
citete de la tastatur doi ntregi i afieaz suma lor.
Exemplu
test_iostream_iterators.cpp
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
#include <iterator>
int main()
{
cout << "Introduceti doua numere intregi: ";
std::istream_iterator<int> inputInt(cin);
int number1, number2;
number1 = *inputInt; //citeste primul int
++inputInt;
//muta iteratorul pe urmatoarea valoare
number2 = *inputInt; //citeste urmatorul int
cout << "Suma este: ";
std::ostream_iterator<int> outputInt(cout);
*outputInt = number1 + number2; //tiparire la cout
cout << endl;
return 0;
3
}
Rulnd acest program, putem obine urmtorul rezultat:
Introduceti doua numere intregi: 12 25
Suma este: 37
Prin instruciunea
std::istream_iterator<int> inputInt(cin);
se creeaz un iterator de tip istream_iterator care este capabil s extrag valori
de tip int de la dispozitivul standard de intrare cin ntr-o manier sigur.
Prin dereferenierea iteratorului inputInt se citete prima valoare ntreag din
cin:
number1 = *inputInt; //citeste primul int
Operaia ++inputInt mut iteratorul pe urmtoarea valoare din secvena de
date de intrare.
Prin declaraia
std::ostream_iterator<int> outputInt(cout);
se creeaz un iterator de tip ostream_iterator prin care se insereaz valori de tip
int la ieirea standard cout.
Asignarea
*outputInt = number1 + number2;
permite tiprirea sumei celor doi ntregi derefereniind iteratorul outputInt.
Atunci cnd containerul conine elemente ale cror valori pot fi modificate
putem folosi i iteratorul iterator, iar dac elementele nu pot fi modificate folosim
const_iterator.
Categoriile de iteratori folosii n STL sunt urmtoarele:
- input pentru citirea unui element dintr-un container;
- output pentru scrierea unui element ntr-un container;
- forward combin capacitile primelor dou categorii de iteratori;
- bidirectional - combin capacitile unui iterator forward cu posibilitatea de a se
deplasa n ambele direcii;
- random-access - combin capacitile iteratorului bidirectional cu posibilitatea de
a accesa direct un element din container, adic de a se deplasa inainte sau napoi
cu un numr arbitrar de elemente.
Operaiile care pot fi folosite de un iterator sunt urmtoarele (operaiile pentru
un iterator le includ pe toate cele precedente):
Toi iteratorii
- ++p preincrementarea unui iterator
- p++ postincrementarea unui iterator
Iteratorii de tip input
- *p dereferenierea unui iterator pentru a fi folosit ca rvalue
- p = p1 asignarea unui iterator altui iterator
- p == p1 compararea iteratorilor pentru egalitate
- p != p1 compararea iteratorilor pentru inegalitate
Iteratorii de tip output
- *p dereferenierea unui iterator pentru a fi folosit ca lvalue
Iteratorii de tip forward
- --p predecrementarea unui iterator
- p-- postdecrementarea unui iterator
Iteratorii de tip random-access
4
Algoritmi
Un aspect esenial al STL este c ofer algoritmi care pot fi folosii generic
pentru o mare varietate de containeri: inserare, tergere, cutare, sortare etc. STL
include aproximativ 70 de algoritmi standard. Algoritmii opereaz asupra elementelor
unei secvene doar indirect, prin intermediul iteratorilor. Deoarece STL este
extensibil, se pot aduga cu uurin noi algoritmi fr a opera nicio modificare
asupra containerilor. Algoritmii pot opera i asupra tablourilor declarate n formatul
promovat de limbajul C ca pointeri.
Algoritmii STL sunt de dou tipuri. Cei de tip mutating-sequence fac modificri
asupra containerilor pe care sunt aplicati, n timp ce algoritmii non-mutatingsequence se execut fr a schimba coninutul containerilor.
Civa dintre algoritmii care fac parte din fiierul header <numeric> sunt:
- copy();
- fill();
- replace();
- swap();
- count();
- find();
- search().
- Containeri secven
C++ STL ofer trei containeri secven: vector, list i deque. Clasele
vector i deque se bazeaz pe tablouri. Clasa list implementeaz o structur de
dat de tip list nlnuit.
Unul dintre cei mai folosii containeri este vector care este o implementare a
unui tablou. Spere deosebire de tablourile simple din C++, acestea pot fi asignate
unui altuia, i, suplimentar, clasa vector verific ncadrarea indicilor n limite.
Pe lng operaiile comune tuturor containerilor, unii dintre acetia
implementeaz i operaii suplimentare. Containerii secven dispun de operaiile:
- front returnarea unei referine la primul element din container;
- back returnarea unei referine la ultimul element din container;
- push_back inserarea unui element nou pe ultima poziie din container;
- pop_back tergerea ultimului element din container.
Vom prezenta n programele de mai jos cteva dintre funcionalitile claselor
vector i list.
std::vector<int>::reverse_iterator p2;
for(p2 = v.rbegin(); p2 != v.rend(); ++p2)
cout << *p2 << ' ';
cout << endl;
return 0;
}
Rulnd acest program, obinem urmtorul rezultat:
Dimensiunea initiala a lui v este: 0
Capacitatea initiala a lui v este: 0
Dimensiunea lui v este: 3
Capacitatea lui v este: 4
Continutul tabloului folosind notatia pointer: 1 2 3 4 5 6
Continutul vectorului v folosind iteratorul: 2 3 4
Continutul inversat al vectorului v: 4 3 2
Prin instruciunea
std::vector<int> v;
se declar obiectul v din clasa vector care poate pstra valori int. Odat cu
instanierea acestui obiect, se creeaz un vector vid de dimensiune 0, adic numrul
de elemente stocate n acel moment este 0, i capacitate 0, adic numrul de
elemente fr alocare de memorie este 0. Apelurile de funcii v.size() i
v.capacity() returneaz aceste dou valori.
Funcia push_back insereaz o valoare n container. Dac se insereaz o
valoare ntr-un vector plin, dimensiunea acestuia crete automat. Un nou apel al
funciilor v.size() i v.capacity() ilustreaz aceast modificare a dimensiunii
i a capacitii vectorului.
Pentru afiarea coninutului obiectului v n ordine, se instaniaz obiectul p1 de
tip const_iterator. Funcia v.begin() returneaz un const_iterator la
primul element din v, v.end() returneaz un const_iterator care indic poziia
de dup ultimul element din v, iar operaia p1++ poziioneaz iteratorul pe urmtorul
element din v. Afiarea valorii de la poziia curent a iteratorului se face prin
dereferenierea acestuia: *p1.
Afiarea valorilor n ordine invers se face cu ajutorului obiectului p2 de tip
reverse_iterator. Funcia v.rbegin() returneaz un iterator la punctul de start
pentru iterarea n ordine invers , iar v.end() returneaz un iterator la punctul de
terminare a iterrii n ordine invers.
Urmtorul program prezint funcii care permit diverse manipulri ale valorilor
unui obiect de tip vector.
Exemplu
test_vector_manipulation.cpp
#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 << "Vectorul v contine: ";
std::copy(v.begin(), v.end(), output);
cout << "\nPrimul element din v: " << v.front()
<< "\nUltimul element din v: " << v.back();
v[0] = 7;
v.at(2) = 10;
v.insert(v.begin()+1, 22);
cout << "\nContinutul vectorului v dupa modificari: ";
std::copy(v.begin(), v.end(), output);
try
{
v.at(100) = 725;
}
catch(std::exception& e)
{
cout << "\nExceptie: " << e.what();
}
v.erase(v.begin());
cout << "\nContinutul vectorului v dupa stergere: ";
std::copy(v.begin(), v.end(), output);
v.erase(v.begin(), v.end());
cout << "\nDupa stergere, vectorul v "
<< (v.empty() ? "" : "nu ") << "este vid";
v.insert(v.begin(), a, a+SIZE);
cout << "\nContinutul vectorului v inainte de stergere: ";
std::copy(v.begin(), v.end(), output);
v.clear();
cout << "\nDupa stergere, vectorul v "
<< (v.empty() ? "" : "nu ") << "este vid";
cout << endl;
return 0;
}
Rulnd acest program, obinem urmtorul rezultat:
Vectorul v contine: 1 2 3 4 5 6
Primul element din v: 1
8
este o alt variant a acestei funcii care insereaz o mulime de valori dintr-o
secven pentru care se precizeaz poziia de nceput i cea de sfrit dintr-un alt
container. n exemplul nostru, se insereaz elementele dintre a i a+SIZE din tabloul
a.
Funciile
v.erase(v.begin());
v.erase(v.begin(), v.end());
indic faptul c elementul de la locaia specificat sau elementele dintre cele dou
locaii sunt terse din colecie. Funcia
v.clear();
terge ntreg coninutul vectorului v.
values.push_front(2);
values.push_back(4);
values.push_back(3);
cout << "lista values contine: ";
printList(values);
values.sort();
cout << "\ndupa sort lista values contine: ";
printList(values);
otherValues.insert(otherValues.begin(), a, a+SIZE);
cout << "\ndupa insert lista otherValues contine: ";
printList(otherValues);
values.splice(values.end(), otherValues);
cout << "\ndupa splice lista values contine: ";
printList(values);
values.sort();
cout << "\ndupa sort lista values contine: ";
printList(values);
otherValues.insert(otherValues.begin(), a, a+SIZE);
otherValues.sort();
cout << "\nlista otherValues contine: ";
printList(otherValues);
values.merge(otherValues);
cout << "\ndupa merge:\n lista values contine: ";
printList(values);
cout << "\n lista otherValues contine: ";
printList(otherValues);
values.pop_front();
values.pop_back();
cout << "\ndupa pop_front si pop_back lista values contine: ";
printList(values);
values.unique();
cout << "\ndupa unique, lista values contine: ";
printList(values);
//metoda swap poate fi folosita de toti containerii
values.swap(otherValues);
cout << "\ndupa swap:\n lista values contine: ";
printList(values);
cout << "\n lista otherValues contine: ";
printList(otherValues);
values.assign(otherValues.begin(), otherValues.end());
cout << "\ndupa assign lista otherValues contine: ";
printList(values);
values.merge(otherValues);
11
values.push_back(3);
Dup aceste patru operaii de inserare, lista values conine patru valori ordonate
astfel: 2 1 4 3.
Funcia membr sort fr niciun argument realizeaz o ordonare cresctoare
a valorilor din list:
values.sort();
Dup apelul acestei funcii, elementele din list se vor regsi n ordinea 1 2 3 4.
Funcia
values.splice(values.end(), otherValues);
terge elementele din otherValues i le insereaz n values nainte de poziia
specificat de iteratorul care este primul argument su. n exemplul nostru, iteratorul
este values.end(), iar valorile din otherValues sunt adugate la sfritul listei
values. Dup acest operaie, lista values va conine valorile 1 2 3 4 2 4 6 8.
Funcia
values.merge(otherValues);
terge toate elementele din otherValues i le insereaz sortate n lista values.
Ambele liste trebuie sortate n aceeai ordine nainte de a realiza aceast operaie.
n exemplul nostru, inaintea operaiei merge lista values conine valorile 1 2 2 3
4 4 6 8, iar lista otherValues conine valorile 2 4 6 8. Dup apelul funciei,
lista values va conine valorile 1 2 2 2 3 4 4 4 6 6 8 8, iar otherValues
va fi vid.
Funcia pop_front terge primul element din list, iar pop_back terge
ultimul element din list.
Instruciunea
values.unique();
terge elementele duplicate din list. Pentru a garanta tergerea tuturor duplicatelor
din list, aceasta trebuie sortat n prealabil pentru ca valorile care se repet s se
gseasc pe poziii alturate.
Linia
values.swap(otherValues);
folosete funcia swap pentru a interschimba coninutul listei values cu coninutul
listei otherValues.
Instruciunea
values.assign(otherValues.begin(), otherValues.end());
folosete funcia assign pentru a nlocui coninutul vectorului values cu coninutul
din otherValues din domeniul specificat de cei doi iteratori care sunt argumente
ale funciei.
Prin apelul funciei remove cu argumentul 4:
values.remove(4);
sunt terse toate apariiile lui 4 din lista values.
13