Sunteți pe pagina 1din 58

Crash Course in C++

Un tutorial practic despre C++ destinat programatorilor de Java


Adrian Scoic a
Observat , ii s , i comentarii: adrian.scoica@gmail.com

15 februarie 2011

CUPRINS

Cuprins
1 Introducere 1.1 Ce este C++? . . . . . . . . . . . . . . . . . . . . . . . . 1.2 C++ sau Java? (*) . . . . . . . . . . . . . . . . . . . . . 1.3 C++ vs Java. Avantaje s , i dezavantaje comparative. (*) 1.3.1 Compilat vs. interpretat (*) . . . . . . . . . . . . 1.3.2 Timpul de execut , ie (*) . . . . . . . . . . . . . . . 1.3.3 Memoria s . . . . . . . . , i accesul la memorie (*) 2 Clase 2.1 Declarat , ie . . . . . . . . . . . . . . . . . . 2.2 Modicatorii de acces . . . . . . . . . . . 2.3 Constructori . . . . . . . . . . . . . . . . . 2.4 Denire . . . . . . . . . . . . . . . . . . . 2.5 Ciclul de viat a al obiectelor. Destructori. , 2.6 Membrii statici (**) . . . . . . . . . . . . 2.7 Derivarea claselor (**) . . . . . . . . . . . 2.8 Polimorsm. Funct , ii virtuale (**) . . . . . 2.9 Supra nc arcarea operatorilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 2 2 3 3 3 4 6 6 7 8 9 13 16 18 20 23

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

3 Input/Output 27 3.1 Fis iere s i stream-uri . . . . . . . . . . . . . . . . . . . . . . . . . 27 , , 4 S 30 , iruri de caractere 4.1 Clasele std::string s . . . . . . . . . . . . . . . 30 , i std::stringstream 5 Gestiunea memoriei 32 5.1 Alocarea memoriei: new vs. malloc . . . . . . . . . . . . . . . . . 32 5.2 Dezalocarea memoriei: free vs. delete . . . . . . . . . . . . . . . . 34 6 Standard Template Library 6.1 Introducere: de ce STL? (*) . . . . . . . 6.2 Template-uri (**) . . . . . . . . . . . . . 6.3 Componentele STL . . . . . . . . . . . . 6.4 Containere: vector, list, slist, deque, set, 6.5 Adaptori: queue, stack, priority queue . 6.6 Iteratori . . . . . . . . . . . . . . . . . . 6.7 Algoritmi . . . . . . . . . . . . . . . . . 6.8 Gres . . . . . . . . . . . . . , eli frecvente 7 Namespace-uri (***) 8 Tratarea Except , iilor (***) 36 36 37 39 40 46 46 49 51 53 55

. . . . . . . . . map . . . . . . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

CUPRINS

9 Resurse Legend a: (*) informat , ii cu caracter general (**) anumite aspecte de detaliu (***) facilit at , i avansate s , i mai rar folosite ale limbajului

56

INTRODUCERE

1
1.1

Introducere
Ce este C++?

Mult a lume denes a denit , te C++ ca ind un superset de C. Aceast , ie este part a, dar es a n a conferi limbajului credit pentru inovat , ial corect , ueaz , iile s ,i gradul de expersivitate superioare pe care le aduce fat a de limbajul C. , C++ a fost creat de c atre Bjarne Stroustrup pe baza limbajului C, pentru a suporta noile concepte vehiculate n programare la vremea respectiv a, cunoscute colectiv sub numele de Programare Orientat a pe Obiecte. Limbajul a fost proiectat s a permit a exprimarea conceptelor specice program arii orientate spre obiecte, ind construit s a suporte clase, virtualizare, supra nc arcare, mos , tenire multipl a, template-uri s as aturi care l-ar , i tratarea except , iilor, printre altele, tr distant n mod normal de C nc a de la nceput. Cu toate acestea, familiari, at tatea programatorilor cu C-ul, nevoia de compatibilitate invers a cu codul deja existent, precum s , i statutul incontestatbil al C-ului au condus la dezvoltarea unui limbaj care s a cuprind a n gramatica sa aceleas , i construct , ii sintactice. C++ este unul dintre cele mai populare limbaje de programare scrise vreodat a, ind cel put , in la fel de ecient s , i portabil ca C-ul, dar complexitatea crescut a c at s a cel mai adesea sub forma lib, i libertatea de exprimare (amintit ertatea de a gres ncurajat dezvoltarea de limbaje simplicate , i) foarte mare au sau specializate care s a implementeze doar part at , ial funct , ionalit , ile acestuia s ,i s a se concentreze pe alte aspecte. Cele mai cunoscute limbaje care au fost puternic inuent , ate de C++ sunt Java, respectiv C#.

1.2

C++ sau Java?

Uneori se pune ntrebarea: Este C++ mai bun sau mai r au dec at Java? Adev arul este c a un r aspuns la aceast a ntrebare, din p acate, nu exist a. In principiu, C++ pune la dispozit a libertate de exprimare, dar , ia programatorului mult mai mult acest lucru nu este ntotdeauna ceva bun. Mai degrab a, ntrebarea ar face parte din aceeas a (no pun intended) cu ntrebarea Este Engleza mai bun a sau , i clas mai rea dec at Rom ana?. Este destul de evident c a at at Rom ana c at s n calitate de limbi , i Engleza au vorbite, aceeas n , i putere de exprimare. Unele idei sunt mai us , or de exprimat Rom an a, altele sunt mai nr ad acinate n Englez a, dar n denitiv nu exist a nici un concept ce se poate exprima ntr-una din limbi f ar a s a se reg aseasc a printre construct , iile celeilalte un echivalent. In general, n Java s-au sacricat o parte considerabil a dintre funct at , ionalit , ile C++ din dorint abi ritmul de dezvoltare al aplicat atre pro, a de a gr , iilor de c

INTRODUCERE

gramatori, precum s , i din dorint , a de a standardiza mai us , or protocoalele folosite. Motivul principal st a n faptul c a Java a fost init ial conceput pentru a realiza , aplicat ii end-user bazate pe accesul la Internet, n timp ce C++ se foloses , , te mai mult pentru a realiza aplicat , ii de tip back-end (servere) sau aplicat , ii care efectueaz a intens calcule s at mai bine. , i deci trebuie optimizate c

1.3

C++ vs Java. Avantaje s , i dezavantaje comparative.

In ncheierea acestei sect ateva dintre diferent , iuni, voi trata comparativ c , ele majore dintre C++ s atind n acelas , i Java, preg , i timp mindset-ul necesar parcurgerii urm atoarelor capitole. Din dorint a exprima c at mai ecient, voi con, a de a m sidera s a voi concentra mai mult asupra celor , tiute aspectele legate de Java s , i m specice C++. Cu toate acestea, tutorialul de fat a nu este s , , i nu se dores , te a un substitut pentru un manual de C++. Mai degrab a, considerat , i-l un cheat sheet care s a v a introduc a n limbaj, astfel nc at s a v a ajute s a scriet , i cod corect s ateva resurse dedicate de C++, vericat , i mult mai ecient. Pentru c ,i sect , iunea de bibliograe. Recomand parcurgerea urm atoarelor pagini, mai ales dac a Java este primul limbaj de Programare Orientat a Obiect pe care l-at ap anit. Este posibil s a , i st nu realizat a lectur a important a vet , i la o prim , a unora dintre diferent , e, dar v ,i lovi relativ repede de ele dac a vet ncerca s a traducet ,i , i efectiv sintaxa de Java s a de fapt codul nu funct a as , i vet , i constata c , ioneaz , a cum v-at , i as , teptat.

1.3.1

Compilat vs. interpretat

C++ este, ca s Compilatorul parseaz a codul surs a , i C, un limbaj compilat. s n formatul mas a , i produce la ies , ire cod binar , inii zice pe care se compileaz (ex: executabile Linux pe 32bit). Avantaje: executabilele sunt imagini de procese pentru mas a. Aplicat n C++ au toate funct at , ina gazd , iile scrise , ionalit , ile celorlaltor aplicat , ii native ale sistemului: un PID, siere de I/O (stdin, stdout, stderr), s , i accesul direct la toate celelalte resurse ale sistemului de operare (de exemplu: sistemele de s a, apeluri de sistem), , iere, socket , i, memoria virtual linkarea cu biblioteci partajate pentru nc arcarea dinamic a de cod la rulare, etc. Compilatorul de Java, pe de alta parte, produce cod intermediar care trebuie interpretat. Cu alte cuvinte, dup a compilarea Java, bytecode-ul obt , inut este rulat n interiorul unei mas , ini virtuale. Avantaje: deoarece aplicat , iile scrise n Java nu ruleaz a nativ n mas a, ci execut a de , ina zic , ia lor este simulat Java Runtime Environment, acest lucru eradicheaz a practic problemele de compatibilitate ntre platforme, permite nc arcarea dinamic a a claselor la runtime, existent , a blocurilor statice de init , ializare s , i mecanismul de garbage collection. 1.3.2 Timpul de execut , ie

INTRODUCERE

Compilatoarele de C++ sunt, n general, capabile s a produc a cod binar extrem de optimizat. Mai mult, faptul c a acest cod se ruleaz a nativ pe mas a , ina gazd reduce la minimum overhead-ul. De asemenea, C++ nu v a adaug a singur n cod funct at a trag aplicat , ionalit , i suplimentare pe care nu le-at , i cerut s , i care v , ia napoi. Nu n ultimul r and, C++ permite, opt , ional, specicarea de directive care s a instruiasc a compilatorul despre cum s a v a genereze codul de asamblare.

Dac a nici asta nu este sucient, putet and s a introducet n proiectul , i oric ,i vostru de C++ implement ari de funct n limbaj de asam, ii critice scrise direct blare (care daca sunt scrise bine, pot accelera semnicativ aplicat , ia). Des , i ar putea p area un gest extrem, circumstant asuri speciale. , ele speciale impun m Nu este recomandat totus a introducet n C++ cod de asamblare, pentru c a , i s ,i pierdet nainte de nalizarea aplicat , i portabilitatea (s , i categoric nu , iei). Viteza de execut , ie a fost mereu primul s , i cel mai important dezavantaj pentru Java. Deoarece bytecode-ul trebuie interpretat de mas a, acest , ina virtual lucru nseamn a c a pentru ecare operat a de bytecode, mas a , ie elementar , ina zic ar trebui teoretic s a execute dou a instruct , iuni: una pentru interpretare s , i una pentru execut , ie. In cazul noilor mas , ini virtuale, acest lucru nu este valabil, s ,i performant mbun at at , ele aplicat , iilor Java s-au , it constant de la aparit , ia limbajului: dac a la nceput, o aplicat an a la de 20 de ori mai lent , ie Java rula de p dec at echivalentul C++, acum s-a ajuns la un un factor de timp de aproximativ 1,5 - 2. Lupta pentru performant a a dus la aparit , , ia mas , inii virtuale Java HotSpot, scris a n C++ (:D). Ideea din spatele HotSpot este cu adev arat state-of-the-art n ceea ce prives ari adaptive ale , te s , tiint , a compilatoarelor: crearea de optimiz codului surs a n funct , ie de statisticile de runtime ale codului, s , i poate coduce n cazuri particulare la cod la fel de rapid ca s ar a , i echivalentul nativ C++ f optimiz ari specice. Alte inovat arat valoroase aduse de mas , ii cu adev , inile virtuale Java sunt Just-In-Time compilation, Garbage Collection sau Class Data Sharing.

1.3.3

Memoria s , i accesul la memorie

Gestiunea memoriei este poate ultima diferent arat important a din, a cu adev tre C++ s , i Java. In C++, executabilul nal este imaginea unui proces. Cu alte cuvinte, vet a propriu, din care , i avea acces la un spat , iu de memorie virtual putet n C). La fel ca n C, , i aloca/citi orice, conform cu nevoile aplicat , iei (ca putet , i accesa memoria e prin intermediul unu obiect, e prin numele variabilei care ocup a acea zon a de memorie, e prin intermediul unui pointer. Alocarea, eliberarea, precum s n ntregime responsabil, i accesul corect la memorie sunt itatea programatorului. Mai multe despre asta vet nt ,i , elege din ciclul de viat a al obiectelor. Mai mult, C++ pune la dispozit at , , ie s , i facilit , i mai avansate

INTRODUCERE

de acces la memorie: referint n spat , ele: sunt doar dubluri de nume pentru obiecte existente , iul de memorie, s a put at n Java: ele trebuie init , i funct , ioneaz , in diferit dec , ializate la instant a precum obiectul referent , iere, s , i se comport , iat. Nu le putet , i distruge n mod explicit, s a scap a de o parte din gres , i nici atribui, dar v , elile lucrurlui cu pointeri. iteratorii: iteratorii n C++ sunt pur s a , i simplu clase care abstractizeaz un pointer (permit operat nc arcarea , ii precum (*it) sau it++ prin supra operatorilor specici). Spre deosebire de Java, iteratorii sunt stronglytyped s a pot crea , i mai intuitiv de folosit, dar la fel ca s , i pointerii, v probleme dac a nu suntet , i atent , i. smart-pointerii: exist a doar n C++, deoarece n Java nu avet , i acces la memorie. Funct a ca nis a , ioneaz , te pointeri obis , nuit , i, cu ment , iunea c atribuirea se face prin transferul complet al obiectului referent , iat, pentru a evita memory leak-urile. Cel mai probabil nu vet and , i avea prea cur de a face cu ei. Deoarece gestiunea memoriei n C++ este foarte complicat a, s , i este probabil sursa num arul 1 de erori pentru programele scrise n acest limbaj, Java a venit cu o solut a: nu vi se permite accesul la memorie! Toate obiectele sunt , ie radical manipulate prin referint , e (mai apropiate ca funct , ionalitate de pointerii din C++ dec at de referint as a scalare (int, double), , ele de acolo). Se tolereaz , i tipuri de dat dar acestea nu pot integrate n colect , iile de date ale limbajului. Acest lucru poate uneori extrem de inconvenabil, pentru c a duce la scrierea de cod foarte inecient! De asemenea, Java v a descurajeaz a s a contat a al , i pe ciclul de viat , obiectelor, s a las a s a controlat , i nu v , i consumul de memorie (Garbage Collecterul este nedeterminist). Aparent, acest lucru este convenabil pentru programator, dar n realitate v a oblig a s a apelat , i in logica aplicat , iei cod explicit pentru eliberarea resurselor (s , iere, coenxiuni), ceea ce poate complica lucrurile. Totus a permite accesul la memorie are s , i, decizia Java de a nu v , i alte justic ari. Deoarece codul se execut a n mas a, memoria nu este mapat a , ina virtual ad literam n RAM, iar dac a se dorea accesul la memorie, atunci ar trebuit simulat acest lucru. De asemenea, dac a vi s-ar permis accesul la memorie, atunci at arii s , i putut compromite transparent , a serializ , i se pierdea din us , urint , a lim bajului de a scrie aplicat a, nalit at , ii network-oriented. In cele din urm , i diferite justic a decizii de implementare diferite!

CLASE

2
2.1

Clase
Declarat , ie

Cel mai important concept din OOP este conceptul de clas a. Desigur, toat a lumea care a programat n Java are deja o intuit a, dar nu , ie despre ce anume e o clas este corect s a confund am un limbaj cu paradigma pe care trebuie s a o exprime. Eu propun s a uit am o clip a de programare ca s a putem g andi mai elegant: Ce este n realitate o clas a? In principiu, cred c a vom cu tot a o clas a descrie o mult , ii de acord c , ime de lucruri care au ceva n comun. Asem an arile dintre dou a obiecte din aceeas ,i clas a pot s a e: structurale (oamenii au neuroni) comportamentale (oamenii g andesc) Cu alte cuvinte, dac a s a se ncadreaz a ntr-o clas a, , tii despre un obiect c atunci s a l descrii la nivel general. , tii s Haidet a ret a idee s a revenim la C++. In C++, o clas a , i s , inem acest , i s seamn an a mult cu un tip de date. Ca s a folosim o clas a, trebuie mai nt ai s a o descriem. Putem face asta n dou a moduri: folosind keyword-ul class sau folosind keyword-ul struct.
1 class Student{ 2 3 char * name; 4 double grade[3]; 5 6 void setMark(int subject, double newMark); 7 }; // ATENTIE! Nu uitati de ;. Aceasta este o greseala foarte frecventa! 8 9 // sau asa: 10 11 struct Student{ 12 13 char * name; 14 double grade[3]; 15 16 void setMark(int subject, double newMark); 17 }; // ATENTIE! Nu uitati de ;. Aceasta este o greseala foarte frecventa!

CLASE

2.2

Modicatorii de acces

Ca s n Java, membrii din interiorul unei clase, e ei date sau funct ,i , ii, pot avea modicatori de acces: public - Datele sau funct at din interioul c at s , iile se pot folosi at , i din afara clasei. private - Datele sau funct n interiorul clasei. Clasele , iile se pot folosi doar care o deriv a nu au acces la ele. protected - Datele sau funct n interiorul clasei curente , iile pot folosite s i al tuturor claselor care o deriv a . , ntre class s In C++, exist a o singur a diferent a , i struct: nivelul de acces , implicit, care este private - pentru clasele declarate cu class public - pentru clasele declarate cu struct In Java, accesul trebuie specicat pentru ecare membru n parte. In C++, este sucient s a scriem cuv antul cheie o singur a dat a urmat de doua puncte s ,i el r am ane valabil p an a la nt alnirea unui alt modicator de acces, sau p an a la nalul clasei. De exemplu, pentru clasele de mai sus, putem modica accesul la membri ad aug and doar put , in cod: GRES a vet ncerca s a scriet n Java, nainte , EALA FRECVENTA! Dac ,i , i ca de numele membrului f ar a : vet , i primi eroare de compilare!
1 class Student{ 2 3 char * name; //private (implicit pt class) 4 protected: 5 double grade[3]; //protected 6 public: 7 void setMark(int subject, double newMark); //public 8 9 }; 10 11 // sau de exemplu cu struct 12 13 struct Student{ 14 15 char * name; //public (implicit pt struct) 16 private: 17 18 19 20 }; double grade[3]; //private void setMark(int subject, double newMark); //also private

CLASE

2.3

Constructori

Atunci c and vrem s a descriem o clas a, un aspect important este s a denim [cel put a. Ca s n Java, constructorii nu re, in] un constructor pentru acea clas ,i turneaz a valori s , i au numele identic cu al clasei. De ret a dac a nu declar am un constructor pentru o clas a, atunci , inut este c compilatorul va considera din ociu un constructor care nu face nimic (nu are nici o instruct , iune). In exemplul anterior, ar bine s a ne putem asigura cumva n program c a la orice moment de timp, numele unui student este un pointer valid dac a este privit din afara clasei (pentru ca daca reus a nu o s a avem SegFault , im asta, niciodat folosindu-l). Solut a cre am o funct a o structur a, , ia din C era s , ie care init , ializeaz dar n C++ singurul mod corect de a face asta este s a declar am constructori. A folosi o funct a de init nsemna s a avem ncredere c a , ie separat , ializare ar oricine ar declara vreodata un obiect de tip Student va apela cons , tiincios funct nainte de a folosi obiectul. Asta e cea mai sigura cale catre ore n , ia s , ir de debugging.
1 class Student{ 2 //private: este implicit, pentru ca am folosit class 3 char * name; 4 double grade[3]; 5 public: 6 void setMark(int subject, double newMark); 7 8 Student(); // Constructor fara parametri 9 10 }; Student(char * name); // Stim numele dinainte

CLASE

10

2.4

Denire

Chiar dac a nu am amintit dec at pe scurt despre c ateva din conceptele relevante pentru declararea claselor n C++, relativ la Java acestea ar trebui s a e suciente pentru a avea o nt a a sintaxei. Vom trece n continuare , elegere elementar la denirea de clase. Ce este, totus a ea de declarare? Putem s a , i denirea unei clase s , i cum difer ne g andim astfel: Declararea unei clase reprezint a o descriere general a a acesteia. Compilatorul a a c a exist a tipul de dat a corespunz ator, s , tie ce metode sunt specice s a ncapsuleze clasa, cum este ea nrudit a , i ce variabile ar trebui s cu alte clase, dar nu s a. , tie nimic concret despre clas Denirea unei clase, pe de alt a parte, este procesul prin care se detaliaz a exact codul din metode care trebuie executat, dar s , i construirea membrilor statici n memorie. Un alt termen echivalent pentru denire este implementare. In Java, clasele erau organizate n pachete, iar metodele erau denite obligatoriu n acelas aceau parte. Acest mod de , i s , ier cu declarat , ia clasei din care f a scrie codul poate p area mai simplu din anumite puncte de vedere, dar este destul de inexibil, motiv pentru care n C++ este folosit doar n situat , ii speciale pe care le vom aminti mai t arziu. Abordarea corect a n C++ este de a separa declarat , ia s , i denit , ia unei clase n s , iere distincte: Headere (*.h, *.hpp), care cont , in numai declarat , ii Implement ari (*.cpp), care cont , in numai denit , ii Avantajul evident este c a putem nlocui rapid o implementare scurt a dar inecient a a unei clase cu una mai lung a dar mai optimizat a f ar a a trebui s a mai edit am codul, specic and doar alt s ier la compilare. Pe de alt a parte, , separ andu-le, putem evita erorile de denire multipl a atunci c and avem lant , uri mai complexe de includeri. Pentru a deni o metod a dintr-o clas a, trebuie s a prex am metoda cu numele clasei, urmat de operatorul de rezolut ie de scop (::). Facem acest lucru , pentru a informa compilatorul c a de fapt denim o metod a dintr-o clas a, s , i nu o funct ie global a cu acel nume. Un exemplu valoreaza c a t o mie de cuvinte. ,

CLASE

11

Pentru clasa urm atoare:


1 // Student.h 2 3 #pragma once 4 5 #ifndef STUDENT H 6 #define STUDENT H 7 8 class Student{ 9 //private: este implicit, pentru ca am folosit class 10 char * name; 11 double grade[3]; 12 public: 13 void setMark(int subject, double newMark); 14 15 Student(); // Constructor fara parametri 16 Student(char * name); // Stim numele dinainte 17 }; 18 19 #endif

Un exemplu de implementare ar putea urm atoarul:


1 // Student.cpp 2 3 #include <cstring> // Pentru strdup() 4 5 #include "Student.h" 6 7 // din clasa Student, denim functia Student() 8 Student::Student(){ 9 name = strdup("Name Unknown"); 10 } 11 12 // din clasa Student, denim Student(char*) 13 Student::Student(char * name){ 14 // ATENTIE! In C++, this este un pointer, nu o referinta! 15 this->name = strdup(name); 16 } 17 18 // din clasa Student, denim setMark(int, double) 19 void Student::setMark(int subject, double newMark){ 20 grade[subject] = newMark; 21 } 22

CLASE

12

In declarat a elemente noi, care nu sunt obli, ia de mai sus, at , i remarcat dou gatorii n orice situat ie, dar care au devenit standard practice n C++: , #pragma once - aceast a directiv a este specic a C++ s a com, i informeaz pilatorul s a nu includ a acelas ntr-o ierarhie de , i s , ier de mai multe ori clase. #ifndef ... #endif - aceast a directiv a este specic a C s ndeplines ,i , te acelas andou a din motive de compatibilitate cu , i rol. De obicei se pun am compilatoarele care nu nt a corect , eleg una din directive, dar o interpreteaz pe cealalt a. O alt a observat a se refer a la constructori. Practic n exemplul , ie important de mai sus, constructorii init a membrii claselor prin atribuiri. Acest mod , ializeaz de a scrie codul seam an a cu sintaxa din Java, dar nu funct a mereu. In , inoeaz mod corect, n C++, constructorii sunt nis a , te funct , ii speciale care se apeleaz automat nainte ca un obiect s a nceap a efectiv s a existe, deci trebuie invocat ,i corespunz ator. Acest lucru se specic a folosind operatorul : dupa antetul constructorului, urmat de o serie de construct a. Rescriind , ii separate de virgul s , ierul de mai sus, obt , inem:
1 // Student.cpp 2 3 #include <cstring> // Pentru strdup() 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include "Student.h" // invocarea corect a a constructorului pentru membrul name Student::Student() : name(strdup("Name Unknown")) { } // // // // invocarea corect a a constructorului pentru membrul name. Compilatorul s a fac a diferent ntre: , tie s ,a * membrii clasei, pe care se apeleaz a constructorii * parametri, care pot s a apar a doar n paranteze ca valori

Student::Student(char * name) : name(strdup(name)) { } // din clasa Student, denim setMark(int, double) void Student::setMark(int subject, double newMark){ grade[subject] = newMark;

22 23 } 24

CLASE

13

Nu n ultimul r and, C++ v a las a s a denit a n momentul declar arii , i o clas (ca n Java). Mai mult, acest stil de denire devine obligatoriu pentru anumite optimiz ari (funct , ii inline, respectiv headere precompilate). Acest mod de denire poart a tot numele de inline. Clasa de mai sus denit a inline ar ar ata astfel:
1 2 3 4 5 6 7 // Student.h #pragma once #ifndef #define STUDENT H STUDENT H

8 #include <cstring> 9 10 class Student{ 11 char * name; 12 double grade[3]; 13 public: 14 void setMark(int subject, double newMark) { 15 grade[subject] = newMark; 16 } 17 18 Student() : name(strdup("Name Unknown")) { } 19 20 }; 21 22 #endif Student(char * name) : name(strdup(name)) { }

ATENTIE! In C++ nu putet , i reapela alt constructor din interiorul unui constructor pentru un obiect (cum se nt ampla n Java, c and scriat , i this(0,0), de exemplu). De asemenea, nu exist a cuv antul cheie super, mecanismul de instant , iere pentru clasele derivate ind explicat mai jos, la capitolul aferent.

CLASE

14

2.5

Ciclul de viat a al obiectelor. Destructori. ,

Ciclul de viat a al obiectelor este probabil aspectul cel mai divergent al pro, gram arii orientate pe obiecte, as a n C++, respectiv , a cum este ea exprimat Java. In Java exist a mai multe tipuri de obiecte, unele cu scope explicit (de exemplu, scalarii: int, double sau referint n sine) s ar a scope explicit, , ele , i altele f care se creaz a cu operatorul new s aror spat , i a c , iu ocupat este reciclat de c atre garbage collecter n momentul n care nu mai sunt referite din nici un thread. In ciuda aparent n C++ gestionarea memoriei este mult mai simpl a , elor, conceptual (dezavantajul ind faptul c a o mare parte din ea cade n sarcina programatorului). In C++, orice variabil a este declarat a ntr-un scope. Exemple de scope-uri sunt: Spat , iul unei funct , ii/metode Spat , iul global de nume Interiorul blocurilor de program (cuprinse ntre { }) In momentul n care se iese din interiorul unui scope, trebuie eliberat a stiva. Pentru ecare obiect care este scos din stiv a, C++ apeleaz a automat o funct , ie special a care se numes a obiectului s , te destructor. Destructorul ofer , ansa de a elibera resursele pe care le t n cazul constructorilor, dac a nu , inea ocupate. Ca s ,i denim nici un destructor, C++ genereaz a din ociu un destructor vid pentru clasele pe care le scriem. C ateva situat n care ar putea nevoie de destructori sunt: , ii Atunci c and clasa cont , ine resurse pe care le-a alocat dinamic s , i care trebuie dezalocate. Atunci c and clasa a deschis s a le nchid a. , iere pe care trebuie s Atunci c and clasa a deschis socket a i nchid a. , i pe care trebuie s Atunci c and clasa det a le elibereze (nu vet , ine lock-uri pe care trebuie s ,i nt alni la materiile din anul 2). Destructorii se denesc asem an ator cu constructorii, doar c a numele lor trebuie precedat de caracterul tild as , i nu pot primi parametri! De asemenea, des ,i limbajul permite, n teorie nu este corect s a apelat a explicit destruc, i niciodat tori! Ei se apeleaza automat la ies , irea din scop a obiectelor, sau la dezalocarea memoriei alocate dinamic. Vom reveni din nou la exemplu pentru a explica mai bine conceptul:

CLASE

15

1 #include <iostream> 2 #include <cstring> 3 4 class Student{ 5 char * name; 6 double grade[3]; 7 public: 8 void setMark(int subject, double newMark) { 9 grade[subject] = newMark; 10 } 11 12 13 14 15 16 17 char * getName() { return name; } Student() : name(strdup("Name Unknown")) { } Student(char * name) : name(strdup(name)) { }

18 }; 19 20 void functie1(){ 21 // In declaratia de mai jos se apeleaza constructorul fara parametrii pentru a 22 // crea un obiect care este valabil incepand de aici pana la sfarsitul functiei. 23 Student student1; 24 25 std::cout << student1.getName() << "\n"; // Se aseaza Name Unknown 26 } 27 28 void functie2(Student student2){ 29 30 31 32 } 33 34 int 35 { 36 37 38 39 } // student2 este un obiect care se creaza in momentul apeului si este valabil // pana la sfarsitul functiei. std::cout << student2.getName() << "\n"; // Se aseaza Eric Cartman

main() functie1(); functie2(Student("Eric Carman")); return 0;

Pe parcursul execut and, dou a obiecte , iei acestui program, se vor construi, pe r de tip Student. Fiecare instant , iere se face cu apelarea constructorului pentru clasa Student, iar constructorii aloca memorie n care copie un s , ir de caractere care s a reprezinte numele studentului. Dar ce se nt ampl a cu aceast a memorie? Nu apare nic aieri nici apel c atre free. In realitate, aceast a memorie nu se elibereaz a odat a cu obiectele (se distruge doar pointerul, nu s , i memoria pe care

CLASE

16

o pointeaz a), ci produce leak. Pentru a rezolva aceast a problem a, vom declara n clas a un destructor care s a elibereze memoria atunci c and obiectele ies din scop. Programul corect este:
1 #include <iostream> 2 #include <cstring> 3 #include <cstdlib> 4 5 class Student{ 6 char * name; 7 double grade[3]; 8 public: 9 10 11 12 13 14 15 16 17 18 19 20 void setMark(int subject, double newMark) { grade[subject] = newMark; } char * getName() { return name; } Student() : name(strdup("Name Unknown")) { } Student(char * name) : name(strdup(name)) { } // Cand obiectul trebuie distrus, se executa codul din destructor!

21 Student() { free(name); } 22 }; 23 24 void functie1(){ 25 // In declaratia de mai jos se apeleaza constructorul fara parametrii. 26 Student student1; 27 std::cout << student1.getName() << "\n"; // Se aseaza Name Unknown 28 // AICI se apeleaza destructorul pentru student1. Variabila e locala functiei. 29 } 30 31 void functie2(Student student2){ 32 33 34 35 } 36 37 int 38 { 39 40 41 42 } // student2 este un obiect care se creaza in momentul apeului. std::cout << student2.getName() << "\n"; // Se aseaza Eric Cartman // AICI se apeleaza destructorul pentru student2. Parametrul e local functiei.

main() functie1(); functie2(Student("Eric Carman")); return 0;

CLASE

17

2.6

Membrii statici

In C++, membrii statici ai claselor sunt resurse pe care le au n comun toate obiectele clasei. La fel ca n Java, ei se declara folosind cuv antul cheie static n fat a asem an ator, , a membrului ce trebuie declarat (NU denit), s , i funct , ioneaz dar exist a c ateva deosebiri. Variabilele membre statice terbuie denite n s , ierele de implementare. Acest lucru este necesar deoarece prin simpla declarare, lor nu li se aloca efectiv memorie n segmentul de date! Spre deosebire de Java, nu este posibil a crearea unor blocuri statice de init ializare. Cel mai bine este s a considerat a la nceputul execut , , i c , iei programului, variabilele statice au valori nedenite (sau, n anumite compilatoare, initializate cu 0). Variabilele statice de tip clas a sunt construite automat folosind un constructor f ar a parametri, dac a nu se specic a altfel n cod. Funct a au permisiunile corecte, pot folosite ca orice alte , iile statice, dac funct ii globale, s i se pot deduce pointeri din ele astfel nc at s a poat a , , date ca parametri n locul funct iilor globale. Pentru funct iile membre , , nonstatice, acest lucru NU este valabil din cauza ncapsul arii. Aceast a problem a nu ap area n Java oricum, deoarece acolo nu putem avea pointeri la funct , ii. Pentru a apela o funct a, trebuie s a i specic am s , ie static , i spat , iul de nume (prex and apelul cu numele clasei, urmat de operatorul ::). Urm atorul exemplu ilustreaz a folosirea de membrii statici.
1 #include <iostream> 2 #include <cstring> 3 #include <cstdlib> 4 5 class Student{ 6 char * name; 7 8 // Declaram un obiect static 9 static int instanceCount; 10 public: 11 // Declaram o functie statica 12 static void printInstanceCount() { 13 std::cout << instanceCount << "\n"; 14 15 16 17 18 19 } Student() : name(strdup("Name Unknown")) { instanceCount++; }

CLASE

18

20 21 22 23 24

Student(char * name) : name(strdup(name)) { instanceCount++; } Student() {

25 free(name); 26 instanceCount--; 27 } 28 }; 29 30 // DEFINIM zic obiectul instanceCount (i se va aloca memorie) 31 32 33 34 35 36 // si il initializam explicit cu 0 la lansarea in executie int Student::instanceCount = 0; void functie() { Student s2;

37 Student::printInstanceCount(); // Se aseaza 2: s1 si s2 38 // AICI se apeleaza destructorul lui s2, care decrementeaza instanceCount 39 } 40 41 int main() 42 { 43 44 45 46 47 48 49 } 50 Student s1; Student::printInstanceCount(); // Se aseaza 1: s1 functie(); Student::printInstanceCount(); // Se aseaza 1: s1 return 0; // AICI se apeleaza destructorul lui s1, care decrementeaza instanceCount

CLASE

19

2.7

Derivarea claselor

Printre cele mai importante avantaje ale program arii orientate pe obiecte l reprezint a mecanismul de mos , tenire. Din nou, acesta este un capitol la care exist a diferente notabile ntre C++ s , i Java. Paradigma impus a de Java este aceea c a Totul este un obiect. Prin urmare, absolut orice clas a pe care am scrie-o deriv a (sau folosind terminologia Java, extinde) din clasa Object. Mai mult, n Java o clas a nu poate extinde dec at o singur a alt a clas a. Cu alte cuvinte, dac a avem o clas a care extinde java.util.Vector, atunci ea nu poate extinde nici o alt a clas a. Din cauza c a asta ar limitat limbajul foarte tare, cei care au proiectat Java au permis doar un singur tip de mos a: mos ao , tenire multipl , tenirea de interfet , e. Diferent , a este c interfat a cuprinde doar funct area conicte de instant , , ii s , i de aceea nu pot ap , iere multipl a a membrilor de date. Pe de alt a parte, n C++ nu exist a nici o restrict , ie din acest punct de vedere, iar lucrurile stau mult mai complicat (des a avet , i este extrem de improbabil s ,i de-a face cu altceva dec at derivare de tip direct s , i public). Pentru a deriva o clas a, este sucient s a ns , iruit , i clasele pe care aceasta le extinde dup a numele clasei, cu virgul a ntre ele. Numele claselor de baz a (p arinte) trebuie precedat cu tipul de derivare. In acest sens, derivarea public a este aproape universal a. O except a este c a n C++, nu exist a o clas a de baz a din care s a , ie notabil derive toate celelalte clase (echivalent a cu Object din Java)! Unul dintre motivele pentru care n Java exist a clasa Object este tratarea uniform a a tipurilor, dar n C++ exist a pentru acest scop pointerii generici (void*). Un exemplu de sintaxa prin care se specica derivarea de clase reg asim n continuare:
1 #include <iostream> 2 #include <cstring> 3 #include <cstdlib> 4 5 class Om { 6 protected: 7 char * nume; 8 public: 9 Om(const char * nume) : nume(strdup(nume)) { } 10 virtual Om() { free(nume); } 11 }; 12 13 class Student : public Om { 14 double nota;

CLASE

20

15 public: 16 Student(const char * nume, double nota) : Om(nume), nota(nota) { } 17 void display() { 18 std::cout << "(" << nume << "," << nota << ")\n"; 19 } 20 }; 21 22 int main() 23 { 24 Student s("Eric Cartman", 10.0f); 25 s.display(); 26 27 } return 0;

In exemplul de mai sus, exist a trei elemente de sintax a care trebuie explicate: Apelarea constructorului clasei de baza in constructorul clasei Student, pe linia 16. Un Student este, n primul r and, un caz particular de Om, deci nainte ca obiectul Student s a poat a explicit construit, trebuie s a apel am constructorul clasei de baz a folosind sintaxa specic a constructorilor. Acest lucru este echivalent cu apelurile c atre apelurile la constructori de tipul super() din Java. Se remarc a de asemenea c a a trebuit s a declar am numele ca ind protected, pentru ca funct a aib a acces la el. , ia display() din clasa Student s Nu n ultimul r and, destructorul din clasa de baz a este declarat ca ind virtual. In acest caz, acel cuv ant cheie nu produce diferent n cazul , e, dar general este extrem de recomandat ca destructorii claselor s a e declarat ,i ca virtuali. Vom discuta acest lucru la virtualizare. Atunci c and o clas a deriveaz a alt a clas a, aceasta mos , tenes , te din clasa de baz a tot i membrii, mai put in membrii statici. Trebuie de t inut minte c a mem, , , brii statici nu particip a la mecanismul de mos tenire, deoarece acest lucru nu , are sens (ei sunt partajat i doar ntre obiectele unei anumite clase, dar nu se , pot replica pentru clasele derivate). Setarea drepturilor de acces astfel nc at clasele derivate s a aib a acces la ei creaz a uneori o fals a impresie de mos , tenire, dar aceasta este o gres eal a de exprimare des nt a lnit a la programatori. , Un alt lucru care trebuie ret a n C++, supradenirea unei metode , inut este c dintr-o clas a de baz a duce cu sine la pierderea tuturor metodelor cu acelas ,i nume mos a, indiferent de semn atura parametrilor. Metodele , tenite din clasa de baz din clasele de baz a se pot apela prin specicarea namespace-ului corect (numele clasei, urmat de operatorul ::). Ca s a n C++ derivarea poate s a e s , i curiozitate, ar trebui ment , ionat c , i de tip protected/private (sunt rare situat and as arat necesar , iile c , a ceva este cu adev n cod), dar s a (pentru solut n romb). , i virtual , ionarea problemei mos , tenirii

CLASE

21

2.8

Polimorsm. Funct , ii virtuale

Polimorsmul n C++ este la nceput o surs a continu a de confuzie pentru programatorii Java. In Java, un singur comportament este posibil: toate funct , iile sunt implicit polimorce. Cu alte cuvinte, dac a o clas a deriveaz a o clas a de baz a s a, atunci n toate situat n care folosim o referint a la , i suradenes , te o metod , iile , obiecte derivate, se va apela noua metod a. In realitate, acest comportament impune s at , inem evident , a tipului real al tuturor obiectelor instant ncetines , iate, s ,i , te foarte mult toate apelurile de funct , ie din nevoie ntret , inerii unei tabele de virtualizare. Prin urmare, n C++ virtualizarea nu se face automat. Ea trebuie specicat a n mod explicit cu ajutorul cuv antului cheie virtual n declarat , ie (NU denit , ie). Odat a denit a ca ind virtual a o metod a dintr-o clas a r am ane virtual a s n ,i clasele derivate, chiar dac a nu mai specic am acest lucru n noile declarat , ii. Cel mai clar este s a folosim un exemplu care s a justice efectele lipsei virtualiz arii:
1 #include <iostream> 2 #include <cstring> 3 #include <cstdlib> 4 5 class Animal { 6 protected: 7 char * name; 8 public: 9 void makeSound() { 10 std::cout << "No sound\n"; 11 } 12 Animal(const char * name) : name(strdup(name)) { } 13 virtual Animal() { free(name); } 14 }; 15 16 class Bird : public Animal { 17 public: 18 void makeSound() { 19 std::cout << "Cra-Cra\n"; 20 } 21 Bird(const char * name) : Animal(name) { } 22 }; 23 24 class Dog : public Animal { 25 public: 26 void makeSound() { 27 std::cout << "Whoof-Whoof\n";

CLASE

22

28 } 29 Dog(const char * name) : Animal(name) { } 30 }; 31 32 void forceToSpeak(Animal * animal){ 33 34 35 36 37 } 38 // Se va asa mereu No sound, deoarece compilatorul nu poate decide // tipul real al obiectului la care se refera pointerul animal daca nu // specicam noi sa tina evidenta printr-o tabela de virtualizare animal->makeSound();

39 int main() 40 { 41 Bird bird("Tweetie"); 42 Dog dog("Pluto"); 43 std::cout << "// Apeluri directe: \n"; 44 bird.makeSound(); 45 46 47 48 49 50 } 51 dog.makeSound(); std::cout << "// Apeluri prin pointeri: \n"; forceToSpeak(&bird); forceToSpeak(&dog); return 0;

In exemplul de mai sus, putem fort at a evident , a compilatorul s , in , a tipului real folosind cuv antul cheie virtual n fat , a metodei makeSound(). De asemenea, din moment ce nu vom avea niciodat a o instat a de Animal n program, , dup a ce facem metoda makeSound virtual a, putem s a i scoatem implementarea a f ar a default s a o facem pur virtual a. O metod a pur virtual a este o metod , i s implementare, ceea ce face clasa respectiv a s a e abstract a. Exact ca s n ,i Java, o clas a abstract a nu se poate instat a s a e pur , ia. Putem face o metod virtual a specic and n cadrul declarat a nu i se va atas , iei c , a nici un pointer de funct a modic am , ie (este o funct , ie cu pointerul NULL). Astfel, este sucient s urm atoarele n clasa Animal:
1 class Animal { 2 protected: 3 char * name; 4 public: 5 // Am declarat metoda makeSound ca ind virtuala si am specicat 6 // ca aceasta clasa nu o poate implementa folosind = 0; 7 8 9 10 11 }; // Acest lucru face din Animal o clasa abstracta. virtual void makeSound() = 0; Animal(const char * name) : name(strdup(name)) { } virtual Animal() { free(name); }

CLASE

23

Este recomandat ca destructorii claselor s a e mereu declarat , i ca virtuali, deoarece altfel este posibil ca atunci c and vrem s a distrugem un obiect referent a nu se apeleze destructorul corect, ci destructorul , iat de un pointer s unui p arinte al clasei, produc and astfel comportamente neas n program. , teptate

CLASE

24

2.9

Supra nc arcarea operatorilor

Un motiv pentru care C++ este mai expresiv s at , i produce cod mai clar dec Java l reprezint a posibilitatea de a supra nc arca operatori. Supra nc arcarea operatorilor se refer a la interpretarea semnicat n funct , iei lor , ie de tipurile operanzilor. Aceast a facilitate este foarte puternic a n C++, permit and im, plementarea de c atre utilizatori a grupurilor algebrice direct n limbaj! Des ,i supra nc arcarea operatorilor nu necesit a mecanisme de parsare foarte complicate, ea nu a fost implementat a mai departe n Java din motive de claritate a codului nal. Supra nc arcarea operatorilor exista totus a rudi, i intr-o form mentar as n C, unde operatorul plus, de exemplu, are semnicat a n ,i , ie diferit funct , ie de tipul operanzilor: (int)(1)+(int)(2) == (int)(3), pe c and (int*)1+(int)2 == (int*)(1+sizeof(int)*2) In C++, se poate supra nc arca oricare dintre operatorii existent , i ai limbajului: unari ( , !, ++, etc.) sau binari (+, -, +=, -=, ==, <, etc.). Prin supra nc arcare, vom deni o funct a e invocat a automat atunci c and , ie care s nt alnim la st anga s atoare. Din , i [eventual] la dreapta tipurile de date corespunz motive de consistent a a limbajului, nu se pot redeni operatori care exist a deja , bine denit ntre dou a numere ntregi va nsemna , i, de exemplu operatorul + mereu numai adunare. Ceea ce putem redeni ns a sunt operatorii pe tipuri de date denite de utilizator. Cel mai des supra nc arcat , i operatori sunt operatorii mai mic s ,i atribuire. Trebuie ret a supra nc arcarea operatorilor nu afecteaz a pri, inut c orit at and dorim o ordine anume de aplicare a operat , ile lor. Atunci c , iilor, este indicat s a folosim paranteze pentru sigurant a. O pereche de paranteze se tasteaz a , n sub o secund a, dar lipsa lor poate provoca ore ntregi de debugging. Operatorii n C++ seam an a din punct de vedere sintactic (s , i semantic) cu nis te funct ii. Ei pot denit i la nivel global, sau pot ncapsulat n clase. , , , ,i Cu toate acestea, a doua practic a nu este ncurajat a deoarece ascunde nis , te probleme de implementare (deoarece operandul din st anga este implicit obiectul referit de this). Pentru a exemplica sintaxa prin care se pot supra nc arca operatorii n C++, ne vom folosi de un exemplu matematic foarte simplu: vom deni o clas a Complex care s a ret in a numere complexe s i vom implementa operat iile cu numere , , , complexe folosind operatorii nativi din limbaj, n loc de funct , ii.
1 #include <iostream> 2 3 class Complex{ 4 public: 5 // Membrii de date

CLASE

25

6 7 8 9 10 };

double real, imag; // Constructori Complex(double real, double imag) : real(real), imag(imag) { } Complex() : real(0), imag(0) { }

11 12 // Spunem ce executa + intre doua obiecte Complex 13 Complex operator+ (Complex& left, Complex& right){ 14 return Complex(left.real+right.real, left.imag+right.imag); 15 } 16 17 18 19 20 21 22 // Spunem ce executa + intre un obiect Complex si un obiect int Complex operator+ (Complex& left, int right){ return Complex(left.real+right, left.imag); } // Spunem ce executa += intre doua obiecte Complex

23 Complex& operator+= (Complex& left, Complex& right){ 24 left.real += right.real; 25 left.imag += right.imag; 26 return left; 27 } 28 29 // Spunem ce executa << intre un obiect ostream si un obiect Complex 30 std::ostream& operator<< (std::ostream& out, Complex right){ 31 out << "(" << right.real << "," << right.imag << ")"; 32 return out; 33 } 34 35 int main() 36 { 37 Complex a(1,1), b(-1,2); 38 std::cout << "A: " << a << "\nB: " << b; 39 std::cout << "\nA+B: " << (a+b) << "\n"; 40 } 41

Atunci c and scriem operatori este bine s a ncerc am s a i privim mai degrab a ca pe nis a din exemplul de , te aplicat , ii matematice pentru a nu gres , i. Se observ mai sus c a operatorii += s ntorc referint atre obiectul din st anga. V a , i << , e c vet ntreba de ce acest lucru nu este redundant. ,i R aspunsul se a a n modul n care compilatorul interpreteaz a codul. Noi scriem std::cout << a << b; s a execute pe r and, , i ne-am as , tepta ca programul s codul de as , are pentru obiectul a s , i apoi pentru obiectul b pe ecran. In realitate ns a, ceea ce se execut a arat a mai degrab a n felul urm ator (t and cont c a or, in

CLASE

26

dinea de evaluare este de la st anga la dreapta): operator<<( operator<<(std::cout, a), b). Dac a operatorul << nu ar returnat mai departe referint atre , a c obiectul de tip ostream n care se face as area, atunci nu am putut mbrica , apelurile c atre operatori, sau cu alte cuvinte nu am putut nl ant , ui operatorii. Inl ant a atunci c and scriem operatori pentru , uirea este mai ales relevant expresii matematice, deoarece este un caz particular destul de rar ca o expresie matematic a s a cont a un singur operator. De altfel, atunci c and s-au studiat , in operatori la algebr a, aces n , ti operatori erau de fapt funct , ii care luau valori mult a algebra. , imea peste care era denit Dintre tot nc arcat , i operatorii, doi sunt mai des supra ,i s , i pun probleme mai serioase programatorilor: Operatorul =, numit s , i assigment operator Operatorul < Operatorul de atribuire se sintetizeaz a din ociu de c atre compilator dac a nu l supra nc arcat a , i voi explicit. Operatorul din ociu pur s , i simplu realizeaz atriburiea ntre tot a (proces numit shallow copy). Acest , i membrii din clas lucru devine problematic atunci c and avet n clase, deoarece riscat , i pointeri ,i memory leak-uri la ecare atribuire. In acest caz, va trebui s a implementat ,i voi explicit atribuirea. Un am anunt destul de important este de asemenea c a operatorul de atribuire nu trebuie confundat cu copy constructor-ul unei clase, chiar dac a se pot nota am andoi cu simbolul =. Vom discuta despre copy constructor la momentul potrivit. De asemenea, sunt situat n care nu vi se poate , ii sintetiza operatorul de atribuire pentru o clas a, deocare clasa cont , ine referint ,e ca membrii (iar referint n C++ nu li se pot atribui valori dup a instant , elor , iere). Operatorul < este important mai ales pentru c a el este cerut implicit de anumite containere din biblioteca standard de template-uri. El are rol similar cu funct a n C++ vericarea , ia din interfat , a Comparable din Java, doar c existent Cu alte cuvinte, dac a , ei operatorilor corect , i se face abia la linking. folosit a de date sortat a (de exemplu, un map n care cheile sunt , i o structur obiecte denite de voi), va trebui n mod obligatoriu s a denit , i operatorul < pentru chei, altfel nu vet atura , i putea compila codul. Pentru a respecta semn impus a de containerele din C++, este de asemenea necesar ca operatorul < s a primeasc a argumente de tip const, pentru c a altfel nu se garanteaz a corectitudinea algoritmului de sortare. Lipsa specic arii parametrilor de tip const va duce la erori de compilare. Un alt operator interesant este operatorul funct , ional (), care permite obiectelor s a e apelate ca funct a numele de functori s , ii. Aceste obiecte poart , i nu au nici un concept echivalent n Java. Functorii pot face anumite programe greu de nt , eles, dar sunt interesant , i din punct de vedere al logicii limbajului, deoarece

CLASE

27

s ntre clase s , terg practic granit ,a , i funct , ii. In nal, ar trebui amintit c a o serie dintre coding guide-urile consacrate (de exemplu, cel al Google sau cel al Mozilla) sf atuiesc vehement mpotriva supra nc arc arii operatorilor limbajului, doarece semnicat , ia acestora poate deveni ambigu a foarte repede. Except n principiu operatorii de atribuire s , ie fac ,i cei logici (declarat antul cheie const imediat dup a lista de argumente. , i cu cuv Aceast a sintax a restrict a implementarea s a nu produc a efecte laterale (un , ioneaz concept despre vet a). V a vet , i mai auzi mai ales la programarea funct , ional ,i convinge repede c a abuzul de facilit at a nu suntet , ile C++ poate conduce dac ,i atent ntret , i la cod imposibil de , inut.

INPUT/OUTPUT

28

3
3.1

Input/Output
Fis , iere s , i stream-uri

In C, metoda cea mai folosit a de I/O era folosirea de s , iere. Pentru a simplica lucrul cu s iere, C-ul denea o structur a wrapper peste descriptorii de s , , ier pe care s a o folosim (structura FILE). In C++, ns a, s-a dorit abstractizarea lucrului cu resursele de I/O. Astfel, des n continuare folosi funct , i se pot , iile s ,i structurile din C, C++ pune la dispozit ie stream-uri. , Stream-urile din C++ seam an a conceptual (s , i de fapt, constituie sursa de inspirat ie) pentru cele din Java, dar exist a c a teva diferent a. , , e care le separ Stream-urile din biblioteca standard deriv a toate e din clasa ostream (pentru uxuri de ies , ire), e din clasa istream (pentru uxuri de intrare). Este posibil lucrul cu s , iere instant , iind obiecte e din clasa ofstream (pentru s , iere de ies , ire), e din clasa ifstream (pentru s , iere de intrare). Des a spre deosebire , i operat , iile din C++ pot arunca except , ii, vet , i observa c de Java, C++ nu v a oblig a s a tratat , i except , iile. Acest lucru poate s ,i bun s au. Dac a aleget a nu folosit , i r , i s , i except , iile de la streamuri, ele vor ignorate s a se execute (eventual) defectuos. Pentru , i codul va continua s compatibilitate, C++ seteaz a s , i o serie de ag-uri care sunt mos , tenite pentru toate stream-urile din clasa p arinte comun a ios. Cele mai folosite a n funct ag-uri sunt: eof, fail s , i bad. Semnicat , iile lor variaz , ie de tipul de stream folosit s a vericat , i este indicat s , i documentat , ia, mai ales la nceput. Pentru cele trei s , iere clasice care sunt deschise automat pentru orice program care se execut a, C++ denes , te trei stream-uri cu nume consacrate: std::cin - stream-ul denit peste stdin std::cout - stream-ul denit peste stdout std::cerr - stream-ul denit peste stderr Implicit, toate operat n C++ sunt buer-ate (acest lu, iile cu stream-uri cru se poate dezactiva cu ajutorul unei funct , ii handler). Din acest motiv, este a folosit foarte gres , it s , i simultan pentru as , are sau citire s , i stream-uri s , i s , iere, deoarece vi se vor nc aleca rezultatele s , i efectul nal nu va cel dorit. As n/din stream-uri se face n mod convent , area s , i citirea , ional cu operatorii << pentru scriere s , i >> pentru citire.

INPUT/OUTPUT

29

Nu n ultimul r and, trebuie ret a obiectele de tip stream sunt f acute , inut c n mod intent ionat neasignabile n C++ (a fost omis a intent ionat denirea op, , eratorului de atribuire). Motivul pentru care nu se pot atribui stream-uri este riscul duplic arii buerului. Exist a metode prin care putet , i manipula buerele, dar este improbabil s a avet arat de lucrat cu as , i cu adev , a ceva (v-ar putea folosi doar la redirect ari fort , ion , ate, dar putet , i rezolva altfel, folosind pointeri). In urm atorul exemplu de cod, deschidem dou a s , iere, unul de intrare s , i unul de ies am c ateva operat , ire, s , i efectu , ii elementare de I/O.
1 #include <iostream> 2 #include <fstream> 3 4 int main() 5 { 6 // Instantiem si deschidem simultan doua stream-uri: 7 // * in, pentru citire 8 // * out, pentru scriere 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 } std::ifstream in("input.txt"); std::ofstream out("output.txt"); // Vericam daca s-a deschis out cu succes if (out.fail()) { std::cerr << "ERROR: Nu s-a putut deschide fisierul \"output.txt\"\n"; return 0; } int a, b; // Citim doi intregi din in in >> a >> b; // Inchidem in in.close(); // Atasam lui in alt sier de intrare // (pentru siere binare, folosim ifstream::bin) in.open("other input.txt", std::ifstream::in); // Scriem a si b in out, cu niste text suplimentar out << "A este: " << a << " iar B este: " << b << "\n"; // Inchidem toate sierele in.close(); out.close(); return 0;

INPUT/OUTPUT

30

38 39

Un ultim aspect care ar trebui tratat aici s a avet , i de care s-ar putea s , i nevoie la proiect este serializarea. In C++, mecanismul de serializare/deserializare nu se face automat ca n Java, deci va trebui s a folosit a , i stream-uri binare s , i s supra nc arcat , i voi operatorii <<, respectiv >> conform cu protocolul pe care vret a l implementat , i s , i.

S , IRURI DE CARACTERE

31

4
4.1

S , iruri de caractere
Clasele std::string s , i std::stringstream

C++ pune la dispozit a nativ a pentru lucrul cu s , ie o clas , iruri de caractere, s ,i anume clasa string. Aceast a clas a exist a, desigur s n Java, unde funct a ,i , ioneaz aproximativ similar. Pentru clasa string sunt supra nc arcat ativa operatori , i c mai intuitivi: Indexare: [] - permite accesul la caracterul de pe o anumit a pozit , ie Adunare: += - permite ad augarea unui s ars , ir/caracter la sf , itul unui string Egalitate: == - funct a similar cu funct , ioneaz , ia strcmp() Atribuire: = - permite copierea string-urilor Avantajul evident la string-uri este faptul c a acestea pot folosite mult mai intuitiv dec at s at , irurile de caractere, s , i nu mai este nevoie s , inem cont de terminatorii de s , ir sau de problemele de realocare de memorie, deoarece acestea sunt tratate automat n interiorul claselor. De asemenea, folosind operatorii supra nc arcat a mai aerisit s , i, codul arat , i mai intuitiv, ceea ce face implementarea mai rapid as a. , i mai elegant Spre deosebire de Java ns a, C++ nu are suport nativ pentru caracterele Unicode sau UTF-8. Toate caracterele sunt ret n mod ASCII. Exist a separat , inut tipul de date w char pentru tipuri de caractere wide (pe doi Bytes), iar biblioteca standard pune la dispozit , ie inclusiv macro-uri pentru cast-ul implicit, dar gres eli se pot face frecvent. Mai mult, o serie de clase template accept a , alocatori ca parametru n constructori, ceea ce nseamn a c a de fapt ele pot funct a denim dac a scriem codul , iona cu orice tip de caractere am vrea noi s necesar manipul arii tipului de date. Ca s i n alte situat ii, C++ are un pret , , , de pl atit pentru libertatea de alegere pe care o ofer a programatorului: mai mult a b ataie de cap! Cu toate acestea, este extrem de improbabil s a avet , i nevoie de caractere Unicode n aplicat iile de algoritmic a , deci nu trebuie s a v a facet , , i griji cu privire la encoding, ci putet i s a contat i pe faptul c a este mereu ASCII. , , Clasa stringstream este folosit a n C++ pentru a stoca un buer de caractere. De obicei ea este util a atunci c and vrem s a gener am stringuri prin as ari , repetate din diferite obiecte. In Java, puteam face conversia automat folosind o construct n C++ acest cod nu are efectul as , ie de tipul (+object), dar , teptat. Pentru serializare trebuie s a folosim operatorul << pentru a printa informat , iile ntr-un stringstream, s i apoi putem folosi funct ia membru str() f a r a parametrii , , pentru a recupera s a trimitem un parametru n , irul generat din stream. Dac membrul str(), acesta are efectul de a nlocui stringul din stream.

S , IRURI DE CARACTERE In continuare avem un exemplu orientativ de folosire al acestor clase:


1 2 3 4 5 #include #include #include #include <iostream> <string> // Clasa string <cstring> // Functiile pe stringuri din C <sstream>

32

6 int main() 7 { 8 // Instantiem un string: 9 std::string s("Ana are mere"); 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 } // Adaugam la sfarsit un sux: s += ". Eric Cartman "; // Daca caracterul de pe pozitia 3 este spatiu, asam lungimea sirului si sirul if (s[3] == ) { std::cout << "Lungimea " << s.length() << ": " << s << "\n"; } // Obtinem continutul din s sub forma de string n stil C // c str() intoarce un pointer la o zona statica de memorie, deci daca vrem sa // pastram string-ul, trebuie sa folosim strdup() ca sa il clonam char * ps = strdup(s.c str()); char * toFree = ps; // Cream un stream dintr-un string std::stringstream ss; // Impartim ps in cuvinte si le scriem in ss, separate prin underscore ps = strtok(ps," "); while (ps){ ss << ps << " "; ps = strtok(NULL, " "); } // Asam continutul din ss pe ecran std::string rep = ss.str(); std::cout << "Lungimea " << rep.length() << ": " << rep << "\n"; // Distrugem ps; delete[] toFree; return 0;

GESTIUNEA MEMORIEI

33

5
5.1

Gestiunea memoriei
Alocarea memoriei: new vs. malloc

In C, alocarea memoriei se f acea n mod obis , nuit prin intermediul funct , iei malloc (sau alternativ se puteau folosi s , i calloc sau pentru s , iruri de caractere, strdup). Des astrate aproape integral n , i funct , iile din bibliotecile de C sunt p C++, folosirea lui malloc nu este sucient a pentru alocarea de noi obiecte, n acest scop denindu-se operatorul new (care a fost implementat s n Java mai ,i apoi). Atunci c and folosim operatorul new, se execut a n spate dou a lucruri distincte: Se aloc a memorie sucient a pentru a stoca obiectele cerute, conform cu spat a , iul ocupat de clas Se invoc a automat constructorii pentru obiectele nou create Valoarea returnat a de operator este un pointer c atre zona de memorie care cont , ine obiectul sau obiectele nou alocate. Deja ar trebui s a e evident motivul pentru care un simplu apel la malloc nu este sucient pentru a crea un obiect dintr-o clas a: chiar dac a se aloc a memorie sucient a, problema este c a nu este invocat constructorul pentru obiectele respective. Dac a vom lua drept exemplu un obiect de tipul std::vector pe care ncerc am s a l aloc am dinamic cu malloc, n realitate noi nu facem dec at s a rezerv am memorie pentru un obiect de tipul std::vector, dar n acel spat iu nu se va , construi efectiv un astfel de obiect, ci vom avea valori neinit ializate. Incercarea , ulterioar a de a insera un element n acea colect , ie va es , ua aproape sigur, din diferite motive posibile: pointerii interni clasei nu au la r andul lor memorie alocat a, deoarece rezervarea de memorie s-ar f acut n constructor variabilele care t a a obiectului nu au valori consis, in de starea intern tente (de exemplu, capacitatea maxim a rezervat a, num arul de elemente din colect ie, etc.) , a corect a de a aloca dinamic un obiect sau un array de tip Singura metod structural este prin intermediul operatorului new. Se pune atunci, evident, ntrebarea de ce au mai fost p astrate funct , iile de alocare de memorie din C. R aspunsul nu t ine doar de compatibilitate. Funct , , iile din C de alocare de memorie se pot folosi n continuare pentru aloc ari de buere binare sau de obiecte care nu au constructori explicit a oricum nevoie de a exe, i (acolo unde nu exist cuta cod n momentul instant ierii). As a cum v-at i obis a d a , , , , nuit deja, limbajul v

GESTIUNEA MEMORIEI

34

libertatea s a folosit a constr ange n nici un fel, dar scriet , i ce vret ,i s , i nu v , i totul pe propria r aspundere. In continuare am dat ca exemplu c ateva exemple de folosire a operatorului new.
1 #include <iostream> 2 3 class Complex{ 4 public: 5 double real, imag; 6 Complex(double real, double imag) : real(real), imag(imag) { } 7 Complex() : real(0), imag(0) { } 8 }; 9 10 std::ostream& operator<< (std::ostream& out, Complex& right){ 11 return out << "(" << right.real << "," << right.imag << ")\n"; 12 } 13 14 int main() 15 { 16 // Alocam un intreg dinamic si citim o valoare de la tastatur a 17 int * n = new int; 18 std::cin >> (*n); 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 } // Cream un vector de n numere complexe // Pentru toate elementele din array vom invoca constructorul // fara parametri! Complex* v = new Complex[(*n)]; // Citim n numere Complexe si le asam apoi int real, imag; for (int i = 0; i < (*n); i++){ std::cin >> real >> imag; v[i] = Complex(real,imag); } for (int i = 0; i < (*n); i++){ std::cout << v[i]; } // Nu dezalocam memoria inca, vom trata acest lucru in capitolul urmator. return 0;

GESTIUNEA MEMORIEI

35

5.2

Dezalocarea memoriei: free vs. delete

Dezalocarea memoriei alocate dinamic n C se f acea prin intermediul funct , iei free. As atre free nu este sucient pentru a , a cum deja intuit , i, simplul apel c dezaloca memoria n C++, din motive similare cu motivele pe care le-am citat la alocarea de memorie. Pentru a dezaloca memorie, n C++ exist a operatorul delete, care primes a de memorie alocat a , te ca operand un pointer catre o zon dinamic. Diferent a dezalocarea unui obiect cu free , a dintre free s , i delete este aceea c nu face dec at s a elibereze spat n sine s at, n timp ce , iul ocupat de obiectul , i at dezalocarea cu delete invoc a n mod automat s , i destructorul obiectului. Acest lucru este relevant, de exemplu, dac a n interiorul clasei exist a pointeri la memorie alocat a dinamic care trebuie de asemenea distrus as a , i care ar leak-uit dac foloseam free s , i nu se apela destructorul. Din aceleas n sect a, free nu este nicidecum o , i rat , iuni ca s ,i , iunea precedent funct n alte aspecte ale limbajului. Ca regul a , ie obsolete, ci are aplicativitate general a: tot ceea ce este alocat cu new trebuie dezalocat cu delete tot ceea ce este alocat cu malloc (s , i alte funct , ii similare din C) trebuie dezalocat cu free De remarcat aici c a atunci c and vrem s a dezaloc am un array s , i nu un singur obiect, sintaxa corect a este delete[]. In continuare, ad aug and codul pentru dezalocarea de memorie la exemplul de la punctul precedent, se obt , ine:
1 #include <iostream> 2 3 class Complex{ 4 public: 5 double real, imag; 6 Complex(double real, double imag) : real(real), imag(imag) { } 7 Complex() : real(0), imag(0) { } 8 }; 9 10 std::ostream& operator<< (std::ostream& out, Complex& right){ 11 return out << "(" << right.real << "," << right.imag << ")\n"; 12 } 13 14 int main() 15 { 16 int * n = new int; 17 std::cin >> (*n); 18 Complex* v = new Complex[(*n)]; 19

GESTIUNEA MEMORIEI

36

20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 }

int real, imag; for (int i = 0; i < (*n); i++){ std::cin >> real >> imag; v[i] = Complex(real,imag); } for (int i = 0; i < (*n); i++){ std::cout << v[i]; } // Asa se dezaloca un singur obiect (avem nevoie de pointerul sau) delete n; // Asa se dezaloca un array (avem nevoie de pointerul primului element) delete[] v; return 0;

STANDARD TEMPLATE LIBRARY

37

6
6.1

Standard Template Library


Introducere: de ce STL?

Standard Template Library (sau pe scurt, STL) este o bibliotec a generic a de C++, part a n C++ Standard Library. Ea reprezint a una din , ial inclus modalit at ari de template, ile de a folosi cod gata implementat, s , i cont , ine implement uri pentru o serie de structuri de date s ap anire , i algoritmi mai uzuali. Buna st a not a va ajuta s a dezvoltat ari la fel , iunilor legate de STL v , i rapid implement de scurte s at cele din Java. , i mult mai eciente dec Asta face efortul nv at arii folosirii STL s a par a mic n comparat , , ie cu avantajele obt nceput, vet a putet , inute. Pentru , i observa c , i citi valori, sorta vectori, s a ntr-un mod mult mai sigur s , i alte operat , ii de rutin , i mai ecient, scriind un singur r and de cod, iar n timp vet , i descoperi cum operat , ii mai complexe precum folosirea de cozi ordonate sau c autarea n arbori binari pot f acute la fel de us , or s , i sigur. Pentru a sprijini armat acute mai sus, vom porni de la un exemplu , iile f simplu: un program care cites as a pe , te valorile unui vector, le sorteaz , i le as , eaz ecran. Prima variant a este o implmentare clasic a de C.
1 #include <stdlib.h> 2 3 4 5 6 #include <iostream.h> // a and b point to integers. cmp returns -1 if a is less than b, // 0 if they are equal, and 1 if a is greater than b. inline int cmp (const void *a, const void *b) int aa = *(int *)a; int bb = *(int *)b; return (aa < bb) ? -1 : (aa > bb) ? 1 : 0;

7 { 8 9 10 11 } 12 13 14 15 16 17 18 19 20 21 22 23 24 25

// Read a list of integers from stdin // Sort (c library qsort) // Print the list main (int argc, char *argv[]) { const int size = 1000; // array of 1000 integers int array [size]; int n = 0; // read an integer into the n+1 th element of array while (cin >> array[n++]); n--; // it got incremented once too many times

STANDARD TEMPLATE LIBRARY

38

26 27 28 29 30 31 }

qsort (array, n, sizeof(int), cmp); for (int i = 0; i < n; i++) cout << array[i] << "\n";

A doua variant a foloses a facet a nu nt nc a cum , te STL. Nu v , i griji dac , eleget ,i funct a, mai multe detalii se vor reg asi n sect a: , inoeaz , iunile care urmeaz
1 #include <algorithm> 2 #include <vector> 3 4 5 6 7 8 #include <iostream> #include <iterator> using namespace std; int main () vector<int> v; istream iterator<int> start (cin); istream iterator<int> end; back insert iterator< vector<int> > dest(v); copy (start, end, dest); sort (v.begin(), v.end()); copy (v.begin(), v.end(), ostream iterator<int>(cout, "\n")); return 0;

9 { 10 11 12 13 14 15 16 17 18 19 }

6.2

Template-uri

Inainte de a continua, as a precizez c a tot codul din STL se compune din , dori s clase template. Din acest motiv, pare resc s a ne punem ntrebarea Ce este un template?, chiar dac a o parte dintre programatorii ncep atori le folosesc corect f ar a s a cons tientizeze cu adev a rat ce sunt s i cum funct ioneaz a. , , , Un template este n esent a solut , , ia pe care o aduce C++ pentru programarea generic a: este o port iune de cod n care anumite tipuri de date sunt l asate , necompletate n mod intet ionat. Tipurile de date abstractizate se noteaz a cu , nis te nume simbolice (asem a n a toare cu nis te variabile) care vor nlocuite la , , compilare conform cu specializ arile s i instant ierile din restul codului. , , Pentru a da un exemplu scurt, s a ne imagin am c a ne-am dori o clas a care s a ret in a o pereche de obiecte. F a r a template-uri, am avut doar dou a solut , , ii, destul de incomode am andoua:

STANDARD TEMPLATE LIBRARY

39

S a punem n clas a doi pointeri generici (de tip (void*)) s a l as am tratarea , i s tipurilor n ntregime n seama programatorului, dar astfel nu am mai putut face vericarea tipurilor la compilare S a scriem c ate o clas a separat a pentru ecare combinat , ie de tipuri de date. Aceast a solut n mod evident, deoarece mereu se scriu clase , ie nu merge noi, s a ntret ateva mii de versiuni diferite , i ar un de neconceput s , inem c de clas a care difer a doar prin tipurile de dat a ale unor membrii. Solut a C++ prin template-uri este s a scriem codul din , ia pe care o ofer metodele clasei folosind nis and ca acestea s a e , te tipuri parametrizate, urm nlocuite dup a nevoie de compilator. Un template care implementeaz a clasa din aceste exemplu poate urm atorul (chiar dac a exist a deja clasa std::pair<T,V>)
1 2 3 4 5 #include <iostream> // Consideram doua tipuri distincte, la care le spunem generic T si V template<class T, class V> class Pair{

6 public: 7 T tData; 8 V vData; 9 Pair<T,V>(T tData, V vData); 10 }; 11 12 13 14 15 16 17 // Pentru a exemplica sintaxa, am ales sa nu denesc // constructorul inline template<class T, class V> Pair<T,V>::Pair(T tData, V vData) : tData(tData), vData(vData) { }

18 19 template<class T, class V> 20 std::ostream& operator<< (std::ostream& out, Pair<T,V>& pair){ 21 return out << "(" << pair.tData << "," << pair.vData << ")\n"; 22 } 23 24 int main() 25 { 26 // Instantiem un obiect de tip Pair<int,int> 27 Pair<int,int> pair1(1,3); 28 std::cout << pair1; 29 30 31 32 33 34 // Instantiem un obiect de tip Pair<std::string,double> Pair<std::string,double> pair2(std::string("Eric Cartman"), 10.0f); std::cout << pair2; return 0;

STANDARD TEMPLATE LIBRARY

40

35 }

Primul lucru pe care trebuie s a l preciz am aici este c a at at Pair<int,int> c at s , i Pair<std::string,double> sunt clase distincte! Ele sunt generate de c atre compilator folosind substitut a se depisteaz a n cod o nou a , ie de tipuri dac combinat a. Dup a substitut , ie de tipuri utilizat , ia tipurilor, codul template-ului n parte (o specializare este o se compileaz a separat pentru ecare specializare particularizare a template-ului pentru anumite tipuri de date concrete) ca s ,i cum am scris clase distincte. Acest lucru ncetines , te mult compilarea s , i poate genera erori la compilare neas a tipurile de date nu implementeaz a , teptate dac n mod explcit, toate metodele invocate n template ( n C++ nu exist a interfet ,e este responsabilitatea programatorului s a se asigure c a o anumit a clas a implementeaz a toate metodele pe care trebuie s a le pun a la dispozit , ie). De asemenea, copierea repetat a a codului de numes n proiectele mari , te code bloating s ,i ncetines , te compilarea foarte mult s , i duce la binare foarte voluminoase. Al doilea lucru care trebuie remarcat este acela c a template-ul de operator<< presupune c a tipurile T s andul lor, denit operatorul << pentru , i V au, la r ostream-uri. n caz constrar, nu vet , i putea compila codul. Nu am putut scrie ceva similar n C, deoarece printf trebuie s a t a cont de tipurile de date s , in ,i formatele pe care le as a. , eaz O facilitate folosit a destul de mult n STL este aceea de supra nc arcare a specializ arilor. Asta nseamn a c a pentru anumite cazuri particulare de tipuri de date, se pot specica implement ari mai eciente. Cel mai clar exemplu este template-ul std::vector<bool>, care n loc s a e administrat sub forma unui array ca toate celelalte specializari de std::vector, este comprimat pe bit , i. Comparativ, n Java nu exist a efectiv template-uri, chiar dac a exist a ceva similar: Generics. Mecanismul de clase generice permite specicarea de tipuri de date concrete n cod, de exmeplu ne as am ca un obiect care este o instant a de , tept , java.util.Vector<String> s a cont a doar obiecte de tip String. In realitate, , in ns a, Java face aceste veric ari dinamic la runtime, din cauza polimorsmului implicit. Din aceast a cauz a, codul nu se multiplic a n realitate, ci doar apar nis a v a scape de cast-uri inutile. Putet , te restrict , ii care s , i tris , a acest mecanism foarte us a v a punet , or, dac , i mintea. O observat a la cei care , ie undeva la limita dintre comic s , i serios se refer declar a n Java obiecte de tipul java.util.Vector<Object>. Chiar dac a nu este nimic gres a declarat , it la aceast , ie, ea nici nu face nimic special, pentru ca n Java orice obiect este implicit derivat din tipul Object :).

6.3

Componentele STL

In principiu, STL pune la dispozit ari pe care s a le , ie trei tipuri de implement putet i folosi de-a gata n cod: ,

STANDARD TEMPLATE LIBRARY

41

Containere Adaptori Algoritmi In cele ce urmeaz a vom detalia cu exemple practice ecare categorie.

6.4

Containere: vector, list, slist, deque, set, map

Clasa template std::vector<T> nu este cu nimic mai mult dec at un wrapper peste un array alocat dinamic, s a similar cu echivalentul din Java , i funct , ioneaz pe care l-am dat exemplu n mod repetat. Cu toate acestea, n C++ exist ao serie de diferent , e specice limbajului: Insert ars , ia la sf , it se face cu ajutorul metodei push back(). Incercarea de a insera elemente pe pozit , ii nealocate poate duce la SegFault, pentru c a nu vi se garanteaz a nic aieri capacitatea array-ului din interior (folosit ,i resize() pentru asta). Accesul la elemente se poate face e prin operatorul [], f ar a vericarea limitelor, e prin funct , ia membru at(), cu vericarea limitelor. Avantajul funct a nu primit a accesat , iei at() este c , i SegFault dac , i indecs , i invalizi (atunci c and mas a Java ar aruncat dulcele mesaj de except , ina virtual , ie Array Index Out of Bounds). Dezavantajul este c a timpul de acces este mai mare (trebuie executat un if suplimentar). Pentru c a politica C++ este s a v a lase s a v a aleget a folosit a d a , i singuri ce vret , i s , i, operatorul [] v acces direct s a , i rapid la array-ul din interiorul clasei, dar pe propria voastr r aspundere! Putet , i manipula memoria alocata unui vector folosind una din metodele resize() sau reserve(). Din p acate, exist a mult a confuzie cu privire la folosirea lor, care se traduce adesea n SegFault s i , vandalizarea tastaturii. Metoda resize() redimensioneaz a capacitatea array-ului la capacitatea dat a ca parametru. Dac a noua dimensiune este mai mare dec at vechea dimensiune, se invoc a constructorul tipului pentru noile obiecte de la sf ars it, astfel nc a t acestea pot folosite direct! , Metoda reserve() face cu totul altceva. Ea nu redimensioneaz a neap arat array-ul alocat, ci mai degrab a se asigur a c a acesta are o capacitate cel put a cu parametrul. Dac a este nevoie de o realo, in egal care pentru a spori capacitatea vectorului, NU se invoc a constructorul n spat iul de memorie de la sf a rs it! Dac a vet i ncerca s a folosit , , , , i acele obiecte, vet i avea tot felul de surprize nepl a cute. ,

STANDARD TEMPLATE LIBRARY

42

Atribuirea dintre doi vectori se trauce prin dezalocarea memoriei primului vector s n vectorul la care , i clonarea tuturor obiectelor din vectorul atribuit se atribuite. Chiar dac a STL face aceste operat ii n batch, interschimbarea , a doi vectori prin trei atribuiri dureaz a mai mult sau mai put , in o eternitate n termeni de timp de execut a este s a folosim metoda , ie. Solut , ia inteligent swap(), care interschimb a n mod inteligent doar pointerii c atre zonele de memorie din interior s a. , i variabilele de statistic Exist a specializ ari de vector n STL care sunt reimplementate mai ecient. Mai exact, este vorba de specializarea std::vector<bool> care t , ine elementele pe bit , i, nu pe char-uri. Teoretic, acest lucru duce la o economie de memorie de 1/8, dar practic alocarea se face n pas , i incrementali de sizeof(int) din motive de performant a de vitez a. Dac a acest lucru vi se , pare deranjant, avet a instant , i libertatea s , iat , i vectorii cu alocatori scris ,i de voi (v a asigur eu, NU se merit a efortul). Acestea ind zise, lipses , te doar un exemplu concret de folosire. Nu am precizat n mod deosebit numele altor metode din vector, deoarece le putet asi , i g n documentat a foarte rapid. Am ales n schimb s a pun accentul pe , i ocial nt arii. , elegerea funct , ion

STANDARD TEMPLATE LIBRARY

43

1 2 3 4 5

#include <iostream> #include <string> // Headerul care contine template-ul pentru vector #include <vector>

6 7 int main() 8 { 9 int arrayInt[4] = { 0, 1, 2, 3}; 10 // Cream un vector din intregii intre doua adrese dintr-un array 11 std::vector<int> vInt(arrayInt, arrayInt+4); 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 } // Cream un vector de stringuri std::vector<std::string> v; char cifre[2] = { 0, 0 }; for (int i = 0; i < 10; i++){ cifre[0] = i + 0; // Inseram la sfarsit instante de stringuri create din cifre v.push back(std::string(cifre)); } // Asam 0, in doua moduri: cu si fara bounds checking std::cout << v[0] << " " << v.at(0) << "\n"; // Redimensionam vectorul v la 3 elemente // (celelalte 7 se distrug automat) v.resize(3); // Il reextindem la 8 elemente cu reserve() // (cele 5 elemente in plus nu se instantiaza automat) // Reserve se foloseste pentru a evita in avans realocari in cascada v.reserve(8); // Cream un vector nou, gol, si il interschimbam cu cel vechi std::vector<std::string> w; w.swap(v); // O sa cauzeze exceptie pentru ca elementul de pe pozitia 6 nu este instantiat std::cout << w.at(6) << "\n"; return 0;

STANDARD TEMPLATE LIBRARY

44

Clasele template std::list<T> s a liste dublue, , i std::slist<T> implementeaz respectiv simplu nl ant uite de obiecte generice. Metodele de insert ie sunt push back() , , s i push front() n funct ie de cap a tul la care dorim s a inser a m, iar cele de elim, , inare de elemente sunt pop back() s , i pop front(). Aici trebuie de remarcat de asemenea c a nu mai este permis accesul prin subscripting, ca la vector (complexitatea ar O(N)). In schimb, accesul se face mai degrab a la elementele de la captele listelor prin metodele front() s , i back(). In rest, modul de folosire este foarte intuitiv. Un exemplu similar cu cel de la vectori reg asim mai jos:
1 2 3 4 5 6 #include <iostream> #include <string> // Headerul care contine template-urile pentru liste #include <list>

7 int main() 8 { 9 std::list<std::string> l; 10 11 char cifre[2] = { 0, 0 }; 12 for (int i = 0; i < 10; i++){ 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 } cifre[0] = i + 0; l.push back(std::string(cifre)); } // Asam elementele de la inceputul si sfarsitul listei std::cout << l.front() << " ... " << l.back() << "\n"; // Sortam lista l.sort(); // Golim lista de la coada la cap si asam elementele // din ea pe masura ce le scoatem while (!l.empty()){ std::cout << l.back() << " "; l.pop back(); } std::cout << "\n"; return 0;

STANDARD TEMPLATE LIBRARY

45

Un std::deque<T> funct a ca o list a dublu nl ant a, dar permite , ioneaz , uit nis te operat ii suplimentare datorit a modului de reprezentare intern a. Cea mai , , important a operat ie suplimentar a este accesul la elemente prin subscripting , (adic a prin operatorul []). Din acest punct de vedere seam an a mai mult cu un vector. Aceast a structur a de date este alocatorul implicit pentru o serie de adaptori de containere, dar n rest nu are facilit at a nu voi insista , i deosebite as , a c asupra ei. Un std::set<T> este o implementare a unei multimi n sens matematic. Tipul de date cu care se specializeaz a un template trebuie s a suporte comparat , ie (operatorul < s a e bine denit ntre dou a obiecte de tip T). Inserarea ntr-un set se realizeaz a cu ajutorul metodei insert(), iar c autarea se face cu ajutorul metodei nd() care ntoarce un iterator (mai multe despre iteratori n sect , iunea corespunz atoare). Ambele operat n timp amortizat O(logN). Trebuie , ii se fac de asemenea de ret a seturile sunt implementate cu ajutorul arborilor bi, inut c colori (numit 1 #include <iostream> ,i s , i red-black).
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 } // Headerul care contine template-ul pentru set #include <set> int main() { // Cream un set de intregi. Comparatorul este implicit < std::set<int> s; // Inseram un element s.insert(1); // Vericam existenta lui. nd() intoarce un iterator if (s.find(1) != s.end()){ std::cout << "Da!\n"; } else { std::cout << "Nu!\n"; } return 0;

STANDARD TEMPLATE LIBRARY

46

Un std::map<T,V> este un template cu doi parametrii de instantiere: tipul cheilor s a din , i tipul valorilor asociate. Funct , ionarea este bine cunoscut Java, dar sunt c ateva detalii de implementare unice pentru C++. Ca s i std::vector , , un map implementeaz a operatorul de indexare [], dar atent , ie: folosirea opeartorului conduce automat la inserarea n map a cheii! Dac a nu se atribuie o valoare n cadrul instruct i va corespunde ca valoarea un obiect construit cu , iunii, cheii constructorul f ar a parametri. Funct a la inserare se numes , ia alternativ , te chiar insert(), iar funct , ia de s , tergere se numes , te erase(), ca la seturi. De fapt, un map este n realitate doar un set de obiecte de tipul std::pair<T,V> sortate dup a elementul T. Cheile sunt, din nou, t a de arbore bicolor. , inute sub form
1 #include <iostream> 2 3 4 5 6 7 #include <string> // In acest header se aa template-ul pentru map #include<map> int main() std::map<std::string, int> numere; // Inseram implicit un numar prin indexare numere[std::string("unu")] = 1; // Inseram prin apel de functie numere.insert(std::pair<std::string, int>(std::string("doi"), 2)); // Stergem un numar din map numere.erase(std::string("unu")); // Asam un numar din map std::cout << numere[std::string("doi")] << "\n"; return 0;

8 { 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 }

Ce este interesant s a funct a aproape ca un vector , i util la un map este c , ioneaz cu indici de orice tip posibil de dat a, ns a cu nis ari de complexitate. , te penaliz

STANDARD TEMPLATE LIBRARY

47

6.5

Adaptori: queue, stack, priority queue

Adaptorii sunt nis ncapsula n interior alte containere , te clase care au rolul de a mai generalizate, cu scopul de a restrict , iona accesul la ele. Voi lua drept exemplu adaptorul pentru stiv a deoarece este una dintre cele mai folosite structuri de date. In principiu, n STL nu exist a o clas a special a care s a implementeze doar stive. Cei care au proiectat limbajul s a acest efort ar , i-au dat seama c inutil, dar totus a apeleze metode de , i cum ar putut prevenit programatorii s acces ale elementelor de la baza stivei? Solut a de adaptori este foarte simpl a: cre am o clas a nou a care s a , ia oferit cont a n interior un membru privat de alt tip (implicit este vorba de un , in std::deque, dar putem da s a , i alt , i alocatori), iar apelurile la obiectul exterior s e direct atre apeluri de la obiectul intern astfel nc at s a r am an a vizibil a , inoate c doar funct a (de exemplu, la o stiv a ne initereseaz a , ionalitatea care ne intereseaz doar push(), pop() s , i top(). Principalii adaptori din C++ sunt: queue - reprezint a o structur a de tip FIFO (rst in, rst out) stack - reprezint a o structur a de tip LIFO (last in, rst out) a o structur a de tip HEAP (cel mai mic element priority queue - reprezint este primul care este scos)

6.6

Iteratori

Av and n vedere multitudinea de containere s , i adaptori pe care C++ o pune la dispozit n biblioteca standard, a fost nevoie de un alt concept pentru a ab, ie stractiza traversarea colect n C++ se deosebes , iilor: iteratorii. Un iterator , te destul de mult conceptual fat a de un iterator din Java. In esent a, n C++ , , concept a un iterator este orice clas a care se , ia de la care s-a pornit a fost c comport a ca un pointer: Suport a operatorul ++ (eventual s atorul , i ) pentru a trece la urm (eventual precedentul) element din colect ie, respectiv adunarea cu ntregi , Atunci c and l referent nc arc am operatorul *), returneaz a un , iem (supra obiect de tipul celor din clasa pe care o deserves , te. Conform cu aceast a denit , ie, un pointer la int este un iterator perfect legal pentru un array (se pot aplica pe el operatorii ++, + s , i *). Cu toate astea ns a, din cauz a c a toate colect , iile au de-a face cu iteratori, standardul de facto este o structur a de date s a aib a n interior un tip de dat a care s a implementeze un iterator. Deoarece tipul iterator este denit intern n colect , ii, acestea pun de obicei la dispozit a metode prin care se pot obt , ie dou , ine iteratori:

STANDARD TEMPLATE LIBRARY

48

begin() - aceast a metod a ne ofer a primul element din colect , ie end() - aceast a metod a ne ofer a elementul din colect a ul, ie imediat dup timul element. De remarcat c a end() nu ntoarce un iterator la ultimul element din colect , ie, ci primul element din afara colect , iei, cu alte cuvinte nu poate niciodata referent as , iat, ci foloses , te la comparat , ii ca s , tim unde s a ne oprim. rbegin(), rend() - aceste metode sunt oglinditele celor de mai sus, doar c a dinspre coad a spre cap. Anumite structuri de date pun la dispozit , ie travers ari n ambele direct a n cazul acesta, rend() o sa e , ii, doar c prima locat , ie dinainte primul element din colect , ie s , i din nou, nu poate referent a oricum nu exist a). , iat (pentru c Justicarea foarte clar a pentru folosirea operatorilor este aceea c a pot , i parcurge orice colect as , ie dac , tii de unde s a ncepi unde s a te opres , ti cum s a treci de la un element la altul De asemenea, mai exist as , i iteratori de tip const iterator, care garanteaz a c a nu modic a elementele structurii pe m asurs a ce se deplaseaz a prin ea.
1 2 3 4 5 6 #include #include #include #include <iostream> <vector> <set> <map>

int main() // Cream un vector, un map si o multime de intregi std::vector<int> v; std::set<int> s; std::map<int,int> m; // Inseram in amandoua numerele de la 0 la 9 for (int i = 0; i < 10; i++){ v.push back(i); s.insert(i); m[i] = -i; // Cheia e numarul, valoarea e -numarul } // Iteram tot setul, in ordine, si asam for (std::set<int>::iterator it = s.begin(); it != s.end(); it++){ // it este iterator de set<int>, deci functioneaza identic cu un pointer

7 { 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

STANDARD TEMPLATE LIBRARY

49

23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 } 47

// de int (chiar daca nu este un pointer de int, are aceeasi operatori) std::cout << (*it) << " "; } std::cout << "\n"; // Stergem din vector primul si al doilea element. Obsevatie: // v.begin()+2 este, ca si v.end(), primul element care NU participa la stergere v.erase(v.begin(), v.begin()+2); // Asam ce a ramas din vector in ordine inversa std::vector<int>::reverse iterator rit; for (rit = v.rbegin(); rit != v.rend(); rit++){ std::cout << (*rit) << " "; } std::cout << "\n"; // Iteram prin map si asam. Observatie: un map este un set de pair<Key,Value> // deci un iterator de map functioneaza ca un pointer la un pair<Key,Value> for (std::map<int,int>::iterator it = m.begin(); it != m.end(); it++){ std::cout << "(" << it->first << "," << it->second << ")\n"; } return 0;

STANDARD TEMPLATE LIBRARY

50

6.7

Algoritmi

Probabil cel mai mare avantaj din folosirea STL l reprezint a existent , a algoritmilor gata implementat , i pentru conainerele denite. Lista acestor algoritmi este foarte variat a. Astfel, STL pune la dispozit , ie algoritmi pentru: C autare s , i statistici : nd, count, mismatch, equal, search Modicarea ordinii elementelor : swap, copy, reverse, replace, rotate, partition Sortare : sort, partial sort, nth element C autare binar a : binary search, lower bound, upper bound Interclasare : merge, set intersection, set dierence Gestiunea datelor : make heap, sort heap, push heap, pop heap Minim s , i maxim : min, min element, lexicographical compare Din motive de p astrare a generalit at , ii, algoritmii din STL se folosesc de iteratori. Des ari ar putea p area greoaie, odat a , i init , ial folosirea acestor implement st ap anite conceptele care stau la baza lor, vet a realizat ari , i reus , i s , i implement foarte rapide. In continuare, vom da dou a exemple de folosire ale sort arii, respectiv c aut arii binare din STL
1 #include <algorithm> 2 #include <vector> 3 #include <iostream> 4 5 int main() 6 { 7 // Cream un vector cu niste intregi oarecare 8 int myInts[] = { 32, 71, 12, 45, 26, 80, 53, 33 }; 9 std::vector<int> v(myInts, myInts+8); 10 11 12 13 14 15 16 17 18 19 20 21 // Sortam intre ele elementele de pe pozitiile 0, 1, 2, 3 si 4 // Vom specica capetele intre care sortam prin doi iteratori std::sort(v.begin(), v.begin()+5); // Ca sa gasim rapid un element in vector, vom folosi cautarea binara // Intai si intai, terbuie sa sortam tot vectorul pentru a cauta binar std::sort(v.begin(), v.end()); // Vericam daca elementul 15 se aa in vector if (binary search(v.begin(), v.end(), 15)){ std::cout << "15 exista in vector!\n";

STANDARD TEMPLATE LIBRARY

51

22 23 24 25 26 27 }

} else { std::cout << "15 nu exista in vector!\n"; } return 0;

STANDARD TEMPLATE LIBRARY

52

6.8

Gres , eli frecvente

Programatorii de Java tind s a fac a la nceput o serie de gres n folosirea con, eli tainerelor din STL, datorate asem an arii ns atoare dintre sintaxa celor dou a , el limbaje. Derivarea containerelor. In Java, o practic a uzual a este derivarea colect , iilor de date pentru a le modica funct a practic a este foarte gres a , ionalitatea. Aceast , it n C++. Limbajul nu v a interzice s a facet , i asta, dar derivarea claselor rezultate din specializarea template-urilor de STL v a poate cauza memory leakuri s , i comportamente neas , teptate, deoarece destructorii containerelor din C++ NU sunt virtuali. Ei au fost l asat n limbaj din motive , i intent , ionat nevirtuali de performant a. Chiar dac a sunt coincident n care teoretic ar funct , ,e , iona corect s a corect a de a extinde un container de C++ este , i derivarea, singura practic scrierea unui wrapper (poart as , i numele de adaptor, dar nu trebuie confundat ,i cu adaptorii din Java). Un wrapper este o clas a care cont n interior un , ine obiect pe care l manipuleaz a ntr-un anumit mod. Inserarea prin copiere. In Java, atunci c and inseram un obiect ntr-o colect a la un obiect, s , ie, de fapt ceea ce inseram noi era o referint , , i nu obiectul n sine! In C++, ambele comportamente sunt posibile. Putem avea: containere de instant a e atribuibile). Ex: std::vector<MyClass> , e de obiecte (trebuie s containere de pointeri la obiecte (se comport a cam ca n Java). std::vector<MyClass*> Ex:

ATENT and avem containere de obiecte, insert , ia se face prin , IE!. Atunci c copiere! Cu alte cuvinte, dac a scriem v.push back(someObject);, n realitate se va instant n container, iar someObject va , ia un obiect nou care se va depune r am ane neatins. Obiectele din containere trebuie s a poat a asignabile (dac a au constante n interior, va trebui s a redenit , i operatorul de atribuire)! Iteratorii inconsistet a ,i s , i SegFault-urile. Ar trebui ca de ecare dat c and folosit a considerat a aces , i iteratori, s , i c , tia sunt un tip de pointei, s , i deci v a pot cauza SegFault. In principiu, un iterator r am ane valid, sau safe, at ata timp c at colect a. Ca s a nt , ia din care a fost creat nu se modic , elegem mai bine aceast a problem a, s a presupunem urm atorul scenariu: Avem un vector<int> n care simul am o coad a (de exemplu, la parcurgerea n l at , ime a unui graf). In cursul simul arii, parcurgem vectorul cu un iterator s , i pentru ecare element vizitat ad aug am, eventual, alte elemente la nalul vectorului. Aparent, am putea spune c a nimic r au nu se poate nt ampla, dar trebuie s a ne g andim la ce se nt ampl a n interior. De ecare dat a c and ad aug am la nalul vectorului un nou element, exist a posibilitatea s a e nevoie de o realocare n array-ul din interior. Dac a realocarea nu se poate face in-place din diverse motive, atunci se va aloca memorie n alt a parte s n noua locat , i se va muta cont , inutul , ie. Vechea locat , ie de memorie devine invalid a (cu alte cuvinte, a fugit memoria de sub iterator). La urm atoarea referent , iere de operator, vom obt , ine SegFault. Un alt caz

STANDARD TEMPLATE LIBRARY

53

des nt alnit de SegFault este ncercarea de a s , terge elemente din containere pe m asur a ce acestea sunt parcurse de iteratori. In C++, think pointer-style! Containerele sortate. O gres a destul de frecvent a este s , eal , i presupunerea c a un container sortat (de exemplu, un set sau un map) se reorganizeaz a automat atunci c and modic am cont , inutul unui obiect din interior prin intermediul unui pointer. Acest lucru nu este adev arat! Dac a vet , i modifca prin pointeri (sau iteratori) obiectele din interior, vet , i face containerul inconsistent. O abordare corect a de a p astra cont as , inutul sortat este s , terget , i din container obiectul pe care vret a l modicat a reintroducet a cu insert(). , i s ,i s , i s , i versiunea modicat Sincronizarea. Chiar dac a nu vet n anul 2 programe paralele, putet , i scrie ,i ret a NU sunt thread-safe! Dac a vet , ine despre containerele din C++ c , i vrea s a le partajat ntre thread-uri, trebuie s a le protejat ,i , i explicit cu mutex-uri sau semafoare.

NAMESPACE-URI (***)

54

Namespace-uri

Un aspect al c arei lips a se simt n C s , ea , i pe care C++ l-a introdus este posibilitatea separ arii spat n limbaj. , iilor de nume Un spat n care declar am , iu de nume poate comparat cu un context nume (de clase, de tipuri, de variabile, de funct , ii, etc.). In interiorul unui spat , iu de nume, nu putem avea dou a nume identice (de exemplu, nu putem avea dou a variabile sau dou a clase cu acelas n spat , i nume), dar , ii de nume diferite nu avem acest tip de restrict , ii. Cel mai bun exemplu de spat l reprezint a o clas a (des a , iu de nume , i o clas nu este efectiv un spat a asem an ator). Astfel, n , iu de nume, doar se comport interiorul clasei std::vector<int>, numele iterator specic a n mod unic un singur tip de dat a, n timp ce n interiorul clasei std::map<int,int>, numele iterator specic a tot n mod unic, un alt tip de dat a. Chiar dac a cele dou a tipuri de dat a se numesc la fel, ele fac parte din spat , ii de nume diferite s , i nu se confund a ntre ele. Spat a ierarhic a (de arbore) s , iile de nume au structur , i sunt specicate sub form a de prexe, urmate de operatorul de rezolut ie de scop (::). At , , i observat c a n cadrul exemplelor din acest tutorial am scris aproape de ecare dat a std:: n fat a claselor s i constantelor din STL. std:: este spat iul de nume standard n , , , care sunt depuse toate numele care fac parte din standardul limbajului. Un alt spat , iu de nume este spat , iul global (sau implicit) de nume. Din acest spat , iu fac parte clasele denite de utilizator sau variabilele globale care nu sunt incluse n nici un alt spat iu de nume. , Un spat nbr ac and codul ntr-un bloc de forma , iu de nume se poate specica meu de nume { ...cod... } . namespace spat iul , Uneori devine obositor, sau este inestetic s a prex am toate clasele folosite cu namespace-ul din care acestea fac parte (mai ales dac a este vorba de un namespace lung). De exmeplu, dac a avem o funct ie static a numit a makeCof, fee n clasa BestClassEver din namespace-ul best project ever care face parte din namespace-ul best company ever, apelarea ei ar trebui s a e de forma best company ever::best project ever::BestClassEver::makeCoee(); Cu sigurant a nu ne dorim s a scriem as , , a ceva prea des. Namespace-ul implicit n cod este cel curent (prexarea cu el este redundant a). El poate prescurtat s la nevoie, pentru a distinge ntre , i prin :: variabile globale s i locale. Pentru accesul la numele din alte spat ii, trebuie , , s a preciz am explicit acest lucru. Totus a a altor , i, putem evita scrierea repetitiv spat ii de nume dac a instruim compilatorul s a fac a merge ntre dou a spat , , ii de nume. Acest lucru se poate realiza cu ajutorul directivei using ca n exemplul de mai jos:

NAMESPACE-URI (***)

55

1 2 3 4 5 6 7 8 9 10 11 12

#include <vector> // Din acest moment, facem reuniunea spatiilor :: si std:: // Pentru toate clasele folosite, compilatorul va incerca sa caute singur // spatiul de nume corect (:: sau std::, dupa caz). In cazul in care exista // mai mult de o posibilitate, va trebuie sa scrieti namespace-ul explicit! using namespace std; // Se poate specica si reuniunea cu o singura clasa astfel: // using spatiu de nume::Clasa; int main() int numere[5] = { 4, 3, 2, 1, 0 }; vector<int> v(numere, numere+5); sort(v.begin(), v.end()); for (vector<int>::iterator it = v.begin(); it != v.end(); ++it){ cout << (*it) << " "; } cout << "\n"; return 0;

13 { 14 15 16 17 18 19 20 21 22 23 24 25 26 } 27

Chiar dac a folosirea lui using scurteaz a codul, este gres a folosim using , it s n headere! Headerele sunt s n altele s n s , iere care pot incluse unele ,i , iere surs a n orice combinat n ele poate duce accidental la , ii. Existent , a unui using coliziuni de nume din dou a namespace-uri diferite, coliziuni care nu ar fost posibile dac a nu foloseam using. In denitiv, folosirea lui using pentru spat n s , iul std:: , ierele de implementare t aruia. Chiar dac a produce cod mai scurt s , ine de gusturile ec , i mai clar, anumit a s a scrie mereu explicit spat , i programatori prefer , iile de nume.

TRATAREA EXCEPT , IILOR (***)

56

Tratarea Except , iilor

C++ a introdus la aparit , ia sa mecanismul de tratare al except , iilor (try-catch). Aceast a funct a n Java, unde a devenit foarte impor, ionalitate a fost transpus tant a (s a Java v a oblig a s a tratat ns a, , tim deja c , i toate except , iile). In C++, lucrurile stau mult diferit. Limbajul nu oblig a tratarea except a o funct a o except , iilor. Dac , ie arunc , ie care nu poate tratat a de nici o funct , ie de deasupra ei din lant , ul de apeluri, atunci se ncheie fort , at execut , ia programului. Mai mult, except n C++, spre deosebire de Java, pot obiecte de orice , iile tip de dat a! Acest lucru ar f acut ngrozitoare tratarea tuturor tipurilor de except a, motiv pentru care s-a introdus operatorul ellipsis, , ie de ecare dat notat prin trei puncte (...). Semnicat ,i , ia acestui operator este orice except , ie, s funct a similar cu superclasa Exception din Java. , ioneaz In continuare se exemplic a sintaxa de except , ii din C++:
1 #include <iostream> 2 3 int main() 4 { 5 6 7 8 9 10 11 12 13 14 15 16 17 } try{ // Asa se arunca o exceptie de tip int throw 5; } catch (int errCode) { // Care este prinsa aici std::cout << "Exceptie de tip int.\n"; } catch (...) { // Just to make sure, lasam si posibilitatea altor exceptii std::cout << "Orice alta exceptie.\n"; } return 0;

Observat a folosirea , ie: Majoritatea coding guide-urilor renumite condamn except iilor n cod deoarece duc la cod greu de ntret inut. Acest lucru con, , trasteaz a puternic cu mentalitatea din Java! O alt a observat a stream-urile din STL nu arunc a n mod , ie este aceea c obligatoriu except ii. Dac a dorim ca ele s a arunce except ii, acest lucru trebuie , , setat manual din cod.

RESURSE

57

Resurse

In ncheierea acestui tutorial, sper c a at a v a format arere asupra , i reus , it s , i o p limbajului. In decursul sect ncercat s a ating toate chestiunile im, iunilor, am portante, f ar a a merge mai n detaliu dincolo de strictul necesar at at scrierii de cod corect c at s nt ,i , elegerii documentat , iei s , i surselor din biblioteci. Dup a cum am spus s n introducere, acest tutorial nu t ,i , ine nicidecum locul unui manual de C++. Celor care le place limbajul s a , i care sunt interesat , i s l nt a n detaliu, le recomand n special cartea The C++ Programming , eleag Language, scris a de Bjarne Stroustrup, creatorul limbajului. Totus , i, cartea este extrem de voluminoas as a, s a descurajeze pro, i foarte detaliat , i s-ar putea s gramatorii care sunt la primele contacte cu limbajul. In continuare, am ales c ateva surse foarte bune de documentat a vor , ie care v de real ajutor de ecare dat a c and v a lovit n C++: , i de probleme Resurse online: Sursa ociala de documentatie pt C++ (prima s a surs a , i cea mai accesibil de informare despre limbaj!) Un tutorial de baz a pentru STL Un tutorial ceva mai serios despre STL Introducere n STL Tutorialul de STL al www.decompile.com Site-ul ocial al bibliotecii BOOST C arti: The C++ Programming Language - Special Edition, Bjarne Stroustrup C++, Danny Kalev, Michael J. Tobler, Jan Walter

S-ar putea să vă placă și