Documente Academic
Documente Profesional
Documente Cultură
Curs Infoacademy C PDF
Curs Infoacademy C PDF
1. INTRODUCERE
CUPRINS
1.Introducere.................................................................................................................................................2
1.1 Roata invatarii....................................................................................................................................2
1.1.1 Intrebare......................................................................................................................................2
1.1.2 Teoria..........................................................................................................................................2
1.1.3 Aplicarea teoriei..........................................................................................................................2
1.1.4 Reflectia......................................................................................................................................3
1.2 Cele cinci dificultati ale programatorului incepator...........................................................................3
1.3 Organizarea cursului...........................................................................................................................4
1.4 Limbaje de programare: tipuri si caracteristici.................................................................................4
1.4.1 Ce este un limbaj de programare?..............................................................................................4
1.4.2 Generatiile limbajelor de programare.........................................................................................4
1.5 Ce este C++?......................................................................................................................................6
1.5.1 Modelele de programare suportate de C++................................................................................6
1.5.2 Documentatia online pentru limbajul C++.................................................................................7
1.6 Ciclul de viata al programelor software.............................................................................................8
1.6.1 Procesul programarii...................................................................................................................8
1.6.2 Metodologii de dezvoltare a programelor..................................................................................8
1.6.2.1 Programeaza si depaneaza .................................................................................................8
1.6.2.2 Spirala.................................................................................................................................9
1.7 Medii de dezvoltare integrata (IDE)...................................................................................................9
1.8 Scrierea primului program...............................................................................................................10
1.8.1 Crearea proiectului în Visual C++............................................................................................10
1.8.2 Scrierea codului........................................................................................................................14
1.8.3 Compilarea programului...........................................................................................................15
1.9 Sumar...............................................................................................................................................15
1.10 Intrebari si exercitii........................................................................................................................15
1.11 Bibliografie.....................................................................................................................................16
1
1
Adonis Butufei
1. INTRODUCERE
1.1.1 Intrebare
In aceasta etapa se clarifica scopul invatarii si permite canalizarea eforturilor intr-o directie precisa.
• De ce vreau sa invat un limbaj de programare? Ce anume vreau sa stiu? (stabilit cat mai detaliat). Ce
anume trebuie sa fac pentru a invata? Cat de multe resurse sunt dispus sa aloc pentru invatare? Cum
stiu ca am invatat?
• Ce stiu deja despre acest subiect? Scopul acestei intrebari este de a constientiza ce stiu si ce am nevoie
sa invat.
• De unde pot afla ceea ce nu stiu? Care sunt sursele corecte de informare de care dispun?
1.1.2 Teoria
Dupa ce am stabilit scopul si am decis ce material se potriveste cu nivelul de intelegere este necesara
studierea teoriei din acele materiale. Studiul cartilor de obicei se refera la citirea si intelegerea textului.
Studiul programarii presupune scrierea de cod. Pentru a putea scrie cod este necesara mai intai invatarea
sintaxei1 si apoi citirea unor exemple.
Urmatorul set de intrebari ne ajuta sa participam activ in parcurgerea materialului intr-un timp mai scurt:
• Cum este organizat materialul? Care este ideea prinpcipala? Care sunt termenii si conceptele? Ce informatie este importanta aici?
• Ce intrebari imi ridica aceasta informatie (Cine? Ce? Unde? Cand? De ce? Cum?)
• Cum pot reformula si sumariza informatia?
• Cum pot reorganiza informatia pentru a raspunde nevoilor mele?
• Cum pot vizualiza informatia? (harti mentale 2, diagrame, tabele)
• Cum se incadreaza aceasta informatie in ceea ce stiu deja?
• Folosirea debuggerului4 pentru rularea programelor pas cu pas si verificarea codului scris.
• Scrierea codului propriu, schimb exemplele, vad ce se intampla.
• Identificarea lucrurilor pe care nu le inteleg. Reformularea lor in cuvinte proprii. Adresarea intrebarilor
detaliate.
1.1.4 Reflectia
Aceasta etapa ajuta la organizarea intelegerii si la internalizarea cunoasterii. Urmatorul set de intrebari
poatre ajuta la definirea contextului:
• Care este sintaxa?
• Care este tiparul aici?
• Exista cazuri speciale?
• Daca schimb asta ce altceva se mai schimba?
• In cate feluri diferite pot rezolva aceasta problema?
• Exista o solutie mai simpla?
• Se pot rezolva si alte probleme cu ce am invatat?
3. Mesajele de eroare
3.1. La inceput necesita un efort de intelegere.
3.2. Cu timpul ele devin elemente ajutatoare care permit scrierea programelor la un standard de
calitate profesional.
4. Depanarea programelor
4.1. Este o abilitate importanta care se poate froma.
4.2. Permite verificarea corectitudinii.
5. Designul programelor
5.1. Cum se organizeaza codul programelor pentru a obtine functionalitatea dorita.
5.2. Cum se descompun problemele complexe intr-un set minimal de probleme simple.
5.3. Este o activitate care se dezvolta odata cu formarea modului de gandire al programatorului.
In primul capitol sunt prezentate notiunile introductive legate de limbaje de programare si mediu de
dezvoltare. Vom incepe prin a defini limbajele de programare. Apoi vom explora generatiile limbajelor de
programare, vom analiza modelele de programare suportate de C++, vom trece in revista cateva elemente
legate de ciclul de viata al programelor software. La final vom crea primul program folosind mediul de
dezvoltare Visual C++ Express Edition.
A doua generatie
Aceasta generatie a aparut cu limbajul FORTRAN. El permitea scrierea programului folosind un set de
instructiuni care erau mai usor de scris si depanat si erau independente de platforma hardware pe care
lucrau.
Pentru a putea fi rulat pe o masina, codul scris de programator trebuia transformat in instructiuni binare
(cod obiect) si aceasta se realiza cu ajutorul unui program numit compilator.
Deoarece codul sursa putea avea mai multe fisiere, dupa compilare rezultau mai multe fisiere binare care
trebuiau grupate impreuna pentru a crea programul. Aceasta se realiza cu ajutorul unui program numit
linker. Pentru a putea fi transferat pe o alta platforma hardware programul trebuia recompilat cu un
4
4
Adonis Butufei
A treia generatie
Daca a doua generatie a imbunatatit structura logica a limbajelor, a treia generatie a devenit mult mai usor
accesibila dezvoltatorilor. Multe limbaje de uz general folosite astazi, BASIC, C, C++, Java, C# apartin
acestei generatii.
Brian Kernighan si Denis Richie au creat limbajul de programare C. Acest limbaj a fost folosit pentru
rescrierea sistemului de operare UNIX care avut un succes deosebit. Multe platforme hardware au adoptat
variante ale acestui sistem de operare. Dupa raspandirea acestui sistem de operare, scrierea programelor a
devenit mult mai usoara pentru ca dezvoltatorii nu mai erau nevoiti sa interactioneze direct cu
particularitatile platformei hardware ci cu resursele oferite de sistemul de operare.
In aceasta generatie, pe langa compilatoare, care transformau intregul program in comenzi binare, au
aparut interpretoarele care executau programul instructiune cu instructiune. Acest mod de lucru permitea
obtinerea raspunsului imediat fara a mai fi necesara compilarea. Este potrivit pentru interactiunea cu
sistemul de operare. Probabil fiecare am deschis un comand prompt in Windows si am executat comenzi
pentru a rezolva anumite probleme simple.
Deoarece procesul de compilare pentru programele mari era consumator de timp, dezvoltatorii s-au gandit
oare nu putem sa facem ceva invers? In loc sa compilam codul in limbaj binar, sa dezvoltam un program
care sa fie capabil sa execute comenzi complexe si compilarea sa se execute in momentul executiei. Asa
au aparut masinile virtuale care erau capabile sa execute comenzi si compilatoarele Just in time folosite
de limbajele Small Talk, Java si mai tarziu C#. In acest mod era suficient sa se implementeze o masina
virtuala pentru fiecare platforma iar codul scris in acel limbaj putea, cel putin teoretic, sa fie rulat pe toate
platformele.
A patra generatie
Cu limbajele de uz general din a treia generatie puteau fi dezvoltate o multitudine de programe. Insa
pentru anumite domenii specifice cum ar fi interogarea bazelor de date, calcule statistice si altele nu era
eficienta folosirea acestor limbaje. Din acest motiv, limbajele din a patra generatie s-au concentrat pe
rezolvarea problemelor specifice unor domenii.
Scopul acestor limbaje este sa reduca eforturile si costurile de dezvoltare a programelor, oferind un nivel
inalt de abstractizare al domeniului respectiv.
Cateva exemple de limbaje: FoxPro, SQL, PL/SQL, LabView, S, R, Mathematica, ABAP.
A cincea generatie
Aceste limbaje sunt dezvoltate cu intentia de a lasa programul sa rezolve probleme fara interventia
programatorului si sunt folosite in cercetarile legate de inteligenta artificiala. Eforturile in acest domeniu
au ramas inca in faza cercetarilor. Cateva exemple de limbaje: Prolog, OPS5, Mercury.
5
5
Adonis Butufei
Programare modulara
Pe masura ce dimensiunea si complexitatea programelor creste, apare necesitatea unei organizari la nivel
mai inalt. Functionalitatea este impartita in module. Aceste module contin datele si procedurile care
lucreaza cu aceste date.
Abstractizarea datelor
Progamarea modulara este un aspect pentru toate programele mari de succes. Pe masura ce aceste
programe cauta sa rezolve probleme reale, exprimarea conceptelor este destul de dificila folosind doar
tipurile de date oferite de limbajul de programare. Pentru a rezolva acest aspect C++ asigura
dezvolatorilor posibilitatea definirii propriilor tipuri de date. Acestea se numesc tipCuri abstracte de date
sau tipuri definite de utilizator.
Programare obiectuala
Abstractizarea datelor este un element esential al designului de calitate. Totusi, doar tipurile utilizator nu
rezolva o serie de aspecte importante ale programelor reale.
Primul aspect important al programarii obiectuale este incapsularea. Aceasta se realizeaza prin gruparea
datelor care descriu un concept cu functiile care acceseaza acele date intr-o clasa. Mai mult, accesul la
unele date este permis numai functiilor clasei.
Este foarte usor sa intelegem acest concept daca privim in jur la toate dispozitivele inconjuratoare. De
exemplu o telecomanda: toata complexitatea functionarii este „ascunsa” utilizatorului care o foloseste.
Acesta poate interactiona prin intermediul tastaturii (interfetei) oferite pentru indeplinirea obiectivelor.
6
6
Adonis Butufei
Al doilea aspect important al programarii obiectuale este mostenirea. Mostenirea permite extinderea unei
clase si adaugarea de alte functionalitati. Acest mecanism, folosit corect, permite reutilizarea codului
intr-un grad mult mai mare decat era posibil folosind modelele anterioare.
De exemplu putem avea o clasa Angajat. Deoarece un manager este un angajat dar are atributii diferite de
ale angajatului putem deriva clasa Manager si adauga functionalitatea specifica, asa cum este schitat in
exemplul de mai jos. Clasa Angajat se numeste clasa de baza, sau parinte pentru clasa Manager. Clasa
Manager se numeste clasa derivata.
Al treilea aspect important al programarii obiectuale este polimorfismul. Acest mecanism permite
claselor derivate sa specializeze comportamentul claselor de baza. De exemplu tramvaiul si autobuzul
sunt ambele mijloace de transport în comun. Toate mijoloacele de transport în comun se deplaseaza intre
statii. Modul în care se realizeaza aceasta deplasare difera si polimorfismul ne ajuta sa implementam
diferit deplasarea intre statii pentru autobuz si tramvai.
Programare generica
Aplicarea programarii obiectuale in productie a permis reducerea timpului de dezvoltare a proiectelor de
la luni la saptamani si de la ani la trimestre. De asemenea, a permis dezvoltarea mai eficienta a versiunilor
urmatoare ale programelor lansate pe piata.
Exista tipuri de prelucrari care sunt similare indiferent de tipul de date asupra carora se executa aceste
prelucrari. O stiva de caramizi si o stiva de farfurii folosesc acelasi tipar de organizare a elementelor.
Folosind programarea generica putem implementa tiparul, stiva, o singura data si apoi sa-l folosim pentru
diferite tipuri de date.
5 Intrebari frecvente despre C++, este o versiune online a unei carti excelente C++ FAQ de Marshall Cline
7
7
Adonis Butufei
Acest model poate fi util pentru: proiecte foarte mici, de verificare a unui concept, demo, sau prototipuri
care se abandoneaza.
8
8
Adonis Butufei
Pentru orice alt tip de proiect, acest model este periculos. Poate sa nu aduca nicio complicatie dar, de
asemenea, nu aduce nicio modalitate de evaluare a progresului, calitatii sau identificarii riscurilor. Este
posibil ca dupa aproape un an de munca sa constatam ca designul este fundamental gresit si singura
solutie este sa aruncam codul si sa o luam de la inceput pe cand alte modele ar fi putut detecta aceasta
mult mai devreme.
1.6.2.2 Spirala
Acest model constituie una din cele mai bune abordari practice a dezvoltarii programelor deoarece se
orienteaza pe reducerea riscurilor. Fiecare proiect este descompus in mini proiecte. Fiecare miniproiect
adreseaza unul sau mai multe riscuri majore pana ce toate riscurile au fost adresate.
In acest context riscurile pot fi reprezentate de intelegerea defectuoasa a cerintelor, arhitecturii,
problemelor de performanta, problemelor tehnologice etc.
2. identificare si adresare
riscuri
1. Determinare Progres
obiective
3. Evaluare alternative
3 2 1
Livrare
9
9
Adonis Butufei
Acest model se poate combina cu alte modele in mai multe moduri. Se poate incepe proiectul cu cateva
iteratii ale spiralei pentru reducerea riscurilor dupa aceea se schimba modelul. Se pot incorpora alte
modele in iteratiile spiralei.
10
10
Adonis Butufei
Din meniul File alegem New project, selectam Win32 din lista de Project Templates si Win32 Console
Application.
11
11
Adonis Butufei
6 In cazul proiectelor de laborator, pentru primul proiect vom selecta Create directory for solution si vom introduce numele
laboratorului in campul Solution Name
12
12
Adonis Butufei
Si apoi verificam daca avem selectat Console application, selectam Empty Project si apasam finish.
13
13
Adonis Butufei
Acum va trebui sa cream fisierul programului. Pentru aceasta selectam folderul Source Files si dam click
cu butonul din dreapta.
14
14
Adonis Butufei
Prima linie este o directiva preprocesor7, aceasta directiva se copieza continutul fisierului iostream in
fisierul HelloWorld.cpp. Fisierul iostream este necesar pentru a putea scrie mesaje pe ecran.
A doua linie este o directiva de folosire a spatiilor de nume, aceasta ne ajuta sa folosim mai ușor
continutul fisierului iostream8. Daca aceasta linie lipseste, pentru scrierea mesajelor pe ecran trebuie sa
folosim std::cout în loc de cout.
In liniile 4 – 8 este scris codul programului reprezentat de functia main9. Aceasta functie este apelata de
sistemul de operare atunci cand lansam programul.
Cu ajutorul comenzii cout scriem mesajul din dreapta pe ecran. Mesajele sunt incadrate intre ghilimele.
Pentru mutarea cursorului pe urmatoarea linie se adauga caracterul ( \n ) la sfarsitul mesajului.
1.9 Sumar
• Limbajele de progamare permit transmiterea instructiunilor catre calculator.
• Exista 5 generatii de limbaje de programare.
• Primele trei generatii au reprezentat evoulutia tehnologica pentru limbajele de programare de uz
general. A patra generatie de limbaje de programare a cuprins limbaje specializate pe anumite domenii
cum ar fi interogarea bazelor de date. A cincea generatie a ramas doar în domeniul cercetarii.
• Limbajul C++ a fost creat de Bjarne Strostrup în anul 1980 si are la baza limajul C. El suporta patru
modele de scriere a programare: procedurala, modulara, obiectuala si generica.
• Programarea procedurala imparte functionalitatea unui program în functii si date asupra carora se
7 Toate liniile de cod care incep cu caracterul # sunt directive preprocesor si se executa inainte de compilare. Vom discuta
despre directivele preprocesorului în capitolul 5.
8 Spatiile de nume vor fi studiate în capitolul 8.
9 Functiile, dupa cum vom vedea în capitolul 8 ajuta la organizarea codului.
15
15
Adonis Butufei
1.11 Bibliografie
• Practical C++ Programming, O'Reily, Steve Oualline, Cap 7
• Rapid Development, Microsoft Press, Steve McConnell, Cap 7
16
16
Adonis Butufei
CUPRINS
2.Elemente de baza C++...............................................................................................................................3
2.1 Comentariile.......................................................................................................................................3
2.1.1 Comentarii pe mai multe linii.....................................................................................................3
2.1.2 Cel specific C++ care este este pe o singura linie. ....................................................................3
2.1.3 Recomandari...............................................................................................................................3
2.2 Variabile si constante - introducere....................................................................................................3
2.2.1 Ce sunt variabilele......................................................................................................................3
2.2.2 Tipul variabilelor........................................................................................................................4
2.2.3 Declararea variabilelor...............................................................................................................4
2.2.4 Atribuirea valorilor.....................................................................................................................5
2.2.5 Citirea valorilor de la tastatura...................................................................................................5
2.2.6 Constante....................................................................................................................................6
2.2.7 Declararea constantelor simbolice..............................................................................................6
2.2.8 Constante de tip enumerare........................................................................................................7
2.2.9 Sugestii pentru denumirea constantelor......................................................................................8
2.3 Instructiuni, operatori si expresii introducere....................................................................................8
2.3.1 Instructiuni..................................................................................................................................8
2.3.1.1 Instructiuni compuse sau blocuri........................................................................................8
2.3.2 Operatori si expresii....................................................................................................................8
2.3.2.1 Operatorul = .......................................................................................................................9
2.3.2.2 Expresiile ...........................................................................................................................9
2.3.2.3 Operatorii aritmetici............................................................................................................9
2.3.2.4 Incrementarea / decrementarea variabilelor......................................................................10
2.3.2.5 Operatorii de comparare...................................................................................................10
2.4 Elemente de control al executiei......................................................................................................10
2.4.1 Secventele.................................................................................................................................10
2.4.2 Deciziile....................................................................................................................................10
2.4.2.1 Instructiunile if, else, else if..............................................................................................11
2.4.2.2 Instructiunea switch..........................................................................................................14
2.4.3 Bucle.........................................................................................................................................17
2.4.3.1 Bucle while.......................................................................................................................17
2.4.3.2 Bucle do while..................................................................................................................18
2.4.3.3 Bucle for...........................................................................................................................19
2.4.4 Instructiunea break...................................................................................................................20
2.4.5 Instructiunea continue...............................................................................................................21
2.5 Functii...............................................................................................................................................21
2.5.1 Structura functiilor....................................................................................................................21
2.5.2 Headerul functiilor....................................................................................................................22
2.5.3 Prototipul functiilor..................................................................................................................22
2.5.4 Apelul functiilor........................................................................................................................22
2.5.5 Exemple de functii....................................................................................................................22
2.5.6 Executia programelor...............................................................................................................24
2.6 Elemente de depanarea programelor: breakpoint, watches..............................................................24
2.6.1 Setarea breakpointurilor...........................................................................................................25
1
17
Adonis Butufei
2
18
Adonis Butufei
2.1 Comentariile
Comentariile sunt explicatii care ajuta programatorul sa inteleaga mai usor anumite aspecte ale codului.
Pentru a putea fi ignorate de compilator comentariile necesita folosirea unor caractere de idenficare.
Exista doua tipuri de comentarii: pe mai multe linii si pe o singura linie.
2.1.3 Recomandari
Comentariile nu trebuie sa clarifice ce face codul ci de ce este aleasa o anumita abordare.
Atunci cand se schimba codul este important sa actualizam si comentariile aferente.
Trebuie sa fie concise pentru a reduce eforturile de mentenanta.
In tabelul de mai jos sunt prezentate doar tipurile de variabile necesare pentru a scrie programele de
invatare iar in capitolul urmator vom prezenta toate tipurile de date fundamentale din C++ cu detaliile
aferente.
Tip de date folosit Cuvant cheie Detalii
Numere intregi int
#include <string>
using namespace std;
In C++ este necesar sa declaram variabilele inainte de a le putea folosi. Declararea unei variabile
informeaza compilatorul sa rezerve un spatiu de memorie din zona de date si sa-i asocieze un nume.
Variabilele pot fi gandite precum scaunele dintr-o sala de spectacol. Atunci cand cumparam un set de
bilete rezervam un numar de locuri din acea sala. Prin aceasta actiune ne declaram intentia de a participa
la acel spectacol.
• Numele sunt case sensitive, aceasta inseamna ca totalCount si totalcount sunt procesate ca doua
variabile diferite.
In cazul in care primele doua reguli nu sunt respectate apar erori la compilare.
Important
Variabilele trebuie sa fie unice in contextul de executie al programului. Daca doua variabile cu acel nume
sunt declarate in acelasi context compilatorul genereaza mesaje de eroare.
Exemple de declarare:
int contor; // declararea unei singure variabile pe linie
bool isFinished;
char terminator;
string nume;
Important
Pentru a elimina potentialele erori se recomanda setarea unei valori in momentul declararii variabilelor
aceasta se numeste initializare.
Exemplu:
int contor = 0;
5
21
Adonis Butufei
Exemplu:
#include <iostream>
using namespace std;
int main()
{
string nume;
cout << "Introduceti numele: \n";
cin >> nume; // variabila nume este initializata de la consola
string prenume;
cout << "Introduceti prenumele: \n";
cin >> prenume; // variabila prenume este initializata
// de la consola;
cout << "Numele complet este: " << nume << " ";
cout << prenume << "\n";
}
In acest exemplu am citit numele si prenumele in doua variabile de tip string. Apoi am afisat informatia in
consola. Observam ca putem scrie mai multe informatii la consola pe aceeasi linie.
2.2.6 Constante
Cunstantele sunt variabile a caror valoare nu se schimba pe parcursul rularii programului. Se intalnesc in
doua forme de constante: literale si simbolice.
O constanta este literala atunci cand este folosita direct valoarea ei. Exemplu:
int lungimea = 35; // In acest caz 35 este o constanta literala.
// valoarea ei nu se poate schimba.
Aceasta directiva spune preprocesorului sa inlocuiasca toate aparitiile lui PI cu valoarea 3.14 in codul
sursa.
Dupa introducerea in limbaj a specificatiei constantelor acesta este stilul recomandat pentru constante.
Prezinta marele avantaj ca erorile pot fi descoperite in timpul compilarii.
6
22
Adonis Butufei
Constantele trebuie initializare la declarare, omiterea acestui fapt genereaza erori de compilare.
Exemplu
const double FACTOR_CONVERSIE; // genereaza eroare de compilare
Exemplu
enum CuloareSemafor {ROSU, GALBEN, VERDE};
Implicit compilatorul trateaza tipul enumerat ca un caz particular de intreg, initializand crescator fiecare
componenta, pornind de la valoarea 0.
Exemplu
#include <iostream>
using namespace std;
int main()
{
cout << "ROSU= " << ROSU << "\n";
cout << "GALBEN= " << GALBEN << "\n";
cout << "VERDE= " << VERDE << "\n";
return 0;
}
Daca dorim putem specifica valoarea numerica pentru fiecare element din set sau pentru o parte a setului.
In ultimul caz, compilatorul continua incrementarea cu 1 pornind de la ultima valoare specificata ca in
exemplul urmator.
#include <iostream>
using namespace std;
7
23
Adonis Butufei
int main()
{
cout << "VERDE= " << VERDE << "\n";
return 0;
}
2.3.1 Instructiuni
Instructiunile controleaza secventa de executie, evalueaza expresiile sau nu fac nimic (in cazul
instructiunii nule care contine doar carcterul ; ). Caracterul (;) indica sfarsitul instructiunii.
Sa examinam putin urmatoarea instructiune:
a = b + c;
Aceasta poate fi citita in modul urmator: atribuie variabilei a valoarea sumei b + c. Operatorul = atribuie
ce se afla in partea dreapta variabilei din stanga.
2.3.2.1 Operatorul =
Acest operator seteaza valoarea din partea dreapta variabilei care se afla in partea stanga a semnului egal.
2.3.2.2 Expresiile
In C++ o expresie este orice combinatie de operatori, constante, apeluri de functii care calculeaza o
valoare.
Exemple:
4.5; // returneaza valoarea 4.5
a = b+c;
d = a = b + c;
Important
Rezultatul impartirii depinde de tipul operanzilor: daca ambii termeni sunt de tip intreg, compilatorul
efectueaza impartirea intreaga chiar daca in stanga egalului este o variabila de tip real. In cazul in care cel
putin unul din termenii impartirii este real atunci compilatorul efectueaza impartirea reala si rezultatul
este cel asteptat.
Exemplu
double impartireIntreaga = 3 / 4; // rezultatul evaluarii este 0
// deoarece ambii termeni sunt
// numere intregi.
9
25
Adonis Butufei
Deoarece activitatea de programare presupune in primul rand rezolvarea unor probleme din viata reala
aceste elemente le putem exprima si in cod cu ajutorul a trei elemente: secventa, decizia si buclele.
2.4.1 Secventele
Exista probleme a caror rezolvare presupune o succesiune de pasi. In acest caz instructiunile programului
sunt scrise intr-o secventa.
2.4.2 Deciziile
In multe dintre programele reale se evalueaza valoarea unor variabile si in functie de aceasta evaluare
executia se ramifica. Putem gandi partea de decizii ca o intersectie, executia va continua in functie de
directia pe care o alegem.
10
26
Adonis Butufei
Pe prima linie avem testarea conditiei cu instructiunea if(conditie). Aici este important de remarcat ca am
folosit o instructiune compusa. In acest mod se delimiteaza foarte clar instructiunile care se executa atunci
cand este indeplinita conditia.
8: return false;
9: }
Frecvent atunci cand se foloseste instructiunea if se uita scrierea celui de-al doilea egal al operatorului.
Atunci cand expresia se compara cu o constanta eroarea poate fi identificata la compilare daca se scrie
constanta in partea stanga ca in linia 4.
A doua forma de decizie foloseste ambele ramuri ale deciziei. De exemplu folosind functia de mai sus in
urmatorul program:
#include <iostream>
using namespace std;
int main ()
{
int numar;
cin >> numar;
if(EsteNmarPar(numar))
{
cout << "Numarul este par\n";
}
else
{
cout << "Numarul este impar\n";
}
}
Exista cazuri cand trebuie sa testam mai multe alternative. O solutie poate fi sa tratam in interiorul
blocului else variantele ramase. Ca in exemplul urmator unde citim culoarea semaforului de pietoni:
r pentru rosu si v pentru verde de la tastatura. Apoi afisam mesajele "Stop!" pentru rosu,
"Puteti trece." pentru verde si "Culoare invalida." pentru orice alta valoare.
#include <iostream>
using namespace std;
int main()
{
cout << "Introduceti culoarea semaforului: r,v\n";
char culoareSemafor;
if(culoareSemafor == 'r')
{
cout << "Stop!\n";
}
else
{
if(culoareSemafor == 'v')
{
cout << "Puteti trece.\n";
}
else
12
28
Adonis Butufei
{
cout << "Culoare invalida.\n";
}
}
return 0;
}
In cazul in care avem mai multe variante logica acestor conditii devine greu de urmarit si inteles. Din
aceste motive pot apare multe defecte in timpul mentenantei.
O solutie mai buna se poate obtine folosind a treia forma in care folosim si combinatia else if ca in
exemplul de mai jos.
#include <iostream>
using namespace std;
int main()
{
cout << "Introduceti culoarea semaforului: r,v\n";
char culoareSemafor;
if(culoareSemafor == 'r')
{
cout << "Stop!\n";
}
else if(culoareSemafor == 'v')
{
cout << "Puteti trece\n";
}
else
{
cout << "Culoare invalida\n";
}
return 0;
}
Din punct de vedere logic implementarile sunt echivalente insa aceasta forma este mult mai clara si este
13
29
Adonis Butufei
de preferat primeia.
De evitat
1. Adaugarea terminatorului (;)
Aici avem doua instructiuni pe aceeasi linie si este mai greu de intretinut si inteles.
In acest caz valoarea variabilei x este nedeterminata si functionarea programului este aleatoare!
#include <iostream>
#include <string>
14
30
Adonis Butufei
return 0;
}
Observam ca blocul incepe cu instructiunea switch care evalueaza expresia. In interiorul blocului avem
o succesiune de alternative reprezentate de cuvintele cheie case.
Valorile acestor alternative sunt constante. Dupa aceea pentru fiecare valoare avem una sau mai multe
instructiuni. In momentul cand am terminat de tratat acea alternativa se iese din bloc cu instructiunea
break.
Optional exista alternativa default care este aleasa daca nici una dintre celelalte nu a fost rezultatul
evaluarii expresiei.
Important
In absenta instructiunii break din alternativa curenta, se continua executarea secventei de instructiuni din
interiorul blocului switch pana la intalnirea primului break sau pana la sfarsitul blocului switch.
Acest aspect trebuie tratat cu atentie pentru ca exista cazuri cand acest comportament este necesar, dar si
cazuri in care s-a omis instructiunea break din greseala.
int main()
15
31
Adonis Butufei
switch(ch)
{
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
cout << "caracterul este vocala\n"; // se executa pentru atunci cand ch
break; // are una din valorile specificate de
// case (a, e, i, o sau u)
default:
cout << "caracterul este consoana\n";
break;
}
return 0;
}
In exemplu vocalele sunt selectate fiecare in parte si se executa un cod comun pentru ele iar pentru
consoane se executa codul din sectiunea default.
case 0:
cin >> iVal;
cout << iVal;
break;
}
2.4.3 Bucle
Permit executarea unei secvente de instructiuni atat timp cat este indeplinita o conditie. Conceptual exista
trei tipuri de bucle: while, do while si for.
Un exemplu simplu de folosire este programul urmator care calculeaza factorialul unui numar.
1: #include <iostream>
2: using namespace std;
3:
4: int main()
5: {
6: cout << "Introduceti un numar intreg\n";
7:
8: int n;
9: cin >> n;
10:
11: int factorial = 1;
12: while(n > 1)
17
33
Adonis Butufei
13: {
14: factorial = factorial * n;
15: n--;
16: }
17:
18: cout << "n! = " << factorial << "\n";
19: return 0;
20: }
In linia 6 se afiseaza mesajul pe ecran. Valoarea variabilei este citita de la tastatura in linia 9. In linia 11
valoarea factorialului este initializata cu 1. In linia 14 se calculeaza produsul intre valoarea anterioara si
n. In linia 15 valoarea lui n este decrementata. Procesul se continua pana cand n devine egal cu 1 moment
in care bucla se termina si se afiseaza rezultatul pe ecran.
// secventa instructiuni
} while (conditie);
18
34
Adonis Butufei
Singura diferenta intre acest tip de bucla si precedentul este ca aici testul conditiei se face la final.
Secventa se executa prima data indiferent de valoarea conditiei.
Executia programului pana la linia 13 este similara cu exemplele anterioare. Executia buclei for se face in
modul urmator:
1. La prima iteratie se declara si se intializeaza variabila i cu valoarea lui n.
2. Se evalueaza conditia in cazul nostru i > 1.
3. In caz afirmativ porneste executia seventei din corpul buclei.
4. Altfel se merge la urmatoarea instructiune dupa bucla.
19
35
Adonis Butufei
In acest exemplu am definit variabila contor in linia 9 in afara buclei deoarece este utilizata pentru
verificarea conditiei din linia 19. In cazul in care a fost gasit un divizor pentru numar, altul decat 1 sau n
numarul nu este prim si bucla se opreste in linia 16.
20
36
Adonis Butufei
Atunci cand i este numar par conditia din linia 3 este indeplinita si executia se contiuna de la inceputul
buclei.
2.5 Functii4
O functie este un bloc de cod care este executat cand este apelat din program. Functiile permit
descompunerea problemelor complexe intr-un set de probleme simple care pot fi usor de implementat.
Functiile scrise corespunzator ascund detaliile de implementare. Sunt folosite pentru evitarea scrierii
repetate a aceluiasi cod intr-un program sau in programe diferite.
3 In cazul buclelor while si do while trebuie acordata atentie deoarece uzual incrementarea / decrementarea contorului se face
la sfarsit.
4 In capitolul 8 vor fi prezentate functiile in detaliu.
5 Functiile care nu returneaza date folosesc void ca si tip de return.
21
37
Adonis Butufei
Important:
• Ca si variabilele functiile trebuiesc declarate sau definite inainte de a fi folosite.
• Declararea functiilor presupune scrierea prototipului.
• Definirea functiilor presupune scrierea headerului si a corpului functiei.
• O functie poate fi declarata de mai multe ori insa trebuie sa fie definita o singura data.
Daca dorim sa definim functia suma dupa functia main este necesar sa folosim prototipul functiei ca in
exemplul de mai jos:
In linia 1 este declarata functia Suma, apelul funciei este in linia 5 iar definitia functiei este dupa corpul
functiei main in liniile 9 – 12.
Fara declararea functiei in linia 1 obtinem eroare de compilare deoarece compilatorul nu are informatii
pentru apelul functiei Suma.
1: int Suma(int x, int y);
22
38
Adonis Butufei
2:
3: int main()
4: {
5: int s = Suma(3, 5);
6: return 0;
7: }
8:
9: int Suma(int x, int y)
10: {
11: return x + y;
12: }
Recomandari
• Pentru usurinta depanarii si intelegerii programelor este recomandata scrierea unei singure instructiuni
pe linie.
• O functie trebuie sa faca un singur lucru (de exemplu sa citeasca variabile, sa calculeze, sa afiseze
rezultate etc).
• Numele functiei este recomandabil sa inceapa cu litera mare pentru a putea distinge functiile de
variabile.
• Numele functiei trebuie sa reprezinte prelucrarea realizata de functie.
• Este recomandabil ca o functie sa aiba cel mult 7 parametri.
23
39
Adonis Butufei
#include <iostream>
using namespace std;
int main()
{
cout << "Aceasta este prima instructiune a programului.\n";
cout << "Aceasta este a doua instructiune a programului\n";
return 0;
}
Practic toate instructiunile unui program pot fi scrise in interiorul functiei main. Pentru cazurile practice
acest mod nu este recomandabil deoarece un program scris in acest mod este aproape imposibil de
mentinut si inteles.
Pentru o organizare logica mai eficienta functionalitatea unui program se imparte in mai multe functii
care sunt mai usor de inteles, implementat si mentinut.
Multe din programele comerciale „crapa” atunci cand utilizatorul doreste sa faca o actiune normala
deoarece echipa de dezvolatare nu a reusit sa verifice executia prin acea ramura logica.
Pentru a verifica un program dezvoltatorul poate rula programul din mediul de dezvoltare in mod
debbugger. Debuggerul este o componenta a mediului de dezvoltare care ofera printre altele posibilitatea
executarii programului pas cu pas, setarea unor puncte de intrerupere (breakpoint) in care se opreste
executia, examinarea valorilor variabilelor (watches).
24
40
Adonis Butufei
Aceste moduri se pot alege din meniul Debug, butoanele dedicate din toolbarul Debug sau tastele: F11
pentru step into, F10 pentru step over si Shift + F11 pentru step out.
Nota
Executia se va opri inainte de a ajunge la cursor in urmatoarele conditii:
1. Exista un breakpoint intre punctul curent de executie si pozitia cursorului,
2. Exista o zona de cod care solicita interactiunea cu utilizatorul intre punctul curent de executie si
pozitia cursorului.
25
41
Adonis Butufei
Nota
Pentru a putea examina valorile variabilelor este necesar ca programul sa fie compilat in configuratia
Debug. Acest tip de compilare adauga informatii aditionale in executabil care faciliteaza operatiile de
rulare in mod interactiv a executiei. Aceasta se realizeaza din meniul Build/Configuration Manager.
26
42
Adonis Butufei
2.9 Sumar
• Comentariile ajuta programatorul sa inteleaga mai usor anumite aspecte ale programului. Ele nu
trebuie sa explice de ce s-a ales o anumita solutie.
• Variabilele sunt locatii de memorie in care se stocheaza date. Variabilele trebuiesc declarate inainte de
folosire. Declararea variabilelor are doua elemente tipul si numele.
• Atribuirea permite setarea unei valori intr-o variabila.
• Constantele sunt variabile ale caror valori nu se pot schimba in timpul executiei.
• Pentru transmiterea comenzilor se folosesc instructiunile.
• Folosind acoladele se pot grupa instructiunile intr-un bloc.
• Programele au trei categorii elemente de control a executiei: secventele de instructiuni, deciziile si
buclele.
• Pentru decizii se folosesc instructiunile if, if/else, if/else if/ else si switch.
• Sunt trei tipuri de bucle: while, do while si for.
• Functiile sunt folosite pentru evitarea scrierii repetate a aceluiasi cod.
• Pentru verificarea executiei programele se executa in mod debugger. Aceasta permite executia pas cu
pas, verificarea valorilor variabilelor.
#include <iostream>
using namespace std;
main ()
{
cout << Care sunt erorile?\n”;
}
int main()
{
cout << Introduceti anul nasterii\n;
27
43
Adonis Butufei
return 0;
}
return result;
}
8. Scrieti o functie care primeste numarul de ore si minute ca parametri si returneaza valoarea acelui
interval in minute. Exemplu: pentru 1 ora si 30 minute va returna 90 minute.
9. Scrieti o functie care primeste un interval in minute si tipareste pe ecran numarul de ore si minute.
Exemplu: pentru 90 minute va afisa o ora si 30 minute.
10. Scrieti un program care citeste varsta in ani si calculeaza numarul de luni corespunzatoare acestor ani.
28
44
Adonis Butufei
2.11 Bibliografie:
• Sams Teach Yourself C++ in One Hour a Day, sixth edition, Sams, Jesse Liberty, Siddhartha Rao,
Bradley L. Jones; Cap 2 – 5, 7
• C++ Without Fear, second edition, Prentice Hall, Brian Overland; Cap 1, 2.
29
45
Adonis Butufei
3. VARIABILE
CUPRINS
3.Variabile.....................................................................................................................................................2
3.1 Dimensiunea variabilelor...................................................................................................................2
3.1.1 Biti si octeti.................................................................................................................................2
3.1.2 Determinarea numarului de octeti pentru variabile - operatorul sizeof .....................................2
3.1.3 Reprezentarea numerelor in baza 2 (binar)................................................................................3
3.1.4 Reprezentarea numerelor in baza 16 (hexazecimala)................................................................3
3.1.4.1 Initializarea variabilelor intregi cu valori hexazecimale.....................................................4
3.1.4.2 Unde se foloseste reprezentarea hexazecimala...................................................................4
3.1.5 Reprezentarea numerelor in baza 8 (octal).................................................................................5
3.2 Domeniul de viata al variabilelor.......................................................................................................5
3.2.1 Locul declararii variabilelor si influenta acestuia asupra domeniului de viata..........................5
3.2.1.1 Mascarea variabilelor..........................................................................................................7
3.2.1.2 Domeniul de viata al variabilelor si apelul functiilor.........................................................8
3.2.2 Calificatorii de context...............................................................................................................8
3.2.2.1 Calificatorul auto................................................................................................................8
3.2.2.2 Calificatorul register...........................................................................................................9
3.2.2.3 Calificatorul extern.............................................................................................................9
3.2.2.4 Calificatorul static...............................................................................................................9
3.3 Variabile numerice............................................................................................................................10
3.3.1 Variabile intregi........................................................................................................................10
3.3.1.1 Calculul dimensiunii necesare pentru stocarea variabilelor intregi pozitive....................10
3.3.1.2 Procesarea semnului..........................................................................................................11
3.3.1.3 Calificatori de semn..........................................................................................................11
3.3.1.4 Calificatori de marime......................................................................................................11
3.3.1.5 Limitele variabilelor intregi..............................................................................................12
3.3.1.6 Depasirea limitelor............................................................................................................13
3.3.2 Variabile reale...........................................................................................................................14
3.3.2.1 Tipuri de variabile reale suportate de C++ .......................................................................14
3.3.2.2 Moduri de scriere a numerelor reale.................................................................................14
3.3.2.3 Intializarea variabilelor reale............................................................................................15
3.3.2.4 Testarea egalitatii pentru variabilele reale........................................................................15
3.3.2.5 Limitele variabilelor reale.................................................................................................17
3.4 Variabile de tip caracter....................................................................................................................18
3.4.1.1 Afisarea codurilor ASCII pe ecran....................................................................................18
3.4.1.2 Citirea valorilor tip caracter de la tastatura.......................................................................19
3.4.1.3 Categorii de coduri ASCII................................................................................................19
3.5 Sumar...............................................................................................................................................21
3.6 Intrebari si exercitii..........................................................................................................................22
3.7 Bibliografie......................................................................................................................................24
1
46
Adonis Butufei
3. VARIABILE
In acest capitol vom discuta despre:
• Dimensiunea variabilelor: cum se determina dimensiunea variabilelor folosind operatorul sizeof, ce
legatura este intre dimensiunea variabilelor si intervalul de valori cu care lucreaza acea variabila, baze
de numeratie.
• Variabile de tip caracter, codurile ASCII, categorii de caractere si secvente escape.
• Variabile de tip numeric, procesarea semnului, limitele intervalelor de valori pentru fiecare tip de date
standard folosite in C++ si conversia tipurilor de date.
• Contextul de acces al variabilelor.
#include <iostream>
using namespace std;
int main()
{
cout << sizeof(int) << "\n";
}
2
47
Adonis Butufei
3 2 1 0
abcd 10 =a∗10 + b∗10 + c∗10 + d∗10
Exemple:
(0)10=0∗2 1+ 0∗2 0=(00)2
(1)10=0∗21+ 1∗20 =( 01)2
(2)10=1∗21+ 0∗20=(10)2
(3)10=1∗21+ 1∗20=(11)2
1 In literatura de specialitate il putem intalni sub acronimul MSB most significant bit.
2 In literatura de specialitate il putem intalni sub acronimul LSB least significant bit.
3
48
Adonis Butufei
Exemple:
Zecimal Binar Hexazecimal
62 00111110 3E
74 01001010 4A
Se observa ca plecand de la codurile hexazecimale se poate verifica foarte usor reprezentarea binara.
- Definirea constantelor intregi care sunt folosite ca filtre pentru operatiile pe biti. In capitolul viitor le
4
49
Adonis Butufei
Exemplu:
const short OK = 0x00;
- Definirea valorilor pentru constantele enumerate, de obicei cu puterile lui 2, care permit operatii de
concatenare pe biti3.
Exemplu:
enum Flag { OPTIUNE_1 = 0x0001,
OPTIUNE_2 = 0x0002,
OPTIUNE_3 = 0x0004 };
int val = OPTIUNE_1 | OPTIUNE_2; // Ultimii biti sunt 11 (avem ambele optiuni)
3 Pentru aceste valori doar un singur bit are valoarea 1 si operatiile pe biti se pot combina fara a altera valoarea anterioara
asa cum vom vedea in capitolul viitor.
5
50
Adonis Butufei
Variabilele globale sunt declarate in afara oricarui bloc de instructiuni si sunt accesibile pe toata durata
executarii programului.
Exemplu:
int global = 0; // Aceasta variabila este globala,
// ea este accesibila pe toata durata
// executarii programului.
int main()
{
int total = 0; // Aceasta variabila este accesibila
// in interiorul functiei main.
Daca incercam sa folosim o variabila in afara domeniului ei obtinem o eroare de compilare ca in exemplul
de mai jos:
int main()
{
int total;
for(int i = total; i < 10; i++)
{
total = total + i;
}
return 0;
}
Recomandare
Pentru reducerea defectelor programelor este bine ca variabilele sa aiba cel mai mic domeniu de viata.
Acesta este un mod defensiv care asigura eliberarea memoriei nefolosite si reduce cuplajul prin date4
4 Cuplajul prin date apare atunci cand mai multe functii folosesc aceleasi variabile globale.
6
51
Adonis Butufei
In linia 6 am declarat o variabila total care are domeniul de viata functia main. Pe linia 9 am definit o alta
variabila total care are ca domeniu de viata acest bloc si blocurile descendente si care mascheaza variabila
total definita in linia 6.
Bucla dintre liniile 11 – 14 lucreaza cu aceasta variabila, Atunci cand se termina executia blocului,
memoria pentru variabila declarata aici este eliberata si rezultatul se pierde.
Mascarea variabilelor poate genera defecte foarte greu de depanat si trebuie evitata. Folosind variabile
cu domenii de viata cat mai mici este usor de identificat acest fenomen.
7
52
Adonis Butufei
In prima linie am declarat o variabila globala total. Functia sum calculeaza suma parametrilor a si b si
returneaza rezultatul. Functia badsum atribuie rezultatul sumei variabilei globale creand un cuplaj prin
date.
Daca o alta functie schimba valoarea acestei variabile intre punctul de apel si cel in care se foloseste
rezultatul, executia este incorecta.
Parametri transmisi unei functii sunt considerati variabile locale si pot fi folositi ca si cum ar fi fost
declarati in corpul functiei.
8
53
Adonis Butufei
Exemplu:
auto int test;
Variabila globala definita in acest fisier are domeniul de viata in acest fisier, Daca se incearca accesarea ei
din alt fisier folosind o declaratie: extern int variabilaGlobala; se obtine o eroare de compilare.
1: #include <iostream>
2: using namespace std;
3:
5 Se recomanda masurarea precisa a performantei si identificarea portiunilor care necesita optimizarea performantei.
Optimizarea prematura a performantei este o sursa de probleme si poate rezulta intr-un cod greu de mentiut.
9
54
Adonis Butufei
4: void ExempluVariabilaStatica()
5: {
6: static int nrApeluri = 1; // Se executa doar la primul apel.
7:
8: cout << "Numarul apelurilor functiei:"
9: << nrApeluri << "\n";
10:
11: nrApeluri++;
12: }
13:
14: int main()
15: {
16: ExempluVariabilaStatica();
17: ExempluVariabilaStatica();
18:
19: return 0;
20: }
In acest exemplu linia 6 se executa doar la prima rulare. Apelurile succesive ale functiei incrementeaza
variabila si, in acest caz, putem calcula numarul apelurilor.
Exemplu:
In cazul unui octet putem stoca numere pozitive cuprinse intre 0 si 2 8−1 adica in intervalul [0, 255].
10
55
Adonis Butufei
Important
• Atunci cand una dintre limitele intervalului este depasita cu o unitate prin incrementare sau
decrementare urmatoarea valoare va fi cealalta limita a intervalului.
• Putem gandi valorile unui interval dispuse pe un cerc. Dupa o rotatie completa in oricare sens valorile
se repeta.
• Acest aspect este foarte important la bucle: daca valoarea din conditia de oprire a buclei nu este in
intervalul variabilei utilizate pentru testul buclei, vom avea fie o bucla infinita, fie secventa buclei nu
se va executa niciodata.
Exemplu:
Pentru un octet am avea limita maxima pozitiva de 2 7−1 adica 127, iar limita inferioara va fi de
7
−2 adica -128. Pe un octet putem stoca numere cuprinse in intervalul [-128, 127].
Important
Sa presupunem ca avem o variabila de marimea unui octet care are valoarea 127 si o incrementam. Si in
aceasta situatie se aplica observatiile anterioare. Noua valoare va fi -128. Iar daca variabila are valoarea
-128 si o decrementam noua valoare va fi 127.
11
56
Adonis Butufei
12
57
Adonis Butufei
int main()
{
cout << "Valoarea minima pentru int: " << INT_MIN << "\n";
cout << "Valoarea minima pentru int: " << INT_MAX << "\n";
cout << "Valoarea maxima pentru unsigned int: " << UINT_MAX << "\n";
return 0;
}
Exemplu:
1: unsigned short v1;
2: // ...
3: short v2 = v1;
Atribuirea din linia 3 necesita multa atentie. Daca valoarea variabilei v1 depaseste limita SHRT_MAX,
atunci este posibil ca functionarea sa fie defectuoasa.
Acest fenomen poate apare atunci cand se face atribuirea intre doua variabile de tip diferit sau cand o
variabila este folosita pentru a calcula suma sau produsul unei secvente de valori.
Cunoasterea acestui aspect este esentiala pentru alegerea tipurilor de variabile si scrierea codului
de calitate.
Pe de alta parte, daca am folosi numai variabile reale pentru efectuarea calculelor este ca si cum am
incerca sa construim un cub din plastilina. Oricat de mult ne-am stradui nu am obtine muchiile drepte si
precise.
Pentru efectuarea calculelor cu numere reale au trebuit rezolvate doua probleme importante: posibilitatea
de a lucra cu intervale foarte mari de valori si reprezentarea acestor valori intr-o dimensiune predefinita
de memorie.
Pentru adresarea acestor necesitati nu se putea folosi o reprezentare exacta ca in cazul variabilelor intregi
ci s-a folosit o reprezentare aproximativa suficient de precisa a numerelor reale folosind un numar
standard de octeti8.
Deoarece analiza reprezentarii in memorie a variabilelor reale depaseste scopul cursului, in continuare
vom schita aspectele necesare intelegerii principiilor de functionare a numerelor reale.
Variabilele reale au trei elemente: semnul, o fractie zecimala si un exponent:
exponent
y=± f.f 1 f 2 … f n x 10
Unde f.f 1 f 2 … f n este fractia zecimala.
Pentru a separa partea intreaga de partea zecimala, in C++ folosim caracterul punct (.) in loc de virgula
(,).
7 In limba engleza se foloseste termenul floating point pentru acest tip de variabile.
8 Reprezentarile uzuale sunt de 4, 8 sau 16 biti in functie de sistemul de operare.
9 Implementarile curente pentru Windows folosesc aceeasi reprezentare pentru long double si pentru double.
14
59
Adonis Butufei
Exemple:
3.54
0.37
-42.8
Modul stiintific foloseste forma ±abE±cd care este echivalenta cu ±ab x 10±cd . In aceasta notatie
putem folosi ambele litere (E) sau (e).
Exemple:
3e+2 = 3x102 = 300
3.24e-3= 3.24x10−3
De retinut
Nu trebuie sa existe spatii intre caractere atunci cand se foloseste aceasta notatie. Urmatoarele valori sunt
invalide:
3 e+4
-4.5 e-2
Exemple:
float temp = 1.5f;
float y = -3.2e-5f;
double sum = 0.0;
double x = 1.23e5;
int main()
15
60
Adonis Butufei
{
double sum = 0.0;
for(int i =0; i < 10; i++)
{
sum = sum + 0.1;
}
if(1.0 == sum)
{
cout << "suma este 1.0\n";
}
else
{
cout << "suma nu este 1.0\n";
}
return 0;
}
Pentru a compara variabilele reale este necesar sa calculam diferenta absoluta a valorii celor doua
variabile si daca aceasta diferenta este mai mica decat un nivel de eroare considerat acceptabil atunci
numerele sunt considerate egale. Programul urmator exemplifica aceasta idee:
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
double sum = 0.0;
for(int i =0; i < 10; i++)
{
sum = sum + 0.1;
}
16
61
Adonis Butufei
if(AreEqual(1.0, sum))
{
cout << "suma este 1.0\n";
}
else
{
cout << "suma nu este 1.0\n";
}
}
17
62
Adonis Butufei
Este important de facut diferenta intre valoarea numerica si codul ASCII corespunzator unei cifre.
Urmatoarele doua variabile au valori diferite. In primul caz variabila are valoarea 5 in al doilea caz
variabila are valoarea codului asociat pentru caracterul 5 care este 53.
char x = 5; // x are valoarea 5
char y = '5'; // y are valoarea codului ASCII pentru '5' este 53.
Ca si contor al buclei am folosit o variabila de tip int. Pentru a putea tipari ultima valoare a intervalului
trebuia sa fixez limita la 256. Daca foloseam o variabila unsigned char intervalul posibil de valori era [0 –
255]. Atunci cand i lua valoarea 255 se executa codul buclei apoi se incrementa valoarea care in acest caz
ar fi devenit 0. In acest moment totul se repeta si obtineam o bucla infinita.
Pe linia 8 observam expresia (char)i aceasta converteste tipul variabilei i de la intreg la caracter si se
numeste castare. Vom analiza conversiile in detaliu in capitolul urmator cand vom discuta despre
operatori.
18
63
Adonis Butufei
int main()
{
char c;
cout << "Introduceti un caracter\n";
cin >> c;
Exista o categorie speciala de valori care este utilizata pentru formatarea textului. Fiecare valoare din
aceasta categorie incepe cu caracterul (\) pentru a preveni procesarea implicita a caracterului care
urmeaza. Acest grup de doua caractere se numeste secventa escape .
Valoare Explicatie
'\0' marcheaza sfarsitul unui sir de carcatere
'\a' produce un sunet
'\b' muta cursorul un spatiu inapoi
'\t' insereaza un tab orizontal
'\n' muta cursorul pe linia urmatoare
'\v' insereaza un tab vertical
'\f' muta cursorul pe pagina urmatoare
'\r' muta cursorul la inceputul linei
19
64
Adonis Butufei
Valoare Explicatie
'\'' afiseaza caracterul (')
'\"' afiseaza caracterul (")
'\\' afiseaza caracterul (\)
Valorile mai pot fi impartite logic si in alte categorii: caracterele care reprezinta cifre ('0' - '9'), litere
(majuscule, minuscule) etc. Pentru a testa apartenenta unui caracter la o categorie se folosesc functiile
declarate in fisierul <ctype.h>.
Tabelul de mai jos contine o scurta prezentare a acestor functii:
Functie Explicatie
isalpha(c) returneaza 1 daca c este litera mare sau mica A-Z, a-z
isupper(c) returneaza 1 daca c este litera mare A-Z
islower(c) returneaza 1 daca c este litera mica a-z
isdigit(c) returneaza 1 daca c este cifra 0-9
isxdigit(c) returneaza 1 daca c este cifra hexa 0-9,a-f,A-F
isspace(c) returneaza 1 daca c este spatiu ' ','\t','\n','\r','\f' sau '\v'
ispunct(c) returneaza 1 daca c este character de punctuatie
isalnum(c) returneaza 1 daca c este litera sau cifra
isprint(c) returneaza 1 daca c este afisabil cu spatiu
isgraph(c) returneaza 1 daca c este afisabil fara spatiu
iscntrl(c) returneaza 1 daca c este caracter de control
20
65
Adonis Butufei
10: {
11: cout << c << " " << (char)c << "\n";
12: }
13: }
14:
15: return 0;
16: }
3.5 Sumar
• Cea mai mica unitate de informatie este bitul.
• Un grup de 8 biti formeaza un octet.
• Variabilele se masoara in octeti.
• Pentru a determina numarul de octeti ocupati de o variabila se foloseste operatorul sizeof.
• Pe langa reprezentarea zecimala, variabiele intregi mai pot fi reprezentate in baza 8, reprezentare
octala, sau baza 16, reprezentare hexazecimala.
• Domeniul de viata al variabilelor reprezinta zona de cod in care acestea pot fi utilizate in cadrul
programului. Dupa ce sunt executate instructiunile din domeniul de viata al unei variabile, acea
variabila nu mai poate fi accesata si memoria aferenta este eliberata. Exceptie in cazul variabilelor
statice!
• Pentru a reduce cuplajul prin date este recomandabil ca variabilele sa aiba un domeniu de viata cat mai
redus.
• Calificatorul de context este utilizat pentru a accesa variabile declarate in alt fisier.
• Calificatorul de context static are doua caracteristici:
- variabilele globale statice pot fi accesate doar din fisierul curent
- variabilele locale statice sunt alocate in memorie la prima executie si valoarea lor se pastreaza pe
toata perioada rularii programului.
• In C++ sunt definite urmatoarele tipuri de variabile intregi: int, long si short. Primele doua tipuri ocupa
4 octeti, ultimul tip ocupa 2 octeti.
• Variabilele intregi pot avea semn sau nu. In cazul celor fara semn toate valorile sunt pozitive.
• Limitele pentru variabilele intregi sunt definite in fisierul <climits>
• Variabilele reale pot lua valori in intervale largi. Ele folosesc o reprezentare aproximativa a numerelor
datorita faptului ca au un numar finit de cifre.
• Dimensiunea variabilelor reale este de 4 sau 8 octeti.
• Pentru variabilele reale se folosesc doua notatii: zecimala si stiintifica. Cea zecimala este de forma
24.5 iar cea stiintifia este de forma 2.45 * 101 .
• Pentru testarea egalitatii variabilelor reale, datorita aproximarii, nu se poate folosi operatorul == . Se
defineste cea mai mica diferenta acceptabila. Daca diferenta celor doua numere este mai mica in
valoare absoluta decat diferenta acceptabila atunci sunt considerate egale.
21
66
Adonis Butufei
#include <iostream>
using namespace std;
int main()
{
for(int i = 0; i < 3; i++)
{
static int a = 1;
int b = 1;
cout << "a= " << a << " b= " << b << "\n";
a++;
b++;
}
return 0;
}
16. Care este diferenta dintre o variabila de tip int si una de tip short?
22
67
Adonis Butufei
17. Care este diferenta dintre o variabila cu semn si una fara semn?
18. Cum se declara o variabila fara semn de tip long long?
19. Care este valoarea variabilei dupa executarea codului:
int x = INT_MIN;
x--;
21. Ce fisier trebuie inclus pentru a putea lucra cu limitele variabilelor de tip intreg?
22. Extindeti programul de afisare a dimensiunii variabilelor pentru toate tipurile de variabile discutate
pana acum.
23. Extindeti programul de afisare a limitelor pentru toate valorile intregi.
24. Scrieti reprezentarea zecimala pentru urmatoarele valori:
1.345e-2
45.89e-1
1234.789E-3
-324.758e-2
25. Scrieti reprezentarea stiintifica pentru urmatoarele valori astfel incat partea intreaga sa aiba 2 cifre:
234.78
0.756
-2.546
-2459.576
26. Modificati exemplul initial pentru compararea numerelor reale astfel incat sa foloseasca variabile de
tip float. Apar si in acest caz erorile de aproximare?
27. Ce fisier trebuie inclus pentru a folosi limitele variabilelor reale?
28. Scrieti un program care afiseaza valorile limitelor pentru variabilele de tip float si double.
29. Implementati un program care afiseaza codul ASCII si valoarea pentru toate literele si cifrele.
30. Programul urmator ruleaza in bucla infinita. Care este eroarea?
17: #include <iostream>
18: using namespace std;
19: int main()
20: {
21: for(unsigned char = 0; c < 256; c++)
23
68
Adonis Butufei
22: {
23: cout << c << " " << (int)c << "\n";
24: }
25:
26: return 0;
27: }
31. Modificati codul anterior pastrand tipul datelor, dar folosind o bucla do /while
32. Implementati o functie care primeste un caracter si daca acesta este litera o transforma in majuscula folosind tabelul
codurilor ASCII prezentat in bibliografie.
33. Prin ce difera conversiile implicite de conversiile explicite?
34. Care este rezultatul urmatoarei conversii?
double a = 4.5;
int x = int (a);
3.7 Bibliografie
• Practical C++ Programming, second edition, O'Reily, Steve Oualline, Cap 5, Cap 9.
• C++ Primer, sixth edition, Addison-Wesley Professional, Stephen Prata, Cap3.
• Reprezentarea numerelor reale http://steve.hollasch.net/cgindex/coding/ieeefloat.html
• Acuratete vs. precizie:
http://www.cprogramming.com/tutorial/floating_point/understanding_floating_point.html
• Reprezentarea double pe 64 biti: http://en.wikipedia.org/wiki/Double_precision_floating-point_format
• Codurile ASCII http://www.asciitable.com/
24
69
Adonis Butufei
4. OPERATORI
CUPRINS
4.Operatori....................................................................................................................................................2
4.1 Operatorul de atribuire: =...................................................................................................................2
4.2 Asociativitatea si precedenta operatorilor..........................................................................................3
4.3 Operatori de semn: + -........................................................................................................................3
4.4 Operatori aritmetici si operatori de incrementare..............................................................................4
4.4.1 Operatori aritmetici compusi: += -= *= /= %=...........................................................................4
4.4.2 Operatori de incrementare / decrementare: ++ --.......................................................................5
4.4.2.1 Operatorii de prefixare........................................................................................................5
4.4.2.2 Operatorii de postfixare......................................................................................................5
4.5 Operatori logici: || && !....................................................................................................................6
4.5.1 Tabele de adevar pentru operatorii logici...................................................................................8
4.5.1.1 Operatorul || (sau logic).......................................................................................................8
4.5.1.2 Operatorul && (si logic).....................................................................................................8
4.5.1.3 Operatorul ! (negare logica)................................................................................................9
4.5.2 Precedenta operatorilor logici.....................................................................................................9
4.5.3 Efecte secundare.........................................................................................................................9
4.6 Conversii si operatorii cast..............................................................................................................10
4.6.1 Conversii implicite...................................................................................................................10
4.6.2 Conversii explicite: operatorii cast...........................................................................................10
4.6.3 Recomandari pentru conversii..................................................................................................11
4.7 Operatorul conditional: ? .................................................................................................................11
4.8 Operatori pe biti: | & ^ << >> ~.......................................................................................................12
4.8.1 Operatorii compusi: |= &= ^= <<= >>=...................................................................................14
4.9 Operatorul: , ....................................................................................................................................15
4.10 Precedenta operatorilor..................................................................................................................16
4.11 Intrebari si exercitii........................................................................................................................16
4.12 Bibliografie.....................................................................................................................................18
1
70
Adonis Butufei
4. OPERATORI
Stim din capitolul 2 ca operatorii sunt simboluri care determina compilatorul sa interactioneze cu
variabilele. In acest capitol vom discuta in detaliu despre:
• Operatorul de atribuire.
• Asociativitatea operatorilor.
• Operatorii compusi +=, -=, *=, /= si %= care permit scrierea mai concisa a expresiilor.
• Folosirea operatorilor de incrementare si decrementare in expresii.
• Operatorii logici.
• Conversii si operatorii de cast.
• Operatori pe biti.
• Precedenta operatorilor.
In cazul in care in partea dreapta avem o functie acea functie se apeleaza si rezultatul returnat se atribuie
variabilei din stanga.
Exemplu:
double radical = sqrt(45);
De asemenea, daca in partea dreapta avem o expresie acea expresie se evalueaza si se atribuie rezultatul
variabilei din stanga.
Exemplu:
int sum = 4 + 5;
Nota
Cand sunt mai multe variabile declarate pe aceeasi linie atribuirea se face numai pentru ultima variabila
ca in exemplul urmator:
int x,y, z = 0; // numai z are valoarea 0 celelalte au
// valoare nedeterminata.
2
71
Adonis Butufei
Este important de remarcat ca orice exista in partea stanga1 a operatorului = se poate muta in partea
dreapta insa invers nu este intotdeauna corect.
Exemplu:
x = 5; // este corect
5 = x; // nu este corect deoarece 5 este o constanta
// literala si nu-si poate schimba valoarea.
Precedenta operatorilor determina ordinea in care se executa operatiile unei expresii. In C++ fiecare
operator are un nivel de precedenta. Opearatorii care au un nivel mai mic de precedenta se executa
inaintea celor cu precedenta mai mare.
Pentru a asigura evaluarea expresiilor operatorul = se executa in majoritatea cazurilor ultimul. De aceea el
are un nivel de precedenta mare.
In exemplul de mai jos ordinea operatiilor este cea cunoscuta: intai se calculeaza inmultirea apoi
adunarea.
int x = 3 + 4*5; // x are valoarea 23.
Nota
La sfarsitul acestui capitol este prezentat un tabel cu precedenta si asociativitatea operatorilor prezentati
in curs.
In cazul in care dorim sa efectuam mai intai adunarea si dupa aceea inmultirea este necesara folosirea
parantezeor ca in exemplul de mai jos:
int x = (3 + 4)*5; // x are valoarea 35
Important
Ca sa fim siguri de ordinea operatiilor este recomandabila folosirea parantezelor in expresiile care contin
mai mult de 3 operanzi.
1 Operandul din partea stanga a operatorului = se numeste l-value iar operandul din partea dreapta se numeste r-value.
3
72
Adonis Butufei
int y = -34;
double t = -45.2;
int z = +23;
Deoarece semnul + este cel implicit el este omis in cele mai multe cazuri.
In exemplul urmator se evalueaza mai intai valoarea modulo apoi se face adunarea:
int x = 5 + 7 % 2;
Pentru a usura scrierea, in C++ sunt definiti operatorii compusi care permit scrierea mai concisa a
expresiilor. Expresia de mai sus este echivalenta cu:
int x += 10;
In partea dreapta poate sa fie o expresie complexa al carei rezultat este evaluat si atribuit lui x.
In tabelul de mai jos este prezentata forma concisa pentru operatorii aritmetici2:
Operator Exemplu Echivalenta
+= x += y; x = x + y;
-= x -= y; x = x - y;
*= x *= y; x = x * y;
/= x /= y; x = x / y;
%= x %= y; x = x % y;
Acesti operatori au acelasi nivel de precedenta cu operatorul = si asociativitatea este de la dreapta spre
stanga.
In acest caz mai intai se realizeaza incrementarea, apoi se evalueaza expresia y = x si la final ambele
variabile au valoarea 1.
Operatorul de prefixare are un nivel de prioritate mai mic decat operatiile aritmetice si se efectueaza
inaintea lor. In exemplul urmator se efectueaza mai intai operatia de incrementare si dupa aceea operatia
de adunare datorita precedentei si variabila y va fi initializata cu valoarea 2.
int x = 0;
int y = x + ++x;
In acest caz mai intai se evalueaza expresia y = x, apoi se incrementeaza variabila x si la final variabila x
are valoarea 1 si variabila y are valoarea 0.
5
74
Adonis Butufei
Pentru cazul:
int x = 0;
int y = x++ + x;
lucrurile se intampla asemanator: mai intai se evalueaza expresia x + x a carei valoare se atribuie
variabilei y si la final se incrementeaza variabila x.
Recomandari
Pentru a asigura o intelegere a codului si a evita defecte care consuma timp pretios pentru depanare se
recomanda:
• folosirea operatorilor de incrementare in expresii cat mai simple
Exemplu:
Este preferabila expresia
factorial *= n;
n--;
in locul
factorial *= n--;
• in expresie o variabila trebuie sa suporte o singura operatie de incrementare. Urmatorul exemplu este
de evitat:
int x = 0;
int result = x++ + x + ++x; // Complicat! La final x == 2 si result == 3
Din exemplele anterioare observam ca exista un tipar: avem un element de decizie „daca”, o conditie care
are mai multe criterii si o actiune. Acesta este scenariul uzual pe care il intalnim si in programe.
Exemplele de mai sus pot fi exprimate in C++ in modul urmator:
6
75
Adonis Butufei
Exemplul 1:3
In acest exemplu validarea PIN-ului se realizeaza prin apelul ValideazaPIN(). Apoi se verifica situatia
soldului. In final daca ambele conditii sunt evaluate cu true se achita produsul prin apelul functiei
AchitaProdus().
Exemplul 2:
const double DISCOUNT = 0.8;
bool pensionar = VerificaPensionar(numeClient);
bool elev = VerificaElev(numeClient);
if(pensionar || elev)
{
pret = pret * DISCOUNT;
}
In acest exemplu am folosit o constanta simbolica pentru definirea discountului4. Apoi verificam daca
clientul este pensionar sau elev si la final daca una din conditii este indeplinita aplicam discountul.
if(VerificaPensionar(numeClient) || VerificaElev(numeClient))
{
pret = pret * DISCOUNT;
}
Pentru o mai usoara intelegere am separat apelul functiilor de conditia if. De cele mai multe ori in practica
se foloseste aceasta varianta.
Exemplul 3:
3 In acest exemplu variabila pretProdus este declarata anterior, functiile ValideazaPIN(), CitesteSold() sunt implementate
anterior.
4 Folosirea constantelor simbolice ajuta la intelegerea codului si reduce eforturile de mentenanta. Valoarea constantei poate fi
folosita in mai multe locuri in cod. Daca este necesara actualizarea procentului de discount si am folosit o constanta, atunci
este suficienta o singura schimbare. Altfel ar fi trebuit sa cautam toate locurile in cod unde am folosit constanta literala 0.8
si sa o inlocuim cu noua valoare.
7
76
Adonis Butufei
if( ! EsteSemaforRosu())
{
TraverseazaStrada();
}
Rezultat x y
Observam ca rezultatul are valoarea true doar daca daca unul din operanzi are valoarea true.
Observam ca rezultatul are valoarea true doar daca ambii operanzi au valoarea true.
Rezultat x
true false
false true
In acest exemplu rezultatul evaluarii este true deoarece se evalueaza x && y care este false. Cu acest
rezultat se evalueaza || z care este true.
Recomandare:
Pentru a evita erorile este de preferat folosirea parantezelor pentru specificarea succesiunii operatiilor.
Expresia anterioara se poate scrie mai clar in modul urmator:
bool result = (x && y) || z;
In acest caz daca f2 returneaza true f2 nu mai este apelata deoarece rezultatul expresiei s-a evaluat la true.
In acest caz daca f1() returneaza false f2 nu mai este apelata deoarece rezultatul expresiei s-a evaluat la
false.
Nota
In aceste cazuri este necesar sa determinam, in functie de cerintele programului, daca apelul celei de-a
9
78
Adonis Butufei
int main ( )
{
double aria = ArieDreptunghi(3,5);
}
Exemplu:
double result = 45.6;
int parteIntreaga = (int)result;
5 Exista operatori de conversie pentru clase care sunt implementati de utilizator. Vom discuta despre acest subiect intr-un
capitol viitor.
10
79
Adonis Butufei
Notatia de mai sus este preluata din C. In C++ putem face conversia introducand valoarea intre paranteze
ca in exemplul urmator:
double result = 45.6;
int parteIntreaga = int(result);
Important
Desi acest operator poate fi folosit in expresii complexe pentru scrierea unui cod usor de inteles si mentinut este
recomandabil sa fie folosit numai pentru initializarea variabilelor in expresii similare cu exemplul prezentat mai sus.
11
80
Adonis Butufei
Exista situatii in care este necesara efectuarea operatiilor la nivel de bit asupra variabilelor intregi. Pentru
a adresa aceasta cerinta C++ foloseste operatorii pe biti. In tabelul de mai jos sunt prezentati operatorii pe
biti.
Operator Descriere
| sau pe biti
& si pe biti
^ sau exclusiv
Operatorii |, & si ^ compara fiecare bit al primului operand cu bitul corespunzator al celui de-al doilea
operand si seteaza valoarea bitului de pe aceeasi pozitie din variabila rezultat. Modul de calcul al valorii
individuale a bitilor din variabila rezultat este prezentat in tabelul urmator:
Bit operand Bit rezultat
I II | & ^
0 0 0 0 0
0 1 1 0 1
1 0 1 0 1
1 1 1 1 0
Exemplu:
1: #include <iostream>
2: using namespace std;
3:
4: int main()
5: {
6: cout << hex << showbase;
7:
8: // 1110 | 0011 -> 1111 (0xf).
9: cout << "0xe | 0x3 = " << (0xe | 0x3 ) << "\n";
10:
11: // 1110 & 0011 -> 0010 (0x2).
12
81
Adonis Butufei
12: cout << "0xe & 0x3 = " << (0xe & 0x3 ) << "\n";
13:
14: // 1110 ^ 0011 -> 1101 (0xd).
15: cout << "0xe ^ 0x3 = " << (0xe ^ 0x3 ) << "\n";
16:
17: return 0;
18: }
Pentru a putea vizualiza valorile in reprezentare hexazecimala si a afisa baza am folosit setarile din linia
66.
Operatorii de deplasare << si >> muta toti bitii catre dreapta respectiv catre stanga cu un numar
specificat de pozitii.
Deplasarea la stanga cu n biti este echivalenta cu inmultirea valorii cu 2 n iar deplasarea la dreapta cu n
biti este echivalenta cu impartirea valorii cu 2 n .
Exemplu:
#include <iostream>
using namespace std;
int main()
{
int x = 1;
cout << x << " << 1 = " << (x << 1) << "\n";
cout << x << " << 2 = " << (x << 2) << "\n";
cout << x << " << 3 = " << (x << 3) << "\n";
int y = 8;
cout << y << " >> 1 = " << (y >> 1) << "\n";
cout << y << " >> 2 = " << (y >> 2) << "\n";
cout << y << " >> 3 = " << (y >> 3) << "\n";
return 0;
}
6 Detaliile de formatare le vom discuta in detaliu in capitolul 13 care este dedicat notiunilor de intrare si iesire.
13
82
Adonis Butufei
int main()
{
unsigned short x = 0xa;
unsigned short y = ~x; // y = 0xfff5
cout << hex << showbase << "~" << x << " = " << y << "\n";
return 0;
}
|= x |= y x=x|y
^= x ^= y x=x^y
4.9 Operatorul: ,
Acest operator este folosit pentru a separa doua sau mai multe expresii. Aceste expresii sunt evaluate de la
stanga la dreapta si rezultatul expresiei de la marginea dreapta este cel considerat.
Exemplu:
int x = (b = 5, b +3);
14
83
Adonis Butufei
{
cout << x << " " << y << "\n";
}
Deoarece variabilele sunt de tipuri diferite este necesara declararea lor in afara buclei for.
In partea de initializare se executa ambele atribuiri. La verificarea conditiei se testeaza ambele conditii si
atunci cand una din conditii nu mai este evaluata ca true se opereste executia.
Bucla afiseaza valoarea curenta pentru x si y apoi pentru actualizarea ambelor valori ale variabilelor am
folosit din nou operatorul (,) .
Actualizarea variabilei in interiorul lui for se face si in cazurile in care executia codului din bucla
intalneste o instructiune continue.
Exemplu:
int x;
double y;
if ( conditie)
{
continue; // in acest caz nu se mai actualzeaza y!
}
y *= 4.2;
}
9 == != egalitate, diferenta
10 & si pe biti
12 | sau pe biti
13 && si logic
14 || sau logic
16 = operatorul de atribuire
+= -= *= operatori de atribuire compusi
/= %= <<=
>>= &= ^=
|=
18 , virgula de la stanga la dreapta
int y = 0;
int x = ++y;
17
86
Adonis Butufei
x = 2;
}
else
{
x = 3;
}
4.12 Bibliografie
• http://en.cppreference.com/w/cpp/language/operator_precedence
18
87
Adonis Butufei
5. PREPROCESORUL C++
CUPRINS
5.Preprocesorul C++.....................................................................................................................................2
5.1 Ce este preprocesorul?.......................................................................................................................2
5.2 Directiva #define................................................................................................................................2
5.2.1 Macroinstructiuni complexe.......................................................................................................4
5.3 Compilare conditionata #ifdef, #else, #endif.....................................................................................4
5.4 Directiva #include..............................................................................................................................5
5.5 Programe cu mai multe fisiere...........................................................................................................5
5.5.1 Fisierele header...........................................................................................................................6
5.5.2 Fisierele sursa.............................................................................................................................9
5.6 Sumar.................................................................................................................................................9
5.7 Intrebari si exercitii............................................................................................................................9
5.8 Bibliografie.......................................................................................................................................10
1
88
Adonis Butufei
5. PREPROCESORUL C++
In primul capitol am intalnit directiva de preprocesare #include. Atunci cand am discutat despre
constante am intalnit a doua directiva: #define. In acest capitol vom discuta urmatoarele aspecte legate
de preprocesor:
• Ce este preprocesorul?
• Directivele #define si #include
• Compilarea conditionata
• Proiecte cu mai multe fisiere
• Folosirea fisierelor header
Aceasta forma este frecvent folosita pentru declararea constantelor pe care am intalnit-o in capitolul 2.
Preprocesorul substituie in codul sursa NUME cu text.
Codul scris inainte de introducerea in standardul C++ a constantelor utilizeaza aceasta modalitate.
Exemplu:
#define MAX 10
Atunci cand folosim preprocesorul el inlocuieste peste tot unde gaseste MAX cu valoarea specificata in
directiva #define. Acest proces de inlocuire a textului se numeste expandare.
In cazul in care folosim calificatorul const compilatorul verifica sintaxa si semnaleaza eventualele erori
care in cazul anterior ar fi identificate in cadrul executiei. Din acest motiv pentru constante este preferata
folosirea cuvintelor cheie.
Exemplu:
2
89
Adonis Butufei
Este important de remarcat ca atunci cand folosim directiva #define nu adaugam terminatorul (;) in
dreapta valorii deoarece poate introduce efecte secundare ca in urmatorul exemplu:
#define MAX_VAL 10;
#define DELTA = MAX_VAL – 2;
int main()
{
int x = DELTA;
cout << x << "\n"; // afiseaza 10 in loc de 8!
return 0;
}
In acest caz, dupa expandarea DELTA initializarea lui x este realizata in modul urmator:
int x = 10; - 2; Deci valoarea lui x este 10 nu 8.
int main()
{
int x = DELTA;
cout << x << "\n";
return 0;
}
Important
Inlocuirea numelui nu se face in interiorul sirurilor. In exemplul de mai jos pe ecran va apare mesajul
original!
#include <iostream>
using namespace std;
#define FOO bar
int main()
{
string mesaj = "Test substituire FOO\n";
cout << mesaj;
}
3
90
Adonis Butufei
Daca in acest caz am fi omis parantezele si expresia ar fi fost (x* x) atunci o expresie de forma
y = PATRAT(x + 1) ar fi fost inlocuita cu: y = (x + 1 * x +1) ceea ce este diferit de (x + 1) * (x + 1).
Atunci cand avem o expresie mai complicata este de preferat organizarea ei pe mai multe linii. Pentru
continuarea macroinstructiunii pe linia urmatoare se foloseste caracterul (\) la sfarsitul liniei curente.
Acest caracter se aplica pana la penultima linie a macroinstructiunii.
Exemplu:
#define PATRAT(x,y) \
((x) * (y))
Recomandari
Macroinstructiunile ofera un mecanism puternic de mentenanta a codului. Deoarece ele au o sintaxa
diferita de C++ si depanarea lor este putin mai dificila este bine sa fie folosite cu multa atentie.
Atunci cand putem obtine acelasi rezultat folosind cod C++, de exemplu implementand o functie care
contine o secventa ce se repeta. este de preferat evitarea macroinstructiunilor.
Macroinstructiunile trebuie sa fie cat mai simple pentru a reduce efortul de intelegere si mentenanta.
4
91
Adonis Butufei
Constanta _DEBUG este definita de mediul de dezvoltare atunci cand selectam configuratia curenta.
Important
Indiferent care este varianta folosita trebuie ca sectiunea sa aiba la sfarsit directiva #endif
#include "myfile"
Daca fisierul se afla intr-un director al proiectului este necesara specificarea caii catre acel fisier. Exemplu:
#include "myfolder/myfile"
Remarca
Daca preprocesorul nu poate include fisierul specificat cu instructiunea #include vom obtine un mesaj de eroare 1.
Recomandare
Cu toate ca este posibil sa folosim prima varianta si pentru fisierele proprii, pentru usurinta depanarii este de preferat sa
folosim prima varianta exclusiv pentru fisierele mediului C++ si cea de-a doua pentru fisierele proprii.
1 In Visual C++ se poate verifica daca fisierele pot fi incluse folosind meniul contextual Open Document care este
afisat atunci cand am pozitionat cursorul editorului pe linia respectiva.
5
92
Adonis Butufei
Pentru o mai buna organizare, codul sursa al programelor este organizat in mai multe fisiere. In C++
avem doua categorii importante de fisiere: fisierele header si fisierele sursa.
Un fisier header este inclus de regula in cel putin doua fisiere sursa: fisierul sursa care contine
implementarea si fisierul sursa in care se face apelul. Deoarece fisierul header contine declaratii de
variabile includerea lui in mai multe fisiere sursa poate genera erori de redefinire daca nu se pun directive
de preprocesare care previn aceste erori. Aceste directive se numesc garda headerului.
Mai jos este prezentata garda headerului pentru calculul factorialului.
// Fisierul factorial.h
#ifndef FACTORIAL_H
#define FACTORIAL_H
#endif //FACTORIAL_H
In cazul in care nu a fost definit, se face definirea cu directiva #define. Preprocesorul copiaza in fisierul
sursa tot textul dintre directiva #define si directiva #endif asociata cu #ifndef.
Daca acest header este inclus a doua oara, atunci numele FACTORIAL_H a fost deja definit si
preprocesorul ignora textul pana la urmatoarea directiva #endif din acest header.
2 Urmatoarele extensii sunt folosite mai rar pentru fisierele header: h, hpp, hxx, hm, inl, inc
6
93
Adonis Butufei
Nota
Etapele adaugarii unui fiser header in proiectul curent, in Visual C++:
Se selecteaza Header Files din fereastra Solution Explorer.
5.6 Sumar
• Preprocesorul este un editor de text specializat care este rulat inaintea compilarii. El are o sintaxa
diferita de C++ si este folosit pentru modificarea codului sursa folosind instructiuni care se numesc
directive.
• Directivele incep cu caracterul # si se termina la sfarsitul liniei curente.
• Macroinstructiunile ofera un mecanism puternic si flexibil pentru modificarea fisierelor sursa,
compilarea contitionata si definirea unor functionalitati care nu pot fi implementate direct in limbajul
C++.
• Pentru organizare eficienta codul programelor este impartit in doua categorii de fisiere: fisiere header
si fisiere sursa. Prima categorie contine declaratiile, iar a doua contine detaliile de implementare.
3 Urmatoarele extensii sunt folosite mai rar pentru fisierele sursa: cpp, cc, cxx. Atunci cand proiectul foloseste ambele
limbaje C si C++ putem avea in proiecte si fisiere sursa C care au extensia c.
8
95
Adonis Butufei
• Pentru fisierele header se folosesc directive care previn erorile de compilare atunci cand fisierul este
inclus de mai multe ori.
10. Sa se scrie un program care face conversia intre mile si km folosind urmatoarea relatie 1 mila =
1.609344 km. Programul are urmatoarele fisiere:
conversii.h – contine prototipul functiei double Mile2Km(double miles).
conversii.cpp – implementeaza functia double Mile2Km(double miles).
main.cpp – contine functia main in care se citeste de la tastatura numarul de mile, se apeleaza functia
de conversie, afiseaza rezultatul.
11. Sa se extinda programul anterior adaugand conversia inversa Km2Mile folosind aceleasi fisiere.
12. Sa se comenteze garda de include din fisierul header de la exercitiul anterior si sa se includa a doua
oara in fisierul main. Care sunt mesajele de eroare?
5.8 Bibliografie
• Practical C++ Programming, Second edition, O'Reilly Steve Oualline, Cap 10.
• http://www.ebyte.it/library/codesnippets/WritingCppMacros.html
9
96
Adonis Butufei
CUPRINS
6.Tipuri compuse de date..............................................................................................................................2
6.1 Variabile de tip tablou (array).............................................................................................................2
6.1.1 Declararea tablourilor.................................................................................................................2
6.1.2 Initializarea tablourilor...............................................................................................................2
6.1.3 Accesarea elementelor ...............................................................................................................3
6.1.4 Tablouri cu mai multe dimensiuni .............................................................................................4
6.2 Siruri de caractere...............................................................................................................................5
6.2.1 Operatii cu siruri de caractere.....................................................................................................6
6.2.1.1 Determinarea lungimii unui string .....................................................................................6
6.2.1.2 Copierea sirurilor de caractere............................................................................................7
6.2.1.3 Concatenarea sirurilor de caractere.....................................................................................8
6.2.1.4 Compararea sirurilor de carctere.........................................................................................9
6.3 Structuri de date...............................................................................................................................10
6.3.1 Initializarea structurilor............................................................................................................10
6.3.2 Accesul la membrii structurii....................................................................................................11
6.3.3 Atribuirea structurilor...............................................................................................................11
6.4 Uniuni...............................................................................................................................................12
6.5 Sumar...............................................................................................................................................13
6.6 Intrebari si exercitii..........................................................................................................................14
6.7 Bibliografie.......................................................................................................................................15
1
97
Adonis Butufei
Pentru a scrie programe care sa lucreze cu acest tip de date avem nevoie sa folosim tipul de date numit
tablou. Acest tip de date este o colectie secventiala de locatii de memorie de acelasi tip. Fiecare locatie se
numeste element.
Exemplu:
int tablou[3];
Observatii:
2
98
Adonis Butufei
• Se poate omite dimensiunea tabloului la declarare daca se initializeaza toate elementele. In exemplul
de mai jos se initializeaza un tablou de 5 elemente.
int tablou [] = {1, 2, 3, 4, 5};
• Daca numarul de elemente folosite pentru initializare depaseste dimensiunea tabloului se obtine o
eroare de compilare:
int tablou [3] = { 1, 2, 3, 4};
• In cazul in care sunt mai putine valori pentru initializare, ele vor fi folosite pentru initializarea primelor
elemente din tablou. Dupa ce s-au folosit toate valorile, elementele ramase vor fi initializate cu 0:
int tablou[3] = { 1, 2} ;// tablou[2] are valoarea 0.
• Pentru initializarea tuturor elementelor cu 0 este suficient sa folosim o singura valoare 0 in dreapta
egalului:
int tablou [3] = {0}; // echivalent cu tablou[3] = { 0, 0, 0};
3
99
Adonis Butufei
18: }
19:
20: return 0;
21: }
In acest tablou am initializat elementele unui vector de elemente intregi si am afisat valorile pe ecran.
In linia 6 am declarat o constanta care reprezinta dimensiunea tabloului. Aceasta se foloseste pentru
declararea tabloului in linia 7 si pentru conditiile de limita din bucle in liniile 9 si 15.
Valorile tabloului sunt citite de la tastatura (liniile 9 – 13) si afisate pe ecran (liniile 15 – 18).
Important
Deoarece indexul tablourilor porneste de la 0, valoarea indexului corespunzator ultimului element este
SIZE -1. Omiterea acestui fapt este o sursa frecventa de erori.
Declararea tablourilor cu mai multe dimensiuni se face asemanator cu declararea tablourilor cu o singura
dimensiune. In plus, pentru fiecare dimensiune se mai adauga o pereche de paranteze drepte.
Exemplu de declarare a unei matrici de 2 x 2 elemente:
double matrice[2] [2];
0 1
matrice 0 0.0 0.0
1 0.0 0.0
matrice[0][1]
Modul de lucru cu tablouri cu mai multe dimensiuni este prezentat in exemplul de mai jos.
1: #include <iostream>
2: using namespace std;
3:
4
100
Adonis Butufei
4: int main()
5: {
6: const int SIZE1 = 2;
7: const int SIZE2 = 3;
8:
9: // Declarare si initializare
10: int matrice [SIZE1][SIZE2] = { {1,2,3} ,
11: {4,5,6} };
12:
13: for(int i = 0; i < SIZE1; i++)
14: {
15: for(int j = 0; j < SIZE2; j++)
16: {
17: cout << "matrice[" << i << "][" << j << "]=";
18: cout << matrice[i][j] << "\n";
19: }
20: }
21: return 0;
22: }
Initializarea tabloului este realizata in liniile 10 si 11. Pentru accesarea elementelor am folosit doua
bucle, fiecare pentru o dimensiune a tabloului. In linia 18 se acceseaza valorile corespunzatoare
indecsilor.
Spre deosebire tablourile prezentate anterior, sirurile de caractere contin pe ultima pozitie caracterul de
control '\0', care reprezinta terminatorul sirului. Acest caracter este introdus automat de compilator,
insa este necesar ca tabloul sa aiba alocat spatiu pentru el.
In urmatorul exemplu obtinem eroare de compilare deoarece mesajul are 12 caractere si compilatorul nu
mai are spatiu pentru terminator.
char mesaj[12] = "Hello World!"; // Tabloul trebuie sa aiba 13
// caractere.
Din acest motiv, pentru mesajele care nu se modifica pe parcursul executiei programului este
recomandabila prima varianta de declarare, deoarece dimensiunea tabloului se calculeaza automat.
5
101
Adonis Butufei
Terminatorul sirului nu este printabil si orice caracter printabil1 pozitionat dupa acest carcter nu este
afisat.
Exemplu:
#include <iostream>
using namespace std;
int main()
{
char mesaj [] = "Hello\0 World!";
cout << mesaj << "\n";
return 0;
}
int main()
{
In acest exemplu am inclus headerul cstring pentru a putea apela functia strlen. Mesajul din acest
exemplu are 12 caractere.
Important
1 Caracterele printabile sunt caractere care se afiseaza pe ecran si au fost prezentate in capitolul 3.
6
102
Adonis Butufei
Atunci cand calculam spatiul necesar pentru un tablou trebuie sa incrementam valoarea returnata de
strlen pentru a obtine spatiul necesar pentru terminator.
int main()
{
strcpy(mesaj2, mesaj1);
cout << mesaj1 << "\n" << mesaj2 << "\nCopiere cu success.\n";
return 0;
}
In acest exemplu, dupa apelul functiei strcpy, ambele variabile vor contine acelasi mesaj.
Important
Variabila destinatie trebuie sa aiba suficient spatiu pentru a putea copia tot mesajul inclusiv terminatorul
de caractere.
Daca vrem sa copiem numai primele n caractere putem folosi functia strncpy, ca in exemplul urmator:
1: #include <iostream>
2: #include <cstring>
3: using namespace std;
4:
5: int main()
6: {
7: char mesaj1 [] = "Hello World!";
8: char mesaj2 [6];
9:
7
103
Adonis Butufei
In acest exemplu am copiat doar primele 5 caractere din mesaj1. Este important de remarcat adaugarea
terminatorului in linia 11. Acesta este necesar deoarece dupa copierea primelor 5 caractere din variabila
mesaj1 sirul trebuie sa aiba terminatorul setat corect.
8
104
Adonis Butufei
Important
Sirul destinatie trebuie sa aiba suficient spatiu pentru copierea tuturor caracterelor plus terminatorul de sir.
int main()
{
return 0;
}
Important
Compararea sirurilor de caractere este case sensitive, adica literele mari sunt considerate diferite de cele
mici: A != a. Pentru ca doua siruri de caractere sa fie egale trebuie sa contina acelasi tip de litere.
9
105
Adonis Butufei
mod permite scrierea unui cod mai usor de inteles si mentinut. Variabilele grupate in interiorul structurii
se numesc membri sau atribute.
Exemplu:
struct Complex
{
double re;
double im;
};
In acest exemplu am grupat partea reala si imaginara intr-o structura Complex care poate fi folosita pentru
calcule cu numere complexe.
In acest caz varloarea variabilei re este 1.2 iar valoarea variabilei im este 3.4.
10
106
Adonis Butufei
19: }
In acest exemplu in liniile 15 si 16 se afiseaza valorile partii reale si imaginare ale variabilei complex c1.
Dupa executarea instructiunii de atribuire din acest exemplu variabila c2 va avea aceleasi valori pentru re
si im
6.4 Uniuni
Structurile permit definirea tipurilor de date care contin mai multe variabile membru. Fiecare membru
ocupa o zona separata de memorie. Uniunile permit definirea unui tip de date in care o zona de memorie
este partajata de mai multe variabile membru.
Exemplu:
union Valoare
{
int iVal;
double dVal;
};
In acest exemplu avem o zona de memorie partajata intre o variabila intreaga si una reala.
Putem gandi o structura ca o cutie cu mai multe compartimente, iar uniunea ca o cutie cu un singur
compartiment. In cazul structurilor, membrii nu interactioneaza intre ei. In cazul uniunilor, numai un
singur membru este activ la un moment dat: acela caruia i s-a atribuit o valoare.
Pentru a lucra corect cu o uniune este necesara o variabila auxiliara care sa indice membrul activ.
Mai jos este prezentat un exemplu tipic de folosire a uniunilor.
1: #include <iostream>
2: using namespace std;
3:
4: union Valoare
5: {
6: int i;
7: double d;
8: };
9:
11
107
Adonis Butufei
Uniunile au fost preluate din limbajul C unde sunt folosite pentru transmiterea in mod uniform a
parametrilor catre functii atunci cand au tipuri diferite.
Prezinta un avantaj pentru portabilitatea programelor. Cand se adauga un nou camp la membrii uniunii
semnatura functiilor ramane neschimbata, doar detaliile de implementare sunt actualizate.
In C++ acest tip de date este utilizat mai rar deoarece mecanismele programarii obiectuale pe care le vom
studia in capitolele urmatoare ofera solutii mai simple pentru obtinerea aceluiasi rezultat.
12
108
Adonis Butufei
6.5 Sumar
• Tabloul (array) reprezinta o serie de date de acelasi tip plasate in locatii succesive de memorie.
• Fiecare dintre aceste locatii de memorie se poate accesa prin incrementarea unui index atasat numelui
tabloului.
• Indexul porneste de la 0.
• Declararea unui tablou: tip numeTablou[elemente]
• Initializarea: tip numeTablou[] = {element1, element2, element3}
• Accesarea elementelor: numeTablou[index]
• Valoarea indexului corespunzatoare ultimului element este: dimensiunea tabloului (numarul de
elemente) – 1.
• Pentru anumite probleme se pot folosi tablouri cu 2 sau mai multe dimensiuni.
• Pentru tablourile cu mai mult de 2 dimensiuni trebuie tinut cont de memoria consumata.
• Declarare: tip nume[elemente1][elemente1]
• Un tip particular de tablou este sirul de caractere.
• In acest caz elementele tabloului sunt codurile ASCII folosite pentru mesaje de tip text.
• Declarare si initializare: char mesaj [] = "Hello World!";
• Sirurile de caractere se termina cu un caracter de control '\0'. Atentie la alocarea spatiului pentru acest
terminator!
• Deoarece sirurile de caractere sunt un tip de date compus modificarea lor se face cu ajutorul unor
functii.
• In C++ aceste functii sunt implementate in fisierul cstring:
strlen () - lungimea sirului (fara terminator)
strcpy() - copiere
strncpy() – copierea primelor n caractere
strcat() - concatenare
strcmp() - comparare
• Structura (struct) este un tip de date care grupeaza mai multi membri, de diferite tipuri, sub acelasi
nume.
• Fiecare membru al unei structuri ocupa o zona de memorie proprie.
• Uniunea (union) este un tip de date care grupeaza mai multe elemente de diverse tipuri. Acestea ocupa
acelasi spatiu fizic de memorie.
• Pentru a lucra corect cu o uniune este necesara o variabila auxiliara care sa indice membrul activ.
2. Cum se declara un tablou de tip double care are trei dimensiuni si poate contine 4 elemente pentru
prima dimensiune, 5 elemente pentru a doua dimensiune si 10 elemente pentru a treia dimensiune?
7. Sa se scrie un program care calculeaza determinantul unei matrici de doua linii si doua coloane.
9. Sa se scrie un program care citeste de la tastatura numele, prenumele si calculeaza adresa de email
dupa urmatoarea formula prenume.nume@mailserver.com.
10. Sa se scrie un program care foloseste o structura de tip persoana cu urmatorii membrii: nume,
prenume, varsta. Programul citeste de la tastatura valorile pentru acesti membrii.
11. Care este diferenta intre o structura si o uniune?
12. Care sunt asemanarile dintre o structura si o uniune?
6.7 Bibliografie
• C++ Without Fear, Second Edition, Prentice Hall, Brian Overland, Cap 7.
• Practical C++ Programming, Second Edition, O'Reilly, Steve Oualline, Cap 12.
14
110
Adonis Butufei
7. VARIABILE SPECIALE
CUPRINS
7.Variabile speciale.......................................................................................................................................2
7.1 Referinte.............................................................................................................................................2
7.2 Pointeri...............................................................................................................................................4
7.2.1 Determinarea adresei variabilelor, operatorul &........................................................................4
7.2.2 Declararea pointerilor si stocarea adreselor variabilelor............................................................5
7.2.3 Accesarea valorii unei variabile folosind pointerii.....................................................................5
7.2.4 Calificatorul const pentru pointeri .............................................................................................6
7.2.4.1 Pointeri catre constante ......................................................................................................6
7.2.4.2 Pointeri constanti................................................................................................................7
7.2.4.3 Pointeri constanti catre constante.......................................................................................7
7.2.5 Pointeri si vectori........................................................................................................................7
7.2.5.1 Aritmetica pointerilor..........................................................................................................8
7.2.6 De ce se folosesc pointerii?........................................................................................................8
7.2.6.1 Transferul parametrilor prin adresa.....................................................................................9
7.2.6.2 Alocare dinamica a memoriei: operatorii new si delete...................................................10
7.2.6.3 Selectia datelor membru pentru structuri..........................................................................11
7.2.7 Pointerul NULL........................................................................................................................12
7.2.8 Pointerul void...........................................................................................................................12
7.2.9 Pointeri vs. Referinte................................................................................................................13
7.3 Sumar ..............................................................................................................................................13
7.4 Intrebari si exercitii..........................................................................................................................13
7.5 Bibliografie.......................................................................................................................................15
1
111
Adonis Butufei
7. VARIABILE SPECIALE
Exista situatii cand este util sa se lucreze cu adresele locatiilor de memorie unde sunt stocate variabilele.
Pentru a adresa aceste situatii, in acest capitol sunt discutate urmatoarele notiuni:
• Referinte
• Pointeri
7.1 Referinte
Referintele sunt un tip special de variabile. Cu ajutorul referintelor putem atasa unul sau mai multe nume
aceleiasi locatii de memorie.
Referintele se declara folosind tipul variabilei si operatorul &.
Exemplu:
1: #include <iostream>
2: using namespace std;
3:
4: int main()
5: {
6: int valoare = 10;
7: int &test = valoare;
8: cout << "Valoare initiala: " << valoare << "\n";
9:
10: test = 25;
11: cout << "Valoare finala: " << valoare << "\n";
12:
13: return 0;
14: }
In linia 7 am definit o referinta pentru variabila valoare. Din acest moment test si valoare folosesc aceeasi
zona de memorie.
10
valoare
test
Important
Referintele odata setate nu mai pot fi schimbate. Folosirea operatorului = pentru o referinta determina
executia unei atribuiri, nu schimbarea referintei.
Exemplu:
1: #include <iostream>
2: using namespace std;
3:
2
112
Adonis Butufei
4: int main()
5: {
6: int v1 = 10;
7: int &r1 = v1;
8:
9: cout << "v1 " << v1 << "\n";
10: cout << "r1 " << r1 << "\n";
11:
12: int &r2 = v1;
13:
14: int v2 = 20;
15:
16: r2 = v2;
17:
18: cout << "v1 " << v1 << "\n";
19: cout << "r1 " << r1 << "\n";
20:
21: return 0;
22: }
In acest exemplu atribuirea din linia 16 nu schimba referinta catre variabila v2, ci atribuie valoarea
variabilei v2 zonei de memorie conectata cu referinta r2. La final variabila v1 va avea valoarea 20.
Referintele sunt foarte utile pentru transmiterea parametrilor catre functii. Am vazut in capitolul despre
variabile ca parametrii functiilor se pot considera variabile locale functiei. O modificare asupra
parametrilor nu schimba valoarea variabilelelor cu care s-a apelat functia.
In cazul in care dorim ca functia sa modifice variabilele care sunt transmise ca parametri, folosim
referintele ca in exemplul de mai jos:
1: #include <iostream>
2: using namespace std;
3:
4: void Swap(int &x, int &y)
5: {
6: int temp = x;
7: x = y;
8: y = temp;
9: }
10:
11: int main()
12: {
13: int x = 10;
14: int y = 20;
3
113
Adonis Butufei
15:
16: cout << "x: " << x << "\n";
17: cout << "y: " << y << "\n";
18:
19: Swap(x,y);
20:
21: cout << "x: " << x << "\n";
22: cout << "y: " << y << "\n";
23:
24: return 0;
25: }
In acest exemplu avem o functie care inverseaza valorile variabilelor x si y. In linia 6 salvam valoarea
variabilei x intr-o variabila locala. Dupa executia liniei 7 variabila x va avea valoarea variabilei y. Apoi,
dupa executia liniei 8 variabila y va avea valoarea initiala a variabilei x.
Programul afiseaza valorile initiale si finale dupa apelul functiei Swap.
7.2 Pointeri
Pointerii sunt variabile care contin adrese de memorie. Se spune ca o variabila de tip pointer pointeaza
catre o adresa de memorie.
Memoria calculatorului este organizata intr-o secventa de pozitii. Fiecare variabila ocupa o pozitie unica
de memorie. Aceasta pozitie reprezinta adresa variabilei.
Prin analogie putem gandi variabilele ca fiind cladirile dintr-un oras iar adresele acestor cladiri reprezinta
adresele din memorie. Pentru a identifica orice cladire indiferent de dimensiune, adresele trebuie sa
contina numele strazii si numarul. In acelasi mod adresele oricaror variabile se reprezinta prin numere
scrise in hexazecimal.
#include <iostream>
using namespace std;
int main()
{
int x = 10;
return 0;
}
4
114
Adonis Butufei
In acest exemplu adresa variabilei x este tiparita pe ecran. In functie de varianta sistemului de operare
putem obtine o adresa pe 32 sau pe 64 de biti.
In acest exemplu am declarat un pointer care poate stoca adresele variabilelor intregi.
Important
Deoarece in momentul declararii valoarea variabilei este nedeterminata, in cazul pointerilor este esentiala
initializarea variabilei in momentul declararii. De obicei se foloseste adresa 0 sau valoarea NULL1. In caz
contrar, adresa aleatoare pe care o contine acel pointer poate creea defecte greu de identificat.
Stocarea adreselor variabilelor in pointeri se face prin atribuirea adresei unei variabile pointer ca in
exemplul urmator:
int x = 10;
int *p = &x;
In acest exemplu in linia 2 se atribuie pointerului p adresa lui x apoi in linia 3 variabila y este initializata
cu valoarea catre care pointeaza pointerul p. Dupa executarea acestei linii y va avea valoarea 10.
Important
Simbolul (*) este folosit in doua moduri in lucrul cu pointerii:
• La declararea unei variabile pointer, caz in care simbolul urmeaza dupa tipul de date a carui adresa o
stocheaza.
Exemplu:
int *p = 0;
In acest exemplu daca incercam sa atribuim pointerului p adresa constantei x obtinem eroare de
compilare. Varianta corecta este prezentata mai jos.
const int x = 10;
const int* p = &x;
// ...
6
116
Adonis Butufei
In acest caz pointerul este constant, locatia nu este constanta. Atribuirea altei adrese, din ultima linie,
genereaza eroare de compilare.
int main()
{
int x [] = {1,2,3};
int * p = x;
return 0;
7
117
Adonis Butufei
La rularea acestui program obtinem aceeasi valoare pentru adresa primului element si pentru pointerul p.
In acest exemplu la linia 12 expresia p + i deplaseaza pointerul la pozitia i, apoi se extrage valoarea de
la acea adresa si se afiseaza pe ecran.
8
118
Adonis Butufei
Exemplu:
1: #include <iostream>
2: using namespace std;
3:
4: void Swap(int *x, int *y)
5: {
6: int temp = *x;
7: *x = *y;
8: *y = temp;
9: }
10:
11: int main()
12: {
13: int x = 10;
14: int y = 20;
15:
16: cout << "Valori initiale\n";
17: cout << "x " << x << "\n";
18: cout << "y " << y << "\n";
19:
20: Swap(&x , &y);
21:
22: cout << "Valori finale\n";
23: cout << "x " << x << "\n";
24: cout << "y " << y << "\n";
25:
26: return 0;
27: }
In acest exemplu functia Swap schimba valoarea celor doua variabile. Deoarece parametrii sunt transmisi
prin adresa functia lucreaza cu locatiile de memorie declarate in locul apelului.
9
119
Adonis Butufei
Pentru gestionarea acestor situatii, compilatorul foloseste un macanism numit stiva. Cand o variabila este
alocata este pusa pe stiva. Cand domeniul ei de viata se termina, variabila este stearsa din stiva si zona de
memorie este eliberata.
Cand se foloseste alocarea dinamica a memoriei programatorul este responsabil cu dealocarea memoriei
atunci cand variabilele respective nu mai sunt necesare.
Alocarea dinamica a variabilelor simple
Exemplu:
int *p = new int;
// folosirea variabilei
In acest exemplu este alocata dinamic o zona de memorie pentru o variabila de tip int si adresa acestei
zone de memorie este atribuita pointerului p. Cand variabila p nu mai este necesara, se dealoca memoria
de la adresa respectiva.
// folosirea variabilei.
In acest exemplu am alocat un tablou de tip int cu 10 elemente si la final am eliberat memoria.
Important
Pentru dealocarea variabilelor simple se foloseste delete, iar pentru dealocarea variabilelor tablou se
foloseste delete [].
10
120
Adonis Butufei
de viata al unui pointer s-a terminat insa memoria alocata nu a fost eliberata se creaza un memory leak.
Exemplu:
void Functie()
{
int *p = new int;
}
In acest exemplu la apelul funciei se aloca o zona de memorie pentru o variabila de tip int.
Dupa terminarea executiei acestei funcii domeniul de viata al pointerului care a fost utilizat pentru alocare
se termina si zona de memorie ramane ocupata insa nu mai poate fi accesata, generand un memory leak.
Al doilea caz in care apar irosiri de memorie (memory leaks) este atunci cand pointerului utilizat i se
atribuie o alta adresa.
Exemplu:
1: int x = 10;
2: int *p = new int;
3: p = &x;
Zona de memorie alocata in linia 2 nu mai este accesibila dupa executia liniei 3.
Important
Pentru fiecare instructiune new trebuie sa existe o instructiune delete asociata.
Trebuie tinuta evidenta pointerilor catre zone de memorie alocate dinamic si aceste zone trebuiesc
eliberate dupa ce si-au indeplinit scopul.
11
121
Adonis Butufei
In linia 14 este selectat membrul imaginar. Datorita precedentei operatorilor este necesarea folosirea
parantezelor. Mai intai se face indirectarea pointerului apoi se selecteaza membrul structurii.
In C++ se poate folosi operatorul -> pentru a simplifica selectia membrilor. Linia 14 din exemplul de mai
sus se poate scrie in modul urmator:
pC1->im = 10;
In acest exemplu, dupa eliberarea zonei de memorie valoarea lui p contine aceeasi adresa insa continutul
memoriei aflate la acea adresa nu mai este valid. Accesul ulterior al acelei adrese are efecte nedeterminate
si determina defecte greu de fixat! Prin atribuirea pointerului NULL aceste probleme sunt eliminate.
Folosind sistematic acest principiu de initializare putem verifica foarte usor daca pointerul contine o
adresa de memorie valida.
Exemplu:
int x = 10;
double y = 2.5;
void *p = &x;
p = &y;
In acest exemplu p este initializat cu adresa variabilei x de tip int apoi lui p i se atribuie adresa lui y care
are tipul double.
12
122
Adonis Butufei
Datorita faptului ca tipul nu este cunoscut, nu se poate face indirectarea pointerului pentru accesul valorii.
Pentru aceasta este necesara operatia de cast la tipul dorit.
Exemplu:
int x = 10;
void *p = &x;
Folosind pointerii void se ocoleste mecanismul de verificare al tipului datelor folosit de compilator. Din
acest motiv este recomandabila folosirea pointerilor void numai atunci cand este necesar, pentru
mentinerea compatibiliatii.
7.3 Sumar
• Referintele sunt un tip special de variabile, care actioneaza ca un nume alternativ pentru variabile.
• Referintele se declara folosind tipul variabilei si operatorul &.
• Referintele sunt foarte utile pentru transmiterea parametrilor catre functii.
• Memoria calculatorului poate fi privita ca o succesiune de celule de memorie numerotate consecutiv.
La declararea unei variabile se aloca o cantitate de memorie necesara stocarii variabilei la o anumita
locatie in memorie - adresa de memorie.
• Variabilele care contin adrese de memorie se numesc pointeri.
• Pointerii ofera posibilitatea de a lucra direct cu memoria.
13
123
Adonis Butufei
4. Sa se scrie o functie care are 2 parametri de tip referinta la structura Complex (definita in acest
capitol) si returneaza o struncura de tip Complex.
14
124
Adonis Butufei
p+= 2;
11. Dupa apelul urmatoarei functii valorile sunt neschimbate. Care este greseala?
void Swap(int x, int y)
{
int temp = x;
x = y;
y = temp;
}
7.5 Bibliografie
• Teach yourself C++ In an Hour A day, Sixth Edition, Sams, Jesse Liberty, Cap 9.
• Practical C++ Programming, Second Edition, O'Reilly, Steve Oualline, Cap15
• Teach yourself C++ In an Hour A day, Sixth Edition, Sams, Jesse Liberty, Cap8
15
125
Adonis Butufei
CUPRINS
8.Functii si spatii de nume............................................................................................................................2
8.1 Functii.................................................................................................................................................2
8.1.1 Declararea, definirea si apelul functiilor....................................................................................3
8.1.2 Parametri si argumente...............................................................................................................5
8.1.3 Transferul parametrilor...............................................................................................................5
8.1.3.1 Transfer prin valoare...........................................................................................................5
8.1.3.2 Transfer prin referinta.........................................................................................................5
8.1.3.3 Transfer prin adresa............................................................................................................7
8.1.4 Returnarea rezultatului ..............................................................................................................9
8.1.4.1 Prin valoare.........................................................................................................................9
8.1.4.2 Prin referinta.......................................................................................................................9
8.1.4.3 Prin adresa.........................................................................................................................11
8.1.5 Parametri cu valori implicite....................................................................................................11
8.1.6 Supraincarcarea functiilor.........................................................................................................12
8.1.7 Functii inline.............................................................................................................................12
8.1.8 Functii recursive.......................................................................................................................14
8.1.9 Recomandari pentru scrierea functiilor....................................................................................15
8.2 Spatii de nume..................................................................................................................................15
8.2.1 Accesarea entitatilor dintr-un namespace.................................................................................15
8.2.2 Spatiul de nume global.............................................................................................................16
8.2.3 Declararera spatiilor de nume...................................................................................................17
8.2.4 Spatii de nume ierarhice...........................................................................................................17
8.3 Sumar...............................................................................................................................................18
8.4 Intrebari si exercitii..........................................................................................................................19
8.5 Bibliografie.......................................................................................................................................21
1
126
Adonis Butufei
Organizarea codului in functii asigura descompunerea problemelor complexe in sub-probleme mai mici
care sunt usor de inteles, implementat si mentinut.
Gruparea functiilor, variabilelor si tipurilor de date definite de utilizator in spatii de nume usureaza
eforturile de scriere a codului. Aceasta este folosita si in activitatile curente cand pentru toti membrii unei
familii folosim numele de familie pentru a-i diferentia de alte familii.
8.1 Functii
Functiile permit gruparea codului intr-un bloc compact care poate fi folosit in mai multe locuri. De
exemplu, daca dorim calcularea pretului final pentru zece produse, putem sa scriem formula de zece ori
sau putem sa scriem o functie care face acelasi lucru si sa o apelam de zece ori.
Ultima varianta nu reduce numai efortul initial de scriere ci si eforturile de mentenanta. Daca peste un
timp dorim sa adaugam discount procentual pentru produse va trebui sa facem o singura modificare in loc
de zece.
Fiecare functie are un nume si cand acest nume este intalnit in timpul rularii programului, se executa
codul acelei functii. Aceasta se numeste apelarea functiilor.
Functiile proiectate corect realizeaza un singur obiectiv, specific, usor de inteles si identificat de numele
functiei.
Exemplu:
int result = Factorial(10);
In acest exemplu am apelat functia factorial pentru a calcula 10! si am initializat variabila result cu
valoarea calcului.
Problemele complicate trebuiesc descompuse in probleme mai simple care sunt implementate cu ajutorul
functiilor. Prin apelul acestor functii se rezolva problemele initiale.
Exista doua categorii de functii: cele care apartin compilatorului1 si cele dezvoltate de utilizator.
Dupa ce executia unei functii se termina, programul continua din locul in care a fost apelata functia. Daca
1 In literatura se foloseste termenul built in functions.
2
127
Adonis Butufei
functia a returnat o valoare acea valoare este folosita in expresia din apel.
Exemplu:
int result = Factorial(3) ;
In acest exemplu dupa ce executia functiei Factorial se termina variabila result se initializeaza cu
rezultatul returnat de functia Factorial si se continua codul care urmeaza apelului.
In acest exemplu am declarat prototipul functiei Factorial care are un parametru de tip int si tipul valorii
returnate este int.
Pentru functii care nu au parametri prototipul este similar cu cel din exemplul urmator:
int FunctieFaraParametrii();
Prototipul unei functii care nu returneaza rezultate folosesc cuvantul cheie void ca in exemplul urmator.
void FunctieCareNuReturneazaValori();
Exemplu:
int Factorial (int n)
{
int result = 1;
for(int i = 2; i < = n ; i++)
{
result *= i;
}
return result;
}
Exemplu:
#ifndef FACTORIAL_H
#define FACTORIAL_H
int Factorial(int n);
#endif
In acest exemplu am declarat functia factorial in fisierul factorial.h si este necesara folosirea directivei
#include in fisierele in care vrem sa apelam functia Factorial.
int main ()
{
int result = Factorial(10);
}
3. Definirea functiei inainte de apel. In acest caz definirea joaca rolul de prototip.
Exemplu:
int Factorial (int n)
{
int result = 1;
for(int i = 2; i < = n ; i++)
{
result *= i;
}
return result;
}
int main ()
{
int result = Factorial(10);
}
In acest exemplu functia Factorial este definita apoi in functia main este apelata.
4
129
Adonis Butufei
Exemplu:
#include <iostream>
using namespace std;
void Test(int n)
{
n++;
cout << "n modificat in functia Test" << n << "\n";
}
int main()
{
int a = 2;
cout << "valoare inainte apel" << a << "\n";
Test(a);
cout << "valoare dupa apel" << a << "\n";
}
void Test(int& n)
{
n++;
cout << "n modificat in functia Test" << n << "\n";
}
5
130
Adonis Butufei
int main()
{
int a = 2;
cout << "valoare inainte apel" << a << "\n";
Test(a);
cout << "valoare dupa apel" << a << "\n";
}
In acest caz parametrul este un alias al variabilei a. Modificand valoarea lui n modificam si valoarea lui a.
Important
Daca folosim transferul prin referinta pentru optimizarea operatiilor cu memoria dar vrem sa prevenim
modificarea variabilei in interiorul functiei este necesara folosirea referintelor constante in semnatura
functiei.
Exemplu:
void Test (const int& n)
{
n--; // genereaza eroare de compilare
}
Recomandare
Atunci cand se foloseste transferul prin referinta, utilizati directiva const pentru toate cazurile care nu
necesita modificarea valorii parametrului.
6
131
Adonis Butufei
• Este greu de identificat daca un parametru transferat prin referinta este folosit pentru intrare2, pentru
rezultat sau pentru amandoua.
• Din apelul unei functii nu putem determina care dintre parametri sunt transferati prin referinta si care
parametri sunt transferati prin valoare fara a cunoaste semnatura functiei.
void Test(int* n)
{
(*n)++;
cout << "n modificat in functia Test" << n << "\n";
}
int main()
{
int a = 2;
cout << "valoare inainte apel" << a << "\n";
Test(&a);
cout << "valoare dupa apel" << a << "\n";
}
In acest exemplu, deoarece parametrul este pointer este necesara indirectarea pentru a accesa valoarea
care se afla la acea adresa de memorie.
Transferul prin adresa este folosit frecvent pentru variabile alocate dinamic si pentru tablouri. Urmatorul
exemplu afiseaza toate elementele unui tablou:
#include <iostream>
using namespace std;
2 Parametrii de intrare sunt sunt folositi pentru calculele interne functiei. Parametrii de iesire sun rezultate returnate de
functie. Un parametru poate avea ambele roluri atunci cand este folosit si pentru calculele interne si la sfarsit i se atribuie o
valoare pentru rezultat.
7
132
Adonis Butufei
int main()
{
const int lungime = 3;
int tbl [lungime] = { 1 , 2 , 3};
AfiseazaElemente(tbl, lungime);
return 0;
}
Si in acest caz se pot folosi pointeri catre constante pentru a preveni modificarile neintentionate ale
parametrilor.
Ca si la transferul parametrilor, returnarea rezultatului se realizeaza in trei moduri: prin valoare, prin
referinta si prin adresa.
Atunci cand vrem sa returnam un rezultat calculat cu variabile locale functiei, returnarea prin valoare este
indicata.
Pentru variabilele care ocupa multa memorie, precum tablourile si tipurile de date definite de utilizator,
acest mod de returnare a rezultatului este lent.
Important
Trebuie evitata returnarea referintelor la variabilele locale deoarece domeniul de viata se termina odata cu
executia functiei.
Exemplu:
int & Patrat(int n)
{
int result = N * n;
return result;
}
In acest exemplu domeniul de viata al variabilei result se termina dupa terminarea executiei functiei.
Apelantul va obtine o referinta la o variabila inexistenta.
Returnarea prin referinta este utilizata, de obicei, pentru returnarea parametrilor transmisi prin referinta
inapoi catre apelant.
Exemplu:
1: #include <iostream>
2: using namespace std;
3:
4: struct Complex
5: {
6: double re;
7: double im;
8: };
9:
10:
11: double& Im(Complex& c)
9
134
Adonis Butufei
12: {
13: return c.im;
14: }
15:
16: int main()
17: {
18: Complex c1 = { 2.0, 3.5};
19:
20: Im(c1) = 4.11;
21:
22: cout << c1.im << "\n";
23:
24: return 0;
25: }
In acest exemplu functia Im returneaza o referinta la membrul imaginar al numarului complex. Din acest
motiv in linia 20 se poate schimba valoare membrului imaginar pentru variabila c1.
Important
Ca si in cazul precedent trebuie sa evitam returnarea unei adrese la o variabila locala functiei deoarece
domeniul de viata al aceasteia se termina odata cu executia functiei.
Acest tip de returnare este folosit pentru returnarea unui pointer la zona de memorie nou alocata catre
apelant.
Exemplu:
int * AlocaTablou(int lungime)
{
return new int [lungime];
}
int main()
{
int * tablou = AlocaTablou(10);
// lucreaza cu tabloul
delete [] tablou;
return 0;
}
10
135
Adonis Butufei
Exemplu:
#include <iostream>
using namespace std;
int main()
{
In acest exemplu primul apel foloseste valoarea implicita pentru latime. In al doilea apel sunt specificate
valorile pentru ambii parametrii.
Important
Toti parametrii care urmeaza dupa un parametru cu valori implicite trebuie sa aiba valori implicite.
Este util atunci cand avem de efectuat acelasi tip de operatie cu tipuri de date diferite.
Exemplu:
int Add(int x, int y);
double Add(double x, double y);
Daca o functie este declarata folosind cuvantul cheie inline, compilatorul nu creaza o functie ci copiaza
codul acelei functii direct in locul fiecarui apel. In acest caz performanta este imbunatatita insa creste
dimensiunea programului.
Este indicat sa se foloseasca functiile inline doar in cazul in care contin una sau doua linii de cod si sunt
apelate dintr-un numar redus de locuri. In caz contrar, datorita cresterii programului este posibil ca
performantele globale ale programului sa fie mai slabe cand se folosesc functiile inline.
Exemplu:
#include <iostream>
using namespace std;
int main()
{
cout << "Aria dreptunghiului: " << ArieDreptunghi(10,2) << "\n";
return 0;
}
Observatii
12
137
Adonis Butufei
Optimizarea performantei programelor este o provocare dificila si multi programatori nu pot identifica
sectiunea de cod care necesita optimizari. Modalitatea adecvata pentru optimizare este studierea
comportamentului programului folosind programe speciale5 pentru generarea statisticilor precum timpul
consumat de o anumita functie, numarul de apeluri etc. Aceste statistici ajuta programatorul sa-si
concentreze atentia acolo unde este adevarata problema.
Din acest motiv este mult mai utila scrierea unui cod clar si usor de inteles decat un cod care sa contina
presupunerile referitoare la ce ar putea sa mearga incet sau rapid, dar sa fie mai greu de inteles.
Optimizarea unui cod bine organizat este mult mai usoara decat mentinerea unui cod cu optimizari bazate
doar pe presupuneri.
Folosirea directivei inline este o sugestie pentru compilator ca vrem ca functia sa fie inline. In functie de
implementare si de setari compilatorul poate ignora aceasta directiva.
else
{
return (n * Factorial(n-1));
}
}
Este important de realizat ca atunci cand o functie se apeleaza pe ea insasi, o noua copie a functiei
ruleaza. Variabilele locale din a doua functie sunt independente de variabilele locale din prima functie si
nu se pot influenta reciproc.
Functiile recursive necesita o conditie de stop. Ceva trebuie sa se intample pentru a opri apelul recursiv.
5 Profilers.
13
138
Adonis Butufei
In exemplul anterior aceasta conditie de stop este in blocul in care se testeaza daca n < 2.
Important
Functiile recursive permit o implementare mai simpla pentru o categorie de probleme insa este necesar ca
numarul de apeluri recursive trebuie sa fie redus pentru a nu consuma excesiv memoria si a degrada
performanta. In cazul in care sunt necesari un numar mari de pasi pentru apelul recursiv este recomandata
implementarea echivalenta folosind bucle in loc de apel recursiv.
In C++ se poate rezolva aceasta problema impartind programul in spatii de nume. In activitatile curente
folosim spatiile in activitatile curente. In discutiile zilnice folosim prenumele pentru a discuta cu
persoanele apropiate. Daca in acelasi loc sunt doua persoane cu acelasi prenume atunci folosim si numele
de familie pentru a diferentia, de exemplu, pe Stefan Vasilescu de Stefan Georgescu.
Toate entitatile declarate intr-un spatiu de nume se considera a fi membrii aceleiasi familii sau spatiu de
nume.
Exemplu:
namespace CalculFinanciar
{
double profitTrimestrial;
double taxeLunare;
};
6 Se spune ca pentru functii se foloseste PascalCase adica fiecare cuvant din numele functiei incepe cu litera mare, exemplu
CalculeazaSalarii(), iar pentru parametri se foloseste camel case, adica primul cuvant incepe cu litera mica si celelalte cu
litera mare, exemplu CaluleazaSalarii(int lunaCurenta);
7 Prin entitate se poate intelege o functie, variabila sau tip de date definit de utilizator.
14
139
Adonis Butufei
Exemplu: folosind directiva using.Implicit C++ foloseste spatiul de nume global. Acesta se mai numeste
si spatiul de nume anonim. Toate entitatile care nu au specificat un spatiu de nume sunt puse automat aici.
#include <iostream>
using namespace std;
int main()
{
cout << "Hello World!\n";
return 0;
}
int main()
{
std::cout << "Hello World!\n";
return 0;
}
In exemplele de mai sus am folosit spatiul de nume standard (std) definit de C++.
Functionalitatile limbajului C++ sunt organizate in mai multe spatii de nume si ele vor fi introduse pe
masura ce vom folosi entitati din acele spatii de nume.
Remarca
Scrierea codului este mai eficienta folosind directiva using deoarece este scrisa o singura data si permite
folosirea directa a numelui entitatilor in contextul in care a fost declarata. Numele complet se foloseste
doar atunci cand avem suprapunerea numelor pentru entitati din spatii de nume diferite.
Important
Daca omitem directiva using si folosim doar numele entitatii vom obtine o eroare de compilare.
15
140
Adonis Butufei
namespace Development
{
// entitati pentru Development.
int x, y;
}
namespace Contabilitate
{
// ...
}
namespace Management
{
// ...
}
// ...
}
Pentru a folosi entitatile din spatiile de nume Contabilitate respectiv Management vom folosi
16
141
Adonis Butufei
8.3 Sumar
• Functiile permit gruparea codului intr-un bloc compact care poate fi folosit in mai multe locuri.
• O functie este identificata printr-un nume, tipul rezultatului returnat, lista de parametri.
• Functiile sunt declarate, implementate si apelate.
• Interactiunea unei functii poate fi gandita ca un schimb de date dinspre apelant catre functie si dinspre
functie catre apelant.
• Parametrii, in general, sunt utilizati pentru transferul de date dinspre apelant catre functie.
• Parametrii se pot transfera prin valoare, referinta si adresa.
• Parametrii de iesire, care sunt modificati de functie, se considera rezultate returnate.
• Majoritatea functiilor, dupa ce termina prelucrarea datelor, returneaza o valoare apelantului. Pentru
functiile care nu returneaza un rezultat se foloseste termenul void scris inainte de numele functiei.
• Returnarea rezultatului se poate face prin valoare, referinta, adresa.
• Pentru imbunatatirea vitezei de executie a unui program se pot folosi functii inline.
• Cand avem de implementat acelasi tip de prelucrare pentru tipuri de date diferite se poate folosi mecanismul de
supraincarcare a functiilor (function overloading).
• In cazul programelor complexe apare problema gasirii unor nume unice si semnificative in acelasi timp
pentru variabile, functii, tipuri de date. O rezolvare in C++ a acestei probleme este impartirea
programului in spatii de nume.
• Declararea spatiului de nume se face astfel:
namespace nume_namespace
{
entitati
}
• Accesarea entitatilor dintr-un spatiu de nume se poate face in doua moduri: folosind directiva using
nume_namespace; sau folosind numele complet adica nume_namespace::nume_entitate.
• Implicit C++ foloseste spatiul de nume global. Acesta se mai numeste si spatiul de nume anonim. Toate entitatile care
nu au specificat un spatiu de nume sunt puse automat aici.
• Pentru programele complexe se pot folosi structuri ierarhice de spatii de nume.
17
142
Adonis Butufei
#include <iostream>
using namespace std;
struct Complex
{
double re;
double im;
};
int main()
{
Complex a = {2.0, 3.0};
Complex b = {4.0, 1.0};
Complex result;
Add(a,b, result);
AfiseazaComplex(result);
return 0;
}
if(c.im > 0)
{
18
143
Adonis Butufei
int main()
{
cout << "Hello World\n";
}
8.5 Bibliografie
• Teach yourself C++ In an Hour A day, Sixth Edition, Sams, Jesse Liberty, Cap 6.
• Practical C++ Programming, Second Edition, O'Reilly, Steve Oualline, Cap 9
20
145
Adonis Butufei
9. CLASE SI OBIECTE
CUPRINS
9.Clase si obiecte...........................................................................................................................................2
9.1 Clase, membri si obiecte....................................................................................................................2
9.1.1 Declararea claselor.....................................................................................................................2
9.1.2 Definirea obiectelor....................................................................................................................2
9.1.3 Care este diferenta dintre clase si obiecte?.................................................................................3
9.2 Incapsulare si metode de access.........................................................................................................3
9.2.1 Tipuri de access..........................................................................................................................3
9.2.2 Metode de acces la atributele claselor........................................................................................4
9.3 Constructorii.......................................................................................................................................5
9.3.1 Constructorul de copiere.............................................................................................................7
9.4 Destructorii.........................................................................................................................................8
9.5 Metode constante................................................................................................................................8
9.6 Atribute constante...............................................................................................................................9
9.7 Atribute statice..................................................................................................................................10
9.8 Metode statice..................................................................................................................................10
9.9 Metodele inline.................................................................................................................................11
9.10 Organizarea codului........................................................................................................................11
9.11 Structuri de date si clase................................................................................................................12
9.12 Sumar.............................................................................................................................................13
9.13 Intrebari si exercitii........................................................................................................................13
9.14 Bibliografie.....................................................................................................................................15
1
146
Adonis Butufei
9. CLASE SI OBIECTE
In acest capitol vom discuta despre clase si obiecte, diferenta dintre o clasa si un obiect.
Vom invata primul mecanism al programarii obiectuale: incapsularea.
Vom organiza codul clasei in fisiere header si fisiere sursa.
Vom invata cum sa lucram cu obiectele.
Clasele permit combinarea datelor cu functiile care lucreaza cu aceste date. Elementele claselor, datele si
functiile se numesc membri. Pentru datele membru se foloseste termenul atribute si pentru functiile
membru se foloseste termenul metode.
Exemplu:
class TipuriDeAccess
{
// membri privati
public:
// membri publici
protected:
// membri protected
private:
// membri privati
};
Important
• Directivele de acces definesc zone in declararea clasei. O zona incepe pe linia in care se afla directiva
si se termina pe linia in care apare o noua directiva sau pe linia unde se termina declararea clasei.
3
148
Adonis Butufei
Membrii cu tipul de acces public pot fi apelati din afara clasei. Cei cu tipul de acces private pot fi apelati
doar din metodele clasei, iar tipul protected doar din metodele clasei sau claselor derivate1.
Exemplu:
class Complex
{
double _re;
double _im;
};
In acest exemplu este prezentata clasa Complex care are doua atribute private.
Nota
Pentru a diferentia atributele clasei de variabilele locale sau parametri am folosit prefixul (_).
O incercare de acces a acestor atribute in afara clasei genereaza eroare de compilare.
Exemplu:
Complex c1;
c1._re = 1.5; // genereaza eroare
Codul acestui exemplu a fost prezentat pe doua coloane pentru economie de spatiu.
Clasa complex are o pereche de metode pentru fiecare atribut: cate o metoda care returneaza valoarea
curenta2 si o metoda care modifica valoarea curenta a atributului3.
Dupa declararea clasei urmeaza definirea metodelor. Observam si aici operatorul rezolutie (::) pe care
l-am intalnit si in capitolul 8 la spatii de nume pe care il folosim in acelasi mod pentru a specifica
apartenenta metodelor la clasa.
9.3 Constructorii
Variabilele simple pot fi initializate folosind operatorul =. Initializarea asigura faptul ca variabila va avea
intotdeauna o valoare determinata. Pentru initializarea atributelor unei clase se folosesc metodele
constructor care au acelasi nume ca si clasa. Constructorii nu au nici un tip de returnat. In momentul
declararii unui obiect, compilatorul apeleaza constructorul clasei care initializeaza atributele.
Putem adauga la clasa Complex4 prezentata anterior un constructor fara parametri care initializeaza
ambele atribute cu 0. Constructorul fara parametri al unei clase se numeste constructor implicit.
class Complex Complex::Complex()
{ {
public: _re = 0.0;
Complex(); _im = 0.0;
}; }
In momentul declararii unei variabile de tip complex este apelat acest constructor.
Exemplu:
#include <iostream>
using namespace std;
int main()
{
Complex c; // este apelat constructorul
cout << c.Im() << " + " << c.Re() << "i\n";
return 0;
}
Deoarece am folosit prefixul (_) diferentierea parametrilor de atributele clasei este evidenta.
Folosind acest constructor putem initializa un obiect de tip complex cu valorile dorite intr-o singura linie
de cod.
Exemplu:
1: int main()
2: {
3: Complex c(2.5, 3.4);
4: return 0;
5: }
In acest exemplu in linia 3 este apelat al doilea constructor care initializeaza atributele _re si _im cu
valorile 2.5 respectiv 3.4.
Important
In standardul C++ curent un constructor nu poate apela alt constructor al aceleiasi clase. Urmatorul cod
genereaza erori de compilare.
Complex::Complex() : Complex(0.0, 0.0)
{
}
Atunci cand avem o sectiune de cod duplicata in constructorii clasei se poate muta acea sectiune intr-o
functie privata care poate fi apelata din fiecare constructor.
Exemplu:
6
151
Adonis Butufei
In cazul clasei Complex codul pentru initializarea atributelor este similar in ambii constructori.
Declaram metoda Init in sectiunea privata cu urmatorul prototip:
void Init(double re = 0.0, double im = 0.0);
Folosim 0.0 valoare implicita pentru ambii parametri. Implementarea este foarte simpla, practic se
copiaza codul din al doilea constructor. Deoarece am specificat valorile implicite in prototipul functiei nu
mai este necesar sa le scriem si la implementare.
void Complex::Init(double re, double im)
{
_re = re;
_im = im;
}
Actualizam constructorii:
Complex::Complex()
{
Init();
}
Complex::Complex(double re, double im)
{
Init(re,im);
}
Mai jos este prezentata implementarea constructorului de copiere. Initializam noua clasa cu valorile
atributelor _re si _im din clasa sursa.
Complex::Complex(const Complex& src)
{
Init(src._re, src._im);
}
Constructorul de copiere este apelat explicit atunci cand folosim un obiect, sau implicit atunci cand
apelam o functie care are in lista de parametri un obiect de acel tip sau cand folosim pentru a crea o noua
instanta ca in exemplul de mai jos:
Complex c1(2.5, 3.7);
Complex c2(c1); // apeleaza constructorul de copiere.
Complex c3 = c1; // apel implicit al constructorului de copiere.
Complex c4 = Add(c1, c2); // Unde Add are prototipul:
7
152
Adonis Butufei
Pentru evitarea copierii obiectelor, functiile si metodele folosesc transferul prin referinta sau prin adresa
pentru parametrii care sunt obiecte.
9.4 Destructorii
Destructorii sunt apelati de compilator in momentul cand un obiect a ajuns la sfarsitul domeniului de
viata, in scopul eliberarii resurselor folosite de clasa (eliberarea memoriei, inchiderea unui fisier etc). O
clasa poate avea mai multi constructori insa un singur destructor.
Caracteristicile destructorului:
• Numele destructorului foloseste prefixul (~) urmat de numele clasei.
• Destructorul nu are parametri.
• Destructorul nu are tip de returnare.
Exemplu:
class Complex Complex::~Complex()
{ {
}
public:
~Complex();
};
Initializarea acestui atribut nu se poate face in declararea clasei ca in exemplul de mai jos.
class Buffer
{
const int _SIZE = 1024; // Genereaza eroare de compilare.
//....
};
Pentru a putea initializa acest atribut trebuie sa folosim lista de initializare a constructorului. Aceasta lista
de initializare se executa inainte de executia constructorului. Aici compilatorul aloca memoria pentru
atributele clasei si acesta este momentul cand putem seta valorile atributelor constante. Sintaxa este
prezentata in exemplul urmator:
Buffer::Buffer () : _SIZE(1024)
{
}
Se folosesc doua puncte dupa paranteza ) constructorului apoi urmeaza atributele pe care dorim sa le
initializam, separate prin virgula. Lista de initializare se poate folosi si in cazul atributelor normale si
poate aduce o imbunatatire a performantei daca atributele se initializeaza in ordinea in care au fost
declarate.
In capitolul doi am intalnit tipurile de constante enumerate. Aceste constante sunt folosite frecvent in
cazul in care tipul constantei este intreg deoarece permite o initializare mai simpla. Putem modifica
exemplul anterior in modul urmator pentru a folosi constantele enumerate:
class Buffer
{
enum { _SIZE = 1000};
public:
//..
9
154
Adonis Butufei
};
In C++ aceasta problema se poate rezolva foarte simplu folosind un atribut static in loc de o variabila
globala.
Exemplu:
Declaram un atribut static privat:
class Complex
{
private:
static int _nrInstante;
};
Definim atributul:
int Complex::_nrInstante = 0;
Definim metoda:
int Complex::NrInstante()
10
155
Adonis Butufei
{
return _nrInstante;
}
Deoarece aceasta metoda apartine clasei, nu instantelor, ea poate fi apelata folosind numele clasei:
cout << Complex::NrInstante() << "numere complexe sunt in memorie\n";
11
156
Adonis Butufei
};
#endif
Complex::Complex(double re , double im ) :
_re(re), _im(im)
{
cout << "Complex::Complex(re,im)\n";
}
double Complex::Re()const
{
return _re;
}
double Complex::Im()const
{
return _im;
}
structura de date si clase este ca tipul de acces implicit este privat pentru clase si public pentru structuri de
date.
Putem defini metode pentru o structura de date in acelasi mod ca si pentru clase si daca folosim
directivele de acces prezentate anterior putem obtine aceeasi functionalitate.
9.12 Sumar
Clasele creaza noi tipuri de date prin gruparea datelor si functiilor care lucreaza cu aceste date.
Obiectele sunt variabile care au tipul definit de clase. Ca si analogie, clasa reprezinta planul unei case iar
obiectele sunt cladirile construite folosind acel plan.
Incapsularea permite ascunderea detaliilor de implementare, folosind directivele de acces, si ofera un set
de metode prin care putem interactiona cu obiectele.
Constructorii sunt metode speciale care sunt apelate de compilator in momentul crearii obiectelor pentru
initializarea datelor. Un constructor nu poate apela alt constructor.
Destructorii sunt metode speciale care sunt apelate de compilator in momentul in care domeniul de viata
al unui obiect s-a terminat, pentru eliberarea resurselor. O clasa poate avea un singur destructor.
Metodele constante nu modifica atributele obiectelor. Folosind directiva const pentru metode putem
identifica foarte usor erorile de modificare neautorizata a atributelor.
Atributele statice apartin claselor. Ele pot fi accesate de toate instantele acelei clase.
Metodele statice pot accesa doar atributele statice ale clasei si se pot apela fara a crea o instanta a acelei
clase.
Metodele inline se pot declara folosind cuvantul cheie sau scriind implementarea in fisierul header.
Structurile de date sunt echivalente cu clasele in C++. Singura diferenta este ca pentru clase tipul de acces
implicit este privat iar pentru structuri este public.
13
158
Adonis Butufei
4. Dupa corectarea declaratiei clasei anterioare urmatorul cod genereaza erori de compilare. Care este
motivul?
Fractie f;
5. Declarati o clasa Persoana care are urmatoarele atribute private: _nume, _prenume, _varsta. Scrieti
metode de acces (de setare si citire a acestor atribute).
Numar::Numar(int val)
{
_val = val;
}
int main()
{
Numar a(10);
}
14
159
Adonis Butufei
};
int A::X()
{
return _test;
}
9. Scrieti implementarea constructorului care foloseste lista de initializare pentru urmatoarea clasa:
class Punct
{
double _x;
double _y;
double _z;
public:
Punct(double x = 0.0,double y = 0.0, double z = 0.0);
};
int main()
{
A a;
}
9.14 Bibliografie
• Teach yourself C++ In an Hour A day, Sixth Edition, Sams, Jesse Liberty, Cap 10.
• Practical C++ Programming, Second Edition, O'Reilly, Steve Oualline, Cap 13 - 14.
• C++ Without Fear, Second Edition, Prentice Hall, Brian Overland, Cap 11 - 12.
15
160
Adonis Butufei
CUPRINS
10.Supraincarcarea operatorilor....................................................................................................................2
10.1 Ce sunt operatorii in C++?...............................................................................................................2
10.2 Functii, clase si operatori friend.......................................................................................................3
10.3 Operatorii unari................................................................................................................................3
10.3.1 Operatorii de incrementare / decrementare..............................................................................4
10.3.1.1 Operatorul de incrementare cu prefixare..........................................................................5
10.3.1.2 Operatorul de incrementare cu postfixare.........................................................................5
10.3.2 Operatorii de conversie.............................................................................................................6
10.4 Operatorii binari...............................................................................................................................6
10.4.1 Supraincarcarea operatorilor aritmetici....................................................................................7
10.4.2 Supraincarcarea operatorilor aritmetici cu atribuire.................................................................8
10.4.3 Supraincarcarea operatorilor de comparare..............................................................................8
10.4.4 Supraincarcarea operatorilor >, >=, < si <=.............................................................................9
10.4.5 Supraincarcarea operatorilor de indexare operator []...............................................................9
10.5 Operatorii functie operator ().........................................................................................................10
10.6 Operatorul de atribuire operator =..................................................................................................11
10.7 Operatori care nu pot fi supraincarcati...........................................................................................12
10.8 Metode si operatori definiti automat..............................................................................................12
10.8.1 Copierea superficiala vs. copierea completa..........................................................................12
10.9 Sumar.............................................................................................................................................13
10.10 Intrebari si exercitii......................................................................................................................13
10.11 Bibliografie...................................................................................................................................14
1
161
Adonis Butufei
Daca am folosi operatorul + in locul metodei Add prototipul ar arata in modul urmator:
Fractie operator + (const Fractie& src);
2
162
Adonis Butufei
Exemplu:
friend Complex operator – (const Complex& src);
Acest operator creaza un nou obiect de tip complex care are semnul schimbat pentru partea reala si partea
imaginara. El este apelat in urmatorul tip de expresii:
Complex c = -c1;
Deoarece metoda modifica instanta curenta nu mai este necesar un parametru ca in cazul implementarii
functiei.
Recomandare
1 Directiva friend este necesara numai in cazul in care se acceseaza atribute, metode cu nivel de acces protected sau private.
3
163
Adonis Butufei
Este preferabila folosirea metodelor pentru implementarea operatorilor deoarece nu ofera acces la datele
membru din afara. Cazurile in care este necesara folosirea functiilor friend vor fi prezentate in acest
capitol.
++ Incrementare
-- Decrementare
* Indirectarea pointerului
! Negare logica
& Adresa
~ Complement fata de 1
+ Plus unar
- Minus unar
Operatori de conversie
In cele ce urmeaza vom discuta operatorii frecvent utilizati in practica.
public:
Contor::Contor(const Contor& src)
Contor(int valoare = 0) ;
{
Contor(const Contor& src);
_valoare = src._valoare;
};
}
Clasa are un singur atribut _valoare, un constructor pentru initializarea valorii si un constructor de
copiere.
4
164
Adonis Butufei
Returneaza o referinta de tip Contor. Implementarea acestui operator este prezentata mai jos. Mai intai se
incrementeaza valoarea si apoi se returneaza o referinta la instanta curenta.
Contor& Contor::operator ++()
{
_valoare++;
return *this;
}
Observam ca acest operator returneaza un obiect de tip Contor si nu o referinta, deoarece se returneaza
valoarea dinaintea incrementarii.
Implementarea acestui operator este prezentata mai jos:
1: Contor Contor::operator ++ (int)
2: {
3: Contor result = *this;
4: _valoare++;
5: return result;
6: }
In linia 3 se creaza un obiect care contine valoarea initiala. In linia 4 se incrementeaza valoarea. La final
se returneaza obiectul cu valoarea initiala.
Observatii:
Acest operator are un parametru de tip int care nu este folosit. Acesta este necesar compilatorului pentru
a-l diferentia de operatorul de incrementare cu prefixare.
Varianta de prefixare este mai eficienta deoarece nu este necesara o variabila auxiliara pentru copierea
valorii.
5
165
Adonis Butufei
De exemplu, in cazul clasei Contor folosita anterior putem defini un operator de conversie la tipul intreg.
Pentru aceasta declaram urmatorul prototip:
operator int ();
6
166
Adonis Butufei
Ca si in cazul operatorilor unari pentru declarare si implementare putem folosi functii friend sau metode
ale clasei. Vom folosi metodele clasei pentru implementare ori de cate ori este posibil din motivele
prezentate anterior.
In cazul in care vrem sa calculam suma dintre un numar real si un numar complex ca in exemplul de mai
jos operatorul de adunare nu se mai poate apela deoarece primul operand este de tip double.
double x = 3.0;
Complex c(1,2.5);
Complex result = x + c;
Pentru aceasta este necesara implementarea unui operator folosind o functie friend care are prototipul
urmator:
friend Complex operator + (double x, const Complex& y);
7
167
Adonis Butufei
8
168
Adonis Butufei
Pentru a evita duplicarea codului, operatorul != returneaza opusul lui ==. Asa cum am discutat la variabile
reale nu putem compara variabilele de tip double si pentru aceasta am definit precizia: o constanta a carei
valoare este abaterea maxima.
~Vector();
Aceasta clasa are un atribut tablou. Operatorul [] returneaza referinte la elementele tabloului.
9
169
Adonis Butufei
Vector::~Vector()
{
delete [] _vector;
}
In cazul in care folosim obiecte constante este necesara adaugarea unui operator de indexare constant.
Exemplu:
int & operator [] (int index) const;
if(src.Im() > 0)
10
170
Adonis Butufei
{
cout << "+";
}
cout << src.Im() << "i\n";
}
Un exemplu de apel:
Complex c(3,2);
PrintComplex print;
print(c);
Testul din linia 3 asigura functionarea corecta in cazul in care un obiect se atribuie lui insusi.
Exemplu:
Complex c1;
c1 = c1;
Aceasta verificare este importanta in cazul obiectelor care aloca dinamic memoria pentru a preveni
dealocarea eronata a pointerilor.
. Selectia membrilor
11
171
Adonis Butufei
:: Domeniu de acces
?: Operatorul ternar
Atunci cand clasele au atribute de tip pointer sau tablou este necesara implementarea de utilizator a
acestor metode si operatori. Constructorul de copiere si operatorul de asignare implicit realizeaza o copie
a valorilor atributelor bit cu bit care in aceste cazuri nu este intotdeauna corecta.
“Hello”
“Hello”
In copierea completa se aloca memoria pentru variabilele pointeri si se copiaza valoarea acelor variabile
local.
12
172
Adonis Butufei
obiect1 obiect2
ptr ptr
“Hello” “Hello”
strcpy
Este important de identificat cazurile in care copierea superficiala este suficienta si cazurile in care
copierea completa este necesara.
Atunci cand este necesara folosirea copierii complete trebuiesc implementati constructorul de copiere si
operatorul de atribuire.
Pentru siguranta putem declara constructorul de copiere si operatorul de atribuire cu nivel de acces privat.
In acest mod, orice incercare de apel ar produce o eroare de compilare.
10.9 Sumar
• Operatorii permit implementarea unei functionalitati care este mai usor de utilizat si inteles.
• Exista doua categorii de operatori: unari si binari. Operatorii unari lucreaza cu un singur operand,
Operatorii binari au doi operanzi.
• Pentru operatorii de incrementare /decrementare de postfixare se foloseste un parametru de tip int care
permite compilatorului diferentierea de operatorul similar de prefixare.
• Operatorii se pot implementa folosind metode sau functii friend.
• Este preferabila folosirea metodelor in majoritatea cazurilor.
• In cazul in care obiectul nu se modifica putem folosi directiva const pentru operator.
• Exista operatori a caror suprascriere nu este permisa.
• Pentru orice clasa compilatorul adauga urmatoarele metode daca nu sunt definite de utilizator:
constructorul implicit, constructorul de copiere, operatorul de atribuire si destructorul.
• Implementarea implicita a operatorului de atribuire si a constructorului de copiere realizeaza o copiere
superficiala bit cu bit care in cazul claselor cu atribute pointer poate duce la functionarea
necorespunzatoare a programelor.
• Intelegerea diferentei intre copierea superficiala si copierea completa este esentiala.
10.11 Bibliografie
• Teach yourself C++ In an Hour A day, Sixth Edition, Sams, Jesse Liberty, Cap 13.
• Practical C++ Programming, Second Edition, O'Reilly, Steve Oualline, Cap 18.
• C++ Without Fear, Second Edition, Prentice Hall, Brian Overland, Cap 13.
14
174
Adonis Butufei
CUPRINS
11.Agregare si mostenire de clase, polimorfism...........................................................................................2
11.1 Agregarea claselor............................................................................................................................2
11.2 Mostenirea........................................................................................................................................2
11.2.1 Sintaxa derivarii........................................................................................................................3
11.2.2 Tipul de mostenire si accesul la membri din clasa de baza......................................................3
11.2.3 Constructori si destructori.........................................................................................................4
11.2.4 Transferul parametrilor la constructori.....................................................................................5
11.3 Polimorfism......................................................................................................................................6
11.3.1 Suprascrierea metodelor din clasa de baza...............................................................................7
11.3.2 Metode virtuale.........................................................................................................................8
11.3.3 Destructori virtuali....................................................................................................................9
11.3.4 Clonarea obiectelor si constructorii de copiere......................................................................10
11.4 Mosternire multipla........................................................................................................................10
11.4.1 Apelarea constructorilor claselor de baza...............................................................................12
11.4.2 Apelul explicit al metodelor virtuale din clasa de baza..........................................................13
11.4.3 Clase de baza virtuale.............................................................................................................14
11.5 Clase abstracte si interfete..............................................................................................................16
11.5.1 Functii virtuale pure................................................................................................................16
11.5.2 Clase abstracte........................................................................................................................16
11.5.3 Clase interfata.........................................................................................................................17
11.6 Sumar..............................................................................................................................................17
11.7 Intrebari si exercitii........................................................................................................................17
11.8 Bibliografie.....................................................................................................................................19
1
175
Adonis Butufei
In ultimele doua capitole au fost prezentate: organizarea claselor cu metode, atribute si operatori si modul
de realizare al incapsularii folosind directivele de acces. In programele reale clasele nu sunt insule izolate
ci exista relatii si interactiuni intre ele.
In acest capitol vom discuta despre:
• Relatiile de mostenire si agregare a claselor.
• Care sunt tipurile de mostenire.
• Implementarea polimorfismului cu ajutorul functiilor virtuale.
• Clase si metode abstracte.
• Clase interfata.
Nota:
In acest capitol pentru a reduce textul exemplelor si pentru simplitate toate clasele vor avea metodele
implementate inline.
11.2 Mostenirea
Folosind mostenirea sau derivarea putem crea o noua clasa care extinde functionalitatea unei clase
adaugand metode, atribute noi sau schimba comportamtentul unor metode deja existente in clasa de baza.
Clasa pe care o mostenim se numeste clasa parinte sau clasa de baza iar clasa nou creata se numeste
subclasa, clasa derivata1 sau clasa copil. Prin acest mecanism toate caracteristicile (atributele si metodele)
clasei de baza se regasesc in clasa derivata.
Intalnim conceptul de mostenire frecvent in activitatile curente atunci cand de la un concept general
trecem la unul particular. De exemplu tramvaiul si autobuzul sunt vehicule. Conceptul de tramvai are
automat toate caracteristicile unui vehicul.
Mai multe clase intre care exista relatii de mostenire formeaza o ierarhie de clase.
Important
Folosim mostenirea atunci cand intre doua concepte, functionalitati exista o relatie de tipul „este un” sau
„este o”. Exemple: tramvaiul este un vehicul, ferastraul este o unealta.
Exemplu:
class Vehicul
{
int _vitezaMaxima;
public:
int VitezaMaxima() { return _vitezaMaxima; }
void VitezaMaxima(int vitezaMaxima)
{ _vitezaMaxima = vitezaMaxima; }
};
In cazul mostenirii protected membrii publici din clasa de baza devin protected, membrii protected raman
protected iar membrii private sunt inaccesibili.
In cazul mostenirii private membrii publici din clasa de baza devin private in clasa derivata, membrii
3
177
Adonis Butufei
protected din clasa de baza devin private in clasa derivata iar membrii private din clasa de baza sunt
inaccesibili.
In cazul mostenirii publice obiectele claselor derivate au si tipul clasei de baza si pot fi folosite in locul
claselor de baza.
Exemplu:
1: Autobuz a;
2: Vehicul & referinta = a;
3: Vehicul *p = &a;
Definitiile din liniile 2 si 3 sunt corecte si ele permit folosirea polimorfismului despre care vom vorbi mai
tarziu in acest capitol.
Important
Este recomandata folosirea mostenirii publice. Celelalte tipuri de mostenire in majoritatea cazurilor pot fi
inlocuite cu agregarea.
Cand vrem ca o metoda sa fie accesibila in exterior folosim nivelul de acces public. In cazul in care dorim
ca o metoda sa fie accesibila claselor derivate folosim nivelul de acces protected.
4
178
Adonis Butufei
int main()
{
Autobuz v1;
v1.Linia(135);
}
5
179
Adonis Butufei
In acest exemplu in linia 5 am definit un constructor care initializeaza viteza maxima pentru clasa
Vehicul. Acest constructor este apelat din constructorul clasei derivate in linia 18.
Ruland acest program pe ecran se afiseaza urmatoarele mesaje:
Vehicul(int)
Autobuz(int, int)
~Autobuz()
~Vehicul()
11.3 Polimorfism
Mostenirea este doar unul din avantajele programarii obiectuale. Adevarata putere este posibilitatea de a
trata obiectele claselor derivate ca si cum ar fi obiecte ale clasei de baza. Mecanismele care permit aceasta
sunt polimorfismul si legarea dinamica2.
Polimorfismul permite ca un obiect al unei clase derivate sa fie transmis unei functii care are ca parametru
un pointer sau referinta la clasa de baza.
Cand este apelata o metoda folosind un pointer sau o referinta la clasa de baza, mecanismul de legare
dinamica executa codul din clasa derivata. Codul care este executat depinde de tipul obiectului nu de cel
al pointerului sau referintei. In acest mod, obiectele claselor derivate pot fi substituite cu obiectele clasei
de baza fara schimbarea codului in functiile care folosesc obiectele.
Exemplu:
1: Autobuz a;
2: Vehicul & v = a;
3: Vehicul *p = &a;
In acest exemplu am folosit clasele declarate anterior. In linia 2 a fost declarata o referinta de tipul vehicul
iar in linia 3 un pointer la o clasa de tipul vehicul.
};
class Autobuz : public Vehicul
{
public:
// codul anterior ...
void Deplasare() { cout << "Deplasarea autobuzului.\n"; }
};
int main()
{
Autobuz a;
a.Deplasare();
}
In acest exemplu am suprascris metoda Deplasare in clasa Autobuz. La rularea se executa metoda din
clasa derivata si pe ecran apare mesajul:
Vehicul()
Autobuz()
Deplasarea autobuzului.
~Autobuz()
~Vehicul()
Important
• Este recomandabil sa se evite schimbarea nivelului de acces in clasa derivata la metoda suprascrisa.
• Atunci cand ierarhia de mostenire se schimba trebuie analizat codul care apeleaza clasa de baza si daca
este cazul sa fie actualizat numele clasei3.
• Poate sa apara o confuzie intre suprascrierea si supraincarcarea. Supraincarcarea inseamna definirea
mai multor functii care au acelasi nume insa lista de parametri diferiti. Suprascrierea este definirea
unei functii cu aceeasi semnatura in clasa derivata.
3 De exemplu daca parintele clasei Autobuz devine MijlocTrasnportPublic care se deriveaza la randul lui din Vehicul trebuie
verificat daca apelul Vehicul::Deplasare() trebuie actualizat cu numele noii clase parinte.
7
181
Adonis Butufei
Pentru acest lucru este necesara folosirea cuvantului cheie virtual în declararea metodei în clasa de
baza4.
Exemplu:
virtual void Deplasare();
Note
Metodele virtuale folosesc mecanismul legarii dinamice descrise la inceputul paragrafului spre deosebire
de metodele obisnuite care folosesc mecanismul legarii statice.
Atunci cand folosim cuvantul cheie virtual în clasa de baza nu mai este necesara repetarea lui în clasele
derivate insa pentru o intelegere mai usoara putem continua.
Datorita faptului ca p este un pointer la clasa de baza, atunci cand se executa linia 2 este apelat numai
destructorul clasei de baza. Pentru a functiona corect este necesara folosirea cuvantului cheie virtual
pentru destructorul clasei Vehicul.
Exemplu:
class Vehicul
{
public:
virtual ~Vehicul() { cout << "~Vehicul()\n"; }
};
Important
Pentru apelarea corecta a destructorilor claselor derivate este necesar ca destructorul clasei de baza sa fie
virtual.
In acest caz apelul de mai jos creaza o copie a obiectului de tip autobuz.
Vehicul *p = new Autobuz;
Vehicul *p1 = p->Clone();
In linia 15 se declara mostenirea multipla, Sunt enumerate clasele de baza separate prin virgula. Fiecare
clasa de baza are specificat nivelul de acces.
Pentru a se elibera memoria in liniile 5 respectiv 12 au fost declarati destructorii virtuali.
In liniile 24 si 25 au fost atasate referinte de tipul claselor de baza la obiectul service. Datorita mostenirii
multiple se poate folosi polimorfismul pentru ambele clase de baza.
La instantiere constructorii claselor de baza sunt apelati in ordinea declararii apoi este apelat constructorul
clasei derivate. Atunci cand domeniul de viata al obiectului este depasit destructorii sunt apelati in ordinea
inversa: mai intai destructorul clasei derivate apoi destructorii claselor de baza in ordinea inversa
declararii.
Cand este creat un obiect de tipul ServiceAuto ambele clase de baza formeaza parti ale obiectului ca in
figura de mai jos:
10
184
Adonis Butufei
Garaj
Birou ServiceAuto
...
Cand este folosita mostenirea multipla trebuiesc adresate mai multe aspecte. De exemplu: ce se intampla
daca cele doua clase de baza folosesc acelasi nume pentru o metoda virtuala? Cum sunt apelati
constructorii claselor de baza? Ce se intampla atunci cand mai multe clase de baza sunt derivate din
aceeasi clasa? In sectiunile urmatoare vom analiza fiecare din aceste aspecte.
11
185
Adonis Butufei
23: };
24:
25: int main()
26: {
27: ServiceAuto service(20,3);
28: return 0;
29: }
Apelul constructorilor de baza este realizat in linia 2: se specifica explicit care parametru este trimis
fiecarui constructor de baza.
In cazul in care implementarea constructorului din clasa derivata nu este inline codul se scrie in modul
urmator:
class ServiceAuto : public Garaj, public Birou
{
public:
ServiceAuto(int nrMasini, int nrEchipe);
};
class Birou
{
public:
virtual double SuprafataActiva() { return _nrEchipe * 7.2; }
};
Apelul din linia 2 este ambiguu pentru ca se poate apela fie metoda din clasa Garaj fie metoda din clasa
Birou. Din acest motiv compilatorul genereaza eroare de complilare.
1: ServiceAuto service(5,2);
2: double suprafata = service.SuprafataActiva();
12
186
Adonis Butufei
Utilizatorul trebuie sa specifice explicit care metoda doreste sa o apeleze.Apelul metodei din clasa Garaj
se realizeaza in modul urmator:
double suprafata = service.Garaj::SuprafataActiva();
13
187
Adonis Butufei
In acest exemplu in liniile 14 si 28 clasa Cladire este clasa virtuala de baza pentru clasele Birou si Garaj.
Implicit clasa Garaj are 2 etaje si clasa Birou are un etaj. In linia 45 clasa ServiceAuto initializeaza
explicit clasa virtuala de baza. La rularea acestui exemplu se creeaza o singura instanta a clasei Cladire
care are 3 etaje.
Important
Clasele virtuale de baza se folosesc pentru a elimina instantele multiple care pot apare datorita mostenirii
multiple.
Initilaizarea clasei de baza virtuale este realizata de clasa cea mai derivata (in exemplul anterior
ServiceAuto).
In acest exemplu clasa FiguraGeometrica are o metoda virtuala pura Aria. Clasele care au metode virtuale
pure nu pot fi instantiate urmatorul cod genereaza erori de compilare:
FiguraGeometrica f;
Deoarece clasa Patrat implementeaza metoda putem instantia clase de tipul Patrat.
In C++ o clasa care are o metoda virtuala pura este clasa abstracta. Clasa FiguraGeometrica din sectiunea
anterioara este o clasa abstracta.
In cazurile practice clasele abstracte se folosesc pentru implementarea functionalitatii comune unei familii
de clase. Metodele pentru care nu exista suficienta informatie pentru a fi implementare sunt declarate ca
virtual pure.
15
189
Adonis Butufei
11.6 Sumar
Agregarea claselor se foloseste atunci cand intre clase exista o relatie de tipul parte – intreg.
Mostenirea de tipul public se foloseste atunci cand intre clase exista o relatie de tipul „este un” / „este o”.
Mostenirea de tipul protected sau private se foloseste in anumite cazuri particulare pentru implementarea
agregarii.
Metodele virtuale se folosesc pentru implementarea polimorfismului. Acest mecanism permite folosirea
instantelor claselor derivate acolo unde se folosec referinte sau pointeri de tipul clasei de baza.
Clasele de baza trebuie sa aiba destructorul virtual pentru a asigura dealocarea corecta.
Clasele de baza virtuale sunt folosite in cazul mostenrii multiple pentru a asigura o singura instanta a
clasei de baza.
Metodele virtuale pure sunt metodele virtuale care contin doar declararea metodei.
O clasa abstracta are cel putin o metoda virtuala pura.
Clasele interfata au toate metodele virtuale pure.
int main()
{
Imprimanta i;
return 0;
}
int main()
{
Baza *p = new Derivata;
cout << p->Valoare() << "\n";
delete p;
}
11.8 Bibliografie
• Teach yourself C++ In an Hour A day, Sixth Edition, Sams, Jesse Liberty, Cap 11.
17
191
Adonis Butufei
• Teach yourself C++ In an Hour A day, Sixth Edition, Sams, Jesse Liberty, Cap 12.
• Practical C++ Programming, Second Edition, O'Reilly, Steve Oualline, Cap 21.
• C++ Without Fear, Second Edition, Prentice Hall, Brian Overland, Cap 17.
• C++ Without Fear, Second Edition, Prentice Hall, Brian Overland, Cap 18.
18
192
Adonis Butufei
CUPRINS
12.Programare generica................................................................................................................................2
12.1 Functii template................................................................................................................................2
12.1.1 Lista de parametri generici.......................................................................................................3
12.1.2 Definirea functiilor template....................................................................................................3
12.1.3 Apelul functiilor template.........................................................................................................4
12.1.4 Specializarea completa a functiilor template............................................................................4
12.1.5 Supraincarcarea functiilor template..........................................................................................5
12.1.6 Apelul operatorilor si functiile template...................................................................................6
12.1.7 Parametri care nu sunt tipuri generice......................................................................................7
12.1.8 Selectia functiilor pentru apel.................................................................................................10
12.2 Clase template................................................................................................................................12
12.2.1 Declararea claselor template...................................................................................................12
12.2.2 Folosirea claselor template.....................................................................................................13
12.2.3 Folosirea parametrilor care nu sunt tipuri generice................................................................14
12.2.4 Valori implicite pentru parametri generici..............................................................................14
12.2.5 Specializarea completa a claselor template............................................................................16
12.2.6 Specializarea partiala a claselor template...............................................................................19
12.2.7 Rezolvarea ambiguitatii de specializare.................................................................................22
12.2.8 Selectarea specializarii pentru clasele template......................................................................22
12.2.9 Atribute statice pentru clase template.....................................................................................22
12.2.10 Functii friend pentru clase template.....................................................................................23
12.2.11 Clase friend template............................................................................................................24
12.3 Organizarea codului pentru functiile si clasele template................................................................25
12.4 Operatorii de cast generici .............................................................................................................25
12.4.1 Operatorul static_cast<> ........................................................................................................26
12.4.2 Operatorul dynamic_cast<> ..................................................................................................26
12.4.3 Operatorul reinterpret_cast<> ...............................................................................................27
12.5 Sumar.............................................................................................................................................28
12.6 Intrebari și exercitii........................................................................................................................29
12.7 Bibliografie.....................................................................................................................................30
1
193
Adonis Butufei
2
194
Adonis Butufei
Linia 1 contine linia parametrilor template. Linia 2 contine semnatura funciei. Aici putem distinge
elementele: tipul de return (T in cazul nostru), numele functiei urmata de lista de parametri sau argumente
delimitata de paranteze rotuntde. Parametri sunt separati prin virtgula.
Intre liniile 3 – 5 este corpul functiei delimitat de acolade. In cazul de fata se foloseste operatorul ternar ?:
pentru selectia maximului care returneaza x daca x > y respectiv y in caz contrar.
Nota
Desi in cazul de fata atat tipul de return cat si parametrii functiei sunt generici, aceasta nu este obligatoriu
pentru functiile template. Putem scrie functii template care au tip de return void sau orice tip standard sau
definit de utilizator (non template). De asemenea parametri functiilor pot sa fie de orice tip non template.
1 In contextul functiilor si claselor template cuvintele cheie class si typename folosite pentru declararea
parametrilor generici sunt echivalente. Din punct de vedere istoric, la inceput a fost folosit cuvantul cheie
class pentru parametrii generici. Ulterior in standardul C++ a fost introdus cuvantul cheie typename.
3
195
Adonis Butufei
Nota:
Atunci cand se compileaza acest cod compilatorul foloseste definitia functiei template substituind tipurile
generice cu cele folosite pentru apel pentru a genera functiile concrete care se vor apela. O functie
template este folosita pentru generarea de catre compilator a unei intregi familii de functii cu tipuri
concrete.
Definim o versiune a functiei special pentru cazul in care parametrii sunt de tipul siruri de caractere. Cand
compilatorul intalneste un apel cu acest tip de parametrii cauta mai intai functiile specializate si daca
gaseste o functie specializata care prototipul identic cu cel care trebuie apelat, apeleaza acea functie in
locul functiei template.
2 Prin tipuri concrete de date se intelg toate tipurile predefinite in limbaj (char, int, double etc.) precum si tipurile definite de
utilizator (structuri, uniuni, clase).
4
196
Adonis Butufei
Important
Pentru a putea compila codul este necesar ca definitia functiei template sa preceada functia specializata
complet. Aceasta se realizeaza fie definind ambele functiin in aceleasi fisier fie incluzand headerul care
contine definitia functiei template in fisierul care defineste functia specializata complet.
In linia 4 se foloseste compararea valorilor si se returneaza pointerul care are valoarea mai mare.
Folosind aceasta functie se obtine rezultatul corect pentru secventa de cod de mai jos:
1: int *px = new int(10);
2: int *py = new int(9);
3:
4: int *pmax = Max(px,py);
5:
6: delete px;
7: px = 0;
8: delete py;
9: py = 0;
In liniile 1 si 2 se definesc doi pointeri de tipul int. Valorile locatiiloe de memorie sunt 10 respectiv 9.
Apelul din linia 4 returneaza pointerul pentru care locatia de memorie are valoarea cea mai mare (px in
cazul de fata).
In liniile 6 – 9 se dealoca memoria si se initializeaza valorile cu 0 corespunzatoare pointerului NULL.
Important
Compilatorul alege functia generica in functie de tipurile parametrilor de la apel. Daca parametri sunt de
tipul pointer se apeleaza functia definita in aceasta sectiune. Atunci cand parametri nu sunt pointeri se
apeleaza functia template care nu are parametri de tip pointer.
Si dorim sa folosim functia Max pentru a returna maximul a doua fractii folosind urmatoarea secventa:
Fractie f1(5,4);
Fractie f2(2);
Fractie max = Max(f1,f2);
La compilare obtinem erori datorita faptului ca functia template compara doua instante de tipul Fractie si
clasa Fractie nu are definit operatorul >.
6
198
Adonis Butufei
Pentru acest exemplu alegem varianta a doua si implementam operatorul > inline:
bool operator > (const Fractie& src) const
{
return (_numarator * src._numitor > _numitor * src._numarator);
}
Dupa adaugarea acestui operator apelul de mai sus se poate folosi functia template Max pentru tipul
Fractie.
Lista de parametri template poate contine parametri care nu sunt tipuri generice. Acestia pot sa aiba tipul
int, enumerat sau pot sa fie referinta sau pointer. In cazul in care sunt referinta sau pointer
variabilele respective trebuie sa fie variabile globale.
Acest mecanism este util pentru specificarea dimensiunii unui tablou ca in exemplul urmator:
1: template <typename T, int SIZE>
2: void PrintTablou(T tablou[])
3: {
4: for(int i = 0; i < SIZE; i++)
5: {
6: cout << tablou[i] << " ";
7: }
8: cout << "\n";
9: }
Lista de parametri template ai functiei PrintTablou contine SIZE de tipul int. Aceasta valoare este folosita
pentru a transmite dimensiunea tabloului.
In linia 3 este se apeleaza functia pentru un tablou de elemente de tip int specificand dimensiunea curenta
a tabloului (MAX) in lista de parametri template.
In lista generica am folosit un tip de functie care primeste un parametru de tip int si returneaza rezultat
bool. In linia 4 se apeleaza funcia transmisa ca parametru generic si se returneaza rezultatul ei.
In liniile 8 – 11 este declarata o functie de verificare a numerelor pare.
Apelul din linia 18 prezinta modul de folosire.
Acest mecanism poate fi folosit, de exemplu, pentru schimbarea comportamentului unui algoritm de
sortare: daca schimbam functia de comparare putem sorta crescator sau descrescator un tablou fara a mai
schimba algoritmul.
In exemplul anterior am vazut cum se pot folosi parametri template pentru transferul functiilor. Exista
cazuri cand vrem sa transmitem functii template in loc de functii obisnuite. Exemplul de mai jos prezinta
o astfel de solutie:
8
200
Adonis Butufei
Functia generica Limita, definita intre liniile 1 – 11 primeste ca parametru template o functie
Comparator care este folosita pentru selectia limitei maxime sau minime a tabloului.
Se incepe cu valoarea primului element in linia 4. Apoi incepand cu elementul 1 al tabloului se selecteaza
limita dintre elementul curent si valoarea limitei. La final se returneaza valoarea limitei.
Pentru calculul maximului se foloseste functia template Max definita anterior, iar pentru calculul
minimului se foloseste functia template Min definita mai jos:
1: template<typename T>
2: T Min(T x, T y)
3: {
4: return x < y ? x : y;
5: }
In linia 1 este definita constanta pentru dimensiunea tabloului. Tabloul este definit in linia 2. Liniile 4 si 5
folosesc cele 3 functii template pentru calculul valorilor maxime si minime ale tabloului.
9
201
Adonis Butufei
Functia template este definita in liniile 1 – 5. Specializarea completa pentru tipul double este definita in
liniile 7 – 11. Specializarea completa pentru tipul string este definita in liniile 13 – 17 iar functia non
template este definita in liniile 19 – 22.
Se observa doua variante pentru specializare. In cazul specializarii pentru double nu am mai specificat
tipul concret (linia 8 ) asa cum am procedat pentru string (linia 14). Compilatorul poate deduce tipul
concret din lista de parametri.
Acelasi mod de apelare (implicit si explicit) se poate folosi si pentru functia template: liniile 19, 22 si 23.
11
203
Adonis Butufei
12
204
Adonis Butufei
33: private:
34: Tablou(const Tablou& src);
35: Tablou& operator = (const Tablou& src);
36: };
37: #endif
In linia 4 avem lista de parametri generici care in cazul de fata prezinta tipul de elemente al tabloului.
Liniile 7 si 8 contin atributele clasei generice, in acest caz dimensiunea si pointerul pentru alocarea
tabloului.
Intre liniile 11 – 15 este definit constructorul cu ajutorul caruia se creaza un tablou de o anumita
dimensiune, valoarea implicita fiind 10 elemente.
Operatorul de indexare este definit intre liniile 23 – 26. Acesta returneaza o referinta la elementul
specificat de index.
In prima linie este instantiata clasa template. Prin declararea Tablou<int>, tipul int este folosit in locul lui
T. Astfel am creat un tablou pentru 10 elemente intregi. In a treia linie am setat valoarea 10 pentru
elementul cu indexul 2.
Folosind operatorul typedef putem defini un alias pentru tipuri frecvent utilizare ale clasei generice in
modul urmator:
typedef Tablou<int> IntTablou; // Tablouri de intregi
typedef Tablou<double> DoubleTablou; // Tablouri de double
6 Se genereaza eroare de compilare atunci cand se incearca apelarea operatorului de atribuire sau a constructorului de
copiere.
13
205
Adonis Butufei
Nota
Este recomandata folosirea operatorului typedef pentru simplificarea codului si reducerea erorilor de
scriere.
In acest caz SIZE este folosit ca o constanta pentru alocarea automata a tabloului si nu mai este necesara
implementarea constructorului sau a destructorului.
Instantierea acestui tablou se poate face in modul urmator:
Tablou<int, 5> tablou;
14
206
Adonis Butufei
• Daca utilizatorul nu specifica o valoare pentru acel parametru, compilatorul alege valoarea implicita.
• Atunci cand utilizatorul specifica o valoare diferita pentru parametrul generic se alege valoarea
specificata de utilizator.
In exemplul de mai jos este prezentata varianta clasei tablou cu valoare implicita pentru dimensiune:
1: #ifndef TABLOU_H
2: #define TABLOU_H
3:
4: template <typename T, int SIZE = 10>
5: class Tablou
6: {
7: T _data[SIZE];
8: public:
9: Tablou() {}
10: T& operator[] (int index) { return _data[index]; }
11: int Size() { return SIZE; }
12: private:
13: Tablou(const Tablou& src);
14: Tablou& operator = (const Tablou& src);
15: };
16:
17: #endif
Instantierea acestui tablou pentru elemente de tip int cu dimensiunea implicita este prezentata mai jos:
Tablou<int> tablou;
In exemplul de mai jos este prezentata varianta clasei tablou cu valori implicite pentru tipul elementelor si
pentru dimensiune:
1: #ifndef TABLOU_H
2: #define TABLOU_H
3:
4: template <typename T = double, int SIZE = 10>
5: class Tablou
6: {
7: T _data[SIZE];
8:
9: public:
10: Tablou() {}
11: T& operator[] (int index) { return _data[index]; }
12: int Size() { return SIZE; }
15
207
Adonis Butufei
13: private:
14: Tablou(const Tablou& src);
15: Tablou& operator = (const Tablou& src);
16: };
17: #endif
Mai jos este prezentat un exemplu de instantiere folosind valorile implicite pentru toti parametri generici:
Tablou <> tablou;
16
208
Adonis Butufei
21: ~Tablou()
22: {
23: for(int i = 0; i < _size; ++i)
24: {
25: if(_data[i] != 0)
26: {
27: delete [] _data[i];
28: }
29: }
30:
31: delete [] _data;
32: }
33:
34: PCHAR operator [] (int index) { return _data[index]; }
35:
36: void Set(int index, PCHAR elem)
37: {
38: if(_data[index] == elem)
39: {
40: return;
41: }
42:
43: delete [] _data[index];
44:
45: if(elem == 0)
46: {
47: _data[index] = elem;
48: return;
49: }
50:
51: _data[index] = new char[strlen(elem) + 1];
52: strcpy(_data[index], elem);
53: }
54:
55: int Size() { return _size; }
56:
57: private:
58: Tablou(const Tablou& src);
59: Tablou& operator = (const Tablou& src);
60: };
Pentru a simplifica definirea tipului in linia 1 a fost utilizata instructiunea typedef char* PCHAR.
17
209
Adonis Butufei
Observam ca lista parametrilor generici din linia 3 nu are niciun element, in schimb in linia 2 sunt
specificate toate tipurile pentru acesti parametri. Declaratia clasei specializate trebuie sa contina toti
membrii clasei generice iar parametrii generici sunt inlocuiti cu tipurile concrete folosite pentru
specializare.
Destructorul este declarat in liniile 21 – 32. Observam ca mai intai se elibereaza memoria pentru fiecare
sir de caractere din tablou, apoi se elibereaza memoria alocata pentru tablou.
Operatorul de indexare este declarat la linia 34. In clasa template se returna o referinta la elementul de la
indexul specificat. In clasa specializata operatorul returneaza chiar elementul, care este un pointer.
18
210
Adonis Butufei
In liniile 1 – 6 este declarata o clasa template cu doi parametri. In liniile 8 – 13 este declarata o clasa
template partial specializata pentru pointeri la U si V.
Urmatoarele obiecte sunt instante ale clasei generice:
Demo<int,double> demo1;
Demo<int*, double> demo2;
Demo<int, double*> demo3;
Pentru a instantia clasa partial specializata este necesar ca ambii parametri generici sa fie de tip pointer ca
in exemplul de mai jos:
Demo<int*, double*> demo4;
20
212
Adonis Butufei
Pentru acest exemplu s-a pastrat declararea tipului typedef char * PCHAR; prezentata in sectiunile
anterioare. Implementarea este simplificata fata de exemplul cu specializarea completa deoarece tabloul
nu este alocat dinamic.
Mai jos este prezentat un exemplu de instantiere si utilizare pentru aceasta specializare:
1: Tablou1<PCHAR, 3> test;
2: test.Set(0, "aa");
3: test.Set(1, "bb");
4: cout << test[1] << "\n";
In linia 1 se instantiaza un tablou de 3 elemente de tip sir de caractere. Se observa ca dimensiunea este
transmisa ca si parametru template nu ca parametru al constructorului.
In liniile 2 si 3 se seteaza valori pentru elementele tabloului iar in linia 4 se afiseaza continutul
elementului al doilea pe ecran.
Nota
Pentru acest caz exista si varianta de specializare cand se pastreaza parametrul de tip generic si se
specifica valoarea pentru cel care nu este tip generic.
21
213
Adonis Butufei
Important
• Folosisrea specializarii partiale reduce numarul de parametri din lista de parametri generici.
• Folosirea specializarii partiale este recomandata atunci cand functionalitatea pentru variabilele de tip
pointer necesita prelucrari suplimentare pentru alocare, eliberare sau initializare a memoriei.
• Specializarea claselor, completa sau partiala, se poate face doar daca este inclus fisierul clasei genreice
sau in acelasi fisier cu clasa generica.
• Nu se pot folosi valori implicite si specializarea partiala concomitent.
se pot folosi doua specializari partiale: cea prezentata in cazul 1 pentru pointeri si cea prezentata in cazul
2 si din acest motiv compilatorul genereaza eroare.
22
214
Adonis Butufei
Pentru simplitate a fost folosita o structura care are tipul de acces implicit public.
Important
Toate instantele claselor pentru un anumit tip partajeaza acelasi atribut static. La rularea urmatorului
exemplu valoarea afisata pe ecran este 2.0.
1: Test<double> a;
2: a._m = 2.0;
3:
4: Test<double> b;
5: cout << b._m << "\n";
Instantele a si b de tipul double partajeaza atributul static _m. In linia 2 se seteaza valoarea atributului
folosind instanta a. In linia 5 se afiseaza pe ecran folosind instanta b.
Primul parametru generic al functiei este pentru tipul clasei, al doilea tip generic este pentru tipul
atributului. In linia 3 se setaza valoarea pentru atributul _t instantei instanta.
Dorim sa declaram aceasta functie friend pentru urmatoarea clasa:
1: template<typename T>
2: class DemoFriend
3: {
4: T _t;
5: public:
6: T Get() { return _t; }
7: };
Pentru a putea accesa atributul _t din clasa DemoFriend este necesara folosirea specializarii complete
functiei Set ca functie friend ca in exemplul de mai jos:
1: template<typename T>
2: class DemoFriend
3: {
4: T _t;
5: public:
6: T Get() { return _t; }
7: friend void Set<>(DemoFriend<T> &instanta, const T &valoare);
8: };
23
215
Adonis Butufei
Important
Atunci cand se folosesc functii template friend este necesara declararea versiunii specializate ca in
exemplul prezentat in aceasta sectiune.
In exemplul de mai jos este prezentat modul de a folosi clasele friend template:
1: template <typename U> class Accesor;
2:
3: template <typename T>
4: class Container
5: {
6: template <typename U> friend class Accesor;
7: T _t; // Atribut privat.
8: public:
9: Accesor<T> Get() { return Accesor<T> (_t); }
10: };
11:
12: template <typename U>
13: class Accesor
14: {
15: U &_u;
16: public:
17: Accesor(U &u) : _u(u) {}
18: U& operator * () { return _u; }
19: };
In linia 1 se foloseste declararea anticipata pentru clasa Accesor. Aceasta informeaza compilatorul despre
clasa template Accesor.
Clasa Container este declarata in liniile 3 – 10. In linia 6 este scrisa declaratia friend pentru clase
Accesor.
Observam ca este folosit un alt parametru generic decat cel din lista de la linia 3, aceasta este necesar
deoarece parametri generici trebuie sa fie unici.
24
216
Adonis Butufei
25
217
Adonis Butufei
In acest exemplu a fost creata o instanta a clasei A in linia 18 si apoi, in linia 19 convertim o variabila de
tipul pointer la clasa B la adresa instantei d. In linia 20 este apelata metoda Result a clasei B.
Acest cod se compileaza cu succes insa functionarea este incorecta.
Codul din linia 4 se compileaza, insa este incorect. La final avem un pointer la o clasa partial initializata.
Folosirea acestui pointer este periculoasa si poate genera erori greu de identificat.
In cazurile in care nu putem verifica instanta este necesara folosirea operatorului dynamic_cast<> care
foloseste informatia despre tipul obiectelor in timpul rularii7 pentru verificari suplimentare. Cu acest
operator putem realiza conversii intre clasele polimorfice. O clasa este polimorfica daca are cel putin o
metoda virtuala.
Exemplu:
1: class A {};
2: class B : public A {};
3: A *a = new B;
4: B * b = static_cast<B*>(a);
Acest cod genereaza erori de compilare deoarece clasa A nu are metode virtuale. Adaugand destructorul
virtual ca in exemplul de mai jos erorile de compilare sunt eliminate.
1: class A
2: {
3: public:
4: virtual ~A() {};
5: };
6: class B : public A {};
Operatorul dynamic_cast verifica instantele obiectelor si daca nu sunt indeplinite conditiile, rezultatul
operatiei de cast este pointerul NULL, ca in exemplul de mai jos:
1: A *a = new A;
2: B *b = dynamic_cast<B*>(a); // b va fi NULL
3: A *c = new B;
4: B *d = dynamic_cast<B*>(c); // d va fi diferit de NULL.
Deoarece valoarea lui a este o instanta a clasei de baza rezultatul operatiei de cast din linia 2 este
pointerul NULL. In cazul operatiei de cast din linia 4 se obtine adresa corecta a obiectului.
Nota
Executia operatorului dynamic_cast<> necesita mai mult timp si este recomandata folosirea lui doar in
cazurile unde nu se poate verifica tipul obiectelor in momentul compilarii.
27
219
Adonis Butufei
12.5 Sumar
• Functiile si clasele template folosesc o lista de parametri generici a caror declarare incepe cu cuvantul
cheie template.
• Parametrii generici se declara folosind unul dintre cuvintele cheie echivalente typename sau class.
• In cazul in care sunt mai multi parametri generici, acestia sunt separati prin virgula.
• Specializarea functiilor template presupune scrierea functiei inlocuind parametrii generici cu tipuri
concrete si se foloseste in cazurile in care compilatorul nu poate genera cod care sa execute corect
operatiile generice (cum este cazul copierii sirurilor de caractere C).
• Pentru clase exista doua tipuri de specializare: partiala si totala. In cazul specializarii partiale doar o
parte din tipurile generice este inlocuita iar specializarea totala se obtine atunci cand toti parametrii
generici sunt inlocuiti.
• Functiile template permit doar specializarea completa.
• Este recomandabila scrierea integrala a codului generic in fisierele header.
• Operatorul static_cast<> permite verificarea tipului claselor si genereaza erori cand se realizeaza
conversia intre clase care nu apartin aceleiasi ierarhii.
• Operatorul dynamic_cast<> se foloseste pentru verificari suplimentare in timpul rularii. In cazul in
care tipul este valid insa instanta nu este corecta rezultatul operatiei de cast este pointerul NULL.
• Operatorul reinterpret_cast<> se foloseste in cazurile in care static_cast<> si dynamic_cast<> nu se
pot folosi. Dezvoltatorul este responsabil pentru verificari atunci cand foloseste acest operator.
28
220
Adonis Butufei
int main()
{
A *a = new A;
C *x = dynamic_cast<C*>(a);
return 0;
}
11. Implementati operatorul () intr-o clasa template care primeste un parametru T si nu returneaza
valori. Acest operator scrie / afiseaza parametrul pe ecran.
12. Adaugati specializare pentru numerele complexe definite in capitolele anterioare.
13. Se poate compila urmatorul cod fara erori?
class A {};
class B : protected A {};
int main()
{
A *a = new B;
B * b = static_cast<B*>(a);
return 0;
}
29
221
Adonis Butufei
12.7 Bibliografie
• Teach yourself C++ In an Hour A day, Sixth Edition, Sams, Jesse Liberty, Cap 15.
• Teach yourself C++ In an Hour A day, Sixth Edition, Sams, Jesse Liberty, Cap 11.
• Practical C++ Programming, Second Edition, O'Reilly, Steve Oualline, Cap 24.
• C++ Templates: The Complete Guide, Addison Wesley, David Vandevoorde; Nicolai M. Josuttis, Cap
2, 3, 4, 6.
30
222
Adonis Butufei
CUPRINS
13.Intrare / iesire...........................................................................................................................................2
13.1 Fluxuri de date..................................................................................................................................2
13.1.1 Folosirea bufferelor pentru citire si scriere...............................................................................2
13.2 Citirea cu istream.............................................................................................................................2
13.2.1 Citirea sirurilor de caractere.....................................................................................................2
13.3 Scrierea cu ostream..........................................................................................................................3
13.3.1 Formatarea scrierii....................................................................................................................3
13.3.2 Mutarea cursorului pe o linie noua...........................................................................................3
13.3.3 Reprezentarea numerelor in baze diferite.................................................................................4
13.3.4 Setarea preciziei si formatului numerelor reale........................................................................5
13.3.5 Setarea latimii pentru urmatoarea scriere.................................................................................6
13.3.6 Alinierea mesajelor...................................................................................................................7
13.4 Supraincarcarea operatorilor de intrare iesire pentru clase .............................................................7
13.5 Scrierea / citirea fisierelor pe disc....................................................................................................8
13.5.1 Scrierea fisierelor......................................................................................................................9
13.5.2 Citirea fisierelor........................................................................................................................9
13.5.3 Fisiere de intrare si iesire........................................................................................................11
13.5.4 Optiunile pentru deschiderea fisierelor la scriere...................................................................12
13.5.5 Fisiere binare si fisiere text.....................................................................................................12
13.6 Fluxuri pentru siruri de caractere...................................................................................................12
13.7 Sumar.............................................................................................................................................13
13.8 Intrebari si exercitii........................................................................................................................14
13.9 Bibliografie.....................................................................................................................................14
1
223
Adonis Butufei
Ce se intampla atunci cand de la tastatura au fost introduse mai mult de 15 caractere? In acest caz se
depaseste dimensiunea alocata pentru variabila nume si comportamentul programului este nedeterminat.
O solutie pentru aceasta problema este sa folosim manipulatorii.
Un manipulator este un obiect folosit pentru modificarea fluxului de intrare (sau iesire) atunci cand
efectuam operatii de citire (scriere). Pentru a folosi manipulatorii este necesara includerea fisierului
<iomanip>.
Exemplu:
char nume [15];
cin >> setw(15) >> nume;
2
224
Adonis Butufei
In acest caz, datorita manipulatorului setw se vor citi numai 14 caractere de la tastatura1.
Metodele get
Operatorul >> citeste valorile pana la urmatorul spatiu sau enter. Exista cazuri cand dorim sa citim si
aceste valori. In acest caz folosim metodele get. In urmatorul exemplu se citeste cate un caracter:
for(char ch = cin.get(); ch != EOF; ch = cin.get())
{
cout << "ch: " << ch << "\n";
}
Aceasta bucla citeste toate caracterele de la tastatura si le afiseaza pe ecran pana in momentul cand de la
tastatura se introduce Ctrl + z care corespunde sfarsitului de fisier EOF.
A doua metoda get primeste un sir de caractere ca parametru si este prezentata in exemplul urmator:
char nume[15];
cin.get(nume,15);
In acest exemplu se citesc maxim 14 caractere de la tastatura in variabila nume. Citirea se opreste daca
utilizatorul a apasat Enter sau daca s-a atins numarul limita de caractere.
Metoda getline
Aceasta metoda functioneaza similar cu get, primeste bufferul si dimensiunea2 acestuia ca parametri, dar
in momentul cand intalneste caracterul de linie noua muta cursorul dupa el (il descarca din flux), spre
deosebire de get la care pozitia curenta ramane la caracterul de linie noua.
Exemplu:
char nume[15];
cin.getline(nume,15);
usura scrierea, in C++ s-a definit manipulatorul endl care poate fi folosit ca in exemplul urmator:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string mesaj = "hello";
cout << mesaj << endl;
return 0;
}
Folosirea lui endl ofera avantajul separarii mesajului de formatare. In acest fel putem schimba
formatarea3 fara a modifica mesajul.
3 De exemplu daca am vrea sa afisam mesajul diferit pe ecran si intr-un fisier de log.
4
226
Adonis Butufei
24: return 0;
25: }
Pentru afisarea bazei am folosit manipulatorii dec, hex, oct iar pentru afisarea bazei am folosit
ios::showbase.
28: }
In acest exemplu in linia 10 este afisata variabila x cu precizia implicita in formatul fixed. Acest format
presupune ca am un numar fix de zecimale. In linia 13 se foloseste formatul stiintific pentru afisare apoi
se repeta procesul pentru o precizie de 7 zecimale in liniile 20, 23. In linia 25 se seteaza precizia
implicita.
Pentru folosirea acestui manipulator este necesara includerea fisierului iomanip (linia 3).
In linia 10 scriem mesajul normal apoi in linia 11 specificam o latime de 10 caractere pentru scriere.
Deoarece alinierea implicita este la dreapta, cuvantul hello are 5 litere si latimea este de 10 caractere, pe
ecran apare un spatiu de 5 caractere intre cele doua mesaje.
Daca mesajul are mai multe caractere decat latimea specificata cu setw, se ignora valoarea specificata
de setw si se scrie mesajul complet incepand cu marginea stanga ca in exemplul de mai jos:
#include <iostream>
#include <string>
#include <iomanip>
int main()
{
string mesaj = "hello";
return 0;
}
Deoarece variabila mesaj are 5 caractere si am setat o latime de 4 caractere pe ecran va apare al doilea
mesaj complet, scrierea incepe in acest caz din marginea stanga.
int main()
{
string x = "hello";
cout << setw(10) << left << x << endl;
cout << setw(10) << right << x << endl;
return 0;
}
Observam ca acesti manipulatori se folosesc impreuna cu setw. Este necesar ca latimea specificata sa fie
mai mare decat numarul de caractere al mesajelor pentru a obtine rezultatul dorit.
1: #ifndef COMPLEX_H
2: #define COMPLEX_H
3: #include <iostream>
4: using namespace std;
5:
6: class Complex
7
229
Adonis Butufei
7: {
8: // ...
9: public:
10: // ...
11: friend ostream& operator << (ostream& out, const Complex &c);
12: friend istream& operator >> (istream& in, Complex &c);
13: };
14: #endif
Pentru a putea declara operatorii in linia 3 a fost inclus fisierul <iostream> apoi in linia 4 este adaugata
directiva using. In linia 11 a fost declarat operatorul de scriere iar in linia 12 operatorul de citire. Restul
implementarii ramane neschimbat.
Implementarea acestor operatori este prezentata mai jos:
1: ostream& operator << (ostream& out, const Complex &c)
2: {
3: out << c._re << " " << c._im;
4: return out;
5: }
6:
7: istream& operator >> (istream& in, Complex &c)
8: {
9: in >> c._re;
10: in >> c._im;
11: return in;
12: }
In linia 3 scriem valorile campurilor real si imaginar in fluxul de iesire. Observam ca este plasat un spatiu
intre cele doua valori. Acesta este necesar pentru a putea delimita cele doua campuri la citire.
Citirea valorilor din fluxul de intrare se realizeaza in liniile 9 si 10.
8
230
Adonis Butufei
6: int main()
7: {
8: ofstream out("Exemplu.dat");
9:
10: if(! out)
11: {
12: cout << "Deschiderea fisierului a esuat!\n";
13: return 1;
14: }
15:
16: out << "Prima linie" << endl;
17: out << "A doua linie" << endl;
18: out.close();
19:
20: out.open("Exemplu.dat",ios::app);
21: out << "A treia linie" << endl;
22: out.close();
23: return 0;
24: }
In linia 8 este creat un obiect de tipul stream de iesire. In liniiile 10 – 14 se verifica daca deschiderea
fisierului este cu succes. Apoi se scrie informatia in fisier in liniile 16 – 17. Inchidem fisierul in linia 18.
In linia 20 deschidem din nou fisierul pentru a scrie la sfarsitul lui urmatoarea linie.
Nota
In linia 8 am folosit doar numele curent pentru fisier. In acest caz fisierul este creat daca nu exista in
directorul curent.
13: return 1;
14: }
15:
16: const int MAX = 256;
17: char linie[MAX];
18:
19: while(in)
20: {
21: in.getline(linie,MAX);
22: cout << linie << endl;
23: }
24:
25: in.close();
26: return 0;
27: }
In linia 8 este creat un obiect de tipul stream de intrare. In liniiile 10 – 14 se verifica daca deschiderea
fisierului este cu succes. Apoi in liniile 19 – 23 se citeste informatia din fisier si se afiseaza pe ecran.
Nota
Putem folosi clasa string in locul sirurilor de caractere C, incluzand fisierul string. In acest caz
partea de citire a codului devine:
1: while(in)
2: {
3: string linie;
4: getline( in , linie);
5: cout << linie << endl;
6: }
In linia 1 se muta cursorul cu 20 de octeti inainte fata de pozitia curenta. In linia 2 se muta cursorul cu 50
10
232
Adonis Butufei
de octeti inapoi fata de sfarsitul fisierului. In linia 3 se muta cursorul cu 15 octeti fata de inceputul
fisierului. Variabila ifs este o instanta de tip ifstream.
Pentru a muta cursorul de la inceputul fisierului este necesar urmatorul apel:
ifs.seekg(0, ios::beg);
Iar pentru a muta cursorul la sfarsitul fisierului este necesar urmatorul apel:
ifs.seekg(0, ios::end);
11
233
Adonis Butufei
34: }
In linia 9 sunt setate valorile pentru deschiderea fisierului. Cand se foloseste optiunea ios::in pentru
deschiderea unui fisier nou acesta nu este creat. Adaugarea optiunii ios::trunc creaza fisierul atunci cand
nu exista si sterge continutul fisierului existent atunci cand exista. In liniile 17 si 18 s-au scris doua
mesaje in fisier. In linia 20 se muta cursorul de scriere la sfarsitul primului mesaj si se scrie alt mesaj in
linia 21. In linia 23 se muta cursorul de citire la inceputul fisierului si se afisaza continutul fisierului pe
ecran in liniile 25 – 30. In linia 32 se inchide fisierul. La rularea acestui exemplu numai primul si ultimul
mesaj sunt afisate deoarece al doilea a fost sters prin mutarea cursorului de scriere.
ios::trunc sterge vechiul continut al fisierelor existente, acesta este valoarea implicita
12
234
Adonis Butufei
10:
11: cout << stream.str() << endl;
12: stream >> x;
13:
14: stream.str("");
15: stream.clear();
16:
17: return 0;
18: }
In linia 6 se creaza fluxul pentru siruri de caractere. In linia 9 se scrie in flux valoarea variabilei y. Prin
apelul metodei str in lina 11 se afiseaza continutul fluxului pe ecran. In linia 12 se citeste valoarea din
flux in variabila x. Liniile 14 si 15 sterg continutul fluxului.
13.7 Sumar
• Fisierele sunt organizate ca un flux de date. Pentru citirea continutului acestor fisiere se folosesc
clasele de flux.
• Fisierele pot fi stocate pe hard disc sau pot fi reprezentate prin dispozitive de intrare sau iesire precum
tastatura sau ecranul.
• Exista trei clase de flux de baza definite in fisierul iostream: istream pentru fluxuri de intrare,
ostream pentru fluxuri de iesire si iostream pentru fluxuri de intrare si iesire.
• Pentru modificarea fluxurilor de intrare si iesire se folosesc clase speciale numite manipulatori.
• Operatiile de scriere si citire pe disc sunt consumatoare de timp si din acest motiv se folosesc buffere.
Acestea sunt variabile in memorie care colecteaza datele ce trebuiesc scrise in fisier sau in care se
citesc date din fisier.
13
235
Adonis Butufei
10. Implementati supraincarcarea operatorilor de intrare iesire pentru clasa complex si salvati un numar
complex intr-un fisier si apoi cititi si initializati alta variabila complex. La final verificati valoarea
variabilelor.
13.9 Bibliografie
• Teach yourself C++ In an Hour A day, Sixth Edition, Sams, Jesse Liberty, Cap 27.
• Practical C++ Programming, Second Edition, O'Reilly, Steve Oualline, Cap 16.
14
236
Adonis Butufei
CUPRINS
14.Tratarea erorilor si exceptii......................................................................................................................2
14.1 Moduri de tratare a erorilor..............................................................................................................2
14.1.1 Returnarea unei valori de succes..............................................................................................2
14.1.2 Terminarea programului in cazul erorilor ................................................................................3
14.1.3 Testarea conditiilor cu assert....................................................................................................3
14.1.4 Mecanismul exceptiilor ...........................................................................................................4
14.2 Tratarea exceptiilor...........................................................................................................................4
14.2.1 Ridicarea exceptiilor.................................................................................................................5
14.2.2 Tratarea exceptiilor blocurile try / catch...................................................................................5
14.2.3 Utilizarea mai multor blocuri catch pentru tratarea exceptiilor................................................6
14.2.4 Tratarea oricarei exceptii, blocul catch(...)...............................................................................7
14.3 Ridicarea exceptiilor in blocurile catch............................................................................................8
14.4 Exceptii, domeniu de viata si alocarea resurselor............................................................................8
14.5 Exceptii si metodele claselor..........................................................................................................12
14.5.1 Ridicarea exceptiilor in constructori.......................................................................................12
14.5.2 Tratarea exceptiilor in lista de initializare a constructorilor...................................................13
14.5.3 Exceptiile si destructorii.........................................................................................................14
14.6 Crearea claselor si ierarhiilor de clase pentru exceptii...................................................................15
14.6.1 Moduri de transfer al exceptiilor catre blocul catch...............................................................16
14.6.2 Ordinea blocurilor catch in cazul ierarhiilor de exceptii........................................................17
14.7 Sumar.............................................................................................................................................17
14.8 Intrebari si exercitii........................................................................................................................18
14.9 Bibliografie.....................................................................................................................................18
1
237
Adonis Butufei
2
238
Adonis Butufei
{
//...
}
else
{
}
Nu intotdeauna este posibila tratarea erorii in locul in care a aparut. In aceste cazuri este necesar
transferul unor parametri si mesaje care sa semnaleze eroarea. Asta presupune aparitia unor blocuri if /
else in multe locuri in cod si acestea sunt dificil de intretinut si inteles.
Datorita faptului ca tratarea erorilor presupune doar verificarea valorilor returnate de functii este posibila
ignorarea acestor verificari si continuarea executiei in conditii eronate.
In acest exemplu dupa afisarea mesajului de eroare executia programului se termina prin apelul functiei
exit.
Acesta este un mod radical de a trata erorile. Nu intotdeauna exista suficienta informatie pentru a trata
eroarea in locul in care a aparut.
3
239
Adonis Butufei
Functia assert se foloseste pentru verificarea conditiilor in configuratia Debug. Pentru utilizarea acestei
functii este necesara includerea fisierului cassert. Atunci cand conditia nu este verificata, assert
opreste executia programului si afiseaza numele fisierului si linia in care conditia nu a fost verificata.
Mai jos este prezentat un exemplu simplu de folosire a functiei assert:
1: #include <iostream>
2: #include <cassert>
3: using namespace std;
4:
5: void Print(int *x)
6: {
7: assert( x != NULL);
8: cout << *x << endl;
9: }
10:
11: int main()
12: {
13: int *x = NULL;
14: Print(x);
15: return 0;
16: }
Functia print, inainte de afisarea valorii catre care pointeaza parametrul x, apeleaza assert pentru a
verifica daca acesta este un pointer valid. Deoarece apelul este explicit cu valoarea NULL, la rularea
acestui exemplu, dupa linia 7, executia programului se opreste si utilizatorul obtine informatii despre
conditia care a esuat, fisierul si linia in care se afla conditia care a esuat.
Important
assert se foloseste pentru conditiile care nu ar trebui sa apara. Programul se opreste atunci cand aceste
conditii apar. Mentenanta este usurata.
• Exista un cod scris pentru a trata situatiile cand acea secventa esueaza din anumite motive.
• In cazul in care secventa de instructiuni care se executa este in alta functie este necesar un mecanism
de transfer al informatiilor intre punctul in care a aparut problema si codul care trateaza problema.
Mecanismul tratarii exceptiilor conecteaza cele trei elemente prezentate mai sus si permite separarea
codului care trateaza eroarea de codul obisnuit, asigurand o intelegere mai usoara a fiecarei parti si
reducand eforturile de mentenanta.
In acest exemplu, in linia 5 se ridica o exceptie de tipul sir de caractere atunci cand parametrul de intrare
este negativ. Dupa ridicarea unei exceptii, executia programului se opreste daca aceasta nu este tratata
intr-un bloc try / catch prezentat in sectiunea urmatoare. Spre deosebire de valorile returnate
exceptiile nu pot fi ignorate!
5
241
Adonis Butufei
9: }
10: catch(char* msg)
11: {
12: cout << msg << endl;
13: }
14: return 0;
15: }
In blocul try este introdus apelul functiei Factorial (linia 8) apoi in blocul catch liniile 10 – 13 sunt
tratate exceptiile de tip sir de caractere. Dupa ridicarea exceptiei in functia Factorial, executia
programului se muta in linia 12 care trateaza exceptia: in cazul de fata afiseaza mesajul pe ecran.
6
242
Adonis Butufei
5: {
6: try
7: {
8: int result = Factorial(13);
9: }
10: catch(char* msg)
11: {
12: cout << msg << endl;
13: }
14: catch(int x)
15: {
16: cout << "Valoarea maxima: "<< x <<" a parametrului este depasita.\n";
17: }
18: return 0;
19: }
In acest exemplu avem doua blocuri de tratare a exceptiilor: unul pentru siruri de caractere si altul pentru
variabile intregi.
Nota
In practica, pentru fiecare tip de exceptie tratata se foloseste cate un bloc catch.
17: }
Acest exemplu prezinta un aspect important: blocul catch(...) trebuie plasat ultimul.
In momentul cand o exceptie este ridicata in blocul try, compilatorul selecteaza blocul catch
corespunzator acelui tip. Daca nu exista un bloc catch specializat, compilatorul selecteaza blocul
catch(...). In cazul in care nici acesta nu exista, exceptia este transferata un nivel mai sus in stiva de
apel a functiilor1. In cazul in care nu exista nici un bloc care sa trateze aceasta exceptie, executia
programului se opreste.
Important
Inainte de a utiliza blocul catch(...) este utila analiza codului. Folosirea unui bloc catch specializat
este de preferat la nivelul unde exista suficienta informatie pentru a putea trata acea exceptie in mod
adecvat.
Ridicarea aceleiasi exceptii se realizeaza apeland throw fara parametri ca in exemplul de mai jos:
catch(int )
{
throw;
}
Ridicarea altor tipuri de exceptii se face folosind throw urmat de tipul exceptiei.
Variabilele alocate pe stiva in interiorul blocului try sunt dealocate automat in momentul ridicarii
exceptiilor.
Exemplu:
1: #include <iostream>
2: using namespace std;
3: class Data
4: {
5: public:
6: Data() { cout << "Data()" << endl; }
7: ~Data() { cout << "~Data()" << endl; }
1 Executia programelor foloseste o stiva pentru apelul functiilor. In momentul cand se termina executia unei functii aceasta
este eliminata de pe stiva si executia este continuata cu functia apelanta.
8
244
Adonis Butufei
8: };
9:
10: int main()
11: {
12: try
13: {
14: cout << "Inceputul blocului try" << endl;
15: Data d;
16: cout << "Ridicarea exceptiei" << endl;
17: throw 1;
18: cout << "Sfarsitul blocului try" << endl;
19: }
20: catch(int)
21: {
22: cout << "Tratarea exceptiei" << endl;
23: }
24: return 0;
25: }
In acest exemplu a fost utilizata o clasa simpla Data pentru afisarea mesajelor la alocarea si eliberarea
memoriei. In blocul try in linia 15 este creata o instanta a acestei clase apoi este ridicata o exceptie. La
rularea acestui program pe ecran apar urmatoarele mesaje:
Inceputul blocului try
Data()
Ridicarea exceptiei
~Data()
Tratarea exceptiei
Se observa ca dupa ridicarea exceptiei memoria pentru variabila d este eliberata si executia se continua cu
tratarea exceptiei.
9
245
Adonis Butufei
Deoarece este ridicata inainte de eliberarea memoriei si domeniul de viata al pointerului se termina dupa
ridicarea exceptiei aceasta zona de memorie nu se mai elibereaza. La rularea acestui program pe ecran
apar urmatoarele mesaje:
Inceputul blocului try
Data()
Ridicarea exceptiei
Tratarea exceptiei
Important
Atunci cand se folosesc exceptiile este necesara eliberarea resurselor care au fost folosite in interiorul
blocului try. Prin eliberarea resurselor se intelege eliberarea memoriei alocate in acest bloc, inchiderea
fisierelor, inchiderea conexiunilor la bazele de date etc.
O solutie posibila de eliberare a resurselor ar fi declararea variabilelor inaintea blocului try si eliberarea
lor dupa executia blocurilol catch ca in exemplul de mai jos:
1: int main()
2: {
3: Data * d = NULL;
4: try
5: {
6: cout << "Inceputul blocului try" << endl;
7: d = new Data;
8: cout << "Ridicarea exceptiei" << endl;
9: throw 1;
10: cout << "Sfarsitul blocului try" << endl;
11: }
12: catch(int)
13: {
14: cout << "Tratarea exceptiei" << endl;
15: }
16: if (d != NULL)
17: {
18: delete d;
10
246
Adonis Butufei
19: }
20: return 0;
21: }
Acelasi rezultat se poate obtine folosind o clasa auxiliara care in constructor aloca toate resursele iar in
destructor elibereaza aceste resurse.
Exemplu:
1: class Helper
2: {
3: Data *_d;
4: public:
5: Helper (): _d(NULL)
6: {
7: _d = new Data();
8: }
9:
10: ~Helper()
11: {
12: if(_d != NULL)
13: {
14: delete _d;
15: }
16: }
17:
18: Data* D() { return _d; }
19: };
Aceasta clasa are o implementare foarte simpla: alocarea si dealocarea sunt realizate in constructor
respectiv destructor si mai exista o metoda de acces la date.
Acum programul anterior poate fi modificat sa foloseasca aceasta clasa in interiorul blocului try.
1: int main()
2: {
3: try
4: {
5: cout << "Inceputul blocului try" << endl;
6:
7: Helper h;
8:
9: Data *d = h.D();
10:
11: cout << "Ridicarea exceptiei" << endl;
12:
11
247
Adonis Butufei
13: throw 1;
14: cout << "Sfarsitul blocului try" << endl;
15: }
16: catch(int)
17: {
18: cout << "Tratarea exceptiei" << endl;
19: }
20:
21: return 0;
22: }
Folosind aceasta strategie putem grupa toate resursele folosite in interiorul blocului try in clasa Helper.
Deoarece instanta clasei Helper este alocata pe stiva destructorul acestei clase este apelat atunci cand se
termina blocul try sau cand o exceptie este ridicata in interior.
12
248
Adonis Butufei
In acest exemplu, clasei Data i-a fost adaugat un constructor care ridica exceptii in cazul in care
parametrul este un numar par.
Ruland acest program pe ecran se afiseaza urmatoarele mesaje:
Data(int )
Tratarea exceptiei 2
In exemplul de mai jos este prezentata combinatia celor doua cazuri. Pentru a reduce dimensiunea, clasa
Data nu a mai fost introdusa in exemplu:
1: #include "Data.h"
2: #include <iostream>
3: using namespace std;
4: class Container
13
249
Adonis Butufei
5: {
6: Data _d;
7: public:
8: Container(int n)
9: try :
10: _d(n)
11: {
12: cout << "Container ()" << endl;
13: }
14: catch(int x)
15: {
16: cout << "Exceptia " << x << " a fost tratata in constructor" << endl;
17: throw "Containerul nu a putut fi creat";
18: }
19:
20: ~Container()
21: {
22: cout << "~Container()" << endl;
23: }
24: };
25:
26: int main()
27: {
28: try
29: {
30: Container c(2);
31: }
32: catch (char* mesaj)
33: {
34: cout << mesaj << endl;
35: }
36:
37: return 0;
38: }
In linia 9 incepe blocul try care in acest caz contine toata lista de initializare a obiectului. Blocul de
tratare a exceptiei este plasat dupa corpul constructorului. Acest bloc afiseaza pe ecran un mesaj si ridica
o alta exceptie care este tratata in linia 34.
implementarea destructorilor.
Este important ca in interiorul destructorilor sa nu se ridice exceptii, mai ales in destructorii claselor de
baza deoarece acesta previne apelul destructorilor claselor derivate si resursele nu se pot elibera
corespunzator.
15
251
Adonis Butufei
4:
5: if(n < 0)
6: {
7: throw ParametruNegativ();
8: }
9:
10: if (n > MAX )
11: {
12: throw DepasireMax();
13: }
14:
15: // ....
16: }
In blocul catch am putut trata ambele exceptii folosind polimorfismul. Dupa executia liniei 5 se ridica o
exceptie de tipul ParametruNegativ. Comentand aceasta linie se va apela functia Factorial cu valoarea 13
care ridica exceptia DepasireMax.
16
252
Adonis Butufei
{
cout << e.Mesaj() << endl;
}
Pentru transferul prin adresa se folosesc pointerii. In locul in care se ridica exceptia se aloca memoria
pentru instanta exceptiei iar blocul catch are parametru de tip pointer. In acest caz este necesara
eliberarea memoriei in blocul catch.
Nota
Folosirea transferului exceptiei prin referinta este recomandata pentru ca ofera urmatoarele avantaje:
elimina copierea obiectelor si destructorul exceptiei este apelat automat.
14.7 Sumar
• In acest capitol am discutat despre modul de tratare a erorilor: returnarea valorilor de eroare,
terminarea programului, folosirea functiei assert si exceptiile.
• Folosirea valorilor de eroare aglomereaza codul normal cu teste care sunt greu de mentinut.
• Terminarea programului este utila in cazul erorilor grave.
• Folosirea functiei assert este utila in timpul dezvoltarii programului pentru idendificarea unor
conditii care nu trebuie sa apara in executie.
• Exceptiile sunt folosite pentru tratarea erorilor si ofera avantajul separarii codului care trateaza erorile
de executia obisnuita.
17
253
Adonis Butufei
• Exceptiile au trei elemente importante: blocul try in care se executa cod care poate ridica exceptii,
blocurile catch folosite pentru tratarea exceptiilor si ridicarea exceptiilor cu ajutorul apelului throw.
• Atunci cand se folosesc exceptiile trebuiesc eliberate resursele alocate in blocul try.
• Se pot defini clase si ierarhii de clase pentru exceptii care ajuta la intelegerea mai usoara a codului si la
transferul de informatii intre locul unde s-a ridicat exceptia si blocul catch care a tratat exceptia.
• Transferul exceptiilor se poate face prin valoare, referinta sau adresa. Pentru simplitate este preferat
transferul prin referinta deoarece elimina copierea si apeleaza automat destructorul exceptiilor.
6. Implementati si rulati functia Factorial si clasele pentru exceptii prezentate apoi rulati exemplul.
7. Care este scopul tratarii exceptiilor in lista de initializare a constructorului?
8. Ce se intampla daca se ridica o exceptie in destructorul unei clase de baza si aceasta nu este tratata?
9. Care este ordinea blocurilor catch pentru urmatoarele clase de exceptie considerand ca se poate
ridica oricare din cele trei tipuri?
class EA {};
class EB : public EA {};
class EC : public EB {};
14.9 Bibliografie
• Teach yourself C++ In an Hour A day, Sixth Edition, Sams, Jesse Liberty, Cap 28.
• Practical C++ Programming, Second Edition, O'Reilly, Steve Oualline, Cap 22.
18
254
Adonis Butufei
CUPRINS
15.Prezentare STL – Standard Template Library..........................................................................................2
15.1 Containere........................................................................................................................................2
15.1.1 Clasa vector..............................................................................................................................2
15.1.1.1 Instantierea........................................................................................................................3
15.1.1.2 Adaugarea elementelor.....................................................................................................3
15.1.1.3 Accesare valori..................................................................................................................4
15.1.1.4 Stergere elemente de la sfarsit..........................................................................................5
15.1.1.5 Dimensiune si capacitate..................................................................................................6
15.1.2 Clasa deque...............................................................................................................................6
15.1.3 Clasa list...................................................................................................................................7
15.1.3.1 Adaugarea elementelor la sfarsitul listei...........................................................................8
15.1.3.2 Adaugarea elementelor la inceputul listei.........................................................................9
15.1.3.3 Stergerea elementelor din lista........................................................................................10
15.1.4 Clasele set si multiset.............................................................................................................10
15.1.4.1 Adaugarea elementelor in containerele set si multiset....................................................11
15.1.4.2 Cautarea elementelor......................................................................................................12
15.1.4.3 Stergerea elementelor......................................................................................................13
15.1.5 Clasele map si multimap.........................................................................................................14
15.1.5.1 Adaugarea elementelor in containerele map si multimap...............................................14
15.1.5.2 Accesarea elementelor din map si multimap..................................................................15
15.1.5.3 Cautarea elementelor......................................................................................................16
15.1.5.4 Stergerea elementelor......................................................................................................17
15.2 Iteratori...........................................................................................................................................19
15.3 Obiecte functie si predicate............................................................................................................20
15.3.1 Obiecte functie unare..............................................................................................................20
15.3.2 Obiecte functie binare.............................................................................................................21
15.3.3 Predicate unare.......................................................................................................................21
15.3.4 Predicate binare......................................................................................................................22
15.4 Algoritmi........................................................................................................................................23
15.4.1 Numararea si cautarea elementelor.........................................................................................23
15.4.2 Initializarea containerelor ......................................................................................................26
15.4.3 Procesarea elementelor intr-un interval..................................................................................26
15.4.4 Transformari si obiecte functie binare....................................................................................27
15.4.5 Operatii de copiere si stergere................................................................................................28
15.4.6 Inlocuirea elementelor............................................................................................................29
15.4.7 Sortarea, cautarea intr-un container sortat, stergerea elementelor duplicate..........................29
15.5 Sumar.............................................................................................................................................30
15.6 Intrebari si exercitii........................................................................................................................31
15.7 Bibliografie.....................................................................................................................................31
1
255
Adonis Butufei
15.1 Containere
Containerele sunt clase folosite pentru stocarea datelor. STL ofera doua tipuri de containere: secventiale si
asociative.
Containerele secventiale organizeaza datele in mod secvential. Exemple reprezentative sunt tablourile si
listele. Introducerea datelor in aceste containere este rapida insa operatiile de cautare sunt lente. STL are
urmatoarele containere secventiale:
• vector – un tablou dinamic la care elementele pot fi adaugate la sfarsit.
• deque – o coada, similara cu vectorul dar la care elementele pot fi inserate la inceput sau la sfarsit.
• list – functioneaza ca o lista de elemente.
Containerele asociative organizeaza datele similar unui dictionar. Introducerea datelor este mai lenta insa
operatiile de cautare sunt mai rapide. STL are urmatoarele containere asociative:
• set – o lista sortata de elemente unice.
• map – stocheaza perechi cheie – valoare, sortate dupa chei unice.
• multiset – un tip de set care permite mai multe elemente care au aceeasi valoare.
• multimap – un tip de map in care cheile nu sunt unice.
15.1.1.1 Instantierea
Modalitatile de instantiere a containerelor vector sunt prezentate in exemplul de mai jos:
1: #include <iostream>
2: #include <vector>
3: using namespace std;
4: int main()
5: {
6: vector<int> v1;
7:
8: vector<int> v2(10);
9:
10: vector<int> v3(5, 24);
11:
12: vector<int> v4(v3);
13:
14: vector<int> v5(v4.begin(), v4.begin() + 2);
15: return 0;
16: }
In linia 2 este adaugata directiva de includere pentru fisierul vector. In linia 6 este instantiat un vector
pentru elemente de tip int folosind constructorul implicit. In linia 8 este instantiat un vector care are
rezervate 10 elemente de tip int. In linia 10 este instantiat un vector cu 5 elemente de tip int care sunt
initializate cu valoarea 24. In linia 12 este instantiat un vector care contine o copie a elementelor
vectorului v3. In linia 14 este instantiat un vector care contine o copie a primelor 3 elemente ale
vectorului v4.
1: #include <iostream>
2: #include <vector>
3: using namespace std;
4:
5: int main()
6: {
7: vector<int> v;
8: v.push_back(1);
9: v.push_back(5);
10: v.push_back(10);
11:
12: cout << "Vectorul contine " << v.size() << " elemente." << endl;
3
257
Adonis Butufei
13:
14: return 0;
15: }
In linia 7 este instantiat un vector, apoi in liniile 8 – 10 sunt adaugate elemente la sfarsitul vectorului.
Numarul final de elemente este afisat pe ecran in linia 12.
In linia 7 este instantiat un vector cu 2 elemente. Valorile elementelor sunt atribuite in liniile 9 si 10.
1: #include <iostream>
2: #include <vector>
3: using namespace std;
4:
5: int main()
6: {
7: vector<int> v(3);
8:
9: v[0] = 125;
10: v[1] = 476;
11: v[2] = 953;
12:
4
258
Adonis Butufei
Vectorul este instantiat si elementele sunt initializate in liniile 7 - 11. Continutul vectorului este afisat pe
ecran cu ajutorul buclei for din liniile 15 – 18.
5
259
Adonis Butufei
26: }
27:
28: return 0;
29: }
In acest exemplu este creat un vector cu patru elemente de tip int. In linia 4 este sters ultimul element apoi
sunt afisate pe ecran elementele ramase cu ajutorul buclei din liniile 23 – 26.
In linia 7 este instantiat un vector pentru 4 elemente de tip int. In acest caz dimensiunea si capacitatea
sunt egale cu 4. In linia 13 este adaugat un element la sfarsit. Dupa executia acestei linii dimensiunea este
5 si capacitatea 6. Afisarea se face in liniile 16 – 17.
2: #include <deque>
3: using namespace std;
4:
5: int main()
6: {
7: deque<int> d;
8:
9: const int SIZE = 10;
10: for(int i = 0; i < SIZE; i++)
11: {
12: if( 0 == (i % 2))
13: {
14: d.push_back(i);
15: }
16: else
17: {
18: d.push_front(i);
19: }
20: }
21:
22: for(int i = 0; i < SIZE; ++i)
23: {
24: cout << d[i] << endl;
25: }
26:
27: return 0;
28: }
In linia 7 este instantiat un container deque pentru elemente de tip int. Cu ajutorul buclei din liniile 10 –
20 se adauga numerele pare la sfarsit iar numerele impare la inceput. Continutul containerului este afisat
cu bucla din liniile 22 – 25.
indirectare). De asemenea ei pot fi folositi pentru cautarea sau stergerea unui element.
Clasa list contine doua tipuri de iteratori: pentru acces de citire / scriere sau constanti pentru acces doar
de citire.
In linia 3 este inclus fisierul list pentru a putea lucra cu acest container. In linia 3 a fost creata o instanta
pentru o lista de elemente de tipul int. Urmeaza apoi adaugarea a trei elemente la sfarsitul listei: liniile 10
– 12.
Apoi este afisata lista cu ajutorul functiei template PrintContainer care este definita in fisierul
PrintContainer.h prezentata mai jos:
1: #ifndef PRINTCONTAINER_H
2: #define PRINTCONTAINER_H
3: template <typename Container>
4: void PrintContainer(const Container &src)
5: {
6: cout << "{ " ;
7:
8: Container::const_iterator end = src.end();
9:
10: for(Container::const_iterator crt = src.begin(); crt != end; ++crt)
11: {
8
262
Adonis Butufei
Pentru a putea afisa continutul mai multor containere a fost folosita o functie template. Parametrul de
intrare este referinta constanta la container deoarece functia afiseaza continutul pe ecran are nevoie doar
de acces de citire.
In linia 8 este extras iteratorul de sfarsit. Aceasta pozitie a cursorului este dupa ultimul element introdus
in lista.
In blocul for din linia 8 se incepe iteratia de la primul element care poate fi accesat apeland metoda
begin a containerului. Valoarea elementului curent este afisata pe ecran in linia 12 folosind operatorul de
indirectare pentru pozitia curenta a iteratorului.
Dupa afisare, pozitia iteratorului este incrementata in blocul for si comparata cu pozitia de sfarsit.
Continutul containerului este afisat pe o linie delimitata de acolade care sunt afisate in liniile 6 si 15.
In acest exemplu instantierea este idendica, elementele sunt adaugate in lista in liniile 10 - 12 iar pentru
afisarea continutului s-a folosit aceeasi functie template.
La rularea acestui program pe ecran apare urmatorul continut:
9
263
Adonis Butufei
{ 20 15 10 }
In linia 13 sunt afisate elementele listei inainte de stergere. In linia 15 se sterg elementele cuprinse intre
pozitia de inceput si cea de sfarsit. Se pot alege orice alte valori intermediare iar pentru stergerea unui
element se apeleaza metoda erase cu iteratorul la pozitia care trebuie stearsa.
1 Arborele binar este o structura abstracta de date in care fiecare element poate avea cel mult doi descendenti.
10
264
Adonis Butufei
Containerul set este instantiat in linia 8, apoi este populat cu elemente in liniile 9 – 11. In linia 13 se
incearca introducerea unui element duplicat. Continutul acestui container este afisat pe ecran in linia 16
folosind aceeasi functie template.
In continuare se executa aceleasi operatii pentru containerul multiset. In linia 28 este afisat numarul de
elemente care au valoarea 12. La rularea acestui exemplu continutul containerului set este: {12 15 17}
iar continutul containerului multiset este {12 12 15 17 }.
11
265
Adonis Butufei
1: #include <iostream>
2: #include <set>
3: using namespace std;
4:
5: void Find(int element, const set<int> &src)
6: {
7: set<int>::iterator itElement = src.find(element);
8:
9: if(itElement != src.end())
10: {
11: cout << "Elementul " << (*itElement)
12: cout << " a fost gasit in set" << endl;
13: }
14: else
15: {
16: cout << "Elementul " << element << " nu exista in set" << endl;
17: }
18: }
19:
20: int main()
21: {
22: set<int> sint;
23:
24: sint.insert(15);
25: sint.insert(32);
26: sint.insert(11);
27:
28: Find(11, sint);
29:
30: Find(-1, sint);
31:
32: return 0;
33: }
Pentru cautare a fost folosita functia Find definita intre liniile 5 – 18. Aceasta are doi parametri: valoarea
cautata si referinta la container. In linia 7 se foloseste metoda find care returneaza un iterator
corespunzator pozitiei elementului cautat atunci cand elementul se afla in container sau pozitia de sfarsit
in caz contrar. In linia 9 se compara pozitia iteratorului returnat de metoda find si pozitia de sfarsit. In
cazul in care a fost gasit elementul dorit se afiseaza mesajele din liniile 11 si 12. Altfel se afiseaza mesajul
12
266
Adonis Butufei
Codul de initializare si populare a containerului este similar exemplelor anterioare. In linia 30 se face o
cautare pentru un element inexistent.
In liniile 9 – 13 este instantiat si populat un container de tipul multiset. Apoi se afiseaza continutul acestui
container pe ecran (liniile 15,16). In liniile 18 – 20 utilizatorul este rugat sa introduca valoarea care va fi
stearsa din container. In linia 22 se sterge elementul din container, apoi in linia 25 este afisat continutul
containerului. Daca se introduce valoarea 11 vor fi sterse ambele elemente din container.
13
267
Adonis Butufei
14
268
Adonis Butufei
35: cout << "Multimap-ul contine " << mmap.size() << " elemente" << endl;
36: cout << "Continutul multimap-ului este:" << endl;
37: PrintPairs(mmap);
38:
39: return 0;
40: }
In linia 1 este inclus fisierul care contine functia template care afiseaza perechile unui map sau multimap
pe ecran. Pentru simplificarea scrierii in liniile 8 si 9 au fost definite aliasuri pentru tipurile de map
respectiv multimap.
In linia 13 este creata o instanta pentru containerul map. In liniile 15 – 17 este creat cate un element de
tipul pair folosind metode ajutatoare sau instantiind explicit acest tip de clasa. In linia 19 este folosit
operatorul de indexare. In cazul in care nu exista nici un element pereche care are cheia specificata ca
index se creaza acel element si se insereaza in map. Daca pentru acea cheie exista un element pereche
atunci valoarea lui este schimbata. In linia 23 se afiseaza continutul mapului.
Liniile 25 – 37 executa operatii similare pentru o clasa de tip multimap. Pentru multimap se adauga cate
doua elemente pereche cu acceasi cheie si valori diferite. Pentru prima valoare se folosesc cifrele arabe
pentru cealalta se folosesc cifrele romane. In cazul multimapului sunt doar 3 metode de adaugare a
elementelor deoarece multimapul nu mai are operator de indexare.
Implementarea este asemanatoare cu cea a functiei template PrintContainer, prezentata anterior, in acest
15
269
Adonis Butufei
16
270
Adonis Butufei
36: {
37: Map map;
38:
39: map.insert(Map::value_type("unu","1"));
40: map.insert(make_pair("doi","2"));
41: map.insert(pair<string,string>("trei","3"));
42:
43: cout << "Continutul map-ului este:" << endl;
44: PrintPairs(map);
45: Find("trei", map);
46:
47: MultiMap mmap;
48: mmap.insert(MultiMap::value_type("unu","1"));
49: mmap.insert(MultiMap::value_type("unu","I"));
50:
51: mmap.insert(make_pair("doi","2"));
52: mmap.insert(make_pair("doi","II"));
53:
54: mmap.insert(pair<string,string>("trei","3"));
55: mmap.insert(pair<string,string>("trei","III"));
56:
57: cout << "Continutul multimap-ului este:" << endl;
58: PrintPairs(mmap);
59:
60: Find("trei",mmap);
61:
62: return 0;
63: }
Cautarea este implementata in functia template Find. In linia 9 se apeleaza metoda find a containerului.
Daca nu exista perechi care au cheia respectiva, iteratorul are pozitia de sfarsit si se executa liniile 12 si
13. Altfel se apeleaza metoda count (linia 16) pentru a determina numarul de perechi gasite si se afiseaza
informatia pe ecran liniile 18 – 27.
Pentru test au fost folosite doua containere: un map intre liniile 37 – 45 si un multimap intre liniile 47 –
60.
1: #include "PrintPairs.h"
2: #include <map>
17
271
Adonis Butufei
3: #include <string>
4: #include <iostream>
5:
6: int main()
7: {
8: MultiMap mmap;
9: mmap.insert(MultiMap::value_type("unu","1"));
10: mmap.insert(MultiMap::value_type("unu","I"));
11:
12: mmap.insert(make_pair("doi","2"));
13: mmap.insert(make_pair("doi","II"));
14:
15: mmap.insert(pair<string,string>("trei","3"));
16: mmap.insert(pair<string,string>("trei","III"));
17:
18: cout << "Continutul multimap-ului este:" << endl;
19: PrintPairs(mmap);
20:
21: mmap.erase("unu");
22: cout << "Continutul multimap-ului dupa stergerea unei cheii unu:\n";
23: PrintPairs(mmap);
24:
25: MultiMap::iterator it = mmap.find("doi");
26: if(it != mmap.end())
27: {
28: mmap.erase(it);
29:
30: cout << "Continutul multimap-ului dupa stergerea iteratorului:\n”;
31: PrintPairs(mmap);
32: }
33:
34: mmap.erase(mmap.lower_bound("trei"), mmap.upper_bound("trei"));
35: cout << "Continutul multimap-ului dupa stergerea unui interval:\n";
36: PrintPairs(mmap);
37:
38: return 0;
39: }
Elementele sunt adaugate in container intre liniile 8 – 18. In linia 19 este apelata metoda erase pentru
elementele cu cheia "unu". Dupa apelul acestei metode toate elementele cu aceasta cheie sunt sterse din
container. In linia 28 se foloseste un iterator pentru stergerea elementului. In cazul acestui apel numai
elementul care are pozitia corespunzatoare iteratorului este sters. Ultima metoda de stergere este
prezentata in linia 34. Prin folosirea unui interval se pot sterge mai multe elemente ale caror chei se afla in
acel interval.
18
272
Adonis Butufei
15.2 Iteratori
Iteratorii sunt clase template care functioneaza in mod similar cu pointerii. Ei permit utilizatorului sa
execute operatii asupra containerelor (cautare, inserare, sortare etc). Operatiile executate asupra
containerelor pot fi algoritmi care sunt implementati ca functii template. Iteratorii sunt puntea care
permite algoritmilor sa lucreze cu containerele in mod uniform. Iteratorii sunt definiti in fisierul iterator.
Dupa directia in care se executa operatiile de acces al elementelor iteratorii se impart in:
• Iteratori de intrare – care permit operatiile de citire a elementelor din containere.
• Iteratori de iesire – care permit operatii de modificare a containerelor.
Acesti iteratori sunt folositi in operatiile de intrare iesire.
Dupa modul de :
• Iteratori care se deplaseaza in fata – Specializeaza operatiile iteratorilor de intrare si iesire asigurand
deplasarea de la un element al containerului intr-o singura directie implementand operatiile de
incrementare si este folosit in anumite tipuri de liste.
• Iteratori care se deplaseaza in ambele directii – Fata de tipul precedent de iterator permite deplasarea
in ambele sensuri catre elementele containerului, implementand operatiile de incrementare si
decrementare si este folosit de urmatoarele contaiere: list, set, multiset, map multimap.
• Iteratori cu acces aleator – Fata de iteratorii anteriori permite deplasarea in ambele sensuri cu mai
mult de un element si este folosit de urmatoarele containere: vector, deque, string.
In tabelul de mai jos sunt prezentate operatiile suportate de categoriile de iteratori:
Operatie Explicatie Tip iterator
19
273
Adonis Butufei
In acest exemplu este prezentat un obiect functie unar folosit pentru calcularea sumei elementelor unui
container. Un exemplu de utilizare este prezentat mai jos:
1: vector<int> vint;
2: vint.push_back(19);
3: vint.push_back(12);
4: vint.push_back(21);
5:
6: Sum<int> s;
7: int size = vint.size();
8: for(int i =0; i < size; i++)
9: {
10: s(vint[i]);
11: }
12:
13: cout << "Suma elementelor: " << s.Valoare() << endl;
In liniile 1 – 4 se initializeaza un vector cu elemente de tip int. In linia 6 se instantiaza un obiect functie
care este folosit in interiorul buclei pentru calculul sumei elementelor. In linia 13 se afiseaza suma pe
ecran.
In acest exemplu este prezentat un obiect functie binar, el poate fi folosit pentru calculul sumei
componentelor a doi vectori cum este prezentat mai jos.
1: const int SIZE = 10;
2: vector<int> a(SIZE), b(SIZE), c(SIZE);
3:
4: for(int i = 0; i < SIZE; i++)
5: {
6: a[i] = 2 * i;
7: b[i] = a[i] + 1;
8: }
9:
10: AdunaElemente<int> add ;
11:
12: for(int i = 0; i < SIZE; i++)
13: {
14: c[i] = add(a[i], b[i]);
15: }
In linia 2 sunt instantiate trei vectori cu elemente de tip int. Primii doi vectori sunt initializati in liniile 4 –
8. In linia 10 este creat un obiect functie care este folosit in linia 14 pentru calculul componentelor
vectorului c.
21
275
Adonis Butufei
3: int _divizor;
4: public:
5: TestMultiplu(int divizor = 1): _divizor(divizor) {}
6:
7: bool operator() (const int& src) { return (0 == src % _divizor ); }
8: };
In acest exemplu este prezenat un predicat unar care testeaza daca parametrul operatorului () este
multiplul valorii specificate in constructor. Mai jos este prezentat un exemplu de utilizare:
1: const int SIZE =3;
2: vector<int> vint(SIZE);
3: vint [0] = 19;
4: vint [1] = 12;
5: vint [2] = 21;
6:
7: vector<int> m;
8:
9: TestMultiplu multiplu(3);
10:
11: for(int i =0; i < SIZE; i++)
12: {
13: if(multiplu(vint[i]) )
14: {
15: m.push_back(vint[i]);
16: }
17: }
In liniile 1 – 5 este initializat un vector cu elemente de tip int. In linia 7 este creat un vector care va
colecta multiplii. Predicatul unar este instantiat in linia 9. Vectorul m este populat cu multiplii de 3
selectati cu bucla din liniile 11 – 17.
Deoarece atributele clasei Complex sunt de tipul double este necesara folosirii valorii DELTA pentru
compararea egalitatii. Mai jos este prezentat un exemplu de apel:
1: vector<Complex> vcplx;
2: vcplx.push_back(Complex(3.2,4.5));
3: vcplx.push_back(Complex(1.3, 2.6));
4:
5: CompareComplex compare;
6: Complex t(3.2, 4.5);
7:
8: int count = vcplx.size();
9: bool gasit = false;
10:
11: for(int i = 0; i < count; i++)
12: {
13: if(compare(t,vcplx[i]))
14: {
15: gasit = true;
16: break;
17: }
18: }
In liniile 1 – 3 este instaintiat si initializat un vector cu doua elemente de tip Complex. In linia 5 este
creata o instanta a predicatului binar care este folosita pentru cautarea in vector in liniile 11 – 18.
15.4 Algoritmi
Operatiile de cautare, sortare etc sunt cerinte standard care ar trebui implementare o singura data.
Algoritmii permit refolosirea acestei implementari aduce cresteri importante ale productivitatii. Pentru
folosirea algoritmilor este necesara includerea fisierului algorithm.
23
277
Adonis Butufei
In exemplul de mai jos sunt prezentate scenariile de numarare si cautare a unui element in containere.
1: #include "PrintPairs.h"
2: #include <algorithm>
3: #include <iostream>
4: #include <vector>
5: using namespace std;
6:
7: bool NumarImpar(int i)
8: {
9: return (0 != (i % 2));
10: }
11:
12: int main()
13: {
14: vector<int> v;
15:
16: for(int i = -3; i < 5; i++)
17: {
18: v.push_back(i);
19: }
20: v.push_back(3);
21:
22: cout << "Elementele vectorului" << endl;
23: PrintContainer(v);
24:
25: int nrImpare = count_if(v.begin(),v.end(), NumarImpar);
26: cout << "Vectorul are " << nrImpare << " numere impare" << endl;
27:
28: int nr3 = count(v.begin(), v.end(), 3);
29: cout << "Vectorul contine de " << nr3 << " ori numarul 3" << endl;
30:
31: vector<int>::iterator it = find(v.begin(), v.end(), 2);
32: if(it != v.end())
33: {
34: cout << "Numarul 2 a fost gasit in vector" << endl;
35: }
36:
37: vector<int>::iterator itImpar = find_if(v.begin(),v.end(),NumarImpar);
38:
39: if(itImpar != v.end())
40: {
41: cout << "Numarul " << (*itImpar ) << " se afla pe pozitia [";
24
278
Adonis Butufei
In liniile 14 – 20 este initializat un vector cu elemente de tip int. In linia 25 se calculeaza numarul de
elemente impare folosind algoritmul count_if. In apelul acestui algoritm sunt specificate pozitia de
inceput, pozitia de sfarsit si functia NumarImpar definita in liniile 7 – 9.
In linia 28 se calculeaza cate elemente au valoarea 3 folosind algoritmul count. In linia 31 se cauta
elementul cu valoarea 2 folosind algoritmul find. Acest algoritm returneaza un iterator cu pozitia
elementului in cazul in care a fost gasit sau cu valoarea de sfarsit in caz contrar. In linia 37 se cauta
primul mumar folosind algoritmul find_if si functia NumarImpar. In linia 42 se calculeaza pozitia
elementului gasit folosind functia distance definita in STL.
Exista cazuri cand este necesara cautarea unui intreg set de elemente sau a unei secvente de elemente de
valoare egala intr-un container. Exemplul de mai jos prezinta aceste scenarii de cautare.
1: vector<int> v;
2: for(int i = -5; i < 5; i++)
3: {
4: v.push_back(i);
5: }
6: v.push_back(5);
7: v.push_back(5);
8:
9: cout << "Continutul vectorului" << endl;
10: PrintContainer(v);
11:
12: list<int> interval;
13: for(int i = -2; i < 3; i++)
14: {
15: interval.push_back(i);
16: }
17: cout << "Continutul listei" << endl;
18: PrintContainer(interval);
19:
20: cout << "Cautarea elementelor listei in vector" << endl;
21: vector<int>::iterator itSearch = search(v.begin(),v.end(),
22: interval.begin(), interval.end());
23: if(itSearch != v.end())
24: {
25: cout << "Elementele listei au fost gasite la pozitia " ;
25
279
Adonis Butufei
In liniile 1 – 7 este initializat un vector de elemente de tip int. Apoi in liniile 12 – 16 este intializata o
lista cu setul de elemente care va fi cautat in vector. In linia 21 se utilizeaza algoritmul search pentru
cautarea acestui interval. In acest apel au fost specificate limitele containerului in care se face cautarea
precum si limitele intervalului cautat. Algoritmul returneaza iteratorul cu pozitia de inceput unde a fost
gasit intervalul sau cu pozitia de sfarsit a containerului.
In linia 30 se cauta secventa de elemente care are valorile 5 5 folosind algoritmul search_n. In acest apel
sunt specificate limitele containerului in care se face cautarea, numarul de elemente din secventa si
valoarea elementului. Algoritmul returneaza iteratorul cu pozitia de inceput unde a fost gasit intervalul
sau cu pozitia de sfarsit a containerului.
In linia 1 este instantiat un vector de 3 elemente de tip int. Elementele acestui vector sunt initializate cu
valoarea 5 folosind algoritmul fill. Apelul din linia 2 specifica limitele containerului si valoarea de
initializare.
In linia 4 vectorul este redimensionat sa contina 5 elemente. Apoi in linia 5 ultimele doua elemente sunt
initializate cu valoarea -4 folosint algoritmul fill_n. In acest apel se specifica pozitia de inceput,
numarul de elemente si valoarea de initializare.
26
280
Adonis Butufei
4: {
5: v.push_back(i);
6: }
7:
8: cout << "Continutul vectorului" << endl;
9: PrintContainer(v);
10:
11: Sum<int> sum = for_each(v.begin(), v.end(), Sum<int> ());
12: cout << "Suma elementelor este ";
13: cout << sum.Valoare() << endl;
In linia 11se calculeaza suma pentru valorile elementelor din vector folosind obiectul functie unara Sum
prezentat anterior. Apelul specifica limitele intervalului si functia care implementeaza prelucrarea.
Important
Valoarea returnata este o copie a obiectului functie. In cazul de fata contstructorul de copiere implicit
functioneaza corect si nu a fost necesara definirea lui explicita. In cazurile practice daca functia unara are
pointeri care sunt alocati dinamic este necesara implementarea constructorului de copiere, operatorului de
atribuire si destructorului.
19: cout << "Vectorul care contine suma elementelor" << endl;
20: PrintContainer(z);
In linia 2 sunt instantiati doi vectori cu zece elemente de tip int. Primii doi vectori sunt initializati in
liniile 4 – 8. In linia 16 este folosit algoritmul transform pentru a calcula suma componentelor celor doi
vectori. Rezultatul este salvat in cel de-al treilea vector. Suma se calculeaza cu ajutorul obiectului
AdunaElemente prezentat anterior. Apelul specifica pozitiile de inceput si de sfarsit al primului container,
pozitia de inceput pentru al doilea container si obiectul functie care executa transformarea.
In liniile 1 – 5 este initializata o lista cu elemente folosite pentru copiere. In linia 10 este instantiat un
vector care poate contine de doua ori mai multe elemente. In linia 11 se foloseste algoritmul copy pentru
a copia elementele din lista in vector de la inceputul listei catre sfarsit.
In linia 13 se foloseste algoritmul copy_backward pentru a copia elementele din lista incepand de la
28
282
Adonis Butufei
sfarsit catre inceput. Dupa aceste doua operatii vectorul contine de doua ori elementele din lista.
In linia 18 se sterg elementele care au valoarea 0 folosind algoritmul remove. Acest algoritm returneaza
iteratorul corespunzator sfarsitului dupa executarea stergerii. Executia algoritmului nu elimina elemente
din container ci le muta la sfarsit. Pentru eliminarea elementelor din container este necesar apelul metodei
erase a containerului folosind iteratorul returnat de algoritm (linia 19).
In linia 23 se foloseste algoritmul remove_if pentru stergerea elementelor impare din vector. Si acest
algoritm returneaza un iterator corespunzator sfarsitului dupa executarea stergerii. Pentru eliminarea
elementelor din container este apelata metoda erase din linia 24.
In liniile 1 – 4 este initializat vectorul pentru acest exemplu. Apelul din linia 4 repozitioneaza elementele
vectorului in mod aleator.
In linia 10 este apelat algoritmul replace pentru inlocuirea valorii 4 cu valoarea 7. Apoi in linia 15 este
apelat algoritmul replace_if pentru inlocuirea numerelor impare cu -2.
se foloseste algoritmul unique. Folosirea acestor algoritmi este prezentata in exemplul urmator.
1: vector<int> v;
2: for(int i = 0; i < 10; i++)
3: {
4: v.push_back(i % 3);
5: }
6:
7: cout << "Continutul initial al vectorului" << endl;
8: PrintContainer(v);
9:
10: cout << "Sortarea vectorului" << endl;
11: sort(v.begin(), v.end());
12: PrintContainer(v);
13:
14: cout << "Cautarea elementului 2 in vector folosind binary_search\n";
15: bool found = binary_search(v.begin(), v.end(), 2);
16:
17: if(found)
18: {
19: cout << "Elementul 2 a fost gasit" << endl;
20: }
21: else
22: {
23: cout << "Elementul 2 nu a fost gasit" << endl;
24: }
25:
26: cout << "Eliminarea valorilor duplicate" << endl;
27: vector<int>::iterator endAfterRemove = unique(v.begin(), v.end());
28: v.erase(endAfterRemove, v.end());
29: PrintContainer(v);
Initializarea vectorului este realizata in liniile 1 – 5. In linia 11 este apelat algoritmul de sortare. In linia
15 se foloseste algoritmul binary_search pentru cautarea elementului cu valoarea 2. Acest algoritm
returneaza true in cazul in care elementul a fost gasit.
In linia 27 este folosit algorimul unique pentru stergerea elementelor duplicate. Acest algoritm returneaza
un iterator corespunzator pozitiei de sfarsit dupa stergerea elementelor duplicate. Functionarea
algoritmului unique este similara cu remove si necesita apelul erase pentru eliminarea elementelor din
vector (linia 28).
15.5 Sumar
• Folosirea componentelor STL reduce efortul de implementare si imbunatateste calitatea codului.
• Utilizarea clasei string simplifica lucrul cu sirurile de caractere incapsuland operatiile de alocare de
memorie si copiere.
30
284
Adonis Butufei
• Containerele sunt clase template. Ele organizeaza elementele fie in mod secvential, fie similar unui
dictionar.
• Iteratorii sunt similari pointerilor: prin indirectare putem accesa elementele containerelor.
• Algoritmii sunt functii template care folosesc iteratorii pentru a executa operatii asupra containerelor.
• Pentru specificarea conditiilor se folosesc predicatele care se transmit algoritmilor.
15.7 Bibliografie
• Practical C++ Programming, Second Edition, O'Reilly, Steve Oualline, Cap 25.
• Teach yourself C++ In an Hour A day, Sixth Edition, Sams, Jesse Liberty, Cap 16 - 24.
31
285