Programarea Orientata pe Obiecte In Limbajul C++
Programarea orientată pe obiecte în limbajul C++
-suport curs pentru forma de învatamânt ID –
Introducere
Acest curs se adresează studenților anului II, specializarea Informatică, forma de invatamant la distanță. Modul de prezentare ține cont de particularitățile învățământului la distanță, la care studiul individual este determinant. În timp ce profesorul sprijină studentul prin coordonarea învățării și prin feedback periodic asupra acumulării cunoștințelor și a deprinderilor, studentul alege locul, momentul și ritmul pentru studiu, dispune de capacitatea de a studia independent și totodată își asumă responsabilitatea pentru inițierea și continuarea procesului educațional.
Disciplina Programarea orientată pe obiecte utilizează noțiunile predate la disciplinele Bazele Informaticii, Programare procedurală, discipline studiate în anul I.
Competențele dobândite de către studenți prin însușirea conținutului cursului sunt folosite la disciplinele de specialitate precum Programarea în Java, Tehnici avansate de programare, Proiectarea interfețelor grafice etc. O neînțelegere a noțiunilor fundamentale prezentate în acest curs poate genera dificultăți în asimilarea conceptelor mai complexe ce vor fi introduse în aceste cursuri de specialitate.
Principalele obiective ale disciplinei Programarea orientată pe obiecte sunt:
Cunoașterea conceptelor fundamentale specifice tehnicii de programare orientată pe obiecte;
Capacitatea de a realiza corect un proces de abstractizare a datelor în vederea proiectarii unei aplicații;
Capacitatea de a analiza și de a proiecta o ierarhie de clase necesare pentru dezvoltarea unei aplicatii;
Capacitatea de a implementa corect algoritmul rezultat din etapele de analiza si proiectare.
Cunoașterea mediului de programare C++.
Structura cursului este următoarea:
Unitatea de învățare 1. Introducere în programarea orientată pe obiecte
Unitatea de învățare 2. Clase și obiecte
Unitatea de învățare 3. Tablouri de obiecte. Date și membre statice
Unitatea de învățare 4. Clase derivate. Moștenirea
Unitatea de învățare 5. Supraîncarcarea operatorilor
Unitatea de învățare 6. Operații de intrare/ieșire
Unitatea de învățare 7. Facilitații ale limbajului C++
Fiecare unitate de învatare conține și un formular de feedback, care va trebui completat după parcurgerea acesteia.
Pachet software recomandat:
Orice IDE (Integrated Development Environment) pentru limbajul C++ poate fi folosit, dar pentru a face o alegere, mai puțin costisitoare, de altfel gratuită, vă sugerăm IDE-ul numit Dev-Cpp care se poate descărca de pe site-ul http://www.bloodshed.net/dev/devcpp.html
Nota finală care se va acorda fiecărui student, va conține următoarele componente în procentele menționate:
– examen final 60%
– lucrari practice/ proiect 40%
Unitatea de învățare Nr. 1
Introducere în programarea orientată pe obiecte
Cuvinte cheie: abstractizarea datelor, tip abstract de date, încapsulare, referințe, pointer.
1.1 Introducere
Modalitățile (tehnicile, paradigmele) de programare au evoluat de-a lungul anilor, reflectând trecerea de la programe de dimensiuni reduse, la programe și aplicații de dimensiuni foarte mari, pentru coordonarea cărora sunt necesare tehnici evoluate.
Software-ul de dimensiuni mari, care nu poate fi realizat de o singură persoană, intră în categoria sistemelor complexe, cu o mare diversitate de aplicații.
Situațiile reale și experiențe ale psihologilor au relevat limitele capacității umane în perceperea sistemelor complexe, adică imposibilitatea unei persoane de a percepe și controla simultan un număr mare de entități de informație. De aceea, este esențială descompunerea și organizarea sistemelor complexe, pentru a fi percepute, proiectate sau conduse.
Modul în care este abordată programarea, din punct de vedere al descompunerii programelor, definește mai multe tehnici de programare , care s-au dezvoltat și au evoluat odată cu evoluția sistemelor de calcul.
Programarea procedurală este prima modalitate de programare care a fost și este încă frecvent folosită. În programarea procedurală accentul se pune pe descompunerea programului în proceduri (funcții) care sunt apelate în ordinea de desfășurare a algoritmului. Limbajele care suportă această tehnică de programare prevăd posibilități de transfer a argumentelor către funcții și de returnare a valorilor rezultate. Limbajul Fortran a fost primul limbaj de programare procedurală. Au urmat Algol60, Algol68, Pascal, iar C este unul din ultimele invenții în acest domeniu.
Programarea modulară. În cursul evoluției programării procedurale, accentul în proiectarea programelor s-a deplasat de la proiectarea procedurilor către organizarea datelor, această deplasare reflectând creșterea dimensiunilor programelor. O mulțime de proceduri corelate, împreună cu datele pe care le manevrează, sunt organizate ca un modul. Tehnica de programare modulară decide descompunerea unui program în module, care încorporează o parte din datele programului și funcțiile care le manevrează. Această tehnică este cunoscută ca tehnică de ascundere a datelor (data-hiding). În programarea modulară stilul de programare este în continuare procedural, iar datele și procedurile sunt grupate în module, existând posibilitatea de ascundere a unor informații definite într-un modul, față de celelalte module. Gruparea de date și proceduri în module nu implică și o asociere strictă între acestea.
Programarea orientatã pe obiecte apelează la o modalitate nouă de gândire a unei probleme. Spre deosebire de programarea procedurală care se concentrează pe structuri de date și algoritmi, programarea orientată pe obiecte se concentrează pe definirea de obiecte care modelează problema ce trebuie rezolvată.
În programarea orientată pe obiecte (POO) un program are rolul de a simula stările și activitățile obiectelor lumii reale. Pe lângă structuri de date (care descriu stările obiectelor, atributele acestora) trebuie incluse și metodele asociate obiectelor, adică acele funcții care modifică atributele obiectelor și care descriu comportamentul lor.
1.2 Abstractizarea datelor. Tipuri de date abstracte
Primul pas pe care îl facem când scriem un program care să realizeze diferite operații, este să gasim un model care simplifică realitatea, prin separearea detaliilor care interresează, de cele care nu afectează problemă pe care o rezolvam asfel, datele cu care se lucrează, operațiile, țin de specificul fiecarei probleme tratate.
Acest proces de grupare a datelor și metodelor de prelucrare specifice rezolvarii unei probleme se numește abstractizare.
În cazul dezvoltării unui produs software, abstractizarea se poate defini ca fiind o structurare a unei probleme în entitãti bine precizate prin definirea datelor și a operațiilor asociate. Aceste entitãti combinã date și operatii care sunt necuplate între ele. Procedurile si funcțiile au fost primele douã mecanisme de abstractizare larg rãspândite în limbajele de programare.
Procedura a reprezentat si prima modalitate de ascundere a informatiei (interfața cu procedura). Modulele au reprezentat urmãtorul mecanism de abstractizare prin gruparea procedurilor si funcțiilor ce sunt relationate. În continuare, tipurile de date abstracte au realizat un pas important cãtre dezvoltarea unui software de calitate si au permis trecerea la abordarea orientatã pe obiecte a problemelor.
Un tip de date abstract (TDA) constã dintr-o structurã de date abstractã si o multime de operatii. Interfața unui TDA este definitã de multimea de operatii si reprezintã acea portiune a unui TDA care este vizibilã din exterior. Conceptul de tip de date abstract presupune existenta unui mecanism de separare a specificatiei de implementare. Aceasta înseamnã cã un utilizator al unui TDA trebuie sã cunoascã doar modul de utilizare al
tipului de date, nu si detaliile de implementare.
Un tip de date abstract (TDA) este caracterizat de urmãtoarele proprietãti:
1. exportã un tip;
2. exportã o multime de operatii (furnizând interfața TDA);
3. singurul mecanism de acces la structura de date a tipului este furnizat de operatiile definite în interfatã;
4. axiomele si preconditiile definesc domeniul deaplicație al tipului.
De exemplu, dorim să construim TDA persona. Să presupunem că realizam o aplicație necesara pentru realizarea recesamnatului populatiei, atunci datele care interseaza pentru tipul persoana sunt: nume, prenume, loc_nastere, adresa etc. Daca aplicatia presupune, in schimb, gestiunea intretinerii pentru o asociatie de locatari, atunci pentru tipul persoana sunt necesare si date cum ar fi : spatiul_locuit, nr_persoane etc.
Deci, prin procesul de abstractizare, separăm datele care intereseaza de cele care nu fac obiectul aplicației. Construim, ulterior, un tip abstract de date care inglobeaza o structura de date impreuna ca operatii aupra datelor ( ex: calculul intretinerii pentru o persoana, nr_persoane pe fiecare judet etc.).
Exemplu: TDA_persona
nume
prenume
spatiu_locuit
calcul intretinere; – operatii asupra datelor
Programarea orientatã pe obiecte este programarea cu tipuri de date abstracte, care combinã functionalitãtile acestora pentru rezolvarea problemelor. În programarea orientatã pe obiecte, TDA-urile sunt numite clase.
1.3 Concepte ale programãrii orientate pe obiecte
Conceptele programãrii orientate pe obiecte au apãrut din dezvoltãrile realizate în cadrul limbajelor moderne de programare. Astfel, limbajele orientate pe obiecte au noi structuri care îmbunãtãtesc întretinerea programului si fac ca portiuni mari de program sã fie reutilizabile, conducând astfel la scãderea costului de dezvoltare a produselor software.
Cele sase concepte de bazã ce caracterizeazã programarea orientatã pe obiecte sunt :
Obiectele;
Clasele;
Mesajele
Incapsularea;
Mostenirea
Polimorfismul.
Obiectele
Un obiect poate fi considerat ca fiind o entitate care încorporeazã atât structuri de date (denumite atribute) cât si comportament (actiuni). Obiectele sunt inteligente prin faptul cã realizeazã anumite actiuni si “stiu” cum sã execute aceste actiuni. Inteligenta unui obiect reprezintã o formã de abstractizare prin faptul cã presupune cã un obiect poate executa o actiune si ascunde detaliile referitoare la modul în care se va realiza efectiv actiunea. Obiectele pot fi de tipuri diferite: entitãti fizice, algoritmi, relatii sau subsisteme. Practic, obiectele sunt componente de software reutilizabil care modeleazã elemente din lumea realã.
Pentru tipul de date abstract persoana definit mai sus un exemplu de obiect poate fi definit astfel :
Popescu Ion, 34, 56 m2 .
Clasele
Clasele desemnează o colecție de obiecte (de natură materială sau spirituală) care au în comun faptul că pot fi caracterizate similar din punct de vedere informațional și comportamental.
Este evident faptul că identificarea unei clase este în mod normal, rezultatul unui demers cognitiv care presupune caracterizarea unui obiect prin însușirile lui (informaționale și comportamentale) care îi definesc apartenența la o anumită clasă de obiecte. Așadar, conceptul de clasă adună laolaltă datele și metodele de prelucrare a acestora.
O clasă reprezintă de fapt o implemntare a tipului abstract de date. O declarare a unei clase definește un tip nou care reunește date și funcții. Acest tip nou poate fi folosit pentru a declara obiecte de acest tip, deci un obiect este un exemplar (o instanță) a unei clase.
Mesajele
Obiectele pot comunica între ele prin intermediul mesajelor. Trimiterea unui mesaj care cere unui obiect sã aplice o anumitã actiune numitã metodã este similarã apelului de procedurã din limbajele de programare proceduralã. În programarea orientatã pe obiecte se considerã cã obiectele sunt autonome si pot comunica între ele prin interschimb de mesaje. Obiectele reactioneazã atunci când primesc mesaje, aplicând o anumitã metodã, de exemplu. Ele pot refuza executarea metodei respective dacã, de exemplu, obiectului care este apelat nu i se permite sã execute metoda cerutã.
Un mesaj este o cerere adresatã unui obiect pentru a invoca una din metodele sale. Astfel, un mesaj conține numele metodei și argumentele metodei.
Exemplul : calculul intreținerii pentru obiectul Popescu Ion.
Încapsularea
Înțelegerea acestui principiu presupune două nivele de abordare.
Ca metodă de concepție, încapsularea se referă la capacitatea de a separa aspectele externe ale unui obiect (interfața), accesibile altor obiecte, de aspectele implementaționale, interne obiectului, care sunt ascunse față de celelalte obiecte. Utilizatorul unui obiect poate accesa doar anumite metode ale acestuia, numite publice, în timp ce atributele și celelalte metode îi rămân inaccesibile (acestea se numesc private).
Încapsularea este foarte importantă atunci când dorim să schimbăm implementarea anumitor metode (cu scopul de a optimiza un algoritm sau de a elimina posibile erori). Încapsularea ne va împiedica să modificăm toate caracteristicile obiectului iar aplicațiile care utilizează obiectul nu vor avea de suferit deoarece protocolul de comunicație al obiectului moștenit de la interfața clasei (rezultatul încapsulării ca metodă de concepție) nu s-a schimbat.
Ca implementare, la nivelul unui limbaj de programare, încapsularea este asigurată de exigențele sintactice specifice.
Moștenirea
Mecanismul derivarii permite crearea facila de noi clasa, care preiau caracteristicile unor clase de baza, deja definite. Derivarea are ca obiectiv reutilizarea soft-ului, prin folosirea uneor funcții deja scrise pentru clasele existente și eliminarea redundanței descrierilor, în cazul claselor care au elemente comune, funcții sau date. Acest concept este prezentat în detaliu în moulul 3.
Polimorfismul
Termenul polimorfism se referã la comportamente alternative între clase derivate înrudite. În cazul în care mai multe clase mostenesc atributele și comportamentele unei clase de bazã, pot apare situații în care comportamentul unei clase derivate ar trebui sã fie diferit de cel al clasei de bazã sau de cel al clasei derivate de tip frate (de pe acelasi nivel). Aceasta înseamnã cã un mesaj poate avea efecte diferite în funcție de clasa obiectului care primeste mesajul.
De exemplu să considerãm trei clase: clasa de bazã Fisier si clasele derivate FisierASCII si FisierBitmap care mostenesc toate atributele și comportamentele clasei Fisier cu excepția comportamentului Tipãrire.
Un mesaj care va activa comportamentul Tipãrire al unui obiect al clasei Fisier poate determina afisarea atributelor mãrime fisier, tip fisier și data/ora creãrii/ultimei modificãri a fisierului. Același mesaj trimis unui obiect al clasei FisierASCII va determina afișarea textului din fisier, în timp ce dacã va fi trimis unui obiect al clasei FisierBitmap va determina execuția unui program de afișare graficã.
1.4 Elemente întroductive ale programãrii orientate pe obiecte în limbajul C++
Limbajul C++ este unul dintre cele mai utilizate limbaje de programare orientate pe obiecte; compilatoare, biblioteci și instrumente de dezvoltare a programelor C++ sunt disponibile atât pentru calculatoare personale cât și pentru cele mai dezvoltate sisteme și stații de lucru.
Limbajul C++ este o versiune extinsă a limbajului C elaborata de către B. Stroustrup în anul 1980 în laboratoarele Bell din Murray Hill, New Jersey. Extensiile dezvoltate de Stroustrup pentru limbajul C++ permit programarea orientată pe obiecte, păstrând eficiența, flexibilitatea și concepția de bază a limbajului C. Numele inițial a fost “C cu clase”, numele de C++ fiindu-i atribuit în anul 1983.
Scopul pentru care a fost creat C++ este același cu scopul pentru care este abordată în general programarea orientată pe obiecte: dezvoltarea și administrarea programelor foarte mari. Chiar dacă superioritatea limbajului C++ este evidentă în cazul dezvoltării programelor foarte mari, nu există limitări în a fi folosit în orice fel de aplicație, deoarece C++ este un limbaj tot atât de eficient ca și limbajul C.
De la apariția sa, C++ a trecut prin trei revizii, în 1985, 1989 și ultima, prilejuită de definirea standardului ANSI pentru acest limbaj. O primă versiune a standardului a fost publicată în anul 1994, iar următoarea versiune este încă în lucru.
În general, limbajul C++ prevede mai multe facilități și mai puține restricții decât limbajul C, astfel încât majoritatea construcțiilor din C sunt legale și au aceeași semnificație și în C++.
În acest capitol sunt prezentate unitar și concis conceptele de bază în programarea C++, atât cele care sunt preluate din limbajul C cât și cele nou introduse.
Operatii de intrare/iesire. Stream-uri
Cel mai scurt program C++ este:
main(){ }
Acesta definește o funcție numită main (), care nu primește nici un argument, nu execută nimic și nu returnează nici o valoare.
Dacă se dorește ca programul să scrie un mesaj la consolă (de tipul Primul program in C++!), pe lângă utilizarea funcției obișnuite din limbajul C (funcția printf()), în C++ se poate utiliza și o funcție de scriere la ieșirea standard (cout). Această funcție este funcția operator de scriere << care este definită în fișierul antet iostream.h. Un astfel de program este următorul:
#include <iostream.h>
void main(){
cout << “Primul program in C++!”<< endl;
}
Prima operație de scriere afișează la consolă șirul, Primul program in C++!, iar următoarea (endl) introduce caracterul de linie nouă. Operația de citire de la tastatură poate fi realizată în C++ printr-o instrucțiune care folosește funcția operator de citire >>. De exemplu, în instrucțiunile care urmează se citește de la tastatură un număr întreg:
int i;
cin >> i;
Exemplul P1.1 prezintã un exemplu de utilizare a operatiilor de intrare/iesire în limbajul C++.
// fisierul sursa P1_1.cpp
#include <iostream.h>
void main()
{
int a, b;
float m;
cout << "\n Introduceti un numar intreg a = ";
cin >> a;
cout << "\n Introduceti un numar intreg b = ";
cin >> b;
m = (float) (a+b)/2;
cout << "\n Media aritmetica este " << m;
}
Programul P1.1 citeste douã numere întregi, a si b si calculeazã media lor aritmeticã, m, pe care o afiseazã pe ecran. Citirea celor douã numere se poate realiza si cu o singurã operatie de citire asa cum se observã în programul P1_2
Exemplul P1_2
// fisierul sursa P1_1_v2.cpp
#include <iostream.h>
void main()
{
int a, b;
float m;
cout << "\n Introduceti doua numere intregi: ";
cin >> a >> b;
m = (float) (a+b)/2;
cout << "\n Media aritmetica este " << m;
}
Sintaxa operatiei de intrare (citire):
cin >> var_1 >> var_2 >> … >> var_n;
Se citesc de la dispozitivul de intrare valorile variabilelor var_1, var_2, …, var_n.
Sintaxa operatiei de iesire (scriere, afisare):
cout << expr_1 << expr_2 << … << expr_p;
Se afiseazã pe dispozitivul de iesire valorile expresiilor expr_1, expr_2, …, expr_p. Expresiile pot fi, de exemplu, expresii aritmetice, variabile sau pot contine text, care este marcat între ghilimele (“…”). Textul poate contine secvente de tipul \n (trecere la linie nouã), \t (afisarea se face la dreapta în pozitia datã de tab), \a (avertizare sonorã) etc.
1.5 Tipuri de date
În C++ sunt definite următoarele tipuri fundamentale:
Tipuri de întreg, pentru definirea numerelor întregi de diferite dimensiuni (ca număr de octeți ocupați în memorie):
short int 2 octeți
int 2 sau 4 octeți
long 4 sau 8 octeți
Tipuri de numere flotante, pentru definirea numerelor reale (reprezentate ca numere cu virgulă flotantă):
float 4 octeți
double 8 octeți
long double 12 sau 16 octeți
Aceste tipuri sunt denumite împreună tipuri aritmetice. Pentru tipurile întreg, există variante de declarație cu semn (signed) și fără semn (unsigned).
Tipul caracter, pentru definirea caracterelor
char 1 octet
Tipul void specifică o mulțime vidă de valori. Nu se poate declara un obiect cu acest tip, dar acest tip poate fi utilizat în conversii de pointeri și ca tip de returnare al unei funcții
Pe langa tipurile de date fundamentale enumerate, se pot defini conceptual un număr infinit de tipuri derivate pornind de la tipurile fundamentale. Tipurile derivate sunt:
• tablouri de obiecte,
• pointeri la obiecte,
• referințe,
• funcții,
• constante simbolice,
• clase, structuri, uniuni,
• pointeri la membrii claselor.
În continuare se vor prezenta primele cinci tipuri derivate, iar celelate vor fi introduse pe parcursul secțiuniunilor următoare.
Tablouri de obiecte
Un tablou (array) de obiecte poate fi construit din obiecte dintr-un tip fundamental (cu excepția tipului void), din pointeri, din enumerații sau din alte tablouri. În traducere, pentru array se mai întâlnesc termenii vector și matrice. În acest text sunt folosiți termenii tablou (pentru array multidimensional) și vector (pentru array unidimensional).
Declarația: T D[expresie] introduce un tablou de obiecte de tipul T, cu numele D și cu un număr de elemente al tabloului dat de valoarea expresiei, care trebuie să fie de tip constant întreg. Pentru valoarea N a acestei expresii, tabloul are N elemente, numerotate de la 0 la N-1.
Un tablou bidimensional se poate construi printr-o declarație de forma:
T D[dim1][dim2];
și reprezintă dim1 tablouri unidimensionale, fiecare de dimensiune dim2. Elementele tabloului bidimensional se memorează cu valori succesive pentru indicele din dreapta astfel:
D[0][0], D[0][1], … D[0][dim2-1],
D[1][0], D[1][1], … D[1][dim2-1],……….
D[dim1-1][0], D[dim1-1][1],… D[dim1-1][dim2-1].
Într-un mod asemănător se pot construi tablouri multidimensionale, cu o limitare a numărului de dimensiuni care depinde de implementare.
Pointeri
Pentru majoritatea tipurilor T, T* este un tip denumit “pointer la T”, adică o variabilă de tipul T* memorează adresa unui obiect de tipul T.
Operația fundamentală asupra unui pointer este operația de dereferențiere (dereferencing), adică accesarea obiectului a cărui adresă o reprezintă pointerul respectiv. Operatorul de dereferențiere este operatorul unar *. De exemplu:
char c1 = ‘a’; // variabila c1
char* p1 = &c1; // p memorează adresa lui c1
char c2 = *p1; // dereferentiere, c2 = ‘a’;
Operatorul & este operatorul adresă, care se utilizează pentru a obține adresa unei variabile.
Tipul void* este folosit pentru a indica adresa unui obiect de tip necunoscut.
Asupra pointerilor sunt admise unele operații aritmetice. De exemplu, se consideră un vector de caractere dintre care ultimul este caracterul 0 (se mai numește șir de caractere terminat cu nul). Pentru calculul numărului de caractere se pot folosi operații cu pointeri astfel:
int strlen(char* p){
int i = 0;
while (*p++) i++;
return i;
}
Funcția strlen() returnează numărul de caractere ale șirului, fără caracterul terminal 0, folosind operația de incrementare a pointerului și operația de dereferențiere pentru a testa valoarea caracterului. O altă implementare posibilă a funcției este următoarea:
int strlen(char* p){
char* q = p;
while (*q++);
return q-p-1;
}
În C++, ca și în limbajul C, pointerii și tablourile sunt puternic corelate. Un nume al unui tablou poate fi folosit ca un pointer la primul element al tabloului. De exemplu, se poate scrie:
char alpha[] = “abcdef”;
char* p = alpha;
char* q = &alpha[0]; // p = q
Rezultatul aplicării operatorilor aritmetici +, -, ++, – asupra pointerilor depinde de tipul obiectului indicat. Atunci când se aplică un operator aritmetic unui pointer p de tip T*, se consideră că p indică un element al unui tablou de obiecte de tip T; p+1 va indica următorul element al tabloului, iar p-1 va indica elementul precedent al tabloului. Acest lucru înseamnă că valoarea lui p+1 este cu sizeof(T) octeți mai mare decât valoarea lui p.
Referințe
O referință (reference) este un nume alternativ al unui obiect. Utilizarea principală a referințelor se face pentru specificarea argumentelor și a valorilor returnate de funcții, în general, și pentru supraîncărcarea operatorilor în special. Notația X& înseamnă referință la un obiect de tipul X. De exemplu:
int i = 1;
int& r = i; // r și i se referă la aceeași entitate
int x = r; // x = 1
r++; // i = 2;
Implementarea obișnuită a unei referințe se face printr-un pointer constant care este dereferențiat de fiecare dată când este utilizat.
Așa cum se poate observa, pentru definirea unei referințe se folosește operatorul adresă &, dar diferă tipul construcției în care este folosit. De exemplu:
int a = 5;
int* pi = &a; // & calculează adresa;
// pi este adresa lui a
int& r = a; // & introduce o referinta;
// r este o referință (alt nume) pt. a
O referință este utilizată ca argument pentru o funcție care poate să modifice valoarea acestui argument. De exemplu:
void incr(int& x) {x++;}
void f(){
int i = 1;
incr(i); // i = 2;
}
O altă utilizare importantă a referințelor este pentru definirea funcțiilor care pot fi folosite atât ca membru drept cât și ca membru stâng al unei expresii de asignare. De exemplu:
#include <iostream.h>
int& fr(int v[], int i){
return v[i];
}
void main(){
int x[] = {1,2,3,4};
fr(x,2) = 7;
cout <<fr(x,0)<<fr(x,1)<<fr(x,2)<< fr(x,3)<<endl;
}
La execuția acestui program se obține mesajul:
1 2 7 4
Deoarece valoarea returnată de funcție este referința (numele) unui element al vectorului, acesta poate fi modificat prin folosirea funcției fr() ca membru stâng al egalității.
Funcții
Funcțiile sunt tipuri derivate și reprezintă una din cele mai importante caracteristici ale limbajelor C și C++. Forma generală de definire a unei funcții este:
tip_returnat nume_func(tip1 arg1,tip2 arg2,……,tipn argn) {
//corpul funcției
}
Funcția cu numele nume_func returnează o valoare de tip tip_returnat și are un număr n de argumente formale declarate ca tip și nume în lista de argumente formale. Argumentele formale din declarația unei funcții se mai numesc și parametrii funcției. Dacă o funcție nu are argumente, atunci lista din parantezele rotunde este vidă. Notația din C: f(void) este redundantă.
O funcție este un nume global, dacă nu este declarată de tip static. O funcție declarată static are domeniul de vizibilitate restrâns la fișierul în care a fost definită.
Corpul funcției este propriu ei și nu poate fi accesat din afara acesteia (nici printr-o instrucțiune goto). Corpul unei funcții este o instrucțiune compusă, adică o succesiune de instrucțiuni și declarații incluse între acolade. În corpul funcției se pot defini variabile, care sunt locale și se memorează în segmentul de stivă al programului. Dacă nu sunt declarate static, variabilele locale se crează la fiecare apel al funcției și se distrug atunci când este părăsit blocul în care au fost definite. Nu se pot defini funcții în interiorul unei funcții. Argumentele formale ale unei funcții sunt considerate variabile locale ale funcției, ca orice altă variabilă definită în funcția respectivă.
Dacă o funcție nu are de returnat nici o valoare, atunci tip_returnat din declarația funcției este tipul void și nu este necesară o instrucțiune de returnare (return) în funcție. În toate celelalte cazuri, în corpul funcției trebuie să fie prevăzută returnarea unei variabile de tipul tip_returnat, folosind instrucțiunea return. Dacă în definiție nu este prevăzut un tip_returnat, se consideră implicit returnarea unei valori de tip întreg.
Prototipurile funcțiilor. Pentru apelul unei funcții este necesară cunoașterea definiției sau a prototipului acesteia. Prototipul unei funcții este de forma:
tip_returnat nume_func(tip1 arg1,……., tipn argn);
Numele argumentelor formale sunt opționale în prototipul unei funcții. Prototipurile permit compilatorului să verifice tipurile argumentelor de apel și să semnaleze eroare la conversii ilegale. Spre deosebire de limbajul C, unde este admisă și simpla declarație a numelui funcției (fără tipurile argumentelor de apel), utilizarea prototipurilor este obligatorie în C++. De exemplu:
double f2(int, double); // prototip funcție f2
double f3(int a, double f){ // definiție funcție f3
/*.…….*/
double t = f/a;
return t;
}
void fp(){
double r1 = f1(7, 8.9); // eroare,
// identificator nedeclarat
double r2 = f2(7, 8.9); // corect, fol. prototipul
char str[] = "abcde";
double r3 = f3(7, str); // eroare de tip argument
}
double f1(int a, double f) {
/*……..*/
double t = a + f;
return t;
}
double f2(int a, double f) { // definiție funcție f2()
/*.……..*/
double t = a*f;
return t;
}
La compilare apare o eroare datorită apelului funcției f1(), care nu este definită, nici declarată prin prototip în domeniul funcției apelante fp() și o eroare datorată apelului funcției f3() cu un argument (argumentul al doilea) care nu poate fi convertit la tipul argumentului formal.
Transferul argumentelor funcțiilor. La apelul unei funcții, argumentele de apel (se mai numesc și argumente reale sau efective) inițializează argumentele formale
din declarația funcției, în ordinea din declarație. Argumentele unei funcții se pot transfera în două moduri: apelul prin valoare și apelul prin referință.
În apelul prin valoare se copiază valoarea argumentului real în argumentul formal corespunzător al funcției. În acest caz, modificările efectuate asupra argumentului funcției nu modifică argumentul real.
În apelul prin referință este accesată direct variabila din argumentul real transmis funcției, care poate fi deci modificată. Ca exemplificare, se definește o funcție swap() care realizează intershimbul între valorile a două variabile. Dacă nu se folosesc referințe, argumentele de apel ale funcției trebuie să fie pointeri la variabilele respective. Pointerii, ca argumente de apel, nu vor fi modificați, dar variabilele indicate de aceștia pot fi modificate. Funcția swap() cu argumente pointeri arată astfel:
void swap(int* x, int* y){
int t;
t = *x; // dereferentiere
*x = *y;
*y = t;
}
Aceeași funcție swap(), folosind argumente de tip referință, arată astfel:
void swap(int& x, int& y){
int t;
t = x;
x = y;
y = t;
}
Se poate observa perfecta simetrie între cele două implementări și că, în mod evident, referința folosește adresa variabilei pentru a o putea modifica (deci un pointer). Dar, în cazul referințelor, pointerul și defererențierea sunt ascunse, programatorul nu trebuie să le prevadă explicit, programul rezultat este mai concis și mai clar.
Referințele sunt deosebit de utile în apelul funcțiilor ale căror argumente sunt obiecte de dimensiuni mari și copierea lor în argumentele formale (plasate în segmentul de stivă al programului) ar fi foarte ineficientă.
Argumente implicite ale funcțiilor. Se întâmplă frecvent ca o funcție să aibă un număr mai mare de argumente decât sunt necesare în cazurile simple dar frecvente de apel. Dacă nu este necesar să fie transmisă întotdeauna valoarea reală a unui argument și acesta poate lua, de cele mai multe ori, o valoare implicită, atunci în declarația funcției se prevede o expresie de inițializare a acestui argument, iar din apel poate să lipsească valoarea argumentului corespunzător.
De exemplu, o funcție pentru stabilirea datei calendaristice, care prevede valori implicite pentru argumentele luna și an:
struct data{
int zi;
int luna;
int an;
} g_data;
void setdata(int zi, int luna=9, int an =1999){
g_data.zi = zi;
g_data.luna = luna;
g_data.an = an;
}
void main(){
setdata(15); // 15 9 1999
setdata(21,7); // 21 7 1999
setdata(20,1,2000); // 21 1 2000
}
Numai argumentele de la sfârșitul listei pot fi argumente implicite. De exemplu, este eronată următoarea declarație:
void setdata(int zi, int luna=9, int an); // eroare
Constante simbolice
O constantă simbolică (sau constantă cu nume) este un nume a cărui valoare nu poate fi modificată în cursul programului. În C++ există trei modalități de a defini constante simbolice:
• Orice valoare, de orice tip care poate primi un nume, poate fi folosită ca o constantă simbolică prin adăugarea cuvântului-cheie const în declarația acesteia.
• Orice nume de funcție sau de tablou este o constantă simbolică.
• O enumerație definește o mulțime de constante întregi.
De exemplu, următoarele declarații introduc constante simbolice prin folosirea cuvântului-cheie const:
const int val = 100;
const double d[] = {1.2, 2.8, 9.5};
Deoarece constantele nu pot fi modificate, ele trebuie să fie inițializate în declarație. Încercarea de modificare ulterioară este detectată ca eroare în timpul compilării:
val++; // eroare
d = 200; // eroare
Cuvântul-cheie const modifică tipul obiectului, restricționând modul în care acesta poate fi folosit.
1.6 Operatori specifici C++
Majoritatea operatorilor C++ sunt preluați din limbajul C, cu aceeași sintaxă și reguli de operare. În plus față de operatorii C, în C++ mai sunt introduși următorii operatori:
• operatorul de rezoluție (::)
• operatorii de alocare-eliberare dinamică a memoriei new și delete.
Operatorul de rezoluție
Operatorul de rezoluție (::) este folosit pentru modificarea domeniului de vizibilitate al unui nume. Pentru acest operator (scope resolution operator), în traduceri se mai întâlnesc termenii de operator de domeniu sau operator de acces. Operatorul de rezoluție permite folosirea unui identificator într-un bloc în care el nu este vizibil. Dacă operatorul de rezoluție nu este precedat de nici un nume de clasă, atunci este accesat numele global care urmează acestui operator. De exemplu:
int g = 10;
int f(){
int g = 20;
//…………………
return ::g;
}
void main(){
cout << f() << endl; // afiseaza 10
}
În acest exemplu operatorul de rezoluție a fost folosit pentru a accesa variabila globală g, ascunsă de variabila locală cu același nume din funcție. Deoarece utilizarea cea mai extinsă a operatorului de rezoluție este legată de utilizarea claselor, el va fi reluat pe parcursul secțiunilor următoare.
Operatorii new și delete
În limbajul C se pot aloca dinamic zone în memoria liberă (heap) folosind funcții de bibliotecă (de exemplu, malloc(), calloc(), realloc()) și se pot elibera folosind funcția free(). La aceste posibilități, care se păstrează în continuare în C++, se adaugă operatorii de alocare și eliberare dinamică a memoriei, new și delete. Acești operatori unari prezintă avantaje substanțiale față de funcțiile de alocare din C și de aceea sunt în mod evident preferați în programele scrise în C++.
Pentru alocarea unei singure date (obiect), operatorul new are următoarea formă generală:
tip_data* p = new tip_data(initializare);
unde tip_data este un tip de date predefinit sau definit de utilizator (clasă), p este pointerul (adresa de început) a zonei alocate în memoria liberă, returnat la execuția operatorului new, iar initializare este o expresie care depinde de tipul datei și permite inițializarea zonei de memorie alocate. Dacă alocarea nu este posibilă, pointerul returnat este NULL.
Forma de utilizare a operatorului new pentru alocarea unui vector de date (tablou unidimensional) de dimensiune dim, este următoarea:
tip_data* p = new tip_data[dim];
La alocarea unui vector nu se poate transmite o expresie de inițializare a zonei de memorie alocată.
Operatorul delete eliberează o zonă din memoria heap. El poate avea una din următoarele forme:
delete p; delete []p;
Prima formă se utilizează pentru eliberarea unei zone de memorie ocupată de o singură dată (obiect), nu de un vector. Pointerul p trebuie să fie un pointer la o zonă de memorie alocată anterior printr-un operator new. Operatorul delete trebuie să fie folosit doar cu un pointer valid, alocat numai cu new și care nu a fost modificat sau nu a mai fost eliberată zona de memorie mai înainte (cu un alt operator delete sau prin apelul unei funcții free()). Folosirea operatorului delete cu un pointer invalid este o operație cu rezultat nedefinit, cel mai adesea producând erori de execuție grave.
Cea de-a doua formă a operatorului delete[] se folosește pentru eliberarea unei zone de memorie ocupată de un vector de date. Pentru tipurile de date predefinite ale limbajului, se poate folosi și prima formă pentru eliberarea unui vector, dar, în cazul obiectelor de tipuri definite de utilizator, acest lucru nu mai este valabil. Această situație va fi detaliată în secțiunea următoare.
Câteva exemple de utilizare a operatorilor new și delete:
int *pi = new int(3); // alocare int și inițializare
double *pd = new double; // alocare double neinitializat
char *pc1 = new char[12]; // vector de 12 caractere
char *pc2 = new char[20]; // vector de 20 caractere
delete pi;
deletepd
delete pc1; //corect, char e tip predefinit
delete []pc2; // corect, elibereaza vector
În legătură cu cele două metode de alocare dinamică, prin operatorii new-delete și prin funcțiile de bibliotecă malloc-free, fără să fie o regulă precisă, se recomandă evitarea combinării lor, deoarece nu există garanția compatibilității între ele.
Teste de autoevaluare
Răspunsuri la testele de autoevaluare
Temă de autoinstruire
Lucrări de verificare pentru studenți
Formular de feedback
În dorința de ridicare continuă a standardelor desfășurării activitatilor dumneavoastra, va rugăm să completați acest chestionar și să-l transmiteți indrumatorului de an.
Disciplina: ________________________
Unitatea de invatare/modulul:__________________
Anul/grupa:__________________________
Tutore:_________________________
a) Conținut / Metoda de predare
Partea I
1. Care dintre subiectele tratate in aceasta unitate/modul considerați că este cel mai util și eficient? Argumentati raspunsul.
2. Ce aplicatii/proiecte din activitatea dumneavoastra doriți să imbunatatiti/modificați/implementați în viitor în urma cunoștințelor acumulate în cadrul acestei unitati de invatare/modul?
3. Ce subiecte considerați că au lipsit din acesta unitate de invatare/modul?
4. La care aplicatii practice ati intampinat dificultati in realizare? Care credeti ca este motivul dificultatilor intalnite?
6. Timpul alocat acestui modul a fost suficient?
7. Daca ar fi sa va evaluati, care este nota pe care v-o alocati, pe o scala de la 1-10?. Argumentati.
Partea II. Impresii generale
1. Acest modul a întrunit așteptările dumneavoastră?
2) Aveți sugestii care să conducă la creșterea calității acestei unitati de invatare/modul?
3) Aveți propuneri pentru alte unitati de invatare?
Vă mulțumim pentru feedback-ul dumneavoastră!
Unitatea de învățare Nr. 2
Clase și obiecte
Cuvinte cheie: clase, obiecte, constructori, destructor, funcții inline, funcții friend.
Un tip de date într-un limbaj de programare este o reprezentare a unui concept. De exemplu, tipul float din C++, împreună cu operațiile definite asupra estuia (+, -, *, etc.) reprezintă o versiune a conceptului matematic de numere reale. Pentru alte concepte, care nu au o reprezentare directă prin tipurile predefinite ale limbajului, se pot defini noi tipuri de date care să specifice aceste concepte.
O clasă este un tip de date definit de utilizator. O declarare a unei clase definește un tip nou care reunește date și funcții. Acest tip nou poate fi folosit pentru a declara obiecte de acest tip, deci un obiect este un exemplar (o instanță) a unei clase.
Definiție. O clasã este implementarea unui TDA. Ea definește atribute și metode care implementeazã structuri de date și operații ale TDA-ului.
Definiție. Un obiect este o instantã a unei clase. El este unic identificat de numele lui și definește o stare care este reprezentatã de valorile atributelor, la un moment dat. Starea unui obiect se schimbã în raport cu metodele care îi sunt aplicate.
Definiție. Comportamentul unui obiect este definit de mulțimea metodelor care îi pot fi aplicate.
Definiție. O metodã este o funcție asociatã unei clase. Un obiect apeleazã (invocã) o metodã drept reactie la primirea unui mesaj.
Primul pas pentru gruparea datelor și metodelor de prelucrare, l-au reprezentat stucturile; ele permiteau declararea unor ansambluri eterogene de date ce erau manipulate unitar.
Consideram structura angajat care curinde urmatoarele date: nume, varsta, salariul.
struct angajat
{
char nume[20];
int varsta;
float salariul;
}
Declaratii de variabile:
angajat a1; // o structura de tip angajat p1
angajat tablouangajat[10]; // un tablou cu zece elemente de tip angajat
angajat *a2; // un pointer catre o structura de tip angajat a2
angajat &a3=a1; // o referinta catre o structura de tip angajat a3
Membrii unei structuri sau ai unei clase sunt accesați cu ajutorul operatorilor de acces: operatorul “.”, respectiv, operatorul “->”.
Operatorul “.” acceseazã o structurã sau un membru al unei clase prin numele variabilei pentru obiect sau printr-o referintã la obiect.
Operatorul “->” acceseazã un membru al unei structuri sau clase printr-un pointer la obiect (a2 = &a1)
In aplicatia P2.1 se implementeazã tipul de date angajat cu ajutorul unei unei structuri C. Functia afisare( ) are rolul de a afisa datele unui angajat. Programul realizeazã constructia tipului de data abstract angajat prinn definirea unei structuri , setarea valorilor pentru câmpurile structurilor (membrii structurilor) si afisarea acestora.
// fisierul sursa P2_1.c
#include <stdio.h>
#include <conio.h>
#include <string.h>
// definitia structurii angajat
struct angajat {
char num[20];
int varsta;
float salariul;
} a1 = {"Pop", 28,1530};
// definitia funcției de afisare a datelor unei angajat
void afisare(struct angajat a)
{
printf("\n Nume \t Varsta \t Salariul");
printf("%s\t%i\t%d",a.nume,a.varsta,a.salariul);
}
void main()
{
struct angajat a2;
strcpy(a2.nume, "Ion");
a2.varsta = a1.varsta; a2.salariul = 1600;
printf("\n Angajat nr 1 \n");
afisare(a1);
printf("\n Angajat nr 2 \n");
afisare(a2);
getch();
}
2.1 Definirea unei clase
Clasele permit programatorului sã modeleze obiectele care au atribute (reprezentate de datele membru) si comportament (reprezentat de funcțiile membru, numite si metode). Practic, o clasã încapsuleazã o multime de valori si o multime de operatii.
Sintaxa definirii unei clase:
class <nume_clasã>
{
private:
// membrii privati
public:
// membrii publici
protected:
// membrii protejati};
Cuvântul-cheie class introduce declarația clasei (a tipului de date) cu numele nume_clasa. Dacă este urmată de corpul clasei (cuprins între acolade), această declarație este totodată o definiție. Dacă declarația conține numai cuvântul-cheie class și numele clasei, atunci aceasta este doar o declarație de nume de clasă.
Corpul clasei conține definiții de date membre ale clasei și definiții sau declarații de funcții membre ale clasei, despărțite printr-unul sau mai mulți specificatori de acces. Un specificator_acces poate fi unul din cuvintele-cheie din C++:
public private protected
Specificatorii private și protected asigură o protecție de acces la datele sau funcțiile membre ale clasei respective, iar specificatorul public permite accesul la acestea și din afara clasei. Efectul unui specificator de acces durează până la următorul specificator de acces. Implicit, dacă nu se declară nici un specificator de acces, datele sau funcțiile membre sunt de tip private. De aceea, toate datele sau funcțiile declarate de la începutul blocului clasei până la primul specificator de acces sunt de tip private. Într-o declarație de clasă se poate schimba specificatorul de acces ori de câte ori se dorește: unele declarații sunt trecute public, după care se poate reveni la declarații private, etc. Diferența între tipurile de acces private și protected constă în modul în care sunt moștenite drepturile de acces în clase derivate.
2.2 Definirea obiectelor
După definirea unei clase C, aceasta poate fi utilizatã ca tip în definirea obiectelor, tablourilor de obiecte si a pointerilor, astfel:
C o1, // obiect de tip C
C Tablou[20], // tablou de obiecte de tip C
C *ptrC, // pointer la un obiect de tip C
C &tC = o1; // referinta la un obiect de tip C
Numele unei clase devine specificator de tip si se pot defini, teoretic, o infinitate de obiecte ale clasei.
Accesarea membrilor unei clase
Membrii publici ai unei clase pot fi accesați cu ajutorul operatorilor “.” și “->”, dupã cum obiectul de care aparțin este desemnat prin nume sau printr-un pointer. Variabilele membru private nu pot fi accesate decât în cadrul metodelor clasei respective.
In aplicatia2.2 se implementeazã tipul de date angajat sub forma unei clase C++. Clasa angajat cuprinde datele membre: varsta, salariul, nume si funcții membre: init() pentru initializarea datelor membre, funcțiia membra de acces spune_varsta(), functia afisare() pentru afisarea datelor membre.
class angajat
{
private:
int varsta;
protected
float salariul;
public:
char nume[20];
void init ( char n[]=”Anonim”, int v=0, float s=0)
{strcpy(nume,n);
varsta=v;
salariul=s;}
int spune_varsta() { return varsta};
void afisare()
{cout<<”nume: ”<<nume<<endl;
cout<<”varsta: ”<<varsta<<endl;
cout<<”salariul: ” <<salariul;
}}
void main()
{ angajat a;
a.init(); // apelul funcției membre init() pentru obiectul a
cout<<a.spune_varsta();//apelul funcției membre soune_varsta()
} pentru obiectul a
Dupa rolul pe care îl joaca în cadrul clasei, funcțiile membre ale unei clase se împart în patru categorii:
constructori, responsabili cu crearea obiectelor;
destructor, reponsabil cu distrugere obiectelor și eliberarea memoriei ocupate de acesta;
funcții de acces, care mediază legatura obiectului cu exteriorul;
metode, funcții care introduc operațiile și prelucraile specifice obiectului.
2.3 Constructori si destructor
Utilizarea unor funcții membre ale unei clase, așa cum este funcția init() din clasa angajat, pentru inițializarea obiectelor nu este o metodă elegantă, de asemenea permite si strecurarea unor erori de programare. Deoarece nu există nici o constrângere din partea limbajului ca un obiect să fie inițializat (de exemplu, nu apare nici o eroare de compilare dacă nu este apelată funcția init() pentru un obiect din clasa angajat), programatorul poate să uite să apeleze funcția de inițializare sau să o apeleze de mai multe ori. În cazul simplu al clasei prezentate ca exemplu până acum, acest lucru poate produce doar erori care se evidențiază ușor. În schimb, pentru alte clase, erorile de inițializare pot fi dezastruoase sau mai greu de identificat.
Din această cauză, limbajul C++ prevede o modalitate elegantă și unitară pentru inițializarea obiectelor de tipuri definite de utilizator, prin intermediul unor funcții speciale numite funcții constructor.
Constructorul este o funcție membra specială care este apelată automat atunci când este creat un obiect. El poarta numele clasei și nu are nici un tip, deoarece este apelat automat.
Principalele motivații pentru care se utilizează constructorii sunt:
complexitatea structurii obiectelor date de de existența variabilelor și a funcțiilor, de existența sectiunii privată, publică și protejată, face dificilă initializarea directa a obictelor;
există situații în care doar unele date membre trebuie ințializate, altele sunt încarcate în urma apelarii unor metode;
datele de obicei sunt declarate în secțiunea privată si deci nu pot fi accesate din exterior ci prin intermediul metodelor.
O clasă poate menționa mai multi construcori, prin supraîncarcare, folosirea unuia dintre ei la declararea unei variabile de clasă, fiind dedusă în funcție de numarul și tipul parametrilor de apel.
Vom redefinii clasa angajat pentru care vom defini doi constructori astfel:
class angajat
{int varsta;
float salariul;
char nume[20];
public:
angajat() { strcpy(nume,”Noname”); varsta=0;salariul=0}
angajat(char *n, int v, float s)
{strcpy(nume,n);varsta=v;salariul=s;}
char *spune_nume(){return nume;}
int spune_varsta(){return varsta;}
}
Se poate observa că în clasa angajat s-au definit doi constructori:
un constructor fara parametru care ințializează datele membre cu valori constante; constructorul fara parametrii se numeste constructor impicit;
al doilea constructor primeste trei parametri si are ca scop inițializarea datelor membre din variabile elemntare; un asfel de constructor se numeste constructor cu argumente.
Constructorii definiți mai sus se pot apela asfel:
angajat a1 // aplelul constructorului implicit
angajat a2(„Pop”,28,1200) //apelul constructorului cu argumente.
Dacă clasa nu menționeaza nici un constructor, atunci se definește automat de câtre compilator un constructor care este utilizat pentru generarea de obiecte ale casei respective.
În multe aplicații este recomandat ca o clasă să menționeze mai multi constructori deoarece modurile de ințializare a datelor membre sun diverse:
inițializarea membrilor cu constante;
ințializarea din datele elementare;
inițializarea prin citire de la tastatura;
inițializarea prin citire din fisier;
inițializarea din datele unui obiect existent.
Observație. La crearea unui obiect este selectat un singur constructor care este apleat o singură dată.
Aplicația 2.3 impementează clasa Complex care descrie un numar complex. Clasa conține datele membre partea reală a numarului complex re, partea imaginară a numarului complex im. Sunt definiți trei constructori Complex(), Complex (double v), Complex ( double x, double y) precum și o funcție membră care permite afișarea datelor unui numar complex.
#include <iostream.h>
class Complex{
double re;
double im;
public:
Complex(){cout << "Constructor fara argumente\n";
re = 0;
im = 0;
}
Complex(double v){cout << "Constructor cu 1 arg\n");
re = v;
im = v;
}
Complex(double x, double y){
cout << "Constructor cu 2 arg\n";
re = x;
im = y;
}
void afisare()
{ cout<<”partea reala”<<re<<endl;
Cout<<”partea imaginara”<<im;
};
void main (){
Complex c1;
Complex c2(2);
Complex c3(3,5);
}
La execuția funcției main(), sunt afisate următoarele mesaje:
Constructor fara argumente
Constructor cu 1 arg
Constructor cu 2 arg
În fiecare dintre aceste situații a fost creat un obiect de tip Complex, c1, c2, c3 și de fiecare dată a fost apelat constructorul care are același număr și tip de argumente cu cele de apel.
Dintre modurile de intializare amintite mai sus, un rol important o are intializarea unui obiect prin copierea datelor unui alt obiect de același tip. Această operație este posibilă prin intermediul unui constructor mai special al clasei, numit constructor de copiere. Forma generală a constructorului de copiere al unei clase X este:
X::X(X& r){ } // initializare obiect folosind referința r
Constructorul primește ca argument o referință r la un obiect din clasa X și inițializează obiectul nou creat folosind datele conținute în obiectul referință r. Pentru crearea unui obiect printr-un constructor de copiere, argumentul transmis trebuie să fie o referință la un obiect din aceeași clasă.
De exemplu, pentru obiecte de tip Complex:
void main(){
Complex c1(2,3); // Constructor initializare
Complex c2(c1); // Constructor copiere
Complex c3 = c2; // Constructor copiere
c3.afisare(); // afiseaza 2 3
}
La crearea primului obiect c1 este apelat constructorul de inițializare cu două argumente al clasei Complex. Cel de-al doilea obiect c2 este creat prin apelul constructorului de copiere al clasei Complex, avînd ca argument referința la obiectul c1. Este posibilă și declarația de forma Complex c3 = c2; a unui obiect prin care se apelează, de asemenea, constructorul de copiere.
Constructorul de copiere poate fi definit de programator; în caz contarar este definit un constructor de copiere al clasei, compilatorul generează un constructor de copiere care copiază datele membru cu membru din obiectul referință în obiectul nou creat. Această modalitate de copiere mai este denumită copie la nivel de biți (bitwise copy) sau copie membru cu membru.
Pentru clasa Complex, constructorul de copiere definit de programator are următoarea structură:
Complex(Complex &r)
{
cout << “Constructor copiere\n”;
re = r.re;
im = r.im;
}
Importanța constructorului de copiere este subliniată de situația în care datele membre ale unei clase sunt alocate dinamic. Constructorul de copiere generat implicit de compilator copiază doar datele membre declarate în clasă (membru cu membru) și poate să aloce date dinamice pentru obiectul nou creat. Folosind un astfel de constructor, se ajunge la situația în care două obiecte, cel nou creat și cel de referință, să conțină pointeri cu aceeași valoare, care indică spre aceeași zonă din memorie. O astfel se situație este o sursă puternică de erori de execuție subtile și greu de depistat.
Exemplificam acest caz definind clasa angajat asfel:
Aplicatia 2.4
class angajat
{ float salariul;
public:
char *nume;
angajat(char *n,int s)
{int nr=strlen(n);
nume=new char[nr];// alocare dinamica pentru sirul nume
strcpy(nume,n);
salariul=s;}
int spune_salariul(){return salariul;}}
void main()
{ angajat a1(„Popescu”,35);
angajat a2=a1;
strcpy(a2.nume,”Ion”);
cout<<a1.nume<<” ”<<a1.salariul<<endl;
cout<<a2.nume<<” ”<<a2.salariul;
}
Dupa rularea programului se va afișa: Ion 35
Ion 35
La construirea obiectului a2 s-a aplelat constructorul de copiere implicit care a copiat membrul nume al obiectului a1, dar nu a realizat alocarea dinamică. Asfel, obiectele a1 și a2 folosesc aceeași zonă de memorie alocată pentru campul nume.
Se poate observa asfel, necesiatea de a introduce în clasa angajat un constructor de copiere care să aloce explicit memorie , după care să facă copierea continuțului zonei de memorie a obiectului sursă la destinație:
class angajat
{float salariul;
public:
char *nume;
angajat(char *n,int s)
{int nr=strlen(n);
nume=new char[nr];
strcpy(nume,n);
salariul=s;
}
angajat(angajat &a)
{
int nr= strlen(a.nume)
strcpy(nume,p.nume);
varsra=a.varsta;
}
int spune_salariul(){return salariul;}}
void main()
{ angajat a1(„Popescu”,35);
angajat a2=a1;
strcpy(a2.nume,”Ion”);
cout<<a1.nume<<” ”<<a1.salariul<<endl;
cout<<a2.nume<<” ”<<a2.salariul;}
Rezultatul afișat prin rularea aceeluiași program este: Popescu 35
Ion 35
În concluzie, pentru un obiect cu un membru pointer spre o zonă alocată dinamic progrmatorul va furniza un contructor de copiere care sa aloce memorie pentru noul obiect.
Multe clase definite într-un program necesită o operație care efectueza ștergerea completă a obiectelor. O astfel de operație este efectuată de o funcție membră a clasei, numită funcție destructor.
Cand este declarat explicit in cadrul calsei, destructorul porta numele clasei, precedat de semnul ~ (de ex. ~angajat()).
Destructorul, spre deosebire de constructor, este unic și nu are parametrii de apel.
Dacă programatorul nu definește un destructor explicit, compilatorul C++ va genera unul implicit. Destructorul generat de compilator apeleazã destructorii pentru variabilele membru ale clasei. Membrii unei clase sunt întotdeauna distruși în ordinea inversã celei în care au fost creați.
Aplicația 2.5
#include <iostream.h>
class Complex
{
double re;
double im;
public:
Complex(double x, double y){
cout << "Constructor cu 2 arg\n";
re = x;
im = y;
}
~Complex(){cout << "Destructor";
}
Void main()
{
Complex z(3,4);
}
Rezultatul afișat după rularea programului este:
Constructor cu 2 arg
Destructor
Se observă în programul anterior că nu s-a realizat un apel explicit al destructorului. Acesta a fost apelat automat la sfarșitul programului.
Destructorii sunt apelați implicit și în alte situații, cum ar fi:
• atunci când un obiect local sau temporar iese din domeniul de definiție;
• la apelul operatorului delete, pentru obiectele alocate dinamic.
2.4 Pointerul this
Fiecare obiect al unei clase are acces la propria adresã prin intermediul unui pointer numit this. Acest pointer este utilizat în mod implicit pentru referirea atât la membrii date cât și la funcțiile membru ale unui obiect.
Într-o clasă X pointerul constant this este declarat implicit astfel:
X* const this;
Deoarece this este cuvânt cheie, el nu poate fi declarat explicit. De asemenea, fiind declarat implicit pointer constant, el nu poate fi modificat, dar poate fi folosit explicit.
Ca expemplul vom defini, in aplicația 2.6, o clasă care va conține o metodă ce utilizeză pointerul this.
class C
{ int x;
public:
C(int a){
x=a;}
void afisare(){
cout<<this->x;}
}
void main()
{C ob(7);
ob.afisare;
}
2.5 Funcții inline
În programare aplelul funcțiilor poate produce un cost ridicat de execuție, datorită operațiilor necesare pentru rezervarea spațiului în stivă, pentru transferul argumentelor și returnarea unei valori. Pentru a reduce timpul de executii C++ pune la dispozitie funcții inline.
În general, o funcție declarată inline se schimbă la compilare cu corpul ei, și se spune că apelul funcției se realizează prin expandare. În felul acesta se elimină operațiile suplimentare de apel și revenire din funcție. Dezavantajul funcțiilor inline este acela că produc creșterea dimensiunilor programului compilat, de aceea se recomandă a fi utilizate pentru funcții de dimensiuni mici (maximum 3-4 instrucțiuni). În plus, mai există și unele restricții privind funcțiile inline: ele nu pot fi declarate funcții externe, deci nu pot fi utilizate decât în modulul de program în care au fost definite și nu pot conține instrucțiuni ciclice (while, for, do-while).
În cazul claselor, funcțiile membru care sunt definite în interiorul clasei devin în mod implicit inline. În cazul în care le definim în afara clasei si vrem sã fie inline trebuie să utilizăm sintaxa declarării unei funcții inline.
Sintaxa declarãrii unei funcții inline:
inline <tip> <nume_funcție>([<lp>])
unde <tip> reprezintã tipul întors de funcție, iar <lp> lista de parametri.
În aplicația P2.7 se prezintã un exemplu de definire și utilizare a unei funcții inline pentru calculul ariei unui patrat cu latura data, arie_patrat() .
// fisierul sursa P2_6.cpp
#include <iostream.h>
inline double arie_patrat(double a)
{
return a*a;
}
void main()
{
double x;
cout<<"\n Latura patratului:";
cin>>x;
cout<<"\n Aria patratului este "<<arie_patrat(x);
}
2.6 Funcții și clase friend
Este cunscut deja faptul ca aceesul la mebrii unei clase declarati în secțiunile private și protected este restrictionat. Acestea pot fi utilizate doar de funcțiile membre ale clasei.
Accesul la membrii unei clase poate fi îngădiut, totuși și unor funcții externe clasei sau apartinând altor clase. Astfel de funcții se numesc funcții prietene clasei respective (funcții friend) si sunt declarate astfel cu ajutorul specificatorului friend.
Funcțiile friend raman externe, nefiind legate de clasa si cu atat mai putin de un obiect anume. Pentru a avea acces la datele unui obiect, functia friend trebuie sa primeasca drept parametru de intrare referinta la obiectul respectiv.
Declararea unei funcții f() de tip friend se realizeaza in interiorul clasei iar funcția însăși se definește în altă parte în program astfel:
class C{
//……..
friend tip_returnat f(lista_argumente);
};
………………………..……
tip_returnat f(lista_argumente){
// corpul funcției
}
Programul P2.8 prezintă un exemplu de declarare și utilizare a unei funcții friend a clasei Nr_Complex, modulul(), care calculeazã modulul unui numar complex.
// fisierul sursa P2_5.cpp
#include <iostream.h>
#include <math.h>
class Complex {
double re, im;
public:
Complex(double x, double y)
{ real = x;
imag = y;
}
friend double modul(Complex &);};
double abs(Nr_Complex& z)
{
return sqrt(z.real*z.real+z.imag*z.imag);
}
void main()
{
Complex Z=Complex(3,4);
cout<<"\n Modulul lui numarului complex z este "<< modul(Z);
}
Trebuie subliniat faptul ca utilizarea funcțiilor friend trebuie fãcutã cu mare grijã întrucât acestea încalcã principiul încapsulãrii, respectiv, ascunderea informatiei, principiu fundamental în POO.
Avantajul principal al declarãrii unei funcții friend este accesul rapid pe care aceasta îl are direct la membrii privati ai clasei cu care este prietenã.
Teste de autoevaluare
Răspunsuri la testele de autoevaluare
Temă de autoinstruire
Lucrari de verificare pentru studenți
Formular de feedback
În dorința de ridicare continuă a standardelor desfășurării activitatilor dumneavoastra, va rugăm să completați acest chestionar și să-l transmiteți indrumatorului de an.
Disciplina: ________________________________
Unitatea de invatare/modulul:__________________
Anul/grupa:________________________________
Tutore:____________________________________
a) Conținut / Metoda de predare
Partea I
1. Care dintre subiectele tratate in aceasta unitate/modul considerați că este cel mai util și eficient? Argumentati raspunsul.
2. Ce aplicatii/proiecte din activitatea dumneavoastra doriți să imbunatatiti/modificați/implementați în viitor în urma cunoștințelor acumulate în cadrul acestei unitati de invatare/modul?
3. Ce subiecte considerați că au lipsit din acesta unitate de invatare/modul?
4. La care aplicatii practice ati intampinat dificultati in realizare? Care credeti ca este motivul dificultatilor intalnite?
6. Timpul alocat acestui modul a fost suficient?
7. Daca ar fi sa va evaluati, care este nota pe care v-o alocati, pe o scala de la 1-10?. Argumentati.
Partea II. Impresii generale
1. Acest modul a întrunit așteptările dumneavoastră?
2) Aveți sugestii care să conducă la creșterea calității acestei unitati de invatare/modul?
3) Aveți propuneri pentru alte unitati de invatare?
Vă mulțumim pentru feedback-ul dumneavoastră!
Unitatea de învățare Nr. 3
Tablouri de Obiecte. Date Și membre statice
Cuvinte cheie: tablou de obiecte, date statice, metode statice.
3.1 Tablouri de obiecte
În limbajul C++ implementarea tablourilor de obiecte se poate realiza cu condiția respectării urmãtoarei restrictii: clasa care include obiectul trebuie sã continã un constructor implicit.
Aceastã restricție se datoreazã imposibilitãtii de a furniza argumentele necesare constructorilor.
Aplicatia P3.1 definește un tablou de obiecte de tip angajat.
// fisierul sursa P31.cpp
class angajat
{
private:
int varsta;
float salariul;
public:
char nume[20];
angajat()
{strcpy(nume,”Anonim”);
varsta=0;
salariul=0;
}
angajat( char n[]=”Anonim”, int v=0, float s=0)
{strcpy(nume,n);
varsta=v;
salariul=s;}
void seteaza_valori()
{cout<<”Numele:”
cin>>nume;
cout>>”salariul:”
cin>>salariul;
cout<<”varsta”
cin>>varsta;}
void afisare()
{cout<<”nume: ”<<nume<<endl;
cout<<”varsta: ”<<varsta<<endl;
cout<<”salariul: ” <<salariul;
}}
void main()
{ angajat tab[20]; // tablou static sau automatic
int i, n;
cout<<"\n Cate obiecte doriti sa creati?";
cin>>n;
// initializeaza n obiecte
for (i=0;i<n;i++)
tab[i].seteaza_valori();
// afiseaza cele n obiecte
for (i=0;i<n;i++)
tab[i].afisare();
}
3.2 Date și funcții membre statice
Implicit membrii de date sunt alocati pe obiecte. De exemplul fiecare angajat al unei firme are propiul sau nume, cnp etc. Există, însă unele propietați care sunt împartite de câtre toate obiectele unei clase, cum ar fi de exemplul, pentru clasa angajat, numarul total de angajați ai unei firme, salariul mediu al firmei etc.
O varinată posibilă ar fi stocarea acestor informații într-o variabilă globală uzuală. De exemplu, am putea utiliza o variabila de tip întreg pentru a pastra numarul de obiecte angajat. Problema acestei soluții este că variabilele globale sunt declarate în afara clasei; pentru putea fi aduse în interiorul clasei aceste variabile trebuie să fie declarate de tip static.
Datele statice nu se regasesc în fiecare set de valori ale clasei, ci într-un singur exemplar, pentru toate obiectele clasei. Datele ce apar în toate obiectele se alocă de câtre un constructor , dar cum un membru static nu face parte din nici un obiect, nu se alocă prin constructor. Astfel, la definirea clasei, o data statică nu se considera definită, ci doar declarată, urmând a avea o definiție externă clasei. Legatura cu declarația din interiorul clasei se face prin operatorul de rezolutie :: precedat de numele clasei din care face parte precum si de tipul variabilei statice, asfel:
class angajat
{
//…
Static int total_ang;
//…
}
int angajat::total_ang=0;;
Variabila total_ang va fi unică pentru toți angajații. Se poate observă că o data cu definirea varbilei statice s-a realizat și intializarea acesteia.
Funcțiile membre statice efectuează prelucrari care nu sunt individualizate pe obiecte, ci prelucrari care au loc la nivelul clasei. Funcțiile statice nu apartin unui obiect anume si deci nu beneficiaza de referinta implicita a obiectului asociat (pointerul this).
Daca functia membra statica opereaza pe o data nestatica a clasei, obiectul trebuie transmis explicit ca parametru , in timp ce cu datele membre statice , lucreaza in mod direct.
Aplicația 3.2 redefineste clasa angajat, prin adaugarea datelor membre statice total_ang si total_b care vor retine numarul total de angajati si numarul total de barbati. Clasa va contine si trei funcții membre statice care vor returna valorile retinute in cele doua date statice.
// fisierul sursa P2.7.cpp
class angajat
{
private:
int varsta;
float salariul;
char gen;
static int total_ang;
static int total_b;
public:
char nume[20];
angajat()
{strcpy(nume,”Anonim”);
varsta=0;
salariul=0;
total_ang++;
}
~angajat(){total_ang–;}
void seteaza_valori()
{cout<<”Numele:”
cin>>nume;
cout>>”salariul:”
cin>>salariul;
cout<<”varsta”
cin>>varsta;
}
static int spune_total_ang(){return total_ang;}
static int spune_total_b(){return total_b;}
static int numara_total_b(angajat *ang)
{if(ang->gen==”B”) total_b++;}
void afisare()
{cout<<”nume: ”<<nume<<endl;
cout<<”varsta: ”<<varsta<<endl;
cout<<”salariul: ” <<salariul;
}}
int angajat::total_ang=0;
int angajat::total_b=0;
void main()
{ angajat tab[20];
int i, n;
cout<<"\n Cate obiecte doriti sa creati?";
cin>>n;
for (i=0;i<n;i++)
{tab[i].seteaza_valori();
angajat::numara_total_b(&tab[i]);
}
cout<<”firma are un total de angajati”<<angajat::spune_total_ang;
cout<<”din care”<<angajat::spune_total_b<<” sunt barbati”;}
În programul de mai sus se remarcă faptul ca întreținerea variabilei total_ang cade în sarcina constructorilor și a destructorului, care incrementează sau decrementrează acesta variabilă.
De asemenea, funcția membra statică numara_total_b() primește ca parametru referința la un obiect de tip angajat pentru ca utilizează data membra gen care nu este statică.
Întrucât variabilele membru statice nu aparțin unui obiect anume, ele pot fi accesate și prin prefixarea numelui clasei de operatorul “::”.
Teste de autoevaluare
Răspunsuri la testele de autoevaluare
Temă de autoinstruire
Lucrari de verificare pentru studenți
Formular de feedback
În dorința de ridicare continuă a standardelor desfășurării activitatilor dumneavoastra, va rugăm să completați acest chestionar și să-l transmiteți indrumatorului de an.
Disciplina: _________________________________
Unitatea de invatare/modulul:___________________
Anul/grupa:_________________________________
Tutore:_____________________________________
a) Conținut / Metoda de predare
Partea I
1. Care dintre subiectele tratate in aceasta unitate/modul considerați că este cel mai util și eficient? Argumentati raspunsul.
2. Ce aplicatii/proiecte din activitatea dumneavoastra doriți să imbunatatiti/modificați/implementați în viitor în urma cunoștințelor acumulate în cadrul acestei unitati de invatare/modul?
3. Ce subiecte considerați că au lipsit din acesta unitate de invatare/modul?
4. La care aplicatii practice ati intampinat dificultati in realizare? Care credeti ca este motivul dificultatilor intalnite?
6. Timpul alocat acestui modul a fost suficient?
7. Daca ar fi sa va evaluati, care este nota pe care v-o alocati, pe o scala de la 1-10?. Argumentati.
Partea II. Impresii generale
1. Acest modul a întrunit așteptările dumneavoastră?
2) Aveți sugestii care să conducă la creșterea calității acestei unitati de invatare/modul?
3) Aveți propuneri pentru alte unitati de invatare?
Vă mulțumim pentru feedback-ul dumneavoastră!
Unitatea de învățare Nr. 4
Clase derivate. MoȘtenirea
Cuvinte cheie: clase derivate, ierarhii de clase, funcții virtuale, polimorfism.
4.1 Relația de moștenire. Clase de bază și clase derivate
Derivarea permite definirea într-un mod simplu, eficient și flexibil a unor clase noi prin adăugarea unor funcționalități claselor deja existente, fără să fie necesară reprogramarea sau recompilarea acestora. Clasele derivate exprimă relații ierarhice între conceptele pe care acestea le reprezintă și asigură o interfață comună pentru mai multe clase diferite. De exemplu, entitățile cerc, triunghi, dreptunghi, sunt corelate între ele prin aceea că toate sunt forme geometrice, deci ele au în comun conceptul de formă geometrică. Pentru a reprezenta un cerc, un triunghi sau un dreptunghi, într-un program, trebuie ca aceste clase, care reprezintă fiecare formă geometrică în parte, să aibă în comun clasa care reprezintă în general o formă geometrică.
Mostenirea reprezintã o formã de implementare a reutilizãrii codului. Ea apare în urma creãrii de noi clase prin operatia de derivare.
Derivarea reprezintã definirea unei noi clase prin extinderea uneia sau a mai multor clase existente. Noua clasã se numeste clasã derivatã, iar clasele existente din care a fost derivatã se numesc clase de bazã. În cazul în care existã o singurã clasã de bazã, mostenirea se numeste mostenire singularã. Limbajul C++ acceptã existenta mai multor clase de bazã.
O clasã derivatã mosteneste toti membrii tuturor claselor sale de bazã. Adicã, clasa derivatã contine toate variabilele membru continute în clasele de bazã si suportã toate operatiile furnizate
de clasele de bazã. O clasã derivatã poate fi la rândul ei clasã de bazã pentru noi clase. Astfel se poate genera o ierarhie de clase (graf de mostenire).
Sintaxa declarãrii unei clase derivate dintr-o clasã de bazã :
class <clasa_derivatã> : public <clasa_de_bazã>
{ // membrii clasei derivate
};
Moștenirea sau relația de derivare este indicatã în antetul clasei derivate prin cuvântul cheie public prefixat de caracterul “:” si urmat de numele clasei de bazã.
În cadrul relatiei de mostenire poate apare în clasa de bazã o sectiune protejatã, marcatã prin cuvântul cheie protected, care permite accesul claselor derivate din ea la datele si funcțiile membru din sectiunea respectivã.
Observatii
O clasã derivatã nu poate accesa direct membrii privati ai clasei sale de bazã. Dacã s-ar permite asa ceva s-ar încãlca unul din principiile fundamentale ale POO (încapsularea).
O clasã derivatã poate accesa membrii privati doar prin intermediul funcțiilor publice si protejate ale clasei de bazã.
O clasã derivatã poate accesa membrii publici si protejati ai clasei de bazã
Relatia de mostenire este tranzitivã.
Funcțiile friend nu se mostenesc.
Aplicatia P4.1 care descrie organizarea personalului unei instituții fără folosirea claselor derivate. O clasă numită Angajat deține date și funcții referitoare la un angajat al instituției:
class Angajat{
char *nume;
float salariu;
public:
Angajat();
Angajat(char *n, float sal);
Angajat(Angajat& r);
void display();
};
Angajat::display(){
cout << nume << “ ” << salariu << endl;
}
Diferite categorii de angajați necesită date suplimentare față de cele definite în clasa Angajat, corespunzătoare postului pe care îl dețin. De exemplu, un aministrator este un angajat (deci sunt necesare toate datele care descriu această calitate) dar mai sunt necesare și alte informații, de exemplu precizare secției pe care o conduce. De aceea, clasa Administator trebuie să includă un obiect de tipul Angajat, la care adaugă alte date:
class Administrator{
Angajat ang;
int sectie;
public:
void display();}
Posibilitatea de a include într-o clasă date descrise într-o altă clasă are în limbajele orientate pe obiecte un suport mai eficient și mai simplu de utilizat decât includerea unui obiect din tipul dorit: derivarea claselor, care moștenesc (date și funcții membre) de la clasa de bază.
Un administrator este un angajat, de aceea clasa Administrator se poate construi prin derivare din clasa Angajat astfel:
class Administrator : public Angajat {
int sectie;
public:
void display();}
In clasa Administrtrator nu se pot aceesa datele private din clasa Angajat, chiar data tipul mostenerii este public. Asa cu s-a subliniat inainte, datele private ale clasei de baza nu pot fi utilizate de catre clasa derivata.
Metoda cea mai adecvată de acces la membrii private clasei de bază din clasa derivată este prin utilizarea funcțiilor membre publice ale clasei de bază. De exemplu, nu se poate implementa funcția display() din clasa Administrator prin accesarea membrilor private ai clasei Angajat:
void Administrator::display(){
cout << nume << “ ”<< salariu << endl; // eroare
cout << sectie << endl;
}
În schimb, se poate folosi funcția membră publică display() a clasei Angajat:
void Administrator::display(){
Angajat::display();
cout << sectie << endl;
}
Redefinirea funcției display() în clasa derivată ascunde funcția cu același nume din clasa de bază, de aceea este necesară calificarea funcției cu numele clasei de bază folosind operatorul de rezoluție: Angajat::display().
Din clasa derivată (în funcții membre ale acesteia sau pentru obiecte din clasa derivată) este accesat membrul redefinit în clasa derivată. Se spune că membrul din clasa de bază este ascuns (hidden) de membrul redefinit în clasa derivată. Un membru ascuns din clasa de bază poate fi totuși accesat dacă se folosește operatorul de rezoluție (::) pentru clasa de bază. De exemplu:
class Base {
public:
int a, b;
}
class Derived {
public:
int b, c; // b este redefinit
};
void fb() {
Derived d;
d.a = 1; // a din Base
d.Base::b = 2; // b din Base
d.b = 3; // b din Derived
d.c = 4; // c din Derived
}
4.2 Constructori și destructori în clasele derivate
Constructorii și destructorii sunt funcții membre care nu se moștenesc. La crearea unei instanțe a unei clase derivate (obiect) se apelează implicit mai întâi constructorii claselor de bază și apoi constructorul clasei derivate. Ordinea în care sunt apelați constructorii claselor de bază este cea din lista claselor de bază din declarația clasei derivate. Constructorii nu se pot redefini pentru că ei, în mod obligatoriu, au nume diferite (numele clasei respective).
La distrugerea unui obiect al unei clase derivate se apelează implicit destructorii în ordine inversă: mai întâi destructorul clasei derivate, apoi destructorii claselor de bază, în ordinea inversă celei din lista din declarație.
La instanțierea unui obiect al unei clase derivate, dintre argumentele care se transmit constructorului acesteia o parte sunt utilizate pentru inițializarea datelor membre ale clasei derivate, iar altă parte sunt transmise constructorilor claselor de bază. Argumentele necesare pentru inițializarea claselor de bază sunt plasate în definiția (nu în declarația) constructorului clasei derivate.
Un exemplu simplu, în care constructorul clasei derivate D transferă constructorului clasei de bază B un număr de k argumente arată astfel:
class B{
//………………….
public:
B(tip1 arg1,…,tipk argk);
};
class D:public B {
//………………….
public:
D(tip1 arg1, …,tipk argk,…,tipn argn);
};
D::D(tip1 arg1, …,tipk argk, ….…,tipn argn)
:B(arg1, arg2, ……,argk);
{
// initialzare date membre clasa derivata
}
Sintaxa generalã pentru transmiterea de argumente din clasa derivatã cãtre clasa de bazã:
<construct_cls_derivata>(<L1>) : <construct_cls_baza>(<L2>)
{
// corpul constructorului clasei derivate
}
<L1> reprezintã lista de argumente ale constructorului clasei derivate, iar <L2> lista de argumente ale constructorului clasei de bazã. Lista <L1> include lista <L2>.
Observatii:
Constructorii si destructorul clasei de bazã nu se mostenesc.
Constructorul clasei de bazã se va executa înaintea constructorului clasei derivate, iar destructorul clasei derivate se va executa înaintea destructorului clasei de bazã.
4.3 Controlul accesului la membrii clasei de bază
Accesul la membrii clasei de bază moșteniți în clasa derivată este controlat de specificatorul de acces (public, protected, private) din declarația clasei derivate.
O regulă generală este că, indiferent de specificatorul de acces declarat la derivare, datele de tip private în clasa de bază nu pot fi accesate dintr-o clasă derivată. O altă regulă generală este că, prin derivare, nu se modifică tipul datelor în clasa de bază.
Un membru protected într-o clasă se comportă ca un membru private, adică poate fi accesat numai de membrii acelei clase și de funcțiile de tip friend ale clasei. Diferența între tipul private și tipul protected apare în mecanismul de derivare: un membru protected al unei clase moștenită ca public într-o clasă derivată devine tot protected în clasa derivată, adică poate fi accesat numai de funcțiile membre și friend ale clasei derivate și poate fi transmis mai departe, la o nouă derivare, ca tip protected.
Moștenirea de tip public a clasei de bază
Dacă specificatorul de acces din declarația unei clase derivate este public, atunci:
Datele de tip public ale clasei de bază sunt moștenite ca date de tip public în clasa derivată și deci pot fi accesate din orice punct al domeniului de definiție al clasei derivate.
Datele de tip protected în clasa de bază sunt moștenite protected în clasa derivată, deci pot fi accesate numai de funcțiile membre și friend ale clasei derivate.
În aplicația P4.2 sunt prezentate și comentate câteva din situațiile de acces la membrii clasei de bază din clasa derivată atunci când specificatorul de acces este public.
class Base {
int a;
protected:
int b;
public:
int c;
void seta(int x){a = x; cout << "seta din baza\n";}
void setb(int y){b = y; cout << "setb din baza\n";}
void setc(int z){c = z; cout << "setc din baza\n";}
};
class Derived : public Base {
int d;
public:
void seta(int x) {
a = x; // eroare, a este private }
void setb(int y) {
b = y;
cout << "setb din derivata\n";}
void setc(int z) {
c = z;
cout << "setc din derivata\n";}};
void fb(){
Derived obd;
obd.a = 1; // eroare, a este private in baza
obd.seta(2); // corect, se apelează baza::seta
obd.b = 3; // eroare, b este protected
obd.Base::setb(5);// corect, Base::setb este public
obd.setb(4); // corect, Derived::setb este public
obd.c = 6; // corect, c este public
obd.Base::setc(7);// corect, Base::setc este public
obd.setc(8); // corect, Derived::setc este public
}
Dacă se comentează liniile de program care provoacă erori și se execută funcția fb(), se obțin următoarele mesaje la consolă:
seta din baza
setb din baza
setb din derivata
setc din baza
setc din derivata
Moștenirea de tip protected a clasei de bază
Dacă specificatorul de acces din declarația clasei derivate este protected, atunci toți membrii de tip public și protected din clasa de bază devin membri protected în clasa derivată. Bineînțeles, membrii de tip private în clasa de bază nu pot fi accesați din clasa derivată.
Se reiau clasele din exemplul precedent cu moștenire protected:
class Derived : protected Base {
// acelasi corp al clasei };
În această situație, în funcția fb() sunt anunțate ca erori de compilare toate apelurile de funcții ale clasei de bază pentru un obiect derivat, precum și accesul la variabila c a clasei de bază:
void fb(){
Derived obd;
obd.a = 1; // eroare, a este private in baza
obd.seta(2); // eroare, Base::seta()este protected
obd.b = 3; // eroare, b este protected
obd.Base::setb(5); // eroare, Base::setb este prot.
obd.setb(4); // corect, Derived::setb este public
obd.c = 6; // eroare, c este protected
obd.Base::setc(7); // eroare, Base::setc este prot.
obd.setc(8); // corect, Derived::setc este public
}
Dacă se comentează toate liniile din funcția fb() care produc erori, la execuția acesteia se afișează următoarele rezultate:
setb din derivata
setc din derivata
Din acest exemplu reiese pregnant faptul că în moștenirea protected a unei clase de bază nu mai pot fi accesați din afara clasei derivate nici unul dintre membrii clasei de bază.
Moștenirea de tip private a clasei de bază
Dacă specificatorul de acces din declarația clasei derivate este private, atunci toți membrii de tip public și protected din clasa de bază devin membri de tip private în clasa derivată și pot fi accesați numai din funcțiile membre și friend ale clasei derivate. Din nou trebuie reamintit că membrii de tip private în clasa de bază nu pot fi accesați din clasa derivată. Din punct de vedere al clasei derivate, moștenirea de tip private este echivalentă cu moștenirea de tip protected. Într-adevăr, dacă modificăm clasa derivata din Exemplul 5.2 astfel:
class Derived : private Base {
// acelasi corp al clasei
};
mesajele de erori de compilare și de execuție ale funcției fb() sunt aceleași ca și în moștenirea protected.
Ceea ce diferențiază moștenirea de tip private față de moștenirea de tip protected este modul cum vor fi trasmiși mai departe, într-o nouă derivare, membrii clasei de bază. Toți membrii clasei de bază fiind moșteniți de tip private,
o nouă clasă derivată (care moștenește indirect clasa de bază) nu va mai putea accesa nici unul din membrii clasei de bază.
4.4 Mostenirea multipla
Relatia de derivare conduce la generarea unor ierarhii de clase. În astfel de ierarhii poate apare atât mostenirea singularã cât si cea multiplã.
Un exemplu de ierarhie de clasa este reprezentat in figura 3.4
Fig. 4.1 Ierarhie de clase
Clasa de bazã a ierarhiei de clase este clasa Persoana. Din aceastã clasã sunt derivate direct clasele Elev, Student si Salariat. La rândul ei clasa Salariat este clasã de bazã pentru clasele Medic si Profesor.
Aplicația P3.3 prezintã un exemplu de implementare a claselor Persoana, Salariat, Arhitect, Inginer si Medic.
// fisierul sursa p3_5.cpp
#include <iostream.h>
#include <string.h>
#include <assert.h>
// definitia clasei de baza Persoana
class Persoana {
char* nume;
char* pren;
public:
Persoana(char*, char*);
~Persoana();
void afisare();};
Persoana::Persoana(char* N, char* P)
{
nume = new char[strlen(N)+1];
strcpy(nume, N);
pren = new char[strlen(P)+1];
strcpy(pren, P);
}
Persoana::~Persoana()
{
delete nume;
delete pren;
}
void Persoana::afisare()
{
cout << "\n Nume: "<< nume << " Prenume: " << pren;
}
// definitia clasei Salariat derivata din clasa Persoana
class Salariat : public Persoana {
float salariu;
public:
Salariat(char *n, char *p, float s=0);
void seteazaSalariu(float s);
void afisare();};
Salariat::Salariat(char* n, char* p, float S)
:Persoana(n, p)
{
seteazaSalariu(s);
}
void Salariat::seteazaSalariu(float S)
{
salariu = s;
}
void Salariat::afisare()
{
cout << "\n Salariat:";
Persoana::afisare();
cout << "\n Salariu:" << salariu;
}
// definitia clasei Inginer derivata din clasa Salariat
class Inginer : public Salariat {
char* domeniu;
public:
Inginer(char* n, char* p, float s, char* d);
void seteazaDomeniu(char *d);
void afisare();
};
Inginer::Inginer(char* n, char* p, float s, char * d)
:Salariat(n, p, s)
{
domeniu = new char[strlen(d)+1];
strcpy(domeniu, d);
}
void Inginer::seteazaDomeniu(char* d)
{
strcpy(domeniu, d);
}
void Inginer::afisare()
{
cout << "\n Inginer ";
Salariat::afisare();
cout << "\n\t Domeniu de lucru este " << domeniu;
}
class Profesor : public Salariat {
char* tip;
public:
Profesor(char* n, char* p, float s, char* t);
void seteazaTip(char *t);
void afisare();};
Profesor::Profesor(char* n, char* p, float s, char* t)
:Salariat(n, p, s)
{
tip = new char[strlen(t)+1];
strcpy(tip_inginer, t);
}
void Profesor::seteazaTip(char* t)
{
strcpy(tip_inginer, T);
}
void Profesor::afisare()
{
cout << "\n Profesor ";
Salariat::afisare();
cout << "\n\t Tip profesor " << tip;
}
void main()
{
Persoana P1=Persoana("Popescu", "Ana");
Salariat S1=Salariat("Ion", "Alexandru", 2300);
Inginer I1=Inginer("Syan", "Maria", 3400,
"inginer civile");
Profesor PR1=Inginer("Pop", "Cristi", 2200,
"informatica");
P1.afisare();
S1.afisare();
I1.afisare();
PR1.afisare();
}
Moștenirea utilizatã este cea publicã. Functia afisare() a fost redefinitã în toate clasele derivate, ea apelând varianta din clasa imediat urmãtoare pe nivelul superior din ierarhia de clase.
De exemplu, functia afisare() din clasa Salariat apeleazã functia afisare() din clasa Persoana astfel,
Persoana::afisare();
Functia afisare() din clasele Inginer si Profesor apeleazã functia afisare() din clasa Salariat:
Salariat::afisare();
4.5 Clase de bază virtuale
Într-o moștenire multiplă este posibil ca o clasă să fie moștenită indirect de mai multe ori, prin intermediul unor clase care moștenesc, fiecare în parte, clasa de bază. De exemplu:
class L { public: int x;};
class A : public L { /* */};
class B : public L { /* */};
class D : public A, public B { /* */};
Acestă moștenire se poate reprezenta printr-un graf aciclic direcționat care indică relațiile dintre subobiectele unui obiect din clasa D. Din graful de reprezentare a moștenirilor, se poate observa faptul că baza L este replicată în clasa D.
Un obiect din clasa D va conține membrii clasei L de două ori, o dată prin clasa A (A::L) și o dată prin clasa B (B::L). In acest caz ser creaza o ambiguitate in situatia urmatoare :
D ob;
ob.x = 2; // eroare D::x este ambiguu; poate fi în baza L a clasei A sau în baza L a
clasei B
Aceste ambiguitati se pot elimina prin urmatoarele doua metode:
prin calificarea variabilei cu domeniul clasei căreia îi aparține:
ob.A::x = 2; // corect, x din A
ob.B::x = 3; // corect, x din B
crearea unei singure copii a clasei de bază în clasa derivată. Pentru aceasta este necesar ca acea clasă care ar putea produce copii multiple prin moștenire indirectă (clasa L, în exemplul de mai sus) să fie declarată clasă de bază de tip virtual în clasele care o introduc în clasa cu moștenire multipă. De exemplu:
class L { public: int x; };
class A : virtual public L { /* */ };
class B : virtual public L { /* */ };
class D : public A, public B { /* */ };
O clasă de bază virtuală este moștenită o singură dată și creează o singură copie în clasa derivată. Graful de reprezentare a moștenirilor din aceste declarațile de mai sus poate fi ilustrat asfel:
4.6 Funcții virtuale și polimorfism
O funcție virtuală este o funcție care este declarată de tip virtual în clasa de bază și redefinită într-o clasă derivată. Redefinirea unei funcții virtuale într-o clasă derivată domină definiția funcției în clasa de bază. Funcția declarată virtual în clasa de bază acționează ca o descriere generică prin care se definește interfața comună, iar funcțiile redefinite în clasele derivate precizează acțiunile specifice fiecărei clase derivate.
Mecanismul de virtualitate asigură selecția funcției redefinite în clasa derivată numai la apelul funcției pentru un obiect cunoscut printr-un pointer. În apelul ca funcție membră a unui obiect dat cu numele lui, funcțiile virtuale se comportă normal, ca funcții redefinite.
În limbajele de programare, un obiect polimorfic este o entitate, ca de exemplu, o variabilã sau argumentul unei funcții, cãreia i se permite sã pãstreze valori de tipuri diferite în timpul executiei programului. Funcțiile polimorfice sunt acele funcții care au argumente polimorfice. În limbajele de programare orientate pe obiecte, polimorfismul împreunã cu legarea dinamicã reprezintã una din caracteristicile extrem de utile care conduc la cresterea calitãtii programelor.
Implementarea obiectelor polimorfice se realizeazã prin intermediul funcțiilor virtuale.
Sintaxa declarãrii unei funcții virtuale:
virtual <tip_funcție> <nume_funcție> ([<lp>]);
<tip_funcție> reprezintã tipul întors de funcție, <lp> este lista de parametrii
Când un pointer al clasei de bazã puncteazã la o funcție virtualã din clasa derivatã si aceasta este apelatã prin intermediul acestui pointer, compilatorul determinã care versiune a funcției trebuie apelatã, tinând cont de tipul obiectului la care puncteazã acel pointer. Astfel, tipul obiectului la care puncteazã determinã versiunea funcției virtuale care va fi executatã.
In Aplicația P3.4 se consideră o clasă de bază B și două clase derivate D1 și D2. În clasa de bază sunt definite două funcții: funcția normală f()și funcția virtuală g(). În fiecare din clasele derivate se redefinesc cele două funcții f() și g(). În funcția main() se creează trei obiecte: un obiect din clasa de bază B indicat prin pointerul B* pb și două obiecte din clasele derivate D1 și D2. Fiecare dintre obiectele derivate poate fi indicat printr-un pointer la clasa derivată respectivă (D1* pd1, respectiv D2* pd2), precum și printr-un pointer la bază corespunzător (B* pb1 = pd1, respectiv B* pb2 = pd2).
class B {
public:
void f() { cout << "f() din B\n"; }
virtual void g(){ cout << "g() din B\n"; }};
class D1:public B {
public:
void f() { cout << "f() din D1\n"; }
void g() { cout << "g() din D1\n"; }
};
class D2:public B {
public:
void f() { cout << "f() din D2\n"; }
void g() { cout << "g() din D2\n"; } };
void fv1 {
B* pb = new B;
D1* pd1 = new D1;
D2* pd2 = new D2;
B* pb1 = pd1;
B* pb2 = pd2;
// f() este funcție normala,g() este funcție virtuala
// Obiect B, pointer B*
pb->f(); // f() din B
pb->g(); // g() din B
// Obiecte D1, D2 , pointeri D1*, D2*
pd1->f(); // f() din D1
pd2->f(); // f() din D2
pd1->g(); // g() din D1
pd2->g(); // g() din D2
// Obiecte D1, D2 , pointeri B*, B*
pb1->f(); // f() din B
pb2->f(); // f() din B
pb1->g(); // g() din D1
pb2->g(); // g() din D2
delete pb;
delete pd1;
delete pd2;
};
În primele situații, când pointerul este pointer la tipul obiectului, nu se manifestă nici o deosebire între comportarea unei funcții virtuale față de comportarea unei funcții normale: se selectează funcția corespunzătoare tipului pointerului și obiectului.
Diferența de comportare se manifestă în ultima situație, atunci când este apelată o funcție pentru un obiect de tip clasă derivată printr-un pointer la o clasă de bază a acesteia. Pentru funcția normală f() se selectează varianta depinzând de tipul pointerului. Pentru funcția virtuală g() se selectează varianta în funcție de tipul obiectului, chiar dacă este accesat prin pointer de tip bază.
Polimorfismul, adică apelul unei funcții dintr-o clasă derivată prin pointer de tip clasă de bază, este posibil numai prin utilizarea pointerilor la obiecte. Obiectele însele determină varianta funcției apelate, deci nu se pot selecta alte funcții decât cele ale obiectului de tipul respectiv. De exemplu, pentru aceleași clase definite ca mai sus, se consideră funcția fv2():
void fv2(){
B obB;
D1 obD1;
D2 obD2;
obB.f(); // f() din B
obB.g(); // g() din B
obD1.f(); // f() din D1
obD1.g(); // g() din D1
obD2.f(); // f() din D2
obD2.g(); // g() din D2
}
Observati
Constructorii nu pot fi funcții virtuale. În schimb, destructorii pot fi funcții virtuale.
Funcțiile inline nu pot fi virtuale.
Funcțiile virtuale sunt întotdeauna funcții membru nestatice ale unei clase.
Clase abstracte
De cele mai multe ori, o funcție declarată de tip virtual în clasa de bază nu definește o acțiune semnificativă și este neapărat necesar ca ea să fie redefinită în fiecare din clasele derivate. Pentru ca programatorul să fie obligat să redefinească o funcție virtuală în toate clasele derivate în care este folosită această funcție, se declară funcția respectivă virtuală pură. O funcție virtuală pură este o funcție care nu are definiție în clasa de bază, iar declarația ei arată în felul următor:
virtual tip_returnat nume_funcție(lista_argumente) = 0;
O clasă care conține cel puțin o funcție virtuală pură se numește clasă abstractă. Deoarece o clasă abstractă conține una sau mai multe funcții pentru care nu există definiții, nu pot fi create instanțe din acea clasă, dar pot fi creați pointeri și referințe la astfel de clase abstracte. O clasă abstractă este folosită în general ca o clasă fundamentală, din care se construiesc alte clase prin derivare.
Orice clasă derivată dintr-o clasă abstractă este, la rândul ei clasă abstractă (și deci nu se pot crea instanțe ale acesteia) dacă nu se redefinesc toate funcțiile virtuale pure moștenite. Dacă o clasă redefinește toate funcțiile virtuale pure ale claselor ei de bază, devine clasă normală și pot fi create instanțe ale acesteia.
Exemplul următor (5.6) evidențiază caracteristicile claselor abstracte și ale funcțiilor virtuale pure.
Aplicatia P3.5 se realizeaza conversia unor date dintr-o valoare de intrare într-o valoare de ieșire; de exemplu, din grade Farenheit în grade Celsius, din inch în centimetri, etc.
class Convert{
protected:
double x; // valoare intrare
double y; // valoare iesire
public:
Convert(double i){x = i;}
double getx(){return x;}
double gety(){return y;}
virtual void conv() = 0;};
// clasa FC de conversie grade Farenheit in grade Celsius
class FC: public Convert{
public:
FC(double i):Convert(i){}
void conv(){y = (x-32)/1.8;}};
// clasa IC de conversie inch in centimetri
class IC: public Convert{
public:
IC(double i):Convert(i){}
void conv(){y = 2.54*x;}}
void main (){
Convert* p = 0; // pointer la baza
cout<<"Introduceti valoarea si tipul conversiei: ";
double v;
char ch;
cin >> v >> ch;
switch (ch){
case 'i': //conversie inch -> cm (clasa IC)
p = new IC(v);
break;
case 'f': //conv. Farenheit -> Celsius (clasa FC)
p = new FC(v);
break;
}
if (p){
p->conv();
cout << p->getx() << "–> " << p->gety()<< endl;
delete p; }}
Clasa de bază abstractă Convert este folosită pentru crearea prin derivare a unei clase specifice fiecărui tip de conversie de date dorit. Această clasă definește datele comune, necesare oricărui tip de conversie preconizat, de la o valoare de intrare x la o valoare de ieșire y. Funcția de conversie conv() nu se poate defini în clasa de bază, ea fiind specifică fiecărui tip de conversie în parte; de aceea funcția conv() se declară funcție virtuală pură și trebuie să fie redefinită în fiecare clasă derivată.
În funcția main() se execută o conversie a unei valori introduse de la consolă, folosind un tip de conversie (o clasă derivată) care se selectează pe baza unui caracter introdus la consolă.
Acesta este un exemplu în care este destul de pregnantă necesitatea funcțiilor virtuale: deoarece nu se cunoaște în momentul compilării tipul de conversie care se va efectua, se folosește un pointer la clasa de bază pentru orice operație (crearea unui obiect de conversie nou, apelul funcției conv(), afișarea rezultatelor, distrugerea obiectului la terminarea programului). Singura diferențiere care permite selecția corectă a funcțiilor, este tipul obiectului creat, care depinde de tipul conversiei cerute de la consolă.
Polimorfism
Polimorfismul permite unei entitãti (de exemplu, variabilã, funcție, obiect) sã aibã o varietate de reprezentãri. El este furnizat atât la momentul compilãrii (legare timpurie), prin folosirea operatorilor si a funcțiilor redefinite, cât si la momentul executiei (legare târzie), prin utilizarea funcțiilor virtuale.
Conceptul de legare dinamicã permite unei variabile sã aibã tipuri diferite în funcție de continutul ei la un moment dat. Aceastã abilitate a variabilei se numeste polimorfism, iar variabila se numeste variabilã polimorficã.
În limbajul C++ variabilele polimorfice apar doar prin utilizarea pointerilor sau referintelor. În cazul în care un pointer al clasei de bazã puncteazã cãtre o funcție virtualã, programul va determina la momentul executiei la care tip de obiect puncteaza pointerul si apoi va selecta versiunea corespunzãtoare funcției redefinite.
Aplicația P4.4 defineste clasa de bazã Persoana si douã clase derivate, Student si Salariat. Clasa de bazã Persoana este o clasã abstractã având declaratã o funcție virtualã purã, venit(), definitã în clasele derivate. De asemenea, clasa Persoana mai contine o altã funcție virtualã, afisare(), care este redefinitã în clasele derivate. În programul principal sunt create douã obiecte S1 si T1 din clasele Student si respectiv Salariat. Pentru fiecare din cele douã obiecte se executã o secventã de patru instructiuni: primele douã instructiuni ilustreazã legarea staticã, prin apelul celor douã funcții afisare() si venit(), corespunzãtoare obiectului referit prin nume; ultimele douã instructiuni ilustreazã legarea dinamicã apelând cele douã funcții afisare() si venit() prin intermediul a douã funcții, Ref_Pointer(.), Ref_Referinta(.), care utilizeazã un pointer, respectiv o referintã cãtre clasa de bazã Persoana.
Aceste ultime două instructiuni genereazã un comportament polimorfic, la momentul executiei programului.
// fisierul sursa p4_4.cpp
#include <iostream.h>
#include <string.h>
#include <assert.h>
// clasa de baza Persoana, clasa abstracta
class Persoana {
char* prenume;
char* nume;
public:
Persoana(char*, char*);
~Persoana();
char* preiaPrenume();
char* preiaNume();
// funcție virtuala pura
virtual double venit() = 0;
virtual void afisare();
};
Persoana::Persoana(char* P, char* N)
{
prenume = new char[strlen(P)+1];
strcpy(prenume, P);
nume = new char[strlen(N)+1];
strcpy(nume, N);
}
Persoana::~Persoana()
{
delete prenume;
delete nume;
}
char* Persoana::preiaPrenume()
{
return prenume;
}
char* Persoana::preiaNume()
{
return nume;
}
void Persoana::afisare()
{
cout << prenume << " " << nume << "\n";
}
// definim clasa Student derivata din clasa Persoana
class Student : public Persoana {
double bursa;
double media;
public:
Student(char*, char*, double = 0.0, double = 0.0);
void seteazaBursa(double);
void seteazaMedia(double);
virtual double venit();
virtual void afisare();};
Student::Student(char* P, char* N, double B, double M)
:Persoana(P,N)
{
M>=8.50?seteazaBursa(B):seteazaBursa(0.0);
seteazaMedia(M);
}
void Student::seteazaBursa(double B)
{
bursa = B>0?B:0;
}
void Student::seteazaMedia(double M)
{
media = M>0?M:0.0;
}
double Student::venit()
{
return bursa;
}
void Student::afisare()
{
cout << "\n Student:";
Persoana::afisare();
cout << "\t Media = " << media << "\n";
}
// definim clasa Salariat derivata din clasa Persoana
class Salariat:public Persoana {
double salariu;
double venit_ora;
int nr_ore;
public:
Salariat(char*, char*, double = 0.0, double = 0.0, int = 0);
void seteazaSalariu(double);
void seteazaVenitOra(double);
void seteazaNrOre(int);
virtual double venit();
virtual void afisare();
};
Salariat::Salariat(char* P,char* N,double S,double V,int nr):Persoana(P, N)
{
seteazaSalariu(S);
seteazaVenitOra(V);
seteazaNrOre(nr);
}
void Salariat::seteazaSalariu(double S)
{
salariu = S>0 ? S : 0;
}
void Salariat::seteazaVenitOra(double V)
{
venit_ora = V>0.0 ? V : 0.0;
}
void Salariat::seteazaNrOre(int nr)
{
nr_ore = nr>0 ? nr : 0;
}
double Salariat::venit()
{
return salariu + nr_ore*venit_ora;
}
void Salariat::afisare()
{
cout << "\n Salariat:";
Persoana::afisare();
}
// funcție care apeleaza funcțiile virtuale prin legare
// dinamica, in cazul unui pointer al clasei de baza
// referire prin pointer
void Ref_Pointer(Persoana *Ptr)
{
Ptr->afisare();
cout << " venit (lei) " << Ptr->venit();
}
// funcție care apeleaza funcțiile virtuale prin legare
// dinamica, in cazul unei referinte la clasa de baza –
// referire prin referinta
void Ref_Referinta(Persoana &Ptr)
{
Ptr.afisare();
cout << " venit (lei) " << Ptr.venit();
}
void main()
{
Student S1("Alexandra", "Stoica", 1500000, 10);
cout << "\n Legare statica:\n";
S1.afisare();
cout << " venit (lei) "<<S1.venit();
cout << "\n Legare dinamica:\n";
Ref_Pointer(&S1);
Ref_Referinta(S1);
Salariat T1("Silvan", "Manole", 50000000, 100000, 30);
cout << "\n Legare statica:\n";
T1.afisare();
cout<<" venit (lei) "<<T1.venit();
cout << "\n Legare dinamica: \n";
Ref_Pointer(&T1);
Ref_Referinta(T1);
}
Observatii
Principiul care stã la baza polimorfismului este “o singurã interfatã, metode multiple”. Practic, polimorfismul reprezintã abilitatea obiectelor din clase diferite ale unei ierarhii de clase de a rãspunde diferit la acelasi mesaj (adicã,la apelul unei funcții membru).
Implementarea polimorfismului este realizatã prin intermediul funcțiilor virtuale.
În cazul în care o funcție membru ne-virtualã este definitã într-o clasã de bazã si redefinitã într-o clasã derivatã, comportamentul este ne-polimorfic. Astfel, dacã functia membru este apelatã printr-un pointer al clasei de bazã la obiectul clasei derivate, se utilizeazã versiunea clasei de bazã. Dacã functia membru este apelatã printr-un pointer al clasei derivate, se utilizeazã versiunea clasei derivate.
Teste de autoevaluare
Răspunsuri la testele de autoevaluare
Tema de autoinstruire
Lucrari de verificare pentru studenți
Formular de feedback
În dorința de ridicare continuă a standardelor desfășurării activitatilor dumneavoastra, va rugăm să completați acest chestionar și să-l transmiteți indrumatorului de an.
Disciplina: ________________________________
Unitatea de invatare/modulul:__________________
Anul/grupa:________________________________
Tutore:____________________________________
a) Conținut / Metoda de predare
Partea I
1. Care dintre subiectele tratate in aceasta unitate/modul considerați că este cel mai util și eficient? Argumentati raspunsul.
2. Ce aplicatii/proiecte din activitatea dumneavoastra doriți să imbunatatiti/modificați/implementați în viitor în urma cunoștințelor acumulate în cadrul acestei unitati de invatare/modul?
3. Ce subiecte considerați că au lipsit din acesta unitate de invatare/modul?
4. La care aplicatii practice ati intampinat dificultati in realizare? Care credeti ca este motivul dificultatilor intalnite?
6. Timpul alocat acestui modul a fost suficient?
7. Daca ar fi sa va evaluati, care este nota pe care v-o alocati, pe o scala de la 1-10?. Argumentati.
Partea II. Impresii generale
1. Acest modul a întrunit așteptările dumneavoastră?
2) Aveți sugestii care să conducă la creșterea calității acestei unitati de invatare/modul?
3) Aveți propuneri pentru alte unitati de invatare?
Vă mulțumim pentru feedback-ul dumneavoastră!
Unitatea de învățare Nr. 5
Supraîncarcarea operatorilor
Cuvinte cheie: redefinire operatori, funcții operator, asignare.
5.1 Introducere
Limbajul C++ permite programatorului sã defineascã diverse operatii cu obiecte ale claselor, utilizând simbolurile operatorilor standard. Pentru tipurile fundamentale ale limbajului sunt definite seturi de operatori care permit operații de bază executate într-un mod convenabil. Dar, după cum este cunoscut, în limbaj sunt definite prea puține tipuri de date ca date fundamentale, iar pentru reprezentarea altor tipuri care sunt necesare în diferite domenii (cum ar fi aritmetica numerelor complexe, algebra matricilor, etc.), se definesc clase care conțin funcții ce pot opera asupra acestor tipuri.
Limbajul C++ nu permite crearea de noi operatori, în schimb permite redefinirea majoritãtii operatorilor existenti astfel încât atunci când acesti operatori sunt aplicati obiectelor unor clase sã
aibã semnificatia corespunzãtoare noilor tipuri de date.
Principalele avantaje ale redefinirii operatorilor sunt claritatea si usurinta cu care se exprimã anumite operatii specifice unei clase. Solutia alternativã ar fi definirea unor funcții si apelul funcțiilor în cadrul unor expresii.
Funcțiile operator pentru o anumită clasă pot să fie sau nu funcții membre ale clasei. Dacă nu sunt funcții membre ele sunt, totuși, funcții friend ale clasei și trebuie să aibă ca argument cel puțin un obiect din clasa respectivă sau o referință la aceasta.
5.2 Funcții operator membre ale claselor
Forma generală pentru funcțiile operator membre ale clasei este următoarea:
tip_returnat operator # (lista_argumente){
// operații}
Semnul # reprezintă oricare dintre operanzii care pot fi supraîncărcați.
Aplicatia P 5.1 prezinta clasa Point care descrie un vector într-un plan bidimensional prin două numere de tip float, x și y. Valorile x și y reprezintă coordonatele punctului de extremitate al vectorului. Pentru această clasă se pot defini mai multe operații cu vectori, ca de exemplu:
Suma a doi vectori
Diferența a doi vectori
Produsul scalar a doi vectori
Multiplicarea unui vector cu o (scalare)
Aceste operații se pot implementa prin supraîncărcarea corespunzătoare a operatorilor. Pentru început se vor defini funcțiile operator+() și operator–() pentru calculul sumei, respectiv a diferenței a doi vectori.
class Point{
float x;
float y;
public:
Point(){
x = 0;
y = 0;
}
Point(double a, double b){
x = a;
y = b;
}
void Display() {
cout << x << " " << y << endl;
}
Point operator+(Point op2); // suma a doi vectori
Point operator-(Point op2); // diferența a doi vect
double operator*(Point op2);// produs scalar
Point& operator*(double v); // multipl. cu o const.
};
Point Point::operator+(Point op2){
point temp;
temp.x = x + op2.x;
temp.y = y + op2.y;
return temp;
}
Point Point::operator-(Point op2){
point temp;
temp.x = x + op2.x;
temp.y = y + op2.y;
return temp;
}
double Point::operator*(Point op2){
return x*op2.y + y*op2.x;
}
Point& Point::operator*(double v){
x *=v;
y *=v;
return *this;
}
void f1(){
Punct pct1(10,20);
Punct pct2(30,40);
Punct pct3;
pct1.Display(); // afiseaza 10 20
pct2.Display(); // afiseaza 30 40
pct3 = pct1 + pct2;
pct3.Display(); // afiseaza 40 60
pct3 = pct2 – pct1;
pct3.Display(); // afiseaza 20 20
}
Funcția operator+() are un singur argument, chiar dacă ea supraîncarcă un operator binar pentru ca argumentul transmis funcției este operandul din dreapta operației, iar operandul din stânga este chiar obiectul pentru care se apelează funcția operator.
Pentru același operator se pot defini mai multe funcții supraîncărcate, cu condiția ca selecția uneia dintre ele în funcție de numărul și tipul argumentelor să nu fie ambiguă. În clasa Point s-a supraîncărcat operatorul * cu două funcții: prima pentru calculul produsului scalar a doi vectori, cealaltă pentru multiplicarea vectorului cu o .
În implementarea prezentată, funcția operator+() crează un obiect temporar, care este distrus după returnare. În acest fel, ea nu modifică nici unul dintre operanzi, așa cum nici operatorul + pentru tipurile predefinite nu modifică operanzii.
În general, un operator binar poate fi supraîncărcat fie printr-o funcție membră nestatică cu un argument, fie printr-o funcție nemembră cu două argumente.
Un operator unar poate fi supraîncărcat fie printr-o funcție membră nestatică fără nici un argument, fie printr-o funcție nemembră cu un argument. La supraîncărcarea operatorilor de incrementare sau decrementare (++, –) se poate diferenția un operator prefix de un operator postfix folosind două versiuni ale funcției operator. În continuare sunt prezentate câteva funcții operator ale clasei Point pentru operatori unari.
Aplicatia P5.2
class Point{
//……………
public:
Point operator!();
Point operator++();
Point operator—();
Point operator++(int x);
Point operator—(int x);
};
Point operator!(){
x = -x;
y = -y;
return *this;
}
Point Point::operator++(){
x++;
y++;
return *this;
}
Point Point::operator–(){
x–;
y–;
return *this;
}
Point Point::operator ++(int x){
++x;
++y;
return *this;
}
Point Point::operator –(int x){
–x;
–y;
return *this;
}
Dacă ++ precede operandul, este apelată funcția operator++(); dacă ++ urmează operandului, atunci este apelată funcția operator++(int x), iar x are valoarea 0.
5.3 Funcții operator membre ale claselor
La supraîncărcarea unui operator folosind o funcție care nu este membră a clasei este necesar să fie transmiși toți operanzii necesari, deoarece nu mai există un obiect al cărui pointer (this) să fie transferat implicit funcției. Din această cauză, funcțiile operator binar necesită două argumente de tip clasă sau referință la clasă, iar funcțiile operator unar necesită un argument de tip clasă sau referință la clasă. În cazul operatorilor binari, primul argument transmis este operandul stânga, iar al doilea argument este operandul dreapta
In Aplicatia P5.3 o parte din funcțiile operator ale clasei Point sunt implementate ca funcții friend ale clasei.
class Point
{
int x;
int y;
public:
//……………………….
friend Point operator+(Point op1, Point op2);
friend Point operator-(Point op1, Point op2);
Point operator+(Point op1, Point op2){
Point temp;
temp.x = op1.x + op2.x;
temp.y = op1.y + op2.y;
return temp;
}
Point operator-(Point op1, Point op2){
Point temp;
temp.x = op1.x – op2.x;
temp.y = op1.y – op2.y;
return temp;
}
void f2(){
Punct pct1(10,20);
Punct pct2(30,40);
Punct pct3;
pct1.Display(); // afiseaza 10 20
pct2.Display(); // afiseaza 30 40
pct3 = pct1 + pct2;
pct3.Display(); // afiseaza 40 60
pct3 = pct2 – pct1;
}
Observatii
Dacã functia operator este implementatã ca o funcție membru, operandul cel mai din stânga (eventual, unicul operand) trebuie sã fie un obiect al clasei sau o referintã cãtre un obiect al clasei. Implementarea sub formã de funcție nemembru este indicatã în cazul în care cel mai din stânga operand este un obiect al unei clase diferite sau al unui tip predefinit.
O funcție operator ne-membru trebuie declaratã funcție friend dacã functia respectivã trebuie sã acceseze direct membrii privati sau protejati ai clasei respective.
Teste de autoevaluare
Raspunsuri la testele de autoevaluare
Tema de autoinstruire
Lucrari de verificare pentru studenți
Formular de feedback
În dorința de ridicare continuă a standardelor desfășurării activitatilor dumneavoastra, va rugăm să completați acest chestionar și să-l transmiteți indrumatorului de an.
Disciplina: ________________________________
Unitatea de invatare/modulul:__________________
Anul/grupa:________________________________
Tutore:____________________________________
a) Conținut / Metoda de predare
Partea I
1. Care dintre subiectele tratate in aceasta unitate/modul considerați că este cel mai util și eficient? Argumentati raspunsul.
2. Ce aplicatii/proiecte din activitatea dumneavoastra doriți să imbunatatiti/modificați/implementați în viitor în urma cunoștințelor acumulate în cadrul acestei unitati de invatare/modul?
3. Ce subiecte considerați că au lipsit din acesta unitate de invatare/modul?
4. La care aplicatii practice ati intampinat dificultati in realizare? Care credeti ca este motivul dificultatilor intalnite?
6. Timpul alocat acestui modul a fost suficient?
7. Daca ar fi sa va evaluati, care este nota pe care v-o alocati, pe o scala de la 1-10?. Argumentati.
Partea II. Impresii generale
1. Acest modul a întrunit așteptările dumneavoastră?
2) Aveți sugestii care să conducă la creșterea calității acestei unitati de invatare/modul?
3) Aveți propuneri pentru alte unitati de invatare?
Vă mulțumim pentru feedback-ul dumneavoastră!
Unitatea de învățare Nr. 6
OperaȚii de intrare/ieȘire
Cuvinte cheie: stream, ostream, istream, fișier de intrare, fisier de ieșire.
6.1 Funcții de I/O pentru tipurile predefinite
Bibliotecile standard ale limbajului C++ furnizeazã un set extins de facilitãti pentru operatii de intrare/iesire (I/E). Dacã o funcție de I/E este definitã corespunzãtor unui anumit tip de date, ea va putea fi apelatã pentru a lucra cu acel tip de date. Se pot specifica atât tipuri standard, cât si tipuri definite de utilizatori.
Operațiile de I/O din C++ se efectuează folosind funcțiile operator de inserție << și operator de extragere >>.
Funcțiile de operare asupra streamurilor specifică modul în care se execută conversia între un șir de caractere din stream și o variabilă de un anumit tip. Aceste funcții operator sunt definite în clasa ostream, respectiv istream, pentru toate tipurile predefinite ale limbajului, iar pentru tipurile definite de utilizator ele pot fi supraîncărcate.
Funcția de citire de la consolă a unei secvențe de numere întregi separate prin spații albe (whitespace adică unul din caracterele blanc, tab, newline, carriage return, formfeed) poate arăta asfel:
void main(){
int size = 10;
int array[10];
for(int i=0;i<size;i++){
if (cin >> array[i])
cout << array[i] << " ";
else {
cout << "eroare non-int";
break;
}
}
}
O intrare diferită de întreg va cauza eroare în operația de citire și deci oprirea buclei for. De exemplu, din intrarea:
1 2 3 4.7 5 6 7 8 9 0 11
se va citi primele patru numere, după care apare eroare în operația de intrare, citirea numerelor întregi se întrerupe și pe ecran apare mesajul:
1 2 3 4 eroare non-int
Caracterul punct este lăsat în streamul de intrare, ca următor caracter de citit.
O altă soluție pentru citirea unei secvențe de intrare este prin folosirea uneia din funcțiile get() definite în clasa iostream astfel:
class iostream : public virtual ios {
// ……
istream& get(char& c);
istream& get(char* p, int n, char ch=’\n’);
};
Aceste funcții treatează spațiile albe la fel ca pe toate celelalte caractere. Funcția get(char& c) citește un singur caracter în argumentul c. De exemplu, o funcție fg() de copiere caracter cu caracter de la intrare (streamul cin) la ieșire (streamul cout) poate arăta astfel:
void stream(){
char c;
while(cin.get(c)) cout << c;
}
Și funcția operator >> () are un echivalent ca funcție de scriere la consolă fără supraîncărcarea operatorului >>, numită funcția put(), astfel încât funcția fg() se poate rescrie astfel:
void stream(){
char c;
while(cin.get(c)) cout.put(c);
}
6.2 Funcții de I/O pentru tipuri definite de utilizator
Funcțiile de I/O pentru tipuri definite de utilizator se obțin prin supraîncărcarea operatorilor de inserție și de extragere, care au următoarea formă generală:
ostream& operator<<(ostream& os,tip_clasa nume){
// corpul funcției
return os;
}
istream& operator<<(istream& is,tip_clasa& nume){
// corpul funcției
return is;
}
Primul argument al funcției este o referință la streamul de ieșire, respectiv de intrare. Pentru funcția operator de extragere << al doilea argument este dat printr-o referință la obiectul care trebuie să fie extras din stream; în această referință sunt înscrise datele extrase din streamul de intrare. Pentru funcția operator de inserție >> al doilea argument este dat prin tipul și numele obiectului care trebuie să fie inserat, sau printr-o referință la acesta. Funcțiile operator de inserție și extracție returnează o referință la streamul pentru care au fost apelate, astfel încât o altă operație de I/O poate fi adăugată acestuia.
Funcțiile operator << sau >> nu sunt membre ale clasei pentru care au fost definite, dar pot (și este recomandabil) să fie declarate funcții friend în clasa respectivă.
Aplicatia P 6.1 se defineste clasa Complex care cuprinde funcțiile operator << si >>.
class Complex {
double x, y;
public:
Complex(){x = 0; y = 0}
Complex(double r, double i){
x = r;
y = i;
}
…………..
friend ostrem& operator << (ostrem& os,Complex z);
friend istream& operator >>(istream& is,Complex& z);
};
ostream& operator<<(ostream& os, Complex& z){
os << ‘(‘ << z.x << ’,’<< z.y << ‘)’;
return os;
}
istream& operator>>(istream& is, Complex z){
is >> z.x >> z.y;
return is;
}
void main(){
Complex z;
cout << "Introduceti x, y :";
cin >> z;
cout << "z = " << z << '\n';
cin.get();
int size = 10;
Execuția acestei funcții produce următoarele mesaje la consolă:
Introduceti x, y: 1.3 4.5
z = (1.3,4.5)
Introduceti un sir:123456
123456
6. 3 Procesarea fisierelor
Procesarea fisierelor în limbajul C++ necesitã includerea fisierelor antet <iostream.h> si <fstream.h>. Fisierul antet <fstream.h> cuprinde definitiile claselor streamului: ifstream (pentru intrãrile dintr-un fisier – operatii de citire din fisier), ofstream (pentru iesirile cãtre un fisier – operatii de scriere în fisier) si fstream (pentru intrãrile/iesirile de la/cãtre un fisier).
Pentru utilizarea unui fișier pe disc acesta trebuie să fie asociat unui stream. Pentru aceasta se crează mai întâi un stream, iar apelul funcției open() a streamului execută asocierea acestuia cu un fișier ale cărui caracteristici se transmit ca argumente ale funcției open(). Funcția open() este funcție membră a fiecăreia dintre cele trei clase stream (ifstream, ofstream și fstream)
class ios{
public:
// …………..
enum open_mode {
in=1, // desch. pt. citire
out=2, // desch. pt. scriere
ate=4, // desch. pt căutare
app=010, // adaugare (append)
trunc=020, // trunchiere
nocreate=040, // er. daca nu exista fis.
noreplace=0100 // er. daca fis. exista
};
// ………………
};
Argumentul mode este opțional pentru streamuri de intrare ifstream, pentru care valoarea implicită este ios::in, și pentru streamuri de ieșire ofstream, pentru care valoarea implicită este ios::out. Pentru streamuri de intrare/ieșire fstream, argumentul mode trebuie să aibă una din valorile definite în clasa ios. În argumentul mode se pot combina prin operatorul OR (SAU) două sau mai multe din aceste valori definite.
Exemple de apeluri ale funcției open() pentru deschidere de fișiere:
ifstream in;
input.open("date_intrare.txt");
ofstream out;
output.open("date_iesire.txt");
fstream inout;
inout.open("fisier", ios::in|ios::out);
Deschiderea unui fișier se poate realiza prin declarații care apelaza un constructor de inițializare al streamului. De exemplu, se pot rescrie definirile de streamuri și fișiere asociate de mai sus astfel:
ifstream input("intrare.txt");
ofstream output("iesire.txt");
fstream inout("fisier", ios::in|ios::out);
Implicit, fișierele se deschid în mod text. Valoarea ios::binary determină deschiderea în mod binar a fișierului.
Pentru închiderea unui fișier se apelează funcția close(), care funcție membră a claselor stream (ifstream, ofstream și fstream).
Pentru scrierea și citirea dintr-un fișier de tip text se folosesc funcțiile operator << și >> ale streamului asociat acelui fișier. Aceste funcții operator supraîncărcate pentru un anumit tip de date pot fi folosite fără nici o modificare atât pentru a scrie sau citi de la consolă cât și pentru a scrie sau citi dintr-un fișier pe disc.
Aplicatia P6.2 crează un fișier de inventariere “inventar.txt” care conține numele articolului, prețul unitar, numărul de bucăți și valoarea totală.
void finv(){
ofstream output("inventar.txt");
if (!output)
cout << "Nu se poate deschide
fisierul inventar.txt\n";
char buffer[80];
double pret,total;
int buc;
while(1){
cout << "\nArticol: ";
cin.get(buffer,80);
if (buffer[0] == 0)
break;
cout << "Pret unitar: ";
cin >> pret;
cout << "Nr. bucati: ";
cin >> buc; cin.get();
total = buc*pret;
output << buffer << endl;
output << pret<< endl;
output << buc << endl;
output << total << endl;
}
output.close();
}
6.4 Accesul aleator la fișiere
Accesul aleator la datele dintr-un fișier se realizeaza utilizand doi pointeri: pointerul de citire (get) care specifică poziția de unde va avea loc următoarea citire din fișier și pointerul de scriere (put), care specifică poziția unde va avea loc următoarea scriere în fișier. După fiecare operație de citire sau de scriere, pointerul corespunzător este avansat secvențial în fișier. Pentru poziționarea într-o anumită poziție a celor pointeri se folosesc funcții membre publice ale claselor de bază istream, ostream.
Pentru accesul aleator în operațiile de ieșire se pot folosi următoarele funcții membre publice ale clasei ostream:
class ostream: public virtual ios {
// ………………….
public:
ostream& seekp(streampos pos);
ostream& seekp(streamoff offset, seek_dir orig);
streampos tellp();
// …………………
};
Tipurile de date streamoff și streampos sunt definite în fișierul antet iostream.h,
Funcția seekp() cu un argument poziționează pointerul de inserție a fișierului la valoarea pos, dată ca argument, iar funcția seekp() cu două argumente deplasează pointerul de inserție al fișierului asociat streamului cu un număr de caractere egal cu valoarea offset față de originea orig specificată ca argument.
Funcția tellp() returnează poziția curentă de scriere în fișier, deci valoarea pointerului de scriere.
Tema de autoinstruire
6.1 Clasele predefinite, dedicate procesãrii fisierelor;
6.2 Funcții de I/O pentru tipuri predefinite de date.
Lucrari de verificare pentru studenți
6.1 Să se citească un fișier care conține numere flotante, să se construiască numere complexe din perechi de câte două numere flotante și să se afișeze la consolă numerele complexe.
6.2 Creati doua obiecte de tip Student definit in aplicatia 3.1 si afisati datele acestora in fisierul “date_studentii.txt”.
6.3 Incapsulați în clasa Fractie definită în aplicația 2.3 o funcție care citeste datele unui obiect din fisierul “fractie_in.txt” și afisați în fiserul “fractie_iredu.txt” fracția sub formă ireductibilă.
Formular de feedback
În dorința de ridicare continuă a standardelor desfășurării activitatilor dumneavoastra, va rugăm să completați acest chestionar și să-l transmiteți indrumatorului de an.
Disciplina: ________________________________
Unitatea de invatare/modulul:__________________
Anul/grupa:________________________________
Tutore:____________________________________
a) Conținut / Metoda de predare
Partea I
1. Care dintre subiectele tratate in aceasta unitate/modul considerați că este cel mai util și eficient? Argumentati raspunsul.
2. Ce aplicatii/proiecte din activitatea dumneavoastra doriți să imbunatatiti/modificați/implementați în viitor în urma cunoștințelor acumulate în cadrul acestei unitati de invatare/modul?
3. Ce subiecte considerați că au lipsit din acesta unitate de invatare/modul?
4. La care aplicatii practice ati intampinat dificultati in realizare? Care credeti ca este motivul dificultatilor intalnite?
6. Timpul alocat acestui modul a fost suficient?
7. Daca ar fi sa va evaluati, care este nota pe care v-o alocati, pe o scala de la 1-10?. Argumentati.
Partea II. Impresii generale
1. Acest modul a întrunit așteptările dumneavoastră?
2) Aveți sugestii care să conducă la creșterea calității acestei unitati de invatare/modul?
3) Aveți propuneri pentru alte unitati de invatare?
Vă mulțumim pentru feedback-ul dumneavoastră!
Unitatea de învățare Nr. 7
FacilitaȚii Ale limbajului c++
Cuvinte cheie: clase template, funcții template, exceptii.
7. 1 Funcții si clase template
O altã facilitate importantã a limbajului C++ este datã de posibilitatea definirii unor sabloane numite template-uri. Un template reprezintã o modalitate de parametrizare a unei clase sau a unei funcții prin utilizarea unui tip în acelasi mod în care parametrii unei funcții furnizeazã o modalitate de a defini un algoritm abstract fãrã identificarea valorilor specifice.
O clasă template specifică modul în care pot fi construite clase individuale, diferențiate prin tipul sau tipurile de date asupra cărore se operează.
Sintaxa definirii unui template:
template <class <parametru> > class <nume_clasã>
{
// definitia clasei sablon
};
Aplicatia P7.1 prezintã un exemplu de definire a unei funcții template, afisare_vector(.). Aplicarea funcției template celor trei variabile T1, T2 si respectiv T3 va determina afisarea unui
vector de numere întregi, a unui vector de numere reale, respectiv a unui vector de caractere.
// fisierul sursa P7.1.cpp
#include <iostream.h>
template <class T>
void afisare_vector(const T *t, const int nr)
{
for (int i=0;i<nr;i++)
cout<<t[i]<<" ";
}
void main()
{
const int nr1 = 5, nr2 = 7, nr3 = 4;
int T1[nr1] = {1,2,3,4,5};
double T2[nr2] = {0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7};
char T3[nr3] = "Ana";
cout<<"\n Vectorul contine: ";
// functia template pentru vector de numere intregi
afisare_vector(T1, nr1);
cout<<"\n Vectorul contine: ";
// functia template pentru vector de numere reale
afisare_vector(T2, nr2);
cout<<"\n Vectorul contine: ";
// functia template pentru vector de caractere
afisare_vector(T3, nr3);
}
Clasele template sunt descrieri parametrizate de clasa, care vor fi adaptate ulterior diferitelor tipuri de date recunoscute de limbaj.
Aplicatia 7. 2 defineste o clasa template pentru un obiect de tip vector in memorie dinamica, continad adresa ade inceput si dimensiunea lui, ceruta la alocare. Initial, tipul elementelor stocate in vector este generic, iar in functia principala main() se solocita particularizari ale clasei template pentru tipul int si double.
// fisierul sursa P7.1.cpp
template<class T> class vector
{
T * vec;
int size;
public:
vector(int n)
{
dim=n;
vec=new T[n];
for(int i=0;i<size;i++)
cin>>vect[i];
}
void afisare()
{
for(int i=0;i<size;i++)
cout<<vect[i];
}
int get_size()
{ return size;}
}
void main( )
{
vector<int> vi(3);
vi.afisare();
vector<double>vd(4);
vd.afisare();
}
Observatii:
vi este un vector cu trei elemente intregi;
vd este un vector cu patru elemente reale;
metodele se apleaza in mod obisnuit, fara a necesita o sintaxa speciala;
template-urile furnizeazã o altã formã de polimorfism.
7.2 Instantierea sabloanelor
Un sablon de clasă poate fi instanțiat devenind o clasă concretă prin substituirea tipurilor generice cu tipuri concrete. La definirea sabloanelor de clasa atat datele , cât și funcțiile membre se definesc in legatura cu unul sau mai multe tipuri generice. Funcțiile membre pot avea aceeasi denumire prin tehnica de supraincarcare, in schimb doua clase nu pot avea aceelasi nume deoarece o clasa corespunde de fapt unui tip de data. Utilizand clasele template se poate construi un singur tip care sa aiba mai multe denumiri. Adica, programatorul poate referi clasele construite din template dupa n nume compus din numele dat de el in sablon, urmat de diverse nume de tip pentru care se cere instantierea modelului.
Putem avea în consecință clasele vector<int>, vector<douable> etc. care sunt particularizări ale modelului de clasa vector. Numele de instanță a clasei template poate fi folosit nu numai pentru pentru generari de obiecte ale clasei, ci și pentru a descrie funcții care nu sunt template.
In Aplicatia 7.3 se definește o funcție care verifică dacă doi vectori au aceealași numar de componente. Funcția va returna -1, 0, 1 daca primul vector este mai scurt, egal sau mai lung decat cel de al doilea.
int masoara(vector<int> &v1,vector<double>&v2)
{
int size1=v1. get_size(), size2=v2.get_size();
if(size1==size2) return 0;
else
if(size1<size2) return -1;
else
return +1;
}
7.3 Tratarea exceptiilor
Mecanismul de tratare a excepțiilor din C++ oferă posibilitatea ca o eroare care apare într-o funcție și aceasta nu o poate rezolva, să fie definită ca o excepție, astfel incat că funcția apelantă va trata această eroare. Codul care se executa atunci cand are loc captarea exceptiei de catre functia care o trateaza , se numeste rutina de tratare a exceptiei (exception handler).
Într-un program pot apărea mai multe tipuri de erori de execuție (run-time errors) și fiecare tip de eroare poate fi asociat unei excepții cu un nume distinct. Captarea excepțiilor este executată de mai multe blocuri catch, care pot fi asociate unuia sau mai multor blocuri try. Forma generală pentru un bloc try urmat de mai multe blocuri catch care tratează diferite tipuri de excepții este următoarea:
try {
// bloc try
}
catch (tip_arg1 arg1) {
// bloc de tratare a excepției
// de tipul tip_arg1
}
catch (tip_arg2 arg2) {
// bloc catch de tratare a excepției
// de tipul tip_arg2
}
………………………………….
catch (tip_argn argn) {
// bloc de tratare a excepției
// de tipul tip_argn
}
In Aplicatia 7.4 se definește clasa Vector în care pot fi tratate mai multe tipuri de erori: eroarea de depășire a dimensiunii vectorului, eroarea care apare datorită unei valori inacceptabile la construcția vectorului:
class Vector{
int* elem;
int size;
enum {max=1000};
public:
class Domeniu {}; // exceptie la depasire domeniu
class Size {}; // exceptie la constructie
Vector(int s);
int& operator[](int i);
// …………………….
};
Vector::Vector(int s){
if (s < 0 || max < s) throw Size();
size = s;
elem = new int[s];
}
Operatorul de indexare [] al clasei Vector va lansa excepția de tip (clasă) Domeniu dacă este apelat cu un indice în afara domeniului. În mod similar, constructorul Vector::Vector va lansa o excepție de tip Size, dacă este apelat cu un argument cu o valoare inacceptabilă.
În utilizarea clasei Vector se poat trata cele două excepții prin adăugarea a două rutine de tratare a excepțiilor după un bloc try:
void fex(Vector &v){
try {
fv(v); // apelul unei funcții cu arg. v
}
catch(Vector::Domeniu){
// Rutina de tratare a exceptiei
// Vector::Domeniu
// In acest punct se ajunge numai dacă
// in functia fv(v) s-a apelat operatorul []
// cu un indice în afara domeniului
}
catch(Vector::Size){
// Rutina de tratare a exceptiei
// Vector::Size
// In acest punct se ajunge daca in functia
// fv(v) s-a apelat un constructor
// cu un argument cu o valoare inacceptabila
} }
Tema de autoinstruire
7.1 Utilitatea funcțiilor template ;
7.2. Instantierea claselor template;
7.3 Tratarea mai multor exceptii in aceeasi funcție.
Lucrari de verificare pentru studenți
7.1 Să se definească o clasă template pentru reprezentarea unui liste simplu inlantuite de date de un tip oarecare T, Tvector. Definiti o lista de numere intregi, lista de obiecte de tip Student definit in aplicatia 3.1
7.2 Să se definescă un vector de șiruri de caractere (obiecte de clasă String) folosind clasa template TArray<String> și să se verifice funcționarea acestuia ca stivă și coadă de șiruri de caractere.
7.3 Să se definească o clasă template pentru reprezentarea unui stive de date de un tip oarecare T, Tvector. Definiti o stiva de numere intregi, o stiva de obiecte de tip Fractie definit în aplicatia 2.3.
7.4 Să se implementeze o funcție de copiere de la un fișier sursă într-un fișier destinație, în care să se lanseze excepții pentru următoarele situații: erori de deschidere sau închidere fișiere, erori de citire din fișierul sursă, erori de scriere în fișierul destinație.
Formular de feedback
În dorința de ridicare continuă a standardelor desfășurării activitatilor dumneavoastra, va rugăm să completați acest chestionar și să-l transmiteți indrumatorului de an.
Disciplina: ________________________________
Unitatea de invatare/modulul:__________________
Anul/grupa:________________________________
Tutore:____________________________________
a) Conținut / Metoda de predare
Partea I
1. Care dintre subiectele tratate in aceasta unitate/modul considerați că este cel mai util și eficient? Argumentati raspunsul.
2. Ce aplicatii/proiecte din activitatea dumneavoastra doriți să imbunatatiti/modificați/implementați în viitor în urma cunoștințelor acumulate în cadrul acestei unitati de invatare/modul?
3. Ce subiecte considerați că au lipsit din acesta unitate de invatare/modul?
4. La care aplicatii practice ati intampinat dificultati in realizare? Care credeti ca este motivul dificultatilor intalnite?
6. Timpul alocat acestui modul a fost suficient?
7. Daca ar fi sa va evaluati, care este nota pe care v-o alocati, pe o scala de la 1-10?. Argumentati.
Partea II. Impresii generale
1. Acest modul a întrunit așteptările dumneavoastră?
2) Aveți sugestii care să conducă la creșterea calității acestei unitati de invatare/modul?
3) Aveți propuneri pentru alte unitati de invatare?
Vă mulțumim pentru feedback-ul dumneavoastră!
Teste recapitulative:
Specificați varianta corectă :
Clasa
class c { float a;
void afisisare();
}
are membrii:
a). publici; b). privați
c). protected; d). date private și metode pubice.
Fie secvența:
class c {….};
void main()
{ c e
/* instructiuni */
}
În acest caz:
a). c este un obiect și e este clasă;
b). c este o instanța a clasei și e este un obiect;
c). c este o clasa și e este un obiect;
d).descrierea este eronată deoarece se folosește același identificator pentru clasă și obiect.
Având declarația:
class persoana {
char nume[20];
int varsta;
public:
persoana();
int spune_varsta() { return varsta;}
};
void main()
{persoana p;
cout<<p.nume<<p.varsta;
}
În acest caz :
a). programul afisează numele și vârsta unei persoane;
b). nu este permisă afisarea datelor unei persoane;
c). descriere eronată pentru că funcția membra nu este utilizată;
d). afisează doar vârsta unei persoane.
4. Fie clasa :
class c { int a,b;
public:
float c (int, int)
int det_a {return a;}
c (); }
Declarația float c(int, int) ar putea corespunde unui constructor al clasei?
a). da, fiind o supraîncarcare a celui existent;
b). nu, deoarece crează ambiguitate;
c). nu, deoarece constructorul nu are tip returnat;
d). nu, deoarece nu este de tip friend.
Fie declarația :
class c1 {int a ;}
class c2 :public c1
{ public :
int b ;
void scrie_a() {cout<<a ;}
}
void main ()
{ c2 ob ;
ob.scie_a () ;
}
Selectați afirmația corectă :
a). funcția scrie_a() nu are drept de acces asupra unui membru privat;
b). programul afisează valoare lui a;
c). derivarea publică este incorect relaizată;
d). prin derivare publică, accesul la membrii moșteniti devine publică.
6. Fie declarația
class c1{/* instructiuni */}
class c2:public c1 {/* ….*/}
Atunci clasa c2 fața de clasa c1 este:
a). derivată; b). de bază; c). friend d). virtuală.
7. În secvența următoare:
class cls { public:static int s;};
int cls::s=0;
int main(){int i=7;cls::s=i;cout<<cls::s;}
Utilizarea lui s este:
a). ilegală, deoarece nu există nici un obiect creat;
b). ilegală, deoarece variabilele statice pot fi doar private;
c). ilegală, deoarece s este dublu definit, în clasă și în afara ei;
d). corectă, deoarece membrii statici există înainte de a se crea obiecte din clasa.
8. Secvența urmatoare:
class persoana
{ int varsta, salariul;
friend ostream & operator<<(ostream &out,persoana p)
{out<<p.varsta<<” “<<p.salariul; return out;}
public:
persoana(int v){varsta=v;salariul=0;}
persoana(){varsta=0;salariul=0;}
}
int main()
{persoana p(1);cout<<p;}
Afisează:
a). afisează 1 0; b). afisează 0 0
c). afisează 1 1; d). afisează 0 1.
9. Secvența urmatoare:
class vector{int*pe,nr_c;
public:
operator int(){return nr_c;}
vector(int n){
pe=new int[n];nr_c=n;
while(n–) pe[n]=n;}
void f(int i){cout<<i<<endl;}
int main()
{vector x(10); f(x)}
Afisează:
a). 10 b). 9
c). numerele de la 1 la 10
d). numerele de la 0 la
10. Secvența urmatoare:
class vector{int*pe,nr_c;
public:
operator int(){return nr_c;}
vector(int n){
pe=new int[n];nr_c=n;
while(n–) pe[n]=n;}
void f(int i){cout<<i<<endl;}
int main()
{vector x(10); f(x)}
Afiseaza:
a). 10
b). 9
c). numerele de la 1 la 10
d). numerele de la 0 la 9
11. Secvența urmatoare:
class persoana
{int varsta;
public:
persoana(int v=18){varsta=18;}
operator int(){return varsta;}
persoana& operator++()
{varsta++;return *this;}
persoana operator ++(int)
{persoana aux=*this;varsta++;return aux;}
int main(){
persoana p(20);
int x=p++,y=++p;
cout<< x<< y ;}
Afisează:
a).20 20;
b).20 21 ;
c).21 22;
d).20 22.
12 . Fie secvența :
class c { int a;
public: c();
c(const c&);
void operator=(c&);}
int main()
{ c a;
//instructiuni;
c b=a;}
Linia c b=a; determină:
a). execuția constructorului de copiere;
b). execuția metodei prin care se supraincarcă operatorul =;
c). execuția atât a constructorului de copiere, cât și a metodei operator =;
d). execuția contructorului implicit
13. Se consideră urmatoarea secvența de program:
class complex
{
double real;
double imag;
public:
complex(double x=10.0, double y=10.0){real=x; imag=y;}
complex(const complex &u)
{
real=u.real;
imag=u.imag;
}
…………..
}
Precizați ın care situatie se realizeaza o copiere a unui obiect ın alt obiect:
a) complex z1(3.42, -12.9);
b) complex z2=z1;
c) complex z3(1.0,-1.0);
d) complex z(10.7,0.8);
e) complex z(23.25);
14. Se consideră urmatoarea secventa de program:
class complex
{
double re;
double im;
public:
complex(double x=-11.0, double y=-56.90){re=x; im=y;}
complex( const complex &u)
{
real=u.re;
imag=u.im;
}
…………
}
Precizati ın situatie se utilizeaza constructorul de copiere:
a) complex z1(3.4, -12.9);
b) complex z3(0.0,-10.9);
c) complex z2(0.0,1.0);
d) complex z3(z1);
e) complex z(2.25);
15. Se considera urmatoarea secventa de program:
class complex
{
double real;
double imag;
public:
complex(double x=-11.0, double y=-56.90){real=x; imag=y;}
complex( const complex &u)
{
real=u.real;
imag=u.imag;
}
…………..
}
Precizati situatia ın care nu era necesara folosirea unui constructor cu parametri care iau valori ın mod implicit:
a) complex z2(3.42,-12.9);
b) complex z3(z2);
c) complex z=z2;
d) complex z4(z);
e) complex z5=z4;
16. Se dă secvența de program:
class A
{
int a[3];
public:
A(int i, int j, int k){a[0]=i; a[1]=j; a[2]=k;}
int &operator[](int i){return a[i];}
};
void main(void)
{
A ob(1,2,3);
cout << ob[1];
ob[1]=25;
cout<<ob[1];
}
Ce se poate afirma despre operator[]()?
a) produce supraıncarcarea unei funcții;
b) produce supraıncarcarea unui operator unar;
c) supraıncarca operatorul [];
d) este o funcție membru oarecare a clasei A care nu produce supraıncarcarea unui operator;
e) reprezinta un operator ternar;
17. Supraîncarcarea unor operatori se poate realiza prin funcții operator sau funcții friend. Diferenta ıntre aceste doua posibilitati consta ın:
a) lista de parametri;
b) obiect returnat;
c) precedent a operatorilor;
d) n-aritatea operatorului;
e) alte situatii.
18. In secventa de program:
……………..
int k=100;
void fct()
{
int k;
………..
k++;
………..
}
void gct()
{
int k=2;
………..
::k++; // (?)
………..
}
In instructiunea marcata cu (?), k este o variabila:
a) externa;
b) statica;
c) registru;
d) globala;
e) automatica;
19. Se considera secvența de program:
class B1 { class D1:public B1,public B2 {
public: public:
B1(){cout<<"(1)\n";} D1(){cout<<"(7)\n";}
~B1(){cout<<"(2)\n";} ~D1(){cout<<"(8)\n";}
}; };
class B2 { class D2:public D1,public B3 {
public: public:
B2(){cout<<"(3)\n";} D2(){cout<<"(9)\n";}
~B2(){cout<<"(4)\n";} ~D2(){cout<<"(10)\n";}
}; };
class B3 {
public:
B3(){cout<<"(5)\n";}
~B3(){cout<<"(6)\n";}
};
void main(){
D1 ob1;
D2 ob2;
}
Care mesaj se va scrie?
a) (1),(3),(7),(3),(5),(7),(9),(10),(6),(8),(4),(2),(2),(3),(2),(2);
b) (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(9),(7),(2),(3),(2),(2);
c) (1),(3),(7),(1),(3),(7),(5),(9),(10),(6),(8),(4),(2),(8),(4),(2);
d) (1),(3),(5),(7),(9),(2),(4),(6),(6),(8),(8),(10),(2),(2),(4),(2);
e) (1),(3),(7),(1),(3),(7),(9),(5),(10),(6),(4),(8),(2),(8),(4),(2);
20. Spuneți dacă programul de mai jos este corect. În caz afirmativ, spuneți ce afișează, în caz negativ spuneți de ce nu este corect. #include <iostream.h>
template <class tip>
class cls
{ tip z;
public: cls(tip i) { z=i; }
tip operator-(cls); };
template <class tip>
tip cls<tip>::operator-(cls<tip> a)
{ return z-a.z;
}
template <class tip>
tip dif(tip x, tip y)
{ return x-y;
}
int dif(int x, int y)
{ return x>=y?x-y:y-x;
}
int main()
{ cls<int> i=3; cls<float> j=4;
cout<<dif(i,j);
return 0;
}
21. Descrieți pe scurt cum puteți prelua o dată prin incluziune și a doua oară prin moștenire o clasă numar într-o clasă lista care descrie liste nevide de dimensiune variabilă de elemente de tip numar.
22. Spuneți dacă programul de mai jos este corect. În caz afirmativ, spuneți ce afișează, în caz negativ spuneți de ce nu este corect.
#include<iostream.h>
class cls
{ static int x;
public: cls(int i=25) { x=i; }
friend int& f(cls); };
int cls::x=-13;
int& f(cls c) { return c.x; }
int main()
{ cls d(15);
cout<<f(d);
return 0;
}
23. Spuneți dacă programul de mai jos este corect. În caz afirmativ, spuneți ce afișează, în caz negativ spuneți de ce nu este corect.
#include<iostream.h>
class cls
{ int v,nr;
public: cls(int i) { nr=i; v=new int[i]; }
friend int& operator[](int);
friend ostream& operator<<(ostream&,cls); };
int& operator[](cls& x, int i)
{ return x.v[i]; }
ostream& operator<<(ostream& o, cls x)
{ for(int i=0;i<x.nr;i++) cout<<x.v[i]<<” ”; return o; }
int main()
{ cls x(10);
x[5]=7;
cout<<x;
return 0;
}
24. Spuneți dacă programul de mai jos este corect. În caz afirmativ, spuneți ce afișează, în caz negativ spuneți de ce nu este corect.
#include<iostream.h>
class cls
{ static int i;
int j;
public: cls(int x=7) { j=x; }
static int imp(int k){ cls a; return i+k+a.j; } };
int cls::i;
int main()
{ int k=5;
cout<<cls::imp(k);
return 0;
}
25. Spuneți dacă programul de mai jos este corect. În caz afirmativ, spuneți ce afișează, în caz negativ spuneți de ce nu este corect.
#include<iostream.h>
class cls
{ int x;
public: cls(int i=32) { x=i; }
int f() const; };
int cls::f() const { return x++; }
void main()
{ const cls d(-15);
cout<<d.f();
}
26. Spuneți dacă programul de mai jos este corect. În caz afirmativ, spuneți ce afișează pentru o valoare întreagă citită egală cu 7, în caz negativ spuneți de ce nu este corect.
#include <iostream.h>
float f(float f)
{ if (f) throw f;
return f/2;
}
int main()
{ int x;
try
{
cout<<”Da-mi un numar intreg: ”;
cin>>x;
if (x) f(x);
else throw x;
cout<<”Numarul ”<<x<<” e bun!”<<endl;
}
catch (int i)
{ cout<<”Numarul ”<<i<<” nu e bun!”<<endl;
}
return 0;
}
27. Descrieti trei metode de proiectare diferite prin care elementele unei clase se pot regăsi în
dublu exemplar, sub diverse forme, în definitia altei clase.
28. Spuneti dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz
negativ spuneți de ce nu este corect.
#include<iostream.h>
class B
{ int x;
public: B(int i=10) { x=i; }
int get_x() { return x; } };
class D: public B
{ public: D(int i):B(i) {}
D operator+(const D& a) {return x+a.x; } };
int main()
{ D ob1(7), ob2(-12);
cout<<(ob1+ob2).get_x();
return 0;
}
29. Spuneți dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz
negativ spuneti de ce nu este corect.
#include<iostream.h>
class B
{ public: int x;
B(int i=16) { x=i; }
B f(B ob) { return x+ob.x; } };
class D: public B
{ public: D(int i=25) { x=i; }
B f(B ob) { return x+ob.x+1; }
void afisare(){ cout<<x; } };
int main()
{ B *p1=new D, *p2=new B, *p3=new B(p1->f(*p2));
cout<<p3->x;
return 0;
}
30. Spuneți ce este obiectul implicit al unei metode si descrieti pe scurt proprietătile pe care le
cunoasteti despre acesta.
31. Spuneti dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz
negativ spuneti de ce nu este corect.
32. Spuneti dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz
negativ spuneti de ce nu este corect.
#include<iostream.h>
class cls
{ int x;
public: cls(int i=-20) { x=i; }
const int& f(){ return x; } };
int main()
{ cls a(14);
int b=a.f()++;
cout<<b;
return 0;
}
33. Descrieți pe scurt mostenirea virtuală si scopul în care este folosită.
34. Spuneți dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz
negativ spuneti de ce nu este corect.
#include<iostream.h>
class B
{ static int x;
int i;
public: B() { x++; i=1; }
~B() { x–; }
static int get_x() { return x; }
int get_i() { return i; }
};
int B::x;
class D: public B
{ public: D() { x++; }
~D() { x–; }
};
int f(B *q)
{ return (q->get_i())+1;
}
int main()
{ B *p=new B;
cout<<f(p);
delete p;
p=new D;
cout<<f(p);
delete p;
cout<<D::get_x();
return 0;
}
35. Spuneti dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz
negativ spuneti de ce nu este corect.
#include<iostream.h>
class B
{ int x;
public: B(int i=17) { x=i; }
int get_x() { return x; }
operator int() { return x; } };
class D: public B
{ public: D(int i=-16):B(i) {} };
int main()
{ D a;
cout<<27+a;
return 0;
}
36. Enumerați 3 metode de implementare a polimorfismului de compilare.
37. Spuneti dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz
negativ spuneti de ce nu este corect.
#include<iostream.h>
class cls
{ static int x;
public: cls (int i=1) { x=i; }
cls f(cls a) { return x+a.x; }
static int g() { return f()/2; } };
int cls::x=7;
int main()
{ cls ob;
cout<<cls::g();
return 0;
}
38. Spuneti dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz
negativ spuneti de ce nu este corect.
#include<iostream.h>
class cls
{ int *v,nr;
public: cls(int i=0) { nr=i; v=new int[i];
for (int j=0; j<size(); j++) v[j]=3*j; }
~cls() { delete[] v; }
int size() { return nr; }
int& operator[](int i) { return v[i]; }
cls operator+(cls); };
cls cls::operator+(cls y)
{ cls x(size());
for (int i=0; i<size(); i++) x[i]=v[i]+y[i];
return x; }
int main()
{ cls x(10), y=x, z;
x[3]=y[6]=-15;
z=x+y;
for (int i=0; i<x.size(); i++) cout<<z[i];
return 0;
}
39. Descrieti pe scurt mecanismul de tratare a exceptiilor.
40. Spuneți dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz
negativ spuneti de ce nu este corect.
#include<iostream.h>
class B
{ int i;
public: B() { i=1; }
int get_i() { return i; }
};
class D: public B
{ int j;
public: D() { j=2; }
int get_j() {return j; }
};
int main()
{ B *p;
int x=0;
if (x) p=new B;
else p=new D;
if (typeid(p).name()=="D*") cout<<((D*)p)->get_j();
return 0;
}
41. Spuneti dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz
negativ spuneti de ce nu este corect.
#include <iostream.h>
class cls
{ int x;
public: cls(int i) { x=i; }
int set_x(int i) { int y=x; x=i; return y; }
int get_x(){ return x; } };
int main()
{ cls *p=new cls[10];
int i=0;
for(;i<10;i++) p[i].set_x(i);
for(i=0;i<10;i++) cout<<p[i].get_x(i);
return 0;
}
42. Descrieți pe scurt diferenta dintre un pointer si o referintă.
43. Spuneti dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz
negativ spuneti de ce nu este corect.
#include <iostream.h>
template<class T>
int f(T x, T y)
{ return x+y;
}
int f(int x, int y)
{ return x-y;
}
int main()
{ int a=5;
float b=8.6;
cout<<f(a,b);
return 0;
}
44. Spuneți pe scurt prin ce se caracterizează un câmp static al unei clase.
45 . Spuneți dacă programul de mai jos este corect. În caz afirmativ, spuneți ce afișează, în caz negativ spuneți de ce nu este corect. #include<iostream.h>
class cls1
{ public: int a;
cls1() { a=7; } };
class cls2
{ public: int b;
cls2(int i) { b=i; }
cls2(cls1& x) { b=x.a; } };
int main()
{ cls1 x;
cout<<x.a;
cls2 y(x);
cout<<y.b;
return 0; }
Bibliografie
Oprea M., Programare orientatã pe obiecte – Exemple în limbajul C++, Editura Matrix Rom.
Dr. Kris Jamasa, Totul despre C si C++, Editura Teora.
Ion Smeureanu , Programare orientatã pe obiecte in Limbajul C++, Editura CISON, Bucuresti 2005.
Liviu Negrescu, Limbajul C++, Editura ALBASTRA , Cluj 2000.
Luminita Duta, Programarea calculatoarelor in limbajul C++ , Editura Cetatea de Scaun 2006.
Bibliografie
Oprea M., Programare orientatã pe obiecte – Exemple în limbajul C++, Editura Matrix Rom.
Dr. Kris Jamasa, Totul despre C si C++, Editura Teora.
Ion Smeureanu , Programare orientatã pe obiecte in Limbajul C++, Editura CISON, Bucuresti 2005.
Liviu Negrescu, Limbajul C++, Editura ALBASTRA , Cluj 2000.
Luminita Duta, Programarea calculatoarelor in limbajul C++ , Editura Cetatea de Scaun 2006.
Copyright Notice
© Licențiada.org respectă drepturile de proprietate intelectuală și așteaptă ca toți utilizatorii să facă același lucru. Dacă consideri că un conținut de pe site încalcă drepturile tale de autor, te rugăm să trimiți o notificare DMCA.
Acest articol: Programarea Orientata pe Obiecte In Limbajul C++ (ID: 123245)
Dacă considerați că acest conținut vă încalcă drepturile de autor, vă rugăm să depuneți o cerere pe pagina noastră Copyright Takedown.
