curs bun de poo

78
UNIVERSITATEA "ALEXANDRU IOAN CUZA" IAŞI Facultatea de Informatică Departamentul de Învăţământ la Distanţă Dorel LUCANU Introducere în Programarea Orientată-Obiect şi C++ 2006-2007

Upload: cristian-dumitru

Post on 05-Jul-2015

663 views

Category:

Documents


11 download

TRANSCRIPT

Page 1: curs bun de POO

UNIVERSITATEA "ALEXANDRU IOAN CUZA" IAŞI Facultatea de Informatică

Departamentul de Învăţământ la Distanţă

Dorel LUCANU

Introducere în Programarea Orientată-Obiect şi C++

2006-2007

Page 2: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

1

Cuprins Cuprins............................................................................................................ 1

I Introducere ............................................................................................... 2

II O scurtă introducere în programarea orientată-obiect ............................. 3

II.A Ce este programarea orientată-obiect?.............................................. 3

II.B Obiecte şi clase.................................................................................. 4

II.B.1 POO: o primă definiţie pentru obiecte şi clase .......................... 4

II.B.2 Declararea claselor si obiectelor in C++.................................... 6

II.C Tipuri de date abstracte şi obiecte..................................................... 9

II.C.1 POO: a doua definiţie pentru obiecte si clase ............................ 9

II.D Utilizare de clase ............................................................................. 12

III Programare structurată: de la C la C++.............................................. 15

IV Clase C++ ........................................................................................... 35

V Moştenire ............................................................................................... 49

V.A POO: Relaţiile de generalizare şi specializare ................................ 49

V.B Derivare in C++ .............................................................................. 51

VI Polimorfism ........................................................................................ 55

VII Clase parametrizate ............................................................................ 59

VIII Studiu de caz: modelarea înscrierii la discipline şi examinării....... 62

Bibliografie ................................................................................................... 72

A. Fişierul my_string.h............................................................................ 73

B. Fişierul my_string.cpp........................................................................ 74

Page 3: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

2

I Introducere Acest manual este dedicat în special studenţilor de la formele de învăţământ ID (Învăţământ la Distanţă) şi PU (Post universitare) de la Facultatea de Informatică a Universităţii “Alexandru Ioan Cuza” din Iaşi. Cartea se doreşte a fi un suport pentru disciplina Programarea Calculatoarelor II ce are ca obiectiv predarea programării orientate-obiect (POO) utilizând limbajul C++. Cartea nu face o prezentare exhaustiva nici a programării orientate-obiect şi nici a limbajului C++. Sunt prezentate doar principiile de bază ale POO şi modul de descriere a acestora în C++. Se presupune că cititorul manualului cunoaşte limbajul de programare C şi de aceea cartea include numai acele elemente specifice de programare structurată pe care C++ le include. Se recomandă ca orice cursant al acestei discipline să aibă pentru consultare suplimentară un manual care prezintă complet limbajul C++. Manualul este însoţit de o dischetă ce incude fişierele cu exemplele utilizate. Citirea manualului trebuie să se facă într-o manieră interactivă în sensul că exemplele prezentate să fie testate pe una din implementări (Visual C sau g++). Sunt incluse foarte multe exerciţii din care o mare parte se referă la exemplele din fişierele de pe dischetă. Structura manualului este următoarea: Capitolul II face o mică introducere în POO. Se pune accent pe definiţia şi utilizarea claselor şi obiectelor. Se face un prim pas în declararea claselor şi obiectelor în C++. Elementele de programare structurată (procedurală) specifice limbajului C++ sunt prezentate în Capitolul III. Cel de-al patrulea capitol face o prezentare detaliată a reprezentării claselor în C++. Mecansimul de moştenire în POO şi implementarea cu relaţia de derivare din C++ este prezentat în capitolul V. În Capitolul VI sunt prezentate principalele forme de polimorfism: parametric, suprascriere şi legare dinamică (funcţii virtuale). Clasele parametrizate sunt prezentate pe scurt în capitolul VII. Ultimul capitol include prezentarea unui studiu de caz ce modelează parţial activităţile dintr-o facultate.

Page 4: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

3

II O scurtă introducere în programarea orientată-obiect

II.A Ce este programarea orientată-obiect? Paradigma orientat-obiect (corect din punct de vedere a limbii române ar fi orientat spre obiecte) se referă la o tehnologie bazată pe obiecte şi clase. Paradigma oferă cel mai potrivit cadru de lucru pentru ingineria software. Obiectele au devenit elemente centrale atât în analiza, cât şi proiectarea şi implementarea sistemelor software. Deşi nu există un punct de vedere unanim asupra cerinţelor ce trebuie satisfăcute de o abordare orientată-obiect, următoarele patru sunt întâlnite în majoritatea abordărilor: identitatea, clasificarea, polimorfismul şi moştenirea. Identitatea înseamnă că datele sunt cuantificate în enitităţi distincte şi discrete, numite obiecte. Clasificarea înseamnă că obiectele cu aceeaşi structură a datelor (atribute) şi aceeaşi comportare (operaţii) sunt grupate în clase. Polimorfismul se referă la faptul că aceleaşi operaţii (cu aceleaşi nume şi cu aceleaşi tipuri de argumente) pot avea comportări diferite în clase diferite. Moştenirea se referă la partajarea unor atribute şi operaţii de-a lungul mai multor clase pe baza unei relaţii ierarhice. Aceste cerinţe sunt realizate cu ajutorul următoarelor “ingrediente” orientate-obiect: abstractizare, încapsulare (ascunderea informaţiilor), combinarea datelor cu comportarea, generalizare, specializare, legare dinamică. Abstractizarea constă în focalizarea atenţiei asupra aspectelor esenţiale ale obiectului şi ignorarea proprietăţilor accidentale. Încapsularea sau ascunderea informaţiilor se referă la separarea aspectelor externe ale unui obiect, care pot fi accesibile altor obiecte, de detaliile de implementare internă care nu trebuie să fie accesibile altor obiecte. Suma elementelor membre vizibile prin care un obiect interacţionează cu celelalte obiecte se numeşte interfaţă. În abordările orientate-obiect, datele şi operaţiile asupra acestora sunt combinate într-o singură entitate. În abordarea procedurală, datele, precum şi fiecare operaţie asupra acestora, constituie entităţi distincte. Generalizarea este operaţia prin care aspectele comune a mai multor clase mai specializate, sunt grupate într-o singură clasă mai generală. Specializarea este duală generalizării, în sensul că o clasă generală poate fi specializată în mai multe clase cu trăsături specifice. Clasele generalizate sunt legate de cele specializate prin

Page 5: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

4

intermediul relaţiei de moştenire. Legarea dinamică este în strânsă legaătură cu polimorfismul şi se referă la mecanismul prin care se determină comportarea corectă a unei operaţii polimorfice. Un limbaj de programare orientată-obiect include capabilitaţi de construire a programelor utilizând paradigma orientată-obiect. Limbajele de programare orientată-obiect modelează clasele cu ajutorul tipurilor, iar obiectele prin intermediul variabilelor. De asemenea, includ mecanisme proprii prin care implementează polimorfismul şi moştenirea. Primul limbaj orientat-obiect care a permis lucrul cu clase, obiecte, clase de moştenire şi operaţii polimorfice a fost Simula (1967). În prezent C++ şi Java sunt cele mai populare limbaje cu capabiltăţi de programare orientată-obiect. Java a fost creat ca o simplificare a limbajului C++ care să permită executarea pe orice tip de calculator, urmând principiul “scrie o dată, execută oriunde”. C++ este un limbaj mixt care include atât elemente de programare structurată imperativă cât şi elemente de programare orientată-obiect. C++ a fost scris de Bjarne Sroustrup la Laboratoarele Bell în perioada 1983-1985. C++ este o extensie orientată-obiect a limbajului C. El combină trăsăturile orientate-obiect din Simula cu puterea şi eficienţa lui C. De la crearea sa şi până în prezent, C++ a cunoscut o dezvoltare continuă. Dintre cele mai semnificative, menţionăm “ARMC++” – care a adăugat excepţiile şi şabloanele – şi “ISO C++” – care a adăugat RTTI (Run Time Type Identification), spaţii de nume şi biblioteca standard (STL).

II.B Obiecte şi clase

II.B.1 POO: o primă definiţie pentru obiecte şi clase Un obiect este caracterizat de:

• Un nume prin intermediul căruia obiectul poate fi referit. • O mulţime de stări: la un moment dat un obiect se poate afla într-o

singură stare. Într-un program, starea obiectului este dată de o colecţie de variabile de tip dată (atribute). Acceptăm principiul conform căruia un obiect nu poate avea acces direct la starea altui obiect. Această

Page 6: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

5

proprietate a unui obiect de a-şi ascunde anumite informaţii se numeşte încapsulare sau ascunderea informaţiei.

• O colecţie de operaţii, numite şi servicii, cu ajutorul cărora un obiect interacţionează cu alte obiecte şi îşi poate schimba starea. O operaţie care schimbă starea unui obiect se mai numeşte şi metodă. Există două metode ce oferă servicii speciale: metoda constructor, care se execută la crearea obiectului (defineşte starea iniţială a acestuia), şi metoda destructor, care se execută la dispariţia obiectului. Un obiect poate avea cele puţin o metodă constructor şi cel mult o metodă destructor. Colecţia de operaţii descrie comportarea obiectului.

Prin clasă înţelegem descrierea unuia sau a mai multor obiecte ce pot fi precizate printr-un set uniform de atribute şi operaţii.

(a) Exemplul GNPA Ne propunem să definim un generator de numere pseudo-aleatoare (GNPA) ca un obiect. Vom utliza un generator congruenţial liniar, în care numerele pseudo-aleatoare sunt generate cu formula de recurenţă )(mod1 mbaxx nn +=+ , valoarea iniţială 0x fiind dată. Starea unui astfel de GNPA este dată de patru variabile: m, a, b, x, unde m, a, b au semnificaţiile din formulă iar x va memora ultimul numar pseudo-aleator generat. Operaţia principală a unui GNPA este cea care returnează următorul numar pseudo-aleator generat. Notăm această operaţie cu nrAleat(). Observăm că secvenţa de start este puternic dependentă de valoarea de start 0x . De aceea vom considera încă o operaţie reInit() care să stabilească o nouă valoare de start. Aceste elemente descriu în mod uniform toate generatoarele congruenţiale liniare despre care discutăm si deci pot forma o clasă, pe care o notăm Gnpa. Convenim să utilizăm notaţia UML pentru reprezentarea grafică a claselor. Clasa Gnpa este reprezentată în Figura 1.

Page 7: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

6

nrAleat()reInit()

mabx

Gnpa

Figura 1

II.B.2 Declararea claselor si obiectelor in C++ În C++ descrierea unei clase are două părţi: declararea clasei şi definiţiile funcţiilor care implementează metodele clasei. Includem declararea clasei într-un fişier antet (contor.h) iar definiţiile (implementările) metodelor într-un fişier .cpp (contor.cpp). Această regulă nu este obligatorie dar este recomandată. Cele două părţi ale unei clase pot fi incluse şi în acelaşi fişier. Declararea clasei Gnpa este:

typedef unsigned long ulong; class Gnpa { public: Gnpa(); Gnpa(ulong, ulong, ulong, ulong); ulong nrAleat(); void reInit(ulong); private: ulong m, a, b, x; };

Numele ulong este un alias pentru tipul unsigned long. Observăm mai întâi că datele şi operaţiile care descriu obiectele GNPA au fost incluse în aceeaşi unitate sintactică desemnată de cuvântul cheie class. Remarcăm apoi cele două secţiuni ale clasei: public şi private. În secţiunea public se declară acele elemente membre care pot fi accesate de către alte obiecte. Este firesc ca cele două operaţii nrAleat() şi reInit() să fie declarate publice deoarece ele descriu serviciile pe care un GNPA le oferă celorlalte obiecte. Tot în secţiunea publică sunt declarate şi metodele constructor şi

Page 8: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

7

destructor. În C++ constructorii au aceleaşi nume cu numele clasei. Clasa Gnpa are doi constructori: primul făra parametri ce va atribui valori “implicite” atributelor, iar cel de-al doilea având ca parametri valorile iniţiale ale atributelor. Constructorul fără parametri se numeşte constructor implicit. Prezenţa constructorului implicit este obligatorie. Dacă nu se declară nici un constructor, atunci sistemul va construi automat unul implicit. În secţiunea private se declară variabile ce descriu starea obiectului şi funcţii care ajută la implementarea interfeţei şi nu trebuie să fie accesibile celorlalte obiecte. Un GNPA trebuie să fie singurul care are controlul asupra variabilelor m, a, b şi x; orice alt obiect nu trebuie să aibă acces la aceste variabile. Definiţiile operaţiilor ce apar în declararaţia unei clase sunt date separat:

Gnpa::Gnpa() { m = (1UL << 31) - 1; a = 630360016; b = 0; x = 1; } Gnpa::Gnpa (ulong mPar, ulong aPar, ulong bPar, ulong xPar) { m = mPar; a = aPar; b = bPar; x = xPar; } ulong Gnpa::nrAleat() { x = (a * x + b) % m; return x; } void Gnpa::reInit(ulong x0) { x = x0; }

Page 9: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

8

Secvenţa “::” se numeşte operator de rezoluţie. El este utilizat pentru a preciza clasa (spaţiul de nume) la care aparţine un nume. Astfel, construcţia “Gnpa::nrAleat()” referă funcţia nrAleat() din clasa Gnpa. Pot exista şi alte clase care să includă operaţii cu numele nrAleat(). Constructorul implicit Gnpa() atribuie pentru atributele valorile ce corespund generatorului congruenţial liniar SIMSCRIPT. Valoarea iniţială a lui m este numărul prim Merssene 1231 − iar cea a lui a este )12(mod14 3129 − . Aceste valori au un caracter aleator bun pentru secvenţa de numere generate. Constructorul Gnpa(mPar,aPar,bPar,xPar) atribuie valori iniţiale explicite pentru atributele generatorului. Faptul că Gnpa are doi constructori, are ca efect existenţa mai multor posibilităţi de creare a obiectelor. O declaraţie de forma:

Gnpa gnpa; creează un obiect apelând constructorul implicit. Un apel de forma:

Gnpa gnpa(1UL << 31, 65539, 0, 1); creează un obiect apelând constructorul explicit. Valorile corespund generatorului RANDU care, deşi este foarte cunoscut, nu are proprietăţi aleatoare foarte bune. Operaţiile publice ale unui obiect sunt referite asemănător câmpurilor unei structuri. Aici este o secvenţă de program care afişează primele 20 numere pseudo-aleatoare generate către obiectul gnpa:

for (i = 0; i < 20; ++i) printf("%4d %d\n", i, gnpa.nrAleat()); return 0;

Exerciţiul 1

Înlocuiţi în definiţia clasei Gnpa cuvântul cheie class cu struct. Testaţi programul. Ce se întâmplă? Deşi C++ permite utilizarea ambelor construcţii pentru definiţia claselor, se recomandă utilizarea construcţiei class. Exerciţiul 2

Adaugaţi în funcţia main instrucţiunea: gnpa.x++;

şi apoi compilaţi programul. Ce eroare aţi obţinut?

Page 10: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

9

II.C Tipuri de date abstracte şi obiecte

II.C.1 POO: a doua definiţie pentru obiecte si clase O clasă este o implementare a unui tip de date abstract. Ea defineşte atribute şi metode care implementează structura de date şi, respectiv, operaţiile tipului de date abstract. Un obiect este o instanţă a unei clase. El este unic determinat de numele său şi defineşte o stare care este reprezentată de valorile atributelor sale la un anumit moment particular. În consecinţă, o clasă defineşte proprietăţile şi comportarea unei mulţimi de obiecte. Există şi posibilitatea ca mulţimea de obiecte să fie vidă, caz în care clasa se numeşte clasă abstractă. O clasă care are obiecte se numeşte clasă concretă.

(a) Exemplul Stiva Reaminitim că o stivă (listă LIFO) este o listă peste care se pot executa următoarele operaţii: push() – inserarea unui element, pop() – eliminarea ultimului element introdus în stivă, top() – întoarce ultimul element introdus în stivă, esteVida() – testează daca stiva este vidă. Aici prezentăm clasa StivaChar care implementează cu ajutorul tablourilor stivele ce memorează caractere. Reprezentarea grafică a clasei este dată în Figura 2.

push()pop()top()esteVida()

tabvarf

StivaChar

Figura 2

Descrierea C++ a clasei StivaChar este: class StivaChar { public: Stiva(); ~Stiva(); void push(char); void pop();

Page 11: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

10

char top(); bool esteVida(); private: char tab[MAX_STIVA]; int virf; };

Prezentăm ca exemplu definiţia operaţiei push(): void Stiva::push(char c) { if (virf == MAX_STIVA-1) throw "Depasire superioara."; tab[++virf] = c; }

Remarcăm ca element de noutate utilizarea instrucţiunii throw. Aceasta este o parte componentă a mecanismului de tratare a excepţiilor din C++. Ideea de bază a acestui mecanism este foarte simplă: cu ajutorul instrucţiunilor if şi throw, ori de câte ori apare o excepţie, se “aruncă” o structură de date ce descrie excepţia. În exemplul de mai sus, ori de câte ori se depăşeşte capacitatea de memorare a tabloului (dimensiunea maximă a stivei), se aruncă şirul de caractere "Depasire superioara.". O secvenţă de program ce utilizează cod susceptibil de a produce excepţii, utilizează instrucţiunile try şi catch pentru “prinderea” excepţiilor. Iată o secvenţă de program foarte simplă care prinde excepţiile aruncate de stivă:

Stiva s; char c='a'; try { while (true) { s.push(c++); cout << s.top() << endl; } } catch (char *mes_err) { cout << mes_err << endl; }

Page 12: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

11

Codul care utilizează stiva este inclus într-un bloc try. Ori de câte ori execuţia acestui cod produce o excepţie, execuţia blocului try se termină şi controlul se dă următoarei instrucţiuni, care trebuie să fie o instrucţiune catch. Aceasta va “prinde” structura de date aruncată de către apariţia excepţiei şi, în funcţie de tipul excepţiei, va executa codul de tratare a acesteia. În exemplul de mai sus, la o primă vedere, pare că instrucţiunea while nu se termină niciodată. Capacitatea de memorare a stivei este limitată şi atunci când va apărea prima depăşire a acesteia, execuţia blocului try se termină şi controlul execuţiei programului este dat instrucţiunii catch imediat de după try. Structura de date aruncată de excepţie este referită de variabila pointer mes_err, dată ca parametru instrucţiuni catch. Secvenţa de tratare a excepţiei constă aici în afişarea şirului aruncat. Despre excepţii vom discuta mai pe larg în secţiunea III (j). Exerciţiul 3

Modificaţi clasa StivaChar astfel încât tabloul care memorează stiva să fie memorat în memoria dinamică (heap). Pentru alocare dinamică utilizaţi operatorul new prezentat în subsectiunea Alocare dinamică. Dimensiunea tabloului va fi dată ca parametru al metodei constructor. Astfel, o declaraţie de forma

Stiva s(50); va declara o stivă care poate memora cel mult 50 de elemente. Dacă operatorul new nu poate aloca memorie, atunci va arunca o excepţie. Eliberarea memoriei ocupate de tablou va reveni destructorului ~Stiva(). Rolul destructorului este descris în secţiunea III . Exerciţiul 4

Să se descrie următoarele clase: 1. StivaDouble care memorează numere raţionale double;

2. StivaPunct2D care memorează puncte în plan. 3. StivaPunct3D care memorează pointeri la puncte în spaţiu, punctele

fiind memorate in memoria dinamică (heap). Scrieţi programe demo care să testeze noile clase.

Page 13: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

12

II.D Utilizare de clase În POO apare deseori situaţia când trebuie să utilizăm clase deja existente. De exemplu, este foarte probabil să avem nevoie de o clasă care sa fie responsabilă cu manipularea şirurilor. Cele mai multe implementări ale limbajului C++ pun la dispoziţie astfel de clase. Recomandăm utilizarea clasei string din STL. În cazul în care nu există o astfel de clasă, atunci se poate utiliza cea ataşată acestui curs. Pentru a utiliza clasa string, este suficient să cunoaştem doar interfaţa (inclusă în fişierul my_string.h). Această clasă va fi utilizată în multe din exemplele din acest curs. Cei care sunt interesaţi de modul în care sunt implementate operaţiile acestei clase, pot consulta fişierul my_string.cpp (anexa B). Interfaţa clasei string din fişierul my_string.h (anexa A) este:

class string { public: // Constructori/destructori string(); string(char* new_string); string(const string& new_string); ~string(); // Operatii int size() const; const char* c_str() const; friend string operator+ (const string& lhs, const string& rhs); string& operator+= (const string& a_string); string& operator= (const string& a_string); friend bool operator< (const string& lhs, const string& rhs); friend bool operator> (const string& lhs, const string& rhs); friend bool operator== (const string& lhs, const string& rhs); friend ostream& operator<< (ostream& os, const string& a_string); private: char* char_arr; int string_length; };

Page 14: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

13

Există trei constructori: constructorul implicit creează obiectul şir vid, al doilea constructor creează un obiect şir pornind de la un şir C dat ca parametru, iar cel de-al treilea este constructorul de copiere (creează un obiect string având ca parametru alt obiect string). Operaţia size() întoarce lungimea şirului, iar operaţia c_str() întoarce reprezentarea C a şirului memorat de obiect. Operatorul + întoarce un obiect şir obţinut prin concatenarea a două şiruri, operatorul = realizează operaţia de atribuire cu obiecte şiruri, operatorii relaţionali realizează operaţiile de comparaţie corespunzătoare, iar operatorul << scrie şirul memorat de obiect într-un flux de ieşire (a se vedea secţiunile III (c)(d)). Următoarea secvenţă de cod include câteva exemple de utilizare a clasei string:

#include "my_string.h" ... string s1("123"); // creare sir dintr-un sir C string s2; // creare sir vid s2 = s1; // operatorul de atribuire s1 = s1 + s2; // operatorul de concatenare cout << s1 << endl; // afisarea unui sir string *psa = new string[5]; // creare tablou de obiecte sir psa[0] = "abc"; // apelare constructor si atribuire

Pentru a utiliza clasa string din STL este suficient să înlocuim includerea fişierului my_string.h cu declaraţiile:

#include <string> using namespace std;

Aici putem observa unul din marele avantaje aduse de POO: putem schimba o implementare a unui tip de dată cu altă implementare fără să modificăm programul. Singura condiţie care trebuie îndeplinită este ca interfaţa să fie aceeaşi. Clasa string poate fi utilizată şi în definiţia altei clase. Clasa Cont declarată mai jos descrie un cont bancar. Titularul contului, numărul de cont şi data ultimei depuneri sunt reprezentate ca obiecte string. Remarcăm că

Page 15: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

14

operaţia actualizeazaSold() este declarată privată, ea nefiind accesibilă celorlalte obiecte.

class Cont { public: Cont(); ~Cont(); void depune(double); void extrage(double); void setDob(double); private: string titular; string nrCont; string dataUltOp; double sold; double rataDob; void actualizeazaSold(); }

Exerciţiul 5

Să se scrie un program care să utilizeze clasa string pentru a afişa un număr long în litere. De exemplu, pentru intrarea 7123 va afişa "spatemiiosutadouazecisitrei".

Page 16: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

15

III Programare structurată: de la C la C++ În acest capitol descriem pe scurt o parte din construcţiile particulare ale limbajului C++ utilizate în scrierea programelor structurate.

(a) Structuri Structurile în C++ pot include şi operaţii (a se vedea şi Exerciţiul 1):

struct Data { int zi, ln, an; void init(int o_zi, int o_luna, int un_an); void aduna_an(int n); void aduna_luna(int n); void aduna_zi(int n); void afiseaza(); };

Astfel, o structură este echivalentă cu o clasă în care toţi membrii sunt publici:

class Data { public: int zi, ln, an; void init(int o_zi, int o_luna, int un_an); void aduna_an(int n); void aduna_luna(int n); void aduna_zi(int n); void afiseaza(); };

Funcţiile membre sunt definite la fel ca la clase: void Data::init(int o_zi, int o_luna, int un_an) { zi = o_zi; ln = o_luna; an = un_an; }

Variabilele de tip structură sunt utilizate la fel ca obiectele: Data azi; // echivalent cu struct Data azi; azi.init(4, 3, 2003);

Page 17: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

16

Construcţiile sintactice struct şi class sunt echivalente în sensul că orice tip declarat cu struct poate fi descris cu class şi reciproc. Recomandăm utilizarea construcţiei class ori de câte ori se modelează o clasă în sensul programării orientate-obiect şi a construcţiei struct atunci când se modelează o structură de date obişnuită.

(b) Conversia de tip (type casting) Sintaxa pentru conversia de tip este diferită. Reamintim mai întâi modul în care se scrie explicit o conversie de tip în C:

int n; float x; x = (float) n;

Aceeaşi conversie în C++ este descrisă astfel: int n; float x; x = float(n);

Notaţia funcţională utilizată de C++ este mult mai sugestivă şi, în plus, este în plină concordanţă cu utilizarea constructorilor:

class Complex { public: ... Complex(double); ... private: double re, im; } Complex z; double z = 2.7; z = Complex(y);

Clasa Complex de mai sus este responsabilă cu manipularea numerelor complexe. Pentru a modela incluziunea mulţimii numerelor reale în mulţimea numerelor complexe, este suficient să adăugăm la definiţia clasei un constructor cu un parametru double. Acest constructor va fi apelat ori de câte ori se va dori o conversie din double în Complex.

Page 18: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

17

Exerciţiul 6

Să se completeze o definiţie minimală a clasei Complex. Să se adauge un constructor care să convertească int-uri în obiecte Complex.

(c) Fluxurile de intrare/ieşire standard Ca şi limbajul C, C++ nu include în definiţia sa capabilităţi de tratare a fişierelor. În schimb, orice implementare a limbajului vine împreună cu un pachet de clase, numit iostream, responsabile cu manipularea fluxurilor de intrare/ieşire. Un flux este un tip de date care descrie la nivel abstract un fişier “deştept”. Ierarhia de clase iostream va fi descrisă pe larg într-o secţiune ulterioară. Aici vom discuta cele două obiecte care modelează tastatura ca flux de intrare (istream) şi monitorul ca flux de ieşire (ostream). Obiectul istream responsabil cu introducerea datelor de la tastatură se numeşte cin şi rolul lui este asemănător fişierului standard stdin din C. Citirea (a se citi extragerea) datelor din fluxul cin se realizează de catre operatorul >>:

int a; double x; cin >> a; cin >> x;

Operatorul >> este un operator binar: partea stângă este un flux de intare (aici cin) şi partea dreaptă este numele variabilei (referinţa locaţiei) care va memora data extrasă din flux. Tipul variabilei din dreapta operatorului va determina în mod unic lungimea informaţiei extrase precum şi algoritmul de conversie din format extern în format intern. Extragerea informaţiilor se face urmând principiul FIFO (ca la tipul coadă). Ca şi în C, evaluarea unei expresii produce o valoare. Evaluarea operatorului >> are ca rezultat fluxul obţinut după extragere. Aceasta permite scrierea de expresii ce includ mai multe apariţii ale operatorului >>. Cele două instrucţiuni-expresie de mai sus pot fi unite în una singură:

cin >> a >> x; Deoarece clasa string are definit operatorul >>, citirea unui şir de caractere se realizează la fel de simplu:

string s; cin >> s;

Page 19: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

18

Responsabilă cu alocarea de memorie este definiţia operatorului >> din clasa string. Operatorul >> poate fi utilizat şi pentru citirea şirurilor în format C:

char s[100]; cin >> s;

Obiectul ostream, responsabil cu afişarea datelor pe monitor, este cout şi rolul lui este asemănător cu cel al fişierului standard stdout din C. Afişarea (a se citi introducerea) datelor în fluxul cout este realizată de către operatorul <<:

cout << a; cout << x; cout << s;

Ca şi >>, operatorul << este binar: partea stângă este un flux de ieşire şi partea dreaptă este o expresie. Rezultatul evaluării expresiei este convertit în format extern şi adăugat la flux urmând tot principiul FIFO. Algoritmul de conversie este determinat de către tipul expresiei. Evaluarea operatorului << are ca rezultat fluxul obţinut după introducerea datei. De aceea, instrucţiunile de mai sus pot fi combinate în una singură:

cout << a << x << s; Introducerea unui marcator de sfârşit de linie se face utilizând macroul endl:

cout << “O linie.” << endl << “Alta linie.” << endl;

Exerciţiul 7

În fişierul ex/c2cpp/c2cpp1.cpp este inclus un exemplu de program care citeşte şi afişează un număr şi un şir.

1. Să se execute programul pentru intrarea “Numar>100” şi “Sir>Doua cuvinte.”. Să se explice de ce şirul este afişat doar parţial.

2. Să se completeze programul cu citirea şi afişarea şi altor tipuri de date predefinite.

Page 20: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

19

Exerciţiul 8

Să se scrie un program care citeşte un şir de numere şi afişează suma acestora. Se vor considera trei cazuri: 1. numerele sunt întregi; 2. numerele sunt raţionale; 3. numerele sunt complexe.

(d) Fişierele ca fluxuri Scrierea şi citirea datelor din fişiere este modelată în C++ tot cu ajutorul fluxurilor. Pregătirea unui fişier din care urmează să se citească date este realizată printr-o secvenţă de program de forma:

ifstream fintrare(“intrare.dat”); if (!fintrare) { cout << “Eroare citire date din intrare.dat” << endl; exit(1); } // citire date

sau de forma (echivalentă): fstream fintrare; fintrare.open(“intrare.dat”, ios::in); if(!fintrare) { cout << “Eroare citire date din intrare.dat” << endl; exit(1); } // citire date fintrare.close()

În a doua formă se observă clar că fintrare este un obiect al clasei ifstream şi că open() şi close() sunt două metode ale acestei clase. Recomandăm citirea manualului de C++ pentru a vedea şi celelalte metode ale clasei. Citirea datelor din fişier se poate face tot cu operatorul >>. Dăm ca exemplu citirea unei matrici:

fintrare >> m >> n; for (i = 0; i < m; ++i) for (j = 0; j < n; ++j) fintrare >> a[i,j];

Page 21: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

20

Menţionăm faptul că operatorul >> ignoră spaţiile albe. Pentru o citire mai fidelă din fişier se recomandă utilizarea metodei fintrare.get(). Pregătirea unui fişier în care urmează să se scrie date este realizată printr-o secvenţă de program de forma:

ofstream fiesire(“iesire.dat”); if (!fiesire) { cout << “Eroare scriere date in iesire.dat” << endl; exit(1); } // scriere date

sau de forma (echivalentă): fstream fiesire; fiesire.open(“intrare.dat”, ios::out); if (!fiesire) { cout << “Eroare scriere date in iesire.dat” << endl; exit(1); } // scriere date fiesire.close()

Scrierea datelor în fişier se poate face şi cu operatorul <<. Dăm ca exemplu scrierea unei matrici:

fiesire “Matricea a[“ << m << “][“ << n << “] este:” << endl; for (i = 0; i < m; ++i) { for (j = 0; j < n; ++j) fiesire << a[i,j] << ‘ ‘; fiesire << endl; }

Despre utilizarea fluxurilor pentru manipularea fişierelor în C++ vom discuta mai detaliat într-o secţiune ulterioară. Exerciţiul 9

În fişierul ex/c2cpp/c2cpp10.cpp este inclus un exemplu de program care copie un fişier în alt fişier. Sunt prezentate două variante.

Page 22: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

21

1. Să se execute programul şi să se consulte apoi cele două copii create de program. Să se explice diferenţele dintre ele.

2. Citiţi cu atenţie programul şi înţelegeţi fiecare instrucţiune ce se referă la lucrul cu fişiere.

Exerciţiul 10

Să se scrie un program care citeşte un text dintr-un fişier de intrare şi scrie într-un fişier de ieşire textul citit dar în care toate vocalele sunt dublate.

(e) Declaraţii de variabile În limbajul C declaraţiile şi instrucţiunile sunt separate în cadrul unui bloc/funcţii: în orice bloc/funcţie declaraţiile trebuie să preceadă instrucţiunile. În limbajul C++ această regulă nu mai este valabilă: declaraţiile şi instrucţiunile pot fi intercalate cu condiţia ca o variabila să fie declarată înainte de a fi utilizată într-o instrucţiune. Astfel, în C++ este permis să scriem o secvenţă de forma:

double a; a = 10; cout << a << endl; double b = 2.0l; a = 3.14 * b; cout << a << endl;

Această libertate de scriere a declaraţiilor şi instrucţiunilor poate duce la scăderea lizibilităţii programului. De aceea se recomandă gruparea declaraţiilor într-un singur loc astfel încât să poată fi găsite uşor atunci când este nevoie şi să nu încurce citirea logică a programului. Un exemplu de utilizare a declarţiilor “locale” este oferit de variabilele de control într-o instrucţiune for:

for (int i = 1; i < 5; i++) s += i;

Problema cu declaraţia de mai sus este următoarea: este vizibilă variabila i în codul de după for? Răspunsul la această întrebare depinde de implementare (compilator), şi de aceea se recomandă evitarea utilizării variabilei de control în exteriorul lui for.

Page 23: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

22

(f) Referinţe O altă capabilitate pe care o are în plus C++ este posibilitatea de a lucra cu referinţe. O referinţă este un nume asociat unei locaţii de memorie. Referinţele în C++ trebuie deosebite de pointeri, care sunt variabile cu propriile lor locaţii in care sunt memorate adrese. O declaraţie de referinţă are următoarea sintaxă:

tip& nume = expresie; Printr-o astfel de declaraţie se dă un nou nume ("alias") unei locaţii de memorie deja cunoscute: expresie trebuie să producă prin evaluare o l-valoare (adresa unei locaţii de memorie). Următoarea secvenţă de cod declară o singură variabilă cu două nume: alb şi white:

int alb = 0; int& white = alb;

În continuare această variabilă poate fi referită cu oricare din cele două

nume. Reprezentarea grafică a variabilei este dată în Figura 3. O referinţă trebuie să fie totdeauna iniţializată cu o expresie ce indică obiectul referenţiat.

(g) Apel prin referinţă Există două mecanisme prin care se leagă parametrii actuali ai unui apel de funcţie la parametrii formali: apel prin valoare şi apel prin referinţă. Apelul prin valoare se realizează în modul următor. Presupunem existenţa unei funcţii f(T x) cu parametrul formal x de tip T transmis prin valoare. La apelul f(exp) al funcţiei f cu parametrul actual exp, care este o expresie de tip T, se întâmplă următoarele:

alb

white

int

Figura 3

Page 24: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

23

• se creează o locaţie temporară în care se memorează rezultatul evaluării expresiei;

• numele x "se leagă" la această locaţie; • se execută instrucţiunile funcţiei f. Ori de câte ori x este utilizat ca o l-valoare în corpul funcţiei, el va referi locaţia temporară. Apelul prin valoare este recomandat atunci când nu se doreşte modificarea valorilor parametrilor de către funcţie. Apelul prin referinţă diferă de apelul prin valoare prin faptul că permite modificarea valorilor parametrilor. Presupunem existenţa funcţiei f(T& x) cu parametrul formal x de tip T transmis prin referinţă (indicat de existenţa simbolului &). La apelul f(exp) al funcţiei f cu parametrul actual exp, care este o expresie a cărei valoare defineşte adresa unei locaţii de memorie de tip T (este o l-valoare), se întâmplă următoarele:

• numele x "se leagă" la locaţia referită de exp; • se execută instrucţiunile functiei f. Ori de câte ori x este utilizat ca o l-valoare în corpul funcţiei, el va referi locaţia dată de expresie. În consecinţă, orice modificare asupra lui x în interiorul lui f va afecta direct locaţia referită de exp. Exemplul cel mai concludent de utilizare a apelului prin referinţă este oferit de funcţia care interschimbă valorile a două variabile:

void swap (int& x, int& y) { int aux = x; x = y; y = aux; }

Exerciţiul 11

Să se scrie o funcţie care întoarce atât cmmdc cât şi cmmc a două numere întregi.

Page 25: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

24

(h) Funcţii care întorc referinţe Valoarea întoarsă de o funcţie poate fi de tip referinţă. În acest caz, deoarece rezultatul apelului desemnează o locaţie (o l-valoare), poate fi plasat în partea stângă a unei atribuiri. În exemplul de mai jos, funcţia max() are ca parametri două variabile transmise prin referinţă şi întoarce adresa variabilei ce memorează valoarea maximă:

float& var_max(float& x, float& y) { if (x > y) return x; else return y; }

Prin execuţia instrucţiunii: var_max(a, b) *= 2.0;

variabila ce memora înainte de execuţie o valoare mai mare îşi dublează valoarea. Exerciţiul 12

În fişierul ex/c2cpp/c2cpp4.cpp sunt incluse exemple de utilizare a referinţelor.

1. Executaţi programul şi comparaţi dacă ieşirea dată de program corespunde cu ceea ce aţi intuit despre var_max().

2. Scrieţi o funcţie max_tab(int a[], int n)care întoarce referinţa la componenta ce memorează valoarea maximă din tabloul a.

(i) Funcţii inline Noţiunea de funcţie inline a fost introdusă în C++ pentru a oferi o alternativă la utilizarea excesivă a macro-definiţiilor din limbajul C. Compilatorul înlocuieşte orice apel al unei funcţii inline cu codul acesteia. Utilizarea funcţiilor inline măreşte viteza de execuţie a programului prin evitarea operaţiilor pe stivă, ce au loc în cazul apelurilor de funcţii obişnuite, dar plăteşte drept preţ pentru aceasta creşterea dimensiunii codului executabil. Declaraţia unei funcţii inline este formată din declaraţia unei funcţii obişnuite precedată de cuvântul cheie inline:

Page 26: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

25

inline double ipot(double a, double b) { return sqrt(a*a + b*b); };

Un apel z = ipot(x, y); va fi înlocuit de instrucţinea z = sqrt(x*x + y*y);. Sintaxa pentru definiţia claselor permite declararea implicită a unei funcţii inline:

class Data { public: Data(int o_zi, int o_luna, int un_an); int getZi() { return zi; } // functie inline int getLuna() { return luna; } // functie inline int getAn() { return an; } // functie inline void urm(); void prec(); private: int zi, luna, an; };

Declararea implicită de funcţii membre inline în interiorul claselor scade gradul de lizibilitate a programului şi de aceea se recomandă declararea explicită. Exerciţiul 13

Exemplele de mai sus sunt incluse în fişierul ex/c2cpp/c2cpp5.cpp. Modificaţi programul astfel încât funcţia main() să includă mai multe apeluri ale funcţiilor inline. Compilaţi şi reţineţi dimensiunea codului obiect. Eliminaţi cuvântul cheie inline din definiţiile funcţiilor corespunzătoare, compilaţi şi notaţi dimensiunea noului cod obiect. Comparaţi cele două dimensiuni şi justificaţi cele observate.

(j) Excepţii În secţiunea II.C am văzut cum mecanismul de tratare a excepţiilor din C++ poate fi utilizat pentru implementarea operaţiilor unei clase. În această secţiune ne propunem să prezentăm acest mecanism într-un context mai larg.

Page 27: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

26

Autorul unei biblioteci de programe poate detecta apariţia unei erori dar nu ştie în general cum să procedeze cu ele. Pe de altă parte, utilizatorul bibliotecii ştie ce trebuie să facă la apariţia unei erori dar nu ştie cum să le detecteze. Cele trei instrucţiuni din limbajul C++, throw, try şi catch, sunt proiectate exact pentru a rezolva problemele de mai sus.

• throw. Ori de câte ori realizatorul bibliotecii detectează o eroare (excepţie), o "pasează" utilizatorului cu ajutorul instrucţiunii throw.

• try şi catch. Utilizatorul bibliotecii utilizează catch pentru a "prinde" excepţiile "aruncate" de către funcţiile din biblioteca utilizată. Secvenţa de instrucţiuni susceptibilă de a produce excepţii este inclusă într-un bloc try {...}. Instrucţiunea catch trebuie să fie imediat după un bloc try.

Să presupunem că dorim să scriem o funcţie add(a, b) care întoarce rezultatul corect al unei adunări de întregi întotdeauna. Ştim că tipul int include numere întregi cuprinse între INT_MIN ţi INT_MAX (definite în limits.h). Atunci când se adună două numere int, pot apărea două tipuri de excepţii:

• depăşire inferioară - cele două numere sunt negative şi suma lor este mai mică decât INT_MIN;

• depăşire superioară - cele două numere sunt pozitive şi suma lor este mai mare decât INT_MAX.

La apariţia oricărei dintre cele două situaţii, funcţia add() aruncă o excepţie:

int add(int a, int b) { if ((b > 0) && (a > INT_MAX-b)) throw "Depasire superioara"; if ((b < 0) && (a < INT_MIN-b)) throw "Depasire inferioara"; return a+b; }

Următoarea secvenţă aplică funcţia add() peste numere generate aleator şi arată cum eventualele excepţii aruncate de această funcţie sunt prinse şi prelucrate:

Page 28: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

27

try { srand(unsigned(time(NULL))); for (int i = 1; i < INT_MAX; i++) { temp = (i%2+1)*rand(); x = add(x, temp); temp = -((i+1)%2+1)*rand(); y = add(y, temp); } } catch (char *err) { cout << "EROARE: " << err; }

În exemplul de mai sus, tratarea excepţiei constă doar în afişarea mesajului aruncat de funcţie. Putem rescrie funcţia add() astfel încât blocul catch să cunoască valorile care au produs excepţia. Declarăm mai întâi o structură pentru reprezentarea informaţiei aruncate de către add():

struct Dep { public: Dep(int v1, int v2) {vstg = v1; vdrp = v2;} int vstg; int vdrp; };

Funcţia add() se rescrie astfel: int add(int a, int b) { if ((b > 0) && (a > INT_MAX-b)) throw Dep(a, b); if ((b < 0) && (a < INT_MIN-b)) throw Dep(a, b); return a+b; }

Acum catch poate cunoaşte tipul excepţiei şi valorile care au produs această excepţie:

try { ...

Page 29: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

28

} catch (Dep err) { ... }

Dacă în blocul try pot apărea mai multe tipuri de excepţii, atunci se va adăuga câte o instrucţiune catch pentru fiecare tip. Exerciţiul 14

Să se scrie structuri pentru fiecare tip de excepţie care poate să apară la operaţiile elementare peste int: scădere, înmulţire, împărţire, modul. Să se scrie funcţii care realizează aceste operaţii într-o manieră sigură, adică semnalând apariţia oricărei excepţii. Modificaţi programul demo de mai sus pentru a testa toate operaţiile peste int descrise. Exerciţiul 15

În fişierele ex/c2cpp/c2cpp6_1.cpp şi ex/c2cpp/c2cpp6_2.cpp sunt două exemple de utilizare a excepţiilor cu alt scop decât cel de depistare a erorilor. Citiţi cu atenţie programele şi înţelegeţi logica lor. Executaţi programele şi comparaţi dacă ieşirea programului corespunde cu ceea ce aţi dedus din citirea programului.

(k) Parametri impliciţi În C++ există posibilitatea de a preciza pentru parametrii unei funcţii valori implicite. Astfel, aceeaşi funcţie poate fi apelată cu un număr variabil de parametri; parametrii care nu sunt precizaţi în apel sunt legaţi la valorile implicite. Considerăm următoarea funcţie foarte simplă:

int fct(int x=7, int y=9) { return x+y; }

Funcţia poate fi apelată normal, precizând ambii parametri; de exemplu, execuţia instrucţiunii

cout << fct(2, 3); va afişa 6. Dar poate fi apelată şi cu un singur parametru:

cout << fct(2);

Page 30: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

29

şi acum excuţia instrucţiunii va afişa va afisa 11, deoarece parametrul x va fi legat la paramterul actual 2 iar parametrul formal y la valoarea implicită 9; sau fără nici un parametru:

cout << fct(); caz în care execuţia instrucţiunii va afişa 16, deoarece ambii parametri formali sunt legaţi la valorile implicite (x la 7 şi y la 9).

(l) Supraîncărcarea operatorilor În C, operatorul * poate fi utilizat cu diferite înţelesuri: înmulţire de int-uri, înmulţire de long-uri, înmulţire de float-uri, indirectare etc. Spunem despre operatorul * că este supraîncarcat. În C++, utilizatorul poate adăuga el operatorilor noi înţelesuri. De exemplu, am putea adăuga operatorului * înţelesul de înmulţire dintre un punct din plan şi un scalar. Declaraţia unui astfel de operator este asemănătoare cu cea a unei funcţii:

struct punct { int x,y; }; punct operator * (int a, punct p) { punct q; q.x = a * p.x; q.y = a * p.y; return q; }

Singura deosebire faţă de funcţii este prezenţa cuvântului cheie operator între tipul rezultatului returnat de operator şi numele operatorului. Un alt operator care ar fi util pentru puncte ar fi cel care defineşte o relaţie de ordine parţială peste puncte:

bool operator <=(punct p, punct q) { return (p.x <= q.x && p.y <= q.y); }

Operatorii sunt binari şi sunt utilizaţi în notaţie infixată:

Page 31: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

30

punct a,b; a.x = 1; a.y = 2; b = 5 * a; if (a <= b) cout << “mai mic" << endl; else cout << “incomparabil" << endl;

Se pot supraîncărca şi operatori unari. De exemplu putem defini −p ca returnând punctul (-p.x, -p.y):

punct operator - (punct p) { punct temp; temp.x = -p.x; temp.y = -p.y; return q; }

Exerciţiul 16

Să se supraîncarce operatorii +, − , * peste puncte: p + q întoarce punctul (p.x+q.x, p.y+q.y), p - q întoarce punctul (p.x-q.x, p.y-q.y) etc. Să se scrie un program demo care utilizează aceşti operatori. Un alt operator a cărui supraîncărcare este utilă este cel de scriere într-un flux de ieşire:

ostream& operator << (ostream& o, punct p) { return o << "(" << p.x << ", " << p.y << ") "; };

Exerciţiul 17

Să se supraîncarce operatorul de citire dintr-un flux de intrare >> pentru structura punct. Să se scrie un program demo care să testeze utilizarea celor doi operatori.

(m) Supraîncărcarea funcţiilor Şi numele funcţiilor pot fi supraîncărcate. Cel mai frecvent exemplu de funcţie supraîncărcată este cea care interschimbă valorile a două variabile:

• interschimbarea a două int-uri

Page 32: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

31

void swap(int& x, int& y) { int aux = x; x = y; y = aux; }

• interschimbarea a doua double-uri void swap(double& x, double& y) { double aux = x; x = y; y = aux; }

Funcţia care va fi apelată în fiecare caz particular în parte este determinată de tipul parametrilor:

int m = 5; n = 8; swap(m, n); // este apelata swap(int&, int&) double a = 5.7l, b = 7.5l; swap(a, b); // este apelata swap(double&, double&)

Un alt exemplu de supraîncărcare a numelui de funcţii este oferit de constructori. Reamintim că o clasă poate avea mai mulţi constructori; toţi au numele clasei. Exerciţiul 18

În fişierul ex/c2cpp/c2cpp9.cpp sunt incluse exemple de supraîncărcare a funcţiei swap(). Adăugaţi definiţii ale funcţii swap() şi pentru alte tipuri. Includeţi instrucţiuni care să testeze şi noile funcţii.

(n) Template-uri Exemplul funcţiei swap() pune în evidenţă următorul dezavantaj: trebuie scrisă câte o funcţie swap() pentru fiecare tip. Definirea unui nou tip presupune definirea unei noi funcţii swap(). C++ pune la dispoziţie un mecanism prin care tipul sa fie dat ca parametru. Sintaxa este următoarea:

template <class T> void swap(T& x, T& y) { T aux = x; x = y; y = aux; };

Page 33: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

32

Cuvântul cheie template semnalează faptul că urmează o definiţie parametrizată. Parametrii definiţiei sunt descrişi între parantezele unghiulare. În definiţia de mai sus există un singur parametru, anume T. Cuvântul cheie class care precede T specifică faptul că acest parametru este un tip. Pot exista mai mulţi parametri pentru o definiţie. De asemenea, putem avea parametri int, double, punct etc. Utilizarea funcţiei şablon swap() presupune precizarea explicită sau implicită a tipului:

int m = 8, n = 15; swap<int>(m, n); // echivalent cu swap(m, n); double a = 10.0, b = 20.0; swap<double>(a, b); // echivalent cu swap(a,b);

La întâlnirea unui apel swap<int> compilatorul va genera o funcţie swap_int(int x, int y), la întâlnirea apelului compilatorul va genera o funcţie swap_double(double x, double y) etc. Exerciţiul 19

Fişierul ex/c2cpp/c2cpp8.cpp include definiţia funcţiei swap() parametrizate şi câteva exemple de utilizare a ei. Să se adauge şi alte utilizări şi apoi să se testeze programul. Exerciţiul 20

Fişierul ex/c2cpp/template.cpp include include două versiuni ale algoritmului de sortare naiva. Cele două versiuni sunt exemple de programe generice deoarece gradul de generalitate este foarte mare. Genericitatea s-a obţinut prin declararea tipului pentru componente ca parametru.

1. Comparaţi cele două versiuni şi decideţi care este mai bună. Justificaţi decizia luată.

2. Scrieţi şi alţi algoritmi de sortare generici. 3. Scrieţi o funcţie de căutare binară generică.

(o) Alocare dinamică C++ îşi are proprii operatori de alocare şi eliberare a memoriei dinamice. Vom vedea că aceşti operatori au sarcini speciale atunci când creează sau distrug obiecte. Alocarea memoriei dinamice se face cu operatorul new:

Page 34: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

33

• alocare de variabile simple double *pdbl = new double;

• alocare de tablouri double *dblarr = new double[5]; punct *poligon = new Punct[20];

Dacă alocarea nu s-a putut efectua, atunci new întoarce NULL. De aceea se recomandă totdeauna testarea valorii întoarse de new:

if (pdbl == NULL) throw "Memorie insuficienta"; Eliberarea memoriei ocupate de variabilele dinamice simple se face cu operatorul delete:

delete pdbl; iar eliberarea tablourilor dinamice se face cu operatorul delete []:

delete [] dblarr; delete [] poligon;

Nu este nevoie de precizat numărul de componente ale tabloului; acesta este cunoscut de la creare (sarcina mecanismului de gestionare a memoriei dinamice = heap). Crearea unui obiect dinamic este mai complexă decât cea a unei variabile simple deoarece presupune apelarea unui constructor. De exemplu, crearea obiectului string:

string *pstr = new string("Sir dinamic."); presupune apelarea constructorului string(char *). Acum putem înţelege de ce s-a ales ca numele constructorului să fie identic cu cel al clasei. Numele string comunică operatorului new două lucruri: numele clasei – utilizată la determinarea spaţiului de memorie alocat – şi numele constructorului – utilizat la iniţializarea datelor membre şi nu numai. Crearea unui tablou de obiecte dinamice presupune apelarea constructorului implicit pentru fiecare componentă. Execuţia următoarei instrucţiuni:

string *strarr = new string[10]; provoacă 10 apeluri ale constructorului string(). Analog, distrugerea unui obiect dinamic presupune apelul destructrului (reamintim ca acesta este unic pentu o clasă). Execuţia instrucţiunii:

Page 35: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

34

delete pstr; provoacă apelul destructorului ~string(). Sarcina acestuia este de a elibera spatiul de memorie dinamică ocupat de tabloul de caractere care memorează şirul. Distrugerea unui tablou de obiecte dinamice presupune apelul destructorului pentru fiecare componentă. Exerciţiul 21

Să se modifice implementarea clasei string din my_string.cpp astfel încât constructorii şi destructorii să afişeze câte un mesaj specific ori de câte ori sunt apelaţi. Să se scrie un program demo care să scoată în evidenţă apelurile constructorilor şi destructorilor în cazul obiectelor simple şi a tablourilor.

Page 36: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

35

IV Clase C++

(a) Definiţie O clasă C++ are 4 tipuri de elemente:

• o colecţie de date membre; • o colecţie de functii membre; • nivele de acces ale programului; • un nume. Declararea unei clase se face după una din următoarele scheme:

class ⟨nume_clasa⟩ { ⟨declarare membri privati⟩ public: ⟨declarare membri publici⟩ protected: ⟨declarare membri protejati⟩ private: ⟨declarare membri privati⟩ }

sau struct ⟨nume_clasa⟩ { ⟨declarare membri publici⟩ public: ⟨declarare membri publici⟩ protected: ⟨declarare membri protejati⟩ private: ⟨declarare membri privati⟩ }

Pot exista mai multe secţiuni cu acelaşi nivel de acces. Nu este obligatorie prezenţa tuturor nivelelor de acces şi nici ordinea în care acestea apar. Totuşi, se recomandă ca pentru o clasă să fie cel mult o secţiune pentru ficare nivel de acces şi ordinea lor să fie cea de mai sus: public,

Page 37: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

36

protected, private. Despre nivelul protected vom discuta în secţiunea dedicată derivării claselor. Considerăm ca exemplu clasa care implementează data calendaristică:

class Data { public: Data(int o_zi = zi_curenta(), int o_luna = luna_curenta(), int un_an = an_curent()); ~Data() {} void aduna_zi(int n); void aduna_luna(int n); void aduna_an(int n); Data operator ++(int); // postfix Data& operator ++(); // prefix void set_zi(int zi_noua); void set_luna(int ); void set_an(int an_nou); int get_zi() const; int get_luna() const; int get_an() const; friend ostream& operator << (ostream&, Data&); static Data azi(); private: int zi, luna, an; static int zi_curenta(); static int luna_curenta(); static int an_curent(); bool esteAnBisect(); int nrZileLuna(); int nrZileAn(); };

(b) Datele membre Sunt declarate ca variabile într-o manieră similară celei de la structuri. Exemple de date membre in Data sunt: zi, luna şi an.

Page 38: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

37

(c) Funcţii membre Operaţiile asupra datelor membre sunt realizate de către funcţii declarate în corpul clasei, numite funcţii membre. Exemple de funcţii membre în Data sunt aduna_zi(), aduna_luna(), ..., get_an(). Implementarea acestor funcţii (definiţiile lor) este inclusă în fişierul data.cpp. Se recomandă ca declararea clasei să fie inclusă într-un fişier header iar implementările sa fie incluse într-un fişier .cpp separat. Distingem două tipuri de funcţii membre speciale: constructori şi destructori. În definiţia clasei Data, funcţia membră

Data(int o_zi = zi_curenta(), int o_luna = luna_curenta(), int un_an = an_curent());

este o metodă constructor, iar ~Data();

este o metodă destructor. Constructorii pot fi mai mulţi şi se deosebesc de celelalte metode prin aceea că vor avea tot timpul numele clasei. Constructorul fără parametri se numeşte implicit. Poate exista cel mult un destructor şi acesta va avea tot timpul numele format din numele clasei precedat de caracterul "~". Constructorii sunt apelaţi la crearea obiectelor iar destructorii la distrugerea obiectelor.

(d) Ascunderea informaţiei Ascunderea informaţiei este un mecanism formal prin care se restricţionează accesul utilizatorilor clasei la reprezentarea internă a clasei. Se realizează prin precizarea nivelului de acces la membrii clasei. Pentru început, distingem două nivele de acces: public şi private. Cel de-al treilea nivel, protected, îl vom discuta la secţiunea V.B dedicată derivării V.B .

Un membru public este accesibil de oriunde din program. Un membru private poate fi accesat numai de funcţiile membre sau de prietenii clasei (vor fi definiţi mai târziu). În clasa Data datele membre sunt private (ascunse). Un utilizator al clasei Data nu va cunoaşte modul de reprezentare a acestora; el va putea acţiona

Page 39: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

38

asupra acestora numai prin intermediul funcţiilor membre declarate în secţiunea publică. În afară de datele membre, mai sunt declarate private o serie de funcţii (membre) care ajută la implementarea funcţiilor membre publice sau prietene. Un program care utilizează clasa Data nu va putea include instrucţiuni de forma:

n = azi.nrZileLuna();

(e) Obiectele unei clase Un obiect este o instanţă a unei clase şi este declarat ca o variabilă a tipului definit de clasă. La creearea unui obiect se alocă spaţiu pentru toate datele membre. De exemplu, definiţia

Data azi; alocă spaţiu pentru memorarea zilei, lunii şi anului. Asupra acestora se poate acţiona cu ajutorul metodelor:

azi.set_zi(28); ln = azi.get_luna();

Obiectele pot fi atribuite unul celuilalt. De exemplu, ieri = azi;

este echivalent cu: ieri.zi = azi.zi; ieri.luna = azi.luna; ieri.an = azi.an;

(f) O clasificare a funcţiilor membre Funcţiile membre au diferite roluri asupra datelor membre. Aceste roluri ar putea fi clasificate în:

• manageri: se referă la activitatile de iniţializare (constructorii), atribuire (operatorii =, += de la string), copiere (constructorul de copiere string(const string& new_string)), conversie, eliberare memorie (destructori).

• implementori: furnizează capabilităţi asociate clasei la nivel abstract (sunt incluse în specificarea clasei independent de implementare).

Page 40: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

39

Exemple: Data::aduna_zi(), Data::aduna_luna(), stiva::push(), stiva::pop().

• funcţii ajutătoare: sunt utilizate la descrierea implementorilor. Exemple: Data::nrZileLuna(),Data::nrZileAn().

• funcţii de acces: permit accesul la datele membre. Exemple: Data::set_zi(), Data::get_zi().

(g) Funcţii membre const O funcţie membră poate schimba valorile datelor membre (starea obiectului) sau nu. Dacă o funcţie membră nu modifică nici o dată membră, atunci se poate preciza acest lucru explicit prin adăugarea cuvântului cheie const la prototipul funcţiei. Prezenţa acestui cuvânt cheie previne modificările accidentale ale datelor membre, acestea fiind sesizate de compilator. Exemple: Data::get_zi(), Data::get_luna(), Data::get_an().

(h) Pointerul this Fiecare obiect al unei clase îşi are propriile sale copii ale datelor membre dar există o singură copie pentru fiecare funcţie membră a clasei. De exemplu, obiectele azi şi ieri îşi au propriile copii ale datelor membre zi, luna şi an, dar există o singură copie pentru funcţiile membre aduna_zi(), aduna_luna() etc. Se pune următoarea întrebare: cum sunt legate numele datelor din funcţiile membre la copiile instanţelor clasei? Răspunsul este dat de pointerul this. Implementarea funcţiei membre Data::aduna_zi() poate fi gândită ca fiind:

get_zi_Data (Data *this) { return this->zi; }

Acum, instrucţiunea azi.get_zi() este echivalentă cu get_zi_Data (&azi) iar ieri.get_zi() este echivalentă cu get_zi_Data (&ieri).

Pointerul this poate fi utilizat în descrierea funcţiilor membre:

Page 41: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

40

Data& Data::operator ++() { this->zi++; return (*this); }

Exerciţiul 22

Implementarea operatorului ++ de mai sus nu este completă pentru că nu ţine cont de sfârşitul de lună şi de an. Să se scrie o implementare corectă a acestui operator.

(i) Prietenii unei clase În anumite situaţii particulare, ascunderea informaţiei este prea restrictivă. Sunt funcţii, operatori sau chiar clase care au nevoie de acces la membrii privaţi ai unei clase date. În C++, o clasă îşi poate declara o listă de prieteni. Prietenii unei clase au acces la membrii privaţi ai acesteia (atenţie pe cine îţi alegi prieten!). Un prieten al clasei poate fi o funcţie, un operator sau chiar o altă clasă. Un exemplu îl constituie operatorul << declarat în clasa Data; pentru a scrie datele membre într-un flux de ieşire, are nevoie de accesul direct la datele membre (declarate private):

ostream& operator << (ostream &o, Data &d) { o << d.zi << ' ' << d.luna << ' ' << d.an; return o; }

Un alt exemplu clasic îl constituie implementarea listelor liniare cu liste înlănţuite. Avem două clase: NodLLin - pentru descrierea unui nod al listei înlănţuite, şi LLin - pentru descrierea listei liniare. Funcţiile care implementează operaţiile peste lista liniară trebuie sa aibă acces la informaţiile din noduri. O soluţie mai puţin comodă ar fi să declarăm fiecare funcţie membră a clasei LLin prietenă a clasei NodLLin. Dar este mult mai comod să declarăm întreaga clasă LLin ca fiind prietenă a clasei NodLLin. Mai mult chiar, membrii clasei LLin sunt singurii care au acces la membrii unui nod:

Page 42: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

41

class NodLLin { public: friend class LLin; private: Elt elt; NodLLin *pred, *succ; }; class LLin { public: . . . private: NodLLin *prim, *ultim; . . . };

(j) Membri statici Uneori este necesar ca toate obiectele unei clase particulare să aibă acces la aceeaşi variabilă, fără a avea fiecare copia sa. Un astfel de exemplu este o variabilă care numără câte instanţe ale clasei au fost create în timpul execuţiei programului. O soluţie ar fi să declarăm această variabilă ca fiind globală. Dar în acest caz ea nu ar mai fi specifică clasei respective. Soluţia cea mai potrivită este oferită de datele membre statice. Pentru o dată membră statică există o singură instanţă corespunzătoare clasei, indiferent de numărul obiectelor definite. În exemplul de mai jos:

class A { public: A() { nr_instante++; } ~A() { } int get_nr_inst() { return nr_instante; } private: static int nr_instante; };

clasa A are o singură dată membră statică nr_instante care este incrementată de către constructor. Valoarea acesteia poate fi accesată prin

Page 43: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

42

itermediul funcţiei membre A::get_nr_inst(). Iniţializarea datei membre statice se face printr-o instrucţiune de forma:

int A::nr_instante = 0; Deoarece această dată membră este unică pentru întreaga clasă, iniţializarea ei nu poate fi în responsabilitatea constructorului, care este apelat la crearea fiecărui obiect A. Instrucţiunile:

A a; A b; cout << a.get_nr_inst() << endl; cout << b.get_nr_inst() << endl;

vor produce ieşirea:

2 2

Deoarece funcţia membră A::get_nr_inst() acţionează numai asupra datelor membre statice, ea însăşi poate fi declarată ca funcţie membră statică:

static int get_nr_inst() {return nr_instante; }

(k) Tipul unui membru al clasei Prin declaraţia:

int (*fint)(); se defineşte un pointer fint la o funcţie fără parametri ce întoarce o valoare int. Dacă vom scrie:

fint = Data::get_zi; vom obţine o eroare. De ce? Pentru că un pointer la o funcţie membră trebuie să se potrivească în trei elemente: numărul şi tipurile parametrilor, tipul valorii returnate şi tipul clasei al cărei membru este. Dacă declarăm:

int (Data::*fint)(); atunci atribuirea de mai sus este legală.

Page 44: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

43

(l) Spaţiul de nume si domeniul de vizibilitate al unei clase Fiecare clasă îşi are propriul spaţiu de nume. Numele membrilor au domeniul de vizibilitate format din spaţiul de nume definit de clasă. În exemplul de mai jos:

class Data { public: ... int get_zi() {return zi;} private: int zi, luna, an; }

numele zi a fost utilizat înainte de a-l defini. Acest fapt a fost posibil pentru că domeniul său de vizibilitate este format din întregul spaţiu de nume definit de clasa Data. Dacă într-o funcţie membră se defineşte o variabilă cu acelaşi nume cu cel al unei date membre, atunci data membră devine “ascunsă” în acea funcţie. Ea poate redeveni vizibilă dacă se adaugă la numele ei şi clasa (văzută ca spaţiul de nume) în care a fost definită:

void Data::aduna_luna(int n) { int luna = Data::luna + n; }

Evident, nu recomandăm astfel de soluţii care pot duce la confuzii periculoase. Domeniul de vizibilitate al unei clase poate fi:

• global; • intern unei clase - când este definită în interiroul unei clase în oricare

dintre secţiuni (public, private sau protected); • local - când este definită în interiorul unei funcţii membre sau nemembre. În exemplul de mai jos:

class LLin { public:

Page 45: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

44

class Nod { public: void set_elt(Elt un_elt); . . . private: Elt elt; Nod *leg; }; void insereaza(int, Elt); ... private: Nod *prim; ... };

clasa Nod este declarată în interiorul clasei LLin; domeniul ei de vizibilitate este format din clasa LLin. Astfel are sens sa scriem:

void LLin::insereaza(int k, Elt un_e) { Nod *p = new Nod; p->set_elt(un_e); ... };

Dacă înlocuim instrucţiunea p->set_elt(un_e);

cu p->elt = un_e;

atunci copilatorul va semnala o eroare: numele p->elt nu este vizibil pentru funcţia (). Pentru implementarea metodei Nod::set_elt() trebuie să scriem ceva de forma:

void LLin::Nod::set_elt(Elt un_elt) { ... }

Analog, pentru a declara un obiect Nod în programul principal trebuie să precizăm numele extins al clasei (spatiu de nume + nume clasă):

LLin::Nod n;

Page 46: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

45

(m) Obiecte membre unei clase Obiectele unei clase pot apărea ca date membre ale altei clase. În clasa Student, descrisă mai jos, data membră nume este un obiect string:

class Student { public: Student(char*); Student(char*, int); ~Student() {} private: string nume; int virsta; };

Constructorul clasei Student trebuie să includă iniţializarea obiectelor membre. Deoarece aceasta presupune la rândul ei apelarea de constructori, iniţializarea obiectelor membre este precizată înainte de instrucţiunile din corpul constructorului:

Student::Student(char *un_nume, int o_virsta) : nume(un_nume), virsta(o_virsta) { //nimic }

Ordinea apelării constructorilor pentru definiţia: Student(“Ionescu”,22);

este: string::string(char*) virsta = 22 Student::Student(char*, int)

Deşi virsta este o variabilă int (şi nu un obiect), iniţializarea ei poate fi făcută în aceeaşi manieră cu cea a obiectelor membre. Ordinea apelării constructorilor pentru obiectele membre este dată de ordinea în care acestea sunt declarate în definiţia clasei.

Page 47: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

46

(n) Funcţii membre care întorc referinţe la date membre private Proprietatea de ascundere a informaţiei poate fi violată prin scrierea de funcţii membre care întorc referinţe la datele membre. Considerăm următoarea clasă foarte simplă:

class A { public: A(int x = 0): elt(x) { } int& get_elt() { return elt; } private: int elt; };

Următorul cod este corect şi va produce ieşirea 6: A a(5); (a.get_elt())++; cout << a.get_elt() << endl;

Funcţia get_elt() întoarce o referinţă la locaţia de memorie referită de data membră privată elt. Această funcţie poate fi utilizată pentru a modifica data membră privată. Practic, această dată a devenit într-un fel publică pentru că get _elt() este publică.

(o) Iniţializarea membru cu membru, constructorul de copiere X::X(const X&)

Un obiect poate fi iniţializat prin apelarea unui constructor, dând ca argumente (implicite sau explicite) valorile pentru datele membre:

Data azi(4,4,2000); sau precizând ca valoare iniţială un alt obiect al clasei:

Data data_n = azi; În acest din urmă caz, este apelat automat un constructor X::X(const X&). În cazul clasei Data acesta este generat automat şi este echivalent cu:

Data::Data(const Data& d) { zi = d.zi; luna = d.luna; an = d.luna; }

Page 48: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

47

Acum, iniţializarea de mai sus este echivalentă cu: Data data_n(azi);

În cazul când iniţializarea unui obiect presupune alocare dinamică de memorie, atunci ramâne în sarcina proiectantului clasei să definească explicit constructorul de copiere. De exemplu, pentru a putea scrie ceva de forma:

string s("Programare C++"); string curs = s;

trebuie inclusă în clasa string definiţia explicită a constructorului de copiere:

string::string(const string& new_string) { string_length = new_string.size(); char_arr = new char[string_length + 1]; strcpy(char_arr, new_string.char_arr); }

Dacă există obiecte membre, atunci constructorul trebuie să le iniţializeze pe acestea la început prin apelarea unui constructor:

Student::Student(const& Student un_st) : nume(un_st.nume) { virsta = un_st.virsta; }

(p) Operatorul de atribuire X::operator=(const X&) Asa cum am precizat şi mai sus, obiectele pot fi atribuite unul altuia. Aici vom încerca să explicăm mai în detaliu ce se întâmplă când atribuim un obiect altui obiect. Dacă avem definiţiile:

Data azi(4,4,2000); Data ieri;

atunci pentru a realiza atribuirea: ieri = azi;

următoarea instanţă a operatorului X& X::operator=(const X&) este apelată automat:

Page 49: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

48

Data& Data::operator=(const Data& d) { zi = d.zi; luna = d.luna; an = d.an; }

Ca şi în cazul constructorului de copiere, dacă avem alocare dinamică, atunci acest operator trebuie redefinit:

string& string::operator= (const string& a_string) { if ((*this) != a_string ) { if ( string_length != a_string.size()) { delete[] char_arr; string_length = a_string.size(); char_arr = new char[string_length+1]; } strcpy(char_arr, (char*) a_string ); } return (*this); }

Exerciţiul 23

Descrierea completă a date calendaristice aşa cum a fost ea făcută aici se găseşte în dierctorul ex/c2cpp/data.

1. Să se compileze şi să se testeze progarmul demo. 2. Să se adauge o dată membră numeZi care să memoreze numele zilei

din săptămână (Luni, Marţi etc). Această dată membru va fi memorată dinamic.

3. Ce funcţii membru trebuie modificate pentru a putea manipula noua structura a datei calendaristice? Să se modifice aceste funcţii.

4. Să se adauge noi funcţii membre specifice noii structuri.

Page 50: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

49

V Moştenire Moştenirea este mecanismul prin care elementele specifice (specializate) încorporează structura şi comportarea elementelor generale.

V.A POO: Relaţiile de generalizare şi specializare O generalizare este o relaţie între o clasă generală, numită superclasă sau clasă părinte, şi o clasă specializată a celei generale, numită subclasă sau clasă copil. Generalizarea înseamnă că obiectele clasei copil pot fi utilizate oriunde apar obiecte ale clasei părinte dar nu şi reciproc. Cu alte cuvinte, un obiect copil poate substitui un obiect părinte. Un obiect copil moşteneşte atributele şi operaţiile părintelui. Adesea, are atribute şi operaţii suplimentare dar acest lucru nu este obligatoriu. Specializarea este inversa relaţiei de generalizare. Prezentăm exemplul claselor Student şi Profesor. Proiectarea clasei Student:

Sunt un student: îmi cunosc ID-ul, îmi cunosc numele, ştiu disciplinele pe care le urmez, pot să-mi spun ID-ul, pot să-mi spun numele, pot să mă înscriu la o nouă disciplină.

getId()getNume()addDiscUrmata()

idnumediscUrmata

Student

Figura 4

Page 51: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

50

Proiectarea clasei Profesor: Sunt un profesor:

îmi cunosc ID-ul, îmi cunosc numele, ştiu disciplinele pe care le predau, pot să-mi spun ID-ul, pot să-mi spun numele, pot să predau o nouă disciplină.

Clasele Student şi Profesor au în comun atributele ID şi numele şi acţiunile asupra acestor atribute. Este mult mai economic ca acestea să fie descrise o singură dată într-o clasă separată generală Persoana şi apoi atributele şi operaţiilor acesteia să fie moştenite de cele două clase specializate Student şi Profesor. Generalizăm:

Sunt o persoană: îmi cunosc ID-ul, îmi cunosc numele, pot să-mi spun ID-ul, pot să-mi spun numele.

Specializăm: Sunt un student:

moştenesc atributele şi operaţiile de la Persoana, ştiu disciplinele pe care le urmez, pot să mă înscriu la o nouă disciplină.

getId()getNume()addDiscPredate()examineaza()

idnumediscPredate

Profesor

Figura 5

Page 52: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

51

Sunt un profesor: moştenesc atributele şi operaţiile de la Persoana, ştiu disciplinele pe care le predau, pot să predau o nouă disciplină,

Astfel, între clasa Persoana şi clasa Student apare o relaţie de generalizare/specializare. O relaţie de acelaşi tip are loc între clasa Persoana şi clasa Profesor. Acum cele trei clase formează o ierarhie: clasa Persoana este clasa părinte si clasele Student şi Profesor sunt clase copil (fiice). Această ierarhie este reprezentată grafic în Figura 6:

Figura 6

V.B Derivare in C++ Relaţia de generalizare/specializare este implementată în C++ prin relaţia de derivare. Ierarhia de mai sus este descrisă astfel:

class Persoana { public: Persoana(string = "", string = ""); ~Persoana(); string getNume() const; string getId() const; protected: string id; string nume; };

addDiscUrmata()discUrmata

Student

addDiscPredate()examineaza()

discPredateProfesor

getId()getNume()

idnume

Persoana

Page 53: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

52

class Student : public Persoana { public: Student(string="", string=""); ~Student(); void addDiscUrmata(Disciplina*); private: Disciplina *discUrmate[MAX_nrDiscUrmate]; int nrDiscUrmate; }; class Profesor : public Persoana { public: Profesor(string = "", string = ""); ~Profesor(); void addDiscPredata(Disciplina *); void examineaza(Examen&) const; private: Disciplina *discPredate[MAX_nrDiscPredate]; int nrDiscPredate; };

Notăm că nivelul de protecţie al atributelor id şi nume din clasa Persoana este acum protected în loc de private. Aceasta înseamnă că cele două nume sunt vizibile atât în clasa proprietar Persoana cât şi în clasele mostenitoare Student şi Profesor. Dacă am fi folosit private, atunci ele ar fi fost vizibile numai în clasa proprietară Persoana. Declaraţia

class Student : public Persoana precizează faptul că Student derivează din clasa Persoana. Din acest motiv, clasa Student se numeşte şi clasa derivată iar clasa Persoana clasă de bază. Cuvântul public constituie tipul de derivare si precizează modul în care se propagă nivelurile de protecţie a membrilor:

ce e public în clasa de bază ramâne public în clasa derivată; ce e protected în clasa de bază ramâne protected în clasa

derivată. În afară de public, mai există următoarele tipuri de derivare:

Page 54: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

53

protected: ce e public în clasa de bază devine protected în clasa derivată; ce e protected în clasa de bază ramâne protected în clasa

derivată; private:

ce e public în clasa de bază devine private în clasa derivată; ce e protected în clasa de bază devine private în clasa derivată.

(a) Conversii standard În cazul moştenirii publice au loc următoarele conversii:

• un obiect derivat poate fi convertit implicit la un obiect public de bază; • o adresă a unui obiect derivat poate fi convertită implicit la o adresă

publică a unui obiect de bază; • un pointer la un obiect derivat poate fi convertit implicit la un pointer

public la un obiect de bază. Presupunem că avem următoarea funcţie care afişează id-ul şi numele unei persoane:

void scrie(Persoana& p) { cout << p.getId().c_str() << ' ' << p.getNume().c_str(); cout << endl; }

Putem apela funcţia scrie() cu parametru Profesor: Profesor b("101", "Prof. Popescu"); scrie(b);

În apelul de mai sus are loc o conversie de la clasa derivată Profesor la clasa de bază Persoana. Putem de asemenea scrie:

Persoana a; a = b;

Şi în acest caz avem o conversie de acelaşi tip. Exerciţiul 24

În directorul ex/most/ se găseşte un exemplu de ierarhie descrisă în C++.

Page 55: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

54

1. Citiţi fişierul most.h şi desenaţi ierarhia de clase descrisă. 2. Compilaţi şi testaţi programul demo. 3. Să se adauge instrucţiuni astfe încât fiecare metodă constructor sau

destructor să-şi afişeze numele atunci când este execuatată. 4. Scrieţi diferite programe demo care să evidenţieze ordinea de apelare

a constructorilor şi destructorilor. 5. Programul include şi un exemplu de moştenire multiplă. Care este

clasa care beneficiază de moştenire multiplă? Care sunt datele membre şi funcţiile membre ale ei?

Page 56: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

55

VI Polimorfism Polimorfismul se referă la posibilitatea de a folosi acelaşi nume cu înţelesuri diferite. În C++, polimorfismul poate fi realizat în mai multe moduri.

(a) Polimorfism parametric În subsecţiunile III (l) şi III (m) am văzut cum pot fi supraîncărcate numele operatorilor sau funcţiilor. Deoarece înţelesul corect este ales în funcţie de natura parametrilor, numim acest polimorfism ca fiind parametric.

(b) Suprascriere O operaţie a unei clase copil cu acelaşi prototip (signatură) cu cea a unei operaţii a clasei părinte suprascrie operaţia clasei părinte. Considerăm din nou ierarhia Persoana. Presupunem că dorim să adăugăm o metodă semneaza(). Un student va semna adaugând prefixul "Student" iar un profesor va semna adaugând prefixul "Profesor". Urmează ca fiecare dintre cele trei clase să aibă propria sa metodă semneaza():

class Persoana { . . . void semneaza(); }; class Student : public Persoana { . . . void semneaza(); }; class Profesor : public Persoana { . . . void semneaza(); }; void Persoana::semneaza() { cout << getNume() << endl; }

Page 57: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

56

void Student::semneaza() { cout << "Student " << getNume() << endl; } void Profesor::semneaza() { cout << "Profesor " << getNume() << endl; }

În prezenţa declaraţiilor Persoana p("0000", "Strainu Stefan"); Student st("1111", "Ionescu Ion"); Profesor pr("2222", "Popescu Petru");

instrucţiunile p.semneaza(); st.semneaza(); pr.semneaza();

vor produce ieşirea: Strainu Stefan Student Ionescu Ion Profesor Popescu Petru

În schimb, dacă definim funcţia void semneaza(Persoana* p) { p->semneaza(); };

şi executăm apelurile semneaza(&p); semneaza(&st); semneaza(&pr);

vom avea surpriza să obţinem ieşirea: Strainu Stefan Ionescu Ion Popescu Petru

Aceasta se întâmplă din motiv că parametrii actuali ai funcţiei externe semneaza() sunt convertiţi la tipul de bază Persoana şi de fiecare dată se va apela funcţia membră semneaza() a acestei clase.

Page 58: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

57

Exerciţiul 25

Exemplul de mai sus se găseşte descris complet în directorul ex/override. Compilaţi şi executaţi programul demo. Să se adauge o funcţie membră frecventeazaCurs() care pentru profesor afişează “Predă.” iar pentru student afişează “Ascultă şi ia notiţe.”. Modificaţi programul demo pentru a testa noua funcţie membru.

(c) Funcţii virtuale Problema din subsecţiunea anterioară poate fi rezolvată cu ajutorul funcţiilor virtuale:

class Persoana { . . . virtual void semneaza(); }; class Student : public Persoana { . . . virtual void semneaza(); }; class Profesor : public Persoana { . . . virtual void semneaza(); };

Pentru funcţiile membre virtuale există posibilitatea cunoaşterii la momentul execuţiei a funcţiei corecte ce trebuie apelată. Aceasta se realizează prin memorarea pentru fiecare obiect al clasei a unei tabele de pointeri la funcţiile virtuale. Acum, instrucţiunile

semneaza(&p); semneaza(&st); semneaza(&pr);

vor produce ieşirea corectă: Strainu Stefan Student Ionescu Ion Profesor Popescu Petru

Page 59: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

58

Exerciţiul 26

Exemplul de mai sus se găseşte descris complet în directorul ex/polim. Compilaţi şi executaţi programul demo. Să se adauge funcţie membră frecventeazaCurs() ca în Exerciţiul 25 dar acum declarată virtual. Modificaţi programul demo pentru a testa noua funcţie membru şi a evidenţia efectul declaraţiei virtual. Exerciţiul 27

În directorul ex/c2cpp/most2 se gaseşte descrisă ierarhia din ex/c2cpp/most dar în care unele funcţii sunt virtuale.

1. Care funcţii au fost declarate virtuale? 2. Compilaţi şi executaţi programul demo aşa cum este precizat în

comentarii. Explicaţi efectul funcţiilor declarate virtual. Exerciţiul 28

În directorul ex/con_bancar_v04 se găseşte o ierarhie care descrie conturi bancare.

1. Să se deseneze grafic ierarhia descrisă de fişierul cont.h. 2. Să se compileze şi să se execute programul demo. Care este rolul

functiilor declarate virtual? 3. Să se adauge un nou tip de cont bancar, de exemplu ContDebit. 4. Să se modifice şi să se testeze programul demo astfel încât să

evidenţieze utilizarea noul tip de cont.

Page 60: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

59

VII Clase parametrizate Presupunem că dorim să scriem o clasă Cell care să modeleze activitatea unei celule de memorie. Această clasă va avea două metode:

getVal() - întoarce valoarea memorată în celulă; setVal() - memorează o nouă valoare în celulă.

Celulele pot memora informaţii diferite: caractere, numere întregi, şiruri de caractere etc. O soluţie ar fi crearea mai multor clase: CharCell, IntCell, StringCell etc. Evident, o astfel de soluţie nu este de loc comodă pentru proiectantul claselor. Mult mai simplu ar fi dacă am putea defini clasa Cell într-o manieră parametrizată în care tipul informaţiei memorate în celulă să fie transmis ca parametru. În C++ acest lucru este posibil cu ajutorului "template"-urilor (şabloanelor):

template <class Elt> class Cell { public: Cell(); ~Cell(); Elt getVal(); void setVal(Elt); private: Elt* val; };

Cuvântul cheie template specifică faptul că definiţia care urmează este parametrizată. Parametrii sunt descrişi în interiorul parantezelor unghiulare. Cuvântul cheie class dintre parantezele unghiulare spune că parametrul Elt este un tip. Definiţiile funcţiilor membre trebuie şi ele precedate de o declaraţie "template":

template <class Elt> Cell<Elt>::Cell() { val = new Elt; } template <class Elt>

Page 61: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

60

Cell<Elt>::~Cell() { delete val; } template <class Elt> Elt Cell<Elt>::getVal() { return *val; } template <class Elt> void Cell<Elt>::setVal(Elt oVal) { *val = oVal; };

Acum putem crea celule de char-uri: Cell c; c.setVal('A');

sau celule de int-uri: Cell x; x.setVal(100);

Exemplul complet se găseşte în ex\cell.

(a) Stive parametrizate Tipurile de date stivă, coadă, lista liniară, arbori binari etc pot fi implementate cu ajutorul claselor paramterizate într-un mod elegant. Să presupunem că utilizăm într-un program două tipuri de stive: de char-uri şi stive de float-uri. O soluţie neelegantă ar fi definirea a doua clase: StivaChar si StivaFloat. Dacă am avea mai multe tipuri de stive, atunci ar trebuie să definim tot atâtea clase a căror descrieri sunt asemănătoare. O soluţie parametrizată ne salvează de această muncă de rutină. Presupunem că stivele sunt implementate prin liste înlănţuite. Mai întâi definim clasa pentru nodurile listei:

template <class Elt> class NodStiva { public:

Page 62: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

61

friend class Stiva; private: Elt elt; NodStiva *leg; };

Deoarece în definiţia clasei NodStiva intervine numele clasei Stiva, definiţia de mai sus trebuie precedată de o declaraţie "forward" de tipul:

template <class Elt> class Stiva; Clasa Stiva are definiţia aşa cum ne aşteptam:

template <class Elt> class Stiva { public: Stiva() { virf = NULL; } ~Stiva() { } void push(Elt&); void pop(); Elt top(); private: NodStiva *virf; };

Acum putem declara stive de char-uri: Stiva<char> cst;

sau de float-uri: Stiva<float> fst;

Exemplul complet se găseşte în ex\stiva_p. Exerciţiul 29

Să se scrie implementări C++ ale listelor liniare parametrizate, cozilor paramatrizate, arborilor binari parametrizaţi.

Page 63: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

62

VIII Studiu de caz: modelarea înscrierii la discipline şi examinării

Un profesor poate preda mai multe discipline. Un student poate urma mai multe discipline. Fiecare disciplină este finalizată printr-un examen. Simplificând foarte mult, putem presupune că o facultate se compune dintr-o mulţime de profesori, o mulţime de studenţi, un set de discipline şi un set de examene. Se doreşte scrierea unui program care să modeleze activităţile din facultate.

(a) Proiectarea claselor Substantivele subliniate din descrierea problemei candidează la denumiri de clase. Clasele Student si Profesor au fost definite în secţiunea V dedicată moştenirii.

Clasa Disciplina Stabilim mai întâi responsabilităţile clasei:

Sunt o disciplină: îmi cunosc codul, îmi cunosc denumirea, îmi cunosc programa analitică, îmi cunosc titularul, pot schimba titularul, pot schimba programa analitică.

Din descrierea de mai sus rezultă că denumirea, codul şi programa analitică candidează pentru atribute. Ultimele două acţiuni candidează la metode. Între obiectele titular şi disciplină apare o relaţie de asociere ce va fi discutată mai târziu. Aceasta relaţie este materializată prin metodele setTitular() şi getTitular().

Page 64: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

63

Clasa Examen Stabilim mai întâi responsabilităţile clasei:

Sunt un examen: cunosc disciplina care se examinează, cunosc data susţinerii evaluării, cunosc studenţii înscrişi la examen şi notele lor (după evaluare), ştiu să listez notele, ştiu să înscriu studenţi.

Din descrierea de mai sus rezultă că data şi lista studenţilor înscrişi la examen împreună cu notele lor vor candida la atribute. Între obiectele examen şi disciplină apare o relaţie de asociere ce va fi reprezentată mai târziu. Ultimele două acţiuni candidează la metode.

setTitular()getTitular()setProgAnal()getProgAnal()getCod()getNume()

codnumeprogAnaltitular

Disciplina

Figura 7

Page 65: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

64

Clasa Facultate O facultate (mult simplificată) se compune din studenţi, profesori, discipline şi examene. Astfel, între facultate şi aceste obiecte apare o relaţie de compoziţionalitate reprezentată grafic ca în Figura 9 Atributele şi metodele

clasei Facultate materializează această relaţie de compoziţie (a se vedea Figura 9). Descrierea C++ a relaţiei de compoziţie:

class Facultate { public: Facultate(int=0, int=0, int=0, int=0); //. . . private:

Figura 8

Figura 9

adStud()listeazaNote()setNota()

discdat(stud,nota)[]

Examen

Page 66: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

65

Student* stud; int nrStud; int MAX_nrStud; //. . . };

(b) Relaţii de asociere În această secţiune studiem relaţiile de asociere între obiecte şi/sau între clase. Aceste relaţii sunt reprezentate în Figura 11. Relaţiile sunt reprezentate prin linii care unesc obiecte/clase.

inscrieLaExam()examineaza()listeazaNote()listeazaStud()addStud()addProf()addDisc()addExam()

studprofdiscex

Facultate

Figura 10

Figura 11

Page 67: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

66

Relaţia student-disciplină Un student poate urma mai multe discipline, iar o disciplină poate fi urmată de mai mulţi studenţi; aceste proprietăţi ale relaţiei sunt marcate de cele două steluţe de lângă obiecte. Un student cunoaşte disciplinele pe cale le urmează iar disciplina nu cunoaşte studenţii care o urmează; relaţia poate fi navigată într-un singur sens şi această proprietate ar trebui marcată printr-o sageată ce nu a mai fost inclusă pentru a menţine figura cât mai simplă. Implementarea C++ a relaţiei:

class Student : public Persoana { public: . . . void addDiscUrmata(Disciplina*); private: Disciplina *discUrmate[MAX]; int nrDiscUrmate; }; class Disciplina { ... // nimic despre studentii inscrisi };

Relaţia disciplină-profesor O disciplină are ca titular un singur profesor iar un profesor poate fi titular la mai multe discipline. Disciplina îşi cunoaşte titularul iar profesorul îşi cunoaşte disciplinele pe care le predă; relaţia poate fi navigată în ambele sensuri. Implementarea C++ a relaţiei:

class Disciplina { public: ... void setTitular(Profesor*); Profesor* getTitular() const;

Page 68: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

67

private: ... Profesor* titular; };

class Profesor : public Persoana { public: ... void addDiscPredata(Disciplina *); private: Disciplina *discPredate[MAX]; int nrDiscPredate; };

Relaţia disciplină-examen Pentru o disciplină pot fi programate mai multe examene, iar un examen se dă exact la o disciplină. Examenul ştie disciplina la care se evaluează, iar disciplina nu ştie examenele programate (navigare intr-un singur sens). Implementarea C++ a relaţiei:

class Disciplina { // nimic despre examene (?) };

class Examen { public: //. . . Disciplina* getDisc() const; private: Disciplina* disc; //. . . };

Page 69: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

68

Relaţia student-examen Un student poate susţine mai multe examene iar la un examen sunt evaluaţi mai mulţi studenţi; relaţia poate fi navigată într-un singur sens (examenul ştie studenţii înscrişi pentru evaluare). Implementarea C++ a relaţiei:

class Student : public Persoana { // nimic despre examene (?) };

class Examen { public: void addStudent(Student*); private: . . . struct { Student* stud; int nota; } eval[MAX_nrStudEx]; int nrStud; void setNota(Student*, int); };

(c) Comunicarea între obiecte Obiectele colaborează între ele pentru a realiza o anumită sarcină. Colaborarea se realizează prin transmiterea de mesaje. La primirea unui mesaj, un obiect execută o metodă şi poate transmite mesaje altor obiecte. Diagrama din Figura 12 descrie colaborarea între obiecte pentru a realiza examinarea.

Page 70: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

69

Figura 12

Această colaborare se desfăşoară după următorul scenariu: Cineva (un student, secretara, asitentul etc) transmite către facultate mesajul că doreşte să înscrie studenţi la un examen. Facultatea, ca răspuns la acest mesaj, execută metoda inscrieLaExam(). Pentru fiecare student, facultatea transmite examenului un mesaj. La primirea acestui mesaj, examenul execută metoda addStud() (adaugă studentul):

void Facultate::inscrieLaExamen() { ... for (i=0; i<nrInscr; i++) { ... if (j < nrStud) ex[iEx].addStudent(&stud[j]); else cout << "Id student eronat." << endl; } };

Facultatea transmite profesorului mesajul să desfăşoare examenul: void Facultate::examineaza() { ...

anonim

fii : Sco::Facultate1:

insc

rieLa

Exa

m()

: Sco::Profesor

3: examineaza()

: Sco::Examen

4: s

etN

ota(

)

2: adStud()

5: listeazaNote()

Page 71: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

70

prof[P].examineaza(ex[E]); };

Drept răspuns, profesorul execută metoda examineaza(). Pentru fiecare student, profesorul transmite examenului nota luată de student.

void Profesor::examineaza(Examen& unEx) const { ... for (int i=0; i < unEx.nrStud; i++) { ... unEx.setNota(unEx.exam[i].stud, notaStud); } }

După terminarea examenului, facultatea transmite examenului să listeze studenţii cu notele obţinute:

void Facultate::listeazaNote() const { ... ex[iEx].listeazaNote(); };

Programele C++ se găsesc în directorul ex/facultate_v01. Exerciţiul 30

Să se aducă următoarele modificări la aplicaţia descrisă în ex/facultate_v01. 1. Imbunătăţirea dialogului program-utilizator. 2. Incărcarea şi salvarea datelor din fişiere. 3. Să se reproiecteze clasa “Student” astfel încît studenţii să poată fi

clasificaţi pe ani şi pe grupe. 4. Este foarte probabil ca numărul real de studenţi din facultate să

depaşească pe cel preconizat la înfiinţarea facultăţii. Să se scrie o metodă care redimensionează facultatea.

5. Metoda de înscriere la examene poate fi îmbunătăţită în modul următor: se introduce prima literă a numelui, se listează toţi studentii care încep cu acea literă (inclusiv ID-ul fiecaruia), după care

Page 72: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

71

utilizatorul introduce ID-ul studentului care se înscrie. Dacă e necesar, studenţii vor fi listaţi pe două coloane.

6. Gestionarea dinamică a numărului de discipline predate de către un profesor.

7. Selectarea disciplinei la adăugarea unui examen. 8. De modificat selectarea examenului la înscriere la examinare astfel

încît să se poată selecta dintr-o listă mare (peste 50 examene). Validarea examenului selectat.

9. Adaugarea de operatori << şi >>. 10. Adăugarea unui nivel de securitate. De exemplu, notele la un examen

trebuie puse numai de profesorul titular. 11. Adăugarea unui operator de tipul “prof[id]”. 12. Interfaţa utilizator bazată pe meniu de comenzi. Exemplu de meniu:

1. Creare facultate. 2. Adaugă student. 3. Adaugă profesor. 4. Adaugă disciplină. 5. Adaugă examen. 6. Susţine examen. 7. Afişare note examen. 8. Afişare studenţi. 9. Terminare optiune>

13. Proiectarea OO a unei interfeţe utilizator, versiunea consolă. 14. Adăugarea unei interfeţe grafice. 15. În mod real, disciplinele si profesorii sînt arondaţi catedrelor. Să se

adauge o clasă Catedra şi să se stabilească relaţiile facultate – catedra – discipline - profesor.

Page 73: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

72

Bibliografie H. Schildt: C++ manual complet, Teora, 2000 D. Kaler, M.J. Tobler, J. Valter: C++, Teora, 2000 Bjarne Stroustrup: The C++ Programming Language, Adisson-Wesley, 3nd edition, 1997 Stanley B. Lippman: C++ Primer, Addison Wesley, 1992 ***: Programming Languages - C++, ISO/IEC 14882, First edition, 1998-09-01

Resurse electronice Peter Müller: Introduction to Object-Oriented Programming Using C++ http://www.gnacademy.org/uu-gna/text/cc/material.html Bruce Eckel : Thinking in C++, 2nd Edition, http://www.bruceeckel.com/ *** : Online C++ tutorial, http://www.intap.net/~drw/cpp/index.htm *** : SGI Standard Template Library Programmer's Guide, http://www.sgi.com/tech/stl/ *** : Situl de documentatii electronice al facultatii: http://lib.info.uaic.ro/programming.php?catid=1&subcatid=2 *** : Pagina cursului Programare II de la Facultatea de Informatică, Universitatea “Alexandru Ioan Cuza” Iaşi, http://www.infoiasi.ro/fcs/CS1207.php

Page 74: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

73

A. Fişierul my_string.h #ifndef STRING_H #define STRING_H #include <iostream.h> class string { public: // Constructori/destructori string(); string(char* new_string); string(const string& new_string); ~string(); // Implementori int size() const; const char* c_str() const; // Operatori friend string operator+ (const string& lhs, const string& rhs); string& operator+= (const string& a_string); string& operator= (const string& a_string); friend bool operator< (const string& lhs, const string& rhs); friend bool operator> (const string& lhs, const string& rhs); friend bool operator== (const string& lhs, const string& rhs); friend ostream& operator<< (ostream& os, const string& a_string); private: char* char_arr; int string_length; }; #endif

Page 75: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

74

B. Fişierul my_string.cpp #include <iostream.h> #include "my_string.h" #include <string.h> #include <stdio.h> // string - functiile membre string::string() { char_arr = new char[1]; char_arr[0] = '\0'; string_length = 0; } string::string(char* new_string) { string_length = strlen(new_string); char_arr = new char[string_length + 1]; strcpy(char_arr, new_string); } string::string(const string& new_string) { string_length = new_string.string_length; char_arr = new char[string_length + 1]; strcpy(char_arr, new_string.char_arr); } string::~string() { delete [] char_arr; } int string::size() const { return string_length; }

Page 76: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

75

const char* string::c_str() const { return char_arr; } string operator+ (const string& lhs, const string& rhs) { char *pchar; pchar = new char[lhs.string_length + rhs.string_length + 1]; strcpy(pchar, lhs.char_arr); strcat(pchar, rhs.char_arr); return string(pchar); } string& string::operator+= (const string& rhs) { char *pchar; string_length += rhs.size(); pchar = new char[string_length + 1]; strcpy(pchar,char_arr); strcat(pchar, rhs.char_arr); delete char_arr; char_arr = pchar; return (*this); } string& string::operator= (const string& rhs) { string_length = rhs.string_length; delete char_arr; char_arr = new char[string_length + 1]; strcpy(char_arr, rhs.char_arr); return (*this); } bool operator< (const string& lhs, const string& rhs) { int i = 0;

Page 77: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

76

while ((lhs.char_arr[i] == rhs.char_arr[i]) && (i < lhs.string_length) && (i < rhs.size())) i++; if (i == rhs.string_length) return false; if (i == lhs.string_length) return true; if (lhs.char_arr[i] < rhs.char_arr[i]) return true; return false; } bool operator> (const string& lhs, const string& rhs) { int i = 0; while ((lhs.char_arr[i] == rhs.char_arr[i]) && (i < lhs.string_length) && (i < rhs.size())) i++; if (i == lhs.string_length) return false; if (i == rhs.string_length) return true; if (lhs.char_arr[i] > rhs.char_arr[i]) return true; return false; } bool operator== (const string& lhs, const string& rhs) { int i = 0; while ((lhs.char_arr[i] == rhs.char_arr[i]) && (i < lhs.string_length) && (i < rhs.size())) i++; if ((i == lhs.string_length) && (i = rhs.string_length)) return true; return false; }

Page 78: curs bun de POO

D. Lucanu Programarea Calculatoarelor II POO şi C++

77

ostream& operator<< (ostream& os, const string& a_string) { os << a_string.char_arr; return os; }