Download as pdf
Download as pdf
You are on page 1of 115
PARTEA a-II-a PROGRAMAREA {iN LIMBAJULC ++ Programarea orientata spre obiecte (POO) in Turbo C++ 13 SL 13.1 Concepte fundamentale in programarea structurat conceperea unui program se bazeazi pe ecuatia celebra a lui Niklaus Wirth: Algoritmi + Structuri de date = Program Dupa cum aratam in capitolul 1, aceasta tehnica de_programare a constituit un progres important in directia facititariielaborarii aplicatiilor complexe gia cresterii alitajii programetor. S-aconstatat ins 4 programarea structurati rimine deficitard in ceea ce priveste posibilitatea reutilizarii, adaptari si extinderii unor module de program, calitati esentiale Ti cazul unor aplicatii complexe. O deficient consta tn caracterul artificial al tratarii separate a algoritmilor gi structurilor de date. Realitatile de care se ocupii aplicayile nu cunosc aceasta divizare. Un obiect real este caracterizat atft printr-o structura, cit si prin functionalitate. Frecvent, obiectele ‘sint supuse unei anumite evolutii, pastrindu-gi identitatea, dar modificindu-si structura si/sau ameliorindu-gi aspectele functionale. POO se bazeazd pe conceptul de obiect, care Teprezinta ansamblul alcdtuit dintr-o structurd de date si procedurile (metodele) de operare cu aceste date. Se utilizeazd frecvent denumirea gencricd de membri pentru date gi metode: date membre si functii membre. Abordarea POO se bazeazA deci pe relatia: Date + Metode = Obiect . Acest ansamblu este realizat conform principiului fundamental al "incapsulirii datelor", conform ciruia accesul la date se poate face numai prin intermediul setului de metode asociat. Acest Principiu determina o abstractizare a datelor in sensul c& un obiect este caracterizat complet de specificatiile metodelor sale, detaliile de implementare fiind transparente pentru utilizator. Modificarea structurii datelor membre areca efect ‘numai modificdri in setul de metode asociate gi, in masura in care se pastreaza specificatiile metodelor, nu afecteaza utilizatarea obiectului. ‘Acest aspect este esenfial atit in etapa de creare a programelor mari in colective de programatori pentru limitarea dependentetor, eft in activitatea de intretinere a programelor (elaborarea unor versiuni noi, ameliorate din punct de vedere al performantelor, adaptarea la cerinfele specifice ale unei anumite aplicafii sau la modifiediri hardware. ale sistemului). Intre cele doua tehnici se poate constata o diferenté de abordare. Programarea structurata este dirijata de prelucrari ("algoritmi + ..."). In POO efortul initial este definirea obiectelor. Programarea structuraté trateaza complexitatea si diversitatea printr-o structurare a datelor si prelucrarilor. Totusi, de la un anumit grad de complexitate, fara o clasificarea obiectelor, dificultatea tratarii devine covirsitoare. O evaluare calitativa este dificila, dar exist aprecieri conform carora pentru C, de exemplu, acest nivel de complexitate intervine intre 500 si 5000 de functii. Modelele bazate pe abstractizare si clasificare ierarhica sint uzuale pentru tratarea complexitatii, diversitatii si evolutiei, atit tn stiinga, cit si fn viata de toate zilele. In lipsa unui efort de abstractizare si de construire a unei clasificdri in categorii de obiccte, pornind de la aspecte esentiale gi adaugind succesiv calitati suplimentare, specifice, pentru a crea categorii noi, confuzia este inevitabild: s-ar ‘obfine un ansamblu in care, pentru fiecare element fn parte se repeta efortul de specificare a tuturor caracteristicilor. Botanica, zoologia sau mineralogia ofera exemple elocvente in acest sens. POO este o tehnic& de programare care abordcazd realitatea in concordant cu principiile enunate mai sus $i oferé modele mai apropiate de obiectele reale si de modul de reprezentare familiar programatorului. in POO, clasa este o generalizare a nofiunii de tip de date. O casa descrie un ansamblu de obiecte similare (aceeagi structurd a datelor si accleasi metode). Un obiect este deci o variabila de un anumit tip clas. Conceptul de potimorfism se referd, in sens larg, ta posibilitatea de a opera cu diverse variante ale unei functii care efectueaz o anumité operatic fn mod specific pentru diferite obiecte. POO permite asocierea unui nume unei activitati, definirea variantelor si sclectia automata a variantei corespunzatoare obiectului. Evolutia gi ierarhizarea claselor de obiecte sint modelate pe baza conceptului de mostenire. Procedeul numit derivare permite definirea unei clase noi (clasa derivata), pornind de la 0 clasa existent (clasa de baz), prin addugarea de date gi metode. Clasa derivatd preia structura de date $i metodele clasei de baz (deci mosteneste proprietajile acesteia) gi adauga altele suplimentare. Pornind de la un set de clase existent, se pot realiza clase eu proprietati adecvate unei aplicatii noi. Procesul de specializarea claselor se poate efectua in mai multe etape, rezultind 0 ierarhie de clase derivate. Pe de alta parte, este posibila derivarea unei clase din mai multe clase de az (mostenirea multipti). in acest fel, realizarile noi se bazeazA pe eforturi anterioare si este deosebit de important cao solujie deja existent s4 poatd fi preluata si valorificata pentru o aplicatie noua. Din acest punct de vedere, POO ofera avantaje substantiale. Vom vedea c& uneiierarhii de clase deja existente, ise pot adauga relativ usor, pe diferite nivele, noi componente, chiar daca metodele sint disponibile sub forma precompilat, deci nu se poate opera asupra figicrului sursé. Un rol important in aceasta operatie revine polimorfismului reatizat (in C++) cu functii virtuale, variante ale unei functii raspindite pe diferite trepte ale ierarhiei de clase. fn concluzie, POO promite realizarea unor module reutilizabile, simplificarea activitiii de intretinere a programelor si 0 productivitate sporitd in crearea lor, avantaje decisive, in special {n cazul proiectelor de anvergura. 13.2 Limbaje bazate pe POO, LimbajulC++ Exista limbaje concepute strict pe baza conceptelor POO, de exemplu Simula, Smalltalk sau Eiffel. © alt abordare este de a adauga unor limbaje cu o popularitate bine stabilita, de exemplu Pascal gi C, elementele necesare tchnicii POO. Aparijia POO poate fi datati la sfirgitul anilor 60, Odaté cu elaborarea unui limbaj destinat tratarii problemelor de simulare, numit Simula I 124 (1966). Este primul limbaj care incorporeaza intr-o singura entitate datele si metodele asociate. Conceptele de clase si obiecte se clarifica tn timbajul Simuta II (1973). Dezvoltarea POO este marcatd puternic de crearea versiunilor succesive ale limbajului Smalltalk (1972, 1976, 1980) care introduce, printre altele, conceptul de mostenire. Limbajul C+ + a fost pus la punct incepind din 1980 la AT&T Bell Laboratories de catre Bjarne Stroustrup, in cadrul unei activitiji de modelare a unui sistem de comutafie telefonicd de mare capatitate. Stroustrup a urmiérit 0 perfectionare a limbajulu€-3i-completarea sa cu elemente POO, inspirate de limbajul Simula. Numit initial "C cu clase", limbajul a primit denumirea C++ la definirea sa in 1983 de ctre Stroustrup. ‘Modificari ulterioare au intervenit in versiunile 1.1 din 1986, 1.2 din 1987 si 2.0 din 1989 si nu este incd standardizat. Cu toate acestea, limbajul este bine definit in acest moment gi evolutia sa nu va ridica probabil probleme de portabilitate. C++ combina avantaje oferite de limbajul C (cficiens’, flexibilitate si o popularitate deosebit’, materiatizat printr-un volum considerabil de programe) cu avantajele u...ue de tehnica POO. Desi adopta principiile POO, C++ nu impune aplicarea lor stricta (se pot scrie eventual programe fara elemente POO). C++ este in mare parte compatibil cu Csi ofera ameliorari importante care pot fiexprimate succint prin relayia: Ctt=C+I+S+0 unde: ~ Ceste standardul ANSI C. — Isemnificd aspectete de incompatibilitate dintre C++ si ANSI C. Acestea sint relativ minore gi sint determinate de diferentele de abordare in crearea celor doua limbaje si de toleranja normei ANSI C pentru o serie de specificajii desuete din variantele C mai vechi (partial eliminate de C+ +). In consecin{a, compilatorul C++ poate accepta un program C care tine seama de setul de incompatibilitati I. ~ S reprezinta aspecte specifice C++, 0 serie de ‘completari care nu sint strict legate de principiile programarii orientate spre obiecte (POO). — Osint posibilitayile de programare orientata spre obiecte. Turbo C++ poate fi considerat ca 0 extindere a C++ (in sensul in care Turbo C reprezinté o extindere a normei ANSI C) gi este compatibil cu specificafiile C+ + 2.0. In urmatoarele 2 capitole vor urméri succint iricompatibilitayile intre C++ gi C, apoi completarie introduse de C++. Programarea orientata spre obiecte in C++ va fi rezentatd incepind din capitolul 16. i eee Incompatibilitati intre ANSI C siC++ 14 In cele ce urmeazd sint prezentate succint principatele incompatibilitayi intre C si C++. Pentru inceput, este necesara precizarea cuvintelor cheie adaugate de C++ 2.0 la ANSI Tabelul 14.1. Cuvinte cheie specifice C++ catch friend ‘operator public class inline private template Protected this virtual delete new Setul complet de cuvinte cheie Turbo C++ const deci din tabelele 2.1.1 (ANSI C) si 2.1.2 (completari Turbo C), tabelul 14.1 (C++) sisetul de pseudovariabile pentru referirea registrelor microprocesorului. 14. Declaratii i definigii de functit Ca si norma ANSI C (pentru care a fost uneori © sursd de inspiratie), C++ prevede declaratii $i definitii ale functiilor. Se remarcd urmatoarele restrictii faydi de ANSI C: — C++ accept numai varianta moderna a definitiei unei functii, spre deosebire de C care accepta si varianta Kernighan gi Ritchie. — In C++ este obligatorie declararea printr-un prototip sau definirea unei functii inainte de apelarea sa. 14.2 Transferul parametrilor gi al rezultatului functiilor C++ introduce $i in acest domeniu o serie de restrictii care fac declaragile mai exacte: — O declaratic sau definitie de functie cu o lista vidi de parametri specificd o funciie fara parametri (nueste necesard utilizarea cuvintului void); ~ O functie care nu fntoarce un rezultat se declara , gi se defineste obligatoriu de tip void (in ANSI ! C cuvintul void era optional). In lipsa specificatorului void, compilatorul decide c functia intoarce © valoare int si semnaleaza eroare detectind lipsa instrucjiunii return cu 0 expresie intreag’. 14.3 Compatibilitatea intre tipul pointer te tipuri de void gi cel variabile pointer Pentru tipul (void*) C++ admite numai conversia de tip implicitd in sensul: tip pointer oarecare --> void* spre deosebire de C, care admite conversii implicite {fn ambele sensuri. Pentru conversia void* --> alt tip pointer este necesard utilizarea operatorulylui de conversie explicit "cast": ex: void* vp; int* ip; =ip; —_/* corect in Csiin C++ */ vp; __/* corect in C, incorect in C++ */ int*)yp; /* corect in C+ + si C */ 14.4 Modificatorul de acces const Deosebirea principala consta in faptul c& in C++ modificatorul const determina lega intern pentru un identificator global. Astfel, dectaratiile: ext: I* fisier 1 */ const int P* fisier 2*/ const int n=100; determina o eroare la editarea legaturilor in C (dubia definire a unei variabile externe), dar sint acceptate in C++. Exista urmatoarele echivalente fntre declaratii globale cu modificatorul const in C siCt+: ret hCHe tf static const int n; const int 9; const int n; extern const int n; Expresiile care contin constante si obiecte const reprezinta in C+ + expresii constante $i pot 125 fi utilizate in declarafii si pentru iniializari (acest lucru nu este valabil in C). De exemplu: ex.2: const int n=9, m=100; float tabffn+ 1}, matf[n+1][m); Aceste proprietati permit fnlocuirea macrodefinitiilor de constante cu declaratii de obiecte constante. Efectul modificatorului const pentru tipurile clas va fi analizat in par.18.3. 14,5 Alte incompatibilititi Existd in plus urmatoarele deosebiri intre C+ + sic: = Constantele de tip caracter, de exemplu ‘a’, "\n’, etc. au implicit tipul char, deci nu are loc conversia la tipul int ca in C. Prin urmare, fn C++ sizeof('a)==1, iar in C sizeot(’a’ — Instructiunea goto nu poate efectua un salt peste 0 declaratie cu initializare inserata in cod, decit daca aceasta este local unui bloc peste care se sare, = Unei variabile de tip enumerare nu i se pot atribui decit membri ai enumerarii, spre deosebire de C, in care orice valoare intreaga convine. RR meron v— eee ; Completiri aduse de C++ 15 | : 15.1 Operatii de intrare/iesire cu consola i C++ permite utilizarea functiilor de intrarefiegire C, ins dispune de un sistem de intrare/iesire conceput in spiritul POO, mai flexibil - ‘Fai comod. Acest sistem este prezentat pe larg | dG capitolul 22. Paragraful curent constituie 0 introducere sumara a operatiilor cu consola, util pentru exemplele din capitolele urmatoare. ‘ in C++ sint predefinite dispozitive logice de intrare/iesire standard sitnilare celor din C: © cin = console input = dispozitiv de intrare consol, tastatura (echivalent cu stdin din Oo; ‘® cout = console output = dispozitiv de iesire consola, monitorul (echivalent cu stdout din C). Transferul informagiei cu formatare este efectuat de operatorul >> pentru intrare (de la cin) si de << pentru iegire (cdtre cout), deci: cout << var; cin >> var; /* scrie var la cout */ /* citeste var de la cin */ prezentare detaliata a celor doi operatori va apare fn capitolul dedicat sistemului de intrare/iesire. Pentru moment, trebuie acceptaie fara justificare urmatoarele proprietati: — Sint posibile operatii multiple, de tipul: cout << varl << var2.... << varN; cin >> varl >> var2... >> varN; fn acest caz, se efectueaza succesiv, de la stinga la dreapta, scrierea la cout, respectiv citirea de la cin a valorilor vart ... varN. ~ Tipurile datcior transferate cdtre cout pot fi: © toate tipurile aritmetice; @ siruri de caractere; © pointeri de orice tip in afara de char. ~ Tipurile datetor citite de la cin pot © toate tipurile aritmetice; © siruri de caractere, ~ Controlul formatului pentru ambele operatii este posibil (dup cum vom vedea in cap.22), dar ‘hu este obligatoriu, deoarece exist formate standard. Acestea sint satisfacdtoare pentru dialogul cu consola efectuat in exemplele din capitolele urmatoare. Pentru citirea/scrierea sirurilor de caractere se poate specifica o constanta gir sau un pointer de caractere. Din acest motiv, pentru afigarea adresei unui sir este necesara 0 conversie explicit la pointer de alt tip, de exemplu (* void). Valorile adreselor se afigeazi in hexazecimal cu 8 cifre (intreaga adresé logic& 80X86, segment:deplasare, ca valoare unsigned long). Operatiile de citire de la tastaturd efectuate cu operatorul >> sint similare cu cele efectuate cu scanf(). Delimitatorii cimpurilor introduse sint spatiu, tab, linie noua, etc. fn cazul citiri’ unui caracter nevalid, citirea este intrerupta gi caracterul rimine in tamponul de intrare generind probleme similare cu cele descrise la scant(). Utilizarea dispozitivelor si operatorilor de intrarefiesire C++ impune includerea fisierului antet iostream.h. Exemplul urmator este elocvent pentru cleganta gisimplitatea dialogului cu consola inC++ ex: #include void main() inti char nume[21}; float F; cout << "“Introduceti un numar intreg” "si apoi un numar real"; cin>>1>>4; cout << "Ati introdus: "<< i <<"si"<> nume; cout << "Salut," << nume << "!\n"; , Programul afigeaz’: Introduceti un numar intreg si apoi un numar real: 15 3.1416 Ati introdus: 15 si 3.1416 Introduceti numele dvs: JOHN Salut, JOHN ! Trebuie retinut ca simbolurile >> si << fyi pastreaza si semnificajia din C - operatori de deplasare bit cu bit la dreapta, respectiv la stinga. 127 saaceesieeetinneet teieleiae-eaceeieeeneie initiated Abide eens, 60g 15.2 Comentariile de Sfirgit de linie fi C++ admite detimitatori Ci */ pentey t iamentarii care se pot intinde Pe mai multe fini si conduc’ delimitatorul // perhta adaugarea maj comoda de comentarii de stirsit de linie. Tot textul care urmeazai dupa ping be first tiniei este considerat comentariu; ex: yoid maing inti; cin >> i; int j=5¢j for(int k: 5K; k--) 11a fost dectarat anterior for(k=0; k Pentru n $i reprezint3 dec; 7 Un alt nume cute ~ Poate fi réferit acel obieet irare o vatoare stingg gi ul n in orice operatic. Hoare nu este aplicabit. Se Poate C++ cu parametri x2: /* varianta C */ Wold schimbacint™, ine main) { inti, jy Seani("edgea", schimba(&i, &j Primi" %a%a, 10id Sehimbacine® a, int by { , astfel ineit | 8 e f P P a ex.2: //varianta C++ Void schimba(int&, inte); void main() a f { g inti, j cin <>i>>j, - } Void schimba(int& a, int& by { Declaratile int& a si int& b din antetul functiei Schimba( precizeaz c& parametrii formali a $i b sint referinfe de obiecte int. La apelare, lista de argumente efective specificd obiectele care se asociazd referinjelor a si b. Operatiile efectuate asupra parametrilor formali a $i b vor modifica valorile parametrilor efectivi. Avantajul metodei const in faptul cd intregul Proces de transfer prin referind este efectuat de Compilator in mod transparent, ceea ce simplificl Scrierea functiei si apelul ei, Metoda C este desigur Valabild si fn C++, dar in multe situatii ea devine © complicatie inutila. C++ acceptd, la un transfer prin referind, situatii pe care limbajul Pascal le sanctioneazi ca erori. Daca tipul parametrului efectiv nu coincide cu al parametrului formal refering, C++ permite efectuarea unei conversii, ca fn cazul transferului valorii. Diferenjele de dimensiune si de interpretare a informatie nu reprezinta o problema pentru transferul prin valoare, deoarece se creeazA un obiect distinct pentru parametrul formal, in care se inscrie rezultatul conversiei valorii parametrului €fectiv (in mod similar cu o atribuire). La transferul prin referin{a, pentru realizarea conversici este necesara crearea unui obiect temporar de dimensiunea tipului referin{, in care SA se inscrie valoarea convertité a argumentului efectiv. Este evident cd fn acest caz parametrului formal refering4 ise asociaza obiectul temporar, au Parametrul efectiv. Transferul se efectueaza Practic prin valoare, cu toate consecinjele cunoscute. Se poate analiza exemplul urmator: x3: #include #include Void schimba(int& a, int& b) { int t; t=a; bet; cout <<"Functia schimba(): "; cout<< "a=" < int © ausigned short -> int sau unsigned int {in functie de implementare: nu se admite trunchierea) ® float -- >double 3). fn cazul unui nou $€C, se admit in procesul ji standard: °c numeric --> tip numeric (inclusiv dante, de exempiu float--> int) 2.0372 iP numeric sau tip pointer éérecare ® pointer oarecare —> yoid* * pointer spre clasaderivatl > pointer spre ‘lasa de bazd (aceasta conversie va fi studiatd in cap.21) 4). Se admite in ultima instant utilizarea unei singure conversii de tip definite de utilizator (vor fi tratate in cap.20), asociate eventual altor conversii standard, Cautarea se opreste in etapa care permite identificarea univoca a functiei. Daca intr-o etapa se identifica mai mult de 0 solutie posibil’, apelul va fi rejectat de compilator printr-un mesaj de eroare, datorita ambiguitajii. Exemplul urmator ilustreaz’ mecanismul de identificare a functiilor supradefinite, in cazul unui singur parametru: ex: void fet(int); Mout void fet(double); Mfew2 void main() { int i=5; float r=3,33; char e=10; Jong |= 1000; fer(i); Mapel fet | Mapel fet2, conversie float--> double Inedegradanta) fet(r); //apel fet, conversie char —->int 1} (nedegradanta) fet(c); //eroare la compilare, ambiguitate Hin etapa 3 fet(l}; voi exint a) cout << “fetl, argument="<int este corespondenta exacta Fungjile cu parametri cu valori implicite sint tratate ca un set de func{ii supradefinite cu un numar crescditor de parametri. Trebuie remarcat ci intreaga discusie s-a concentrat asupra setului de parametri (numar gi tipuri). Tipul rezultatului nu intervine in procedura de selectie a variantelor functiei. Daca acclagi hume de functie este declarat cu tipuri diferite de rezultat, compilatorul va afiga un mesaj de eroare, care semnaleazd imposibilitatea de a distinge variantele. In urma identificarii functiei care trebuie apelatd, daca definitia ei se afla in alt fisier obiect (© bibliotecd sau rezultatul compilarii unui alt ‘modul sursa al programului), trebuie informat si editorul de legaturi. in acest scop, compilatorul C++ asociaz’ un nume modificat oricarei functii, in mod determinist, pe baza numelui functiei si a setului de parametri, pentru a putea fi distinse eventualele variante. Compilatorul C nu efectueaza aceasta redenumire a funcjiilor. Din acest motiv, dacd se folosesc functii C precompilate (de exemplu iintr-o bibliotecd C) compilatorul C++ adaugd Aumelui declarat in prototip codul parametrilor, iar editorul de legaturi nu va putea identifica functia. Solugia const in declararea prototipului cu specificatorul extern "C*, pentru a determina compilatorul C+ + si respecte convenyia C pentru numele functiilor: extern "C* void fet_¢(...); Pentru functiile din biblioteca standard C, aceasta problema este deja rezolvati in fisierele antet oferite de Turbo C++. 15.7 Alocarea dinamici a memoriei folosind operatorii new si delete C++ introduce 0 metod’ nou’ pentru gestiunea dinamicd a memorici, similara celei utilizate in C (functiile din grupul malloc) si free), dar superioara i adaptaté programérii orientate catre obiecte. Alocarea memoriei se face cu ajutorul operatorului unar new, cu sintaxa: ew tipd(val_init); tipd_ptr=new tipd{n}; unde: ~ tipd = tipul variabilei dinamice, care poate fi un tip de date oarecare; ~ tipd_ptr = 0 variabila pointer de tipul tipd; ~ val init = expresie cu a cdrei valoare se inigializeaza variabila dinamic’. Varianta a treia se utilizeazA pentru alocarea memoriei pentru n elemente de tipul tipd (un tablou cu n elemente) sin este o expresie intreagii. Se pot aloca si tablouri multidimensionale, specificind toate dimensiunile. Iniializarea tablouril ila peratorul new aloca spatiul necesar, corespunzditor tipului, $i ofera ca rezultat: ~ dac& alocarea a reusit, un pointer de tipul (tipd *) continind adresa zonei de memorie alocate; = in caz contrar (memorie insuficientd sau fragmentaté), un pointer cu valoarea NULL (=0). Exemplul urmator ilustreazé sintaxa pentru toate variantele: ext: ip1=new int; //variabita intreaga neinitializata ip2=new int(17) //variabila intreaga initializata cu 17 ipt1 =new int{100}; //tablou unidimensional int ipt2=new int{n][2*}; /tablou bidimensional int In general, este recomandabil ca rezultatul si fie testat inainte de utilizarea variabilei dinamice. Programul din exemplut 2ilustreaza o secventa de alocari care conduce la epuizarea spatiului de memorie: x2: #include void main() {int “ip; Jong dim; . cout << "Dimensiune bloc ? "; cin >> dim; for(int i=1; }i++) if((ip=new int[dim])) cout << "Alocare bloc i="; cout < #include void no_mem(); void main() { int *ip; long dim; set_new_handler(&no_mem); cout << "Dimensiune bloc? *; : cin >> dim; for(int i=1; 31+ +) { ip=new int{dim}; cout << "Alocare bloc i="; cout <b) return ">"; return t Ca si in cazul specificatorului register, inline este 0 cerere adresati compilatorului pe care acesta poate si nu 0 onorezé, ca7 MN care genereaz © functie ordinara (de exempiu daca in program se utilizeaz pointeri catre functia respectiva). 15.9 Operatorul de rezolutie fn C+ + este definit operatorul de rezolutie (::) care permite accesul la un identificator cu domeniu figier, dintr-un bloc in care acesta nu este vizibil datorita unei alte declaraii. Se mai numeste operator de acces (de domeniu). De exemplu: ext: char str[20]="Sir global’; void fet() { char *str; _ /Nariabila locala str="Sir local", puts(::str); //afiseaza sirul global puts(str); //afiseaza sirul local } Principata aplicatie a operatoruluieste legata de clase si obiecte gi va fi prezentatd in capitolul 16, Clase si obiecte ee 16.1 incapsularea datelor in C+ + Categorii de clase gi obiecte in C++ clasa generalizeazi nojiunea de tip de date definit de utilizator prin asocierea unui set de functii la structura de date. Conceptele de domeniu si timp de viata, variabile locale gi globale, variable statice, automatice gidinamice se aplicd si obiectelor. C++ se distinge de limbajele POO pure prin faptul c& permite controlul accesului atit la datele membre (Incapsulare selectiva a datelor), cit gi la funetiile membre ale unei clase. In acest scop, se pot utiliza specificatorii de control al accesului: public, private si protected. Pentru domeniul in care este valabild declaratia obiectului, efectul specificatorilor asupra accesului Ja un membru este: ~ public = membrul poate fi accesat de orice funcfie din domeniul declaratiei clasei, - private = membrul este accesibil numai functiilor membre gi "prietene” ale clasei (functiile "prietene” ale unei clase vor fi prezentate in cap.18); ~ protected = similar cu private, dar accesul se extinde pentru functiile membre si prietene ale claselor derivate din clasa respectiva (clasele derivate sint tratate fn cap.21). © functie membra a unei clase are acces la toate datele membre ale oricdrui obiect din clasa respectiva, indiferent de specificatorul de acces. Unitatea de protectie in C+ + este deci clasa si nu obiectul. In C++ se pot declara mai multe categorii de clase folosind cuvintele cheie: struct, union, respectiv class. Variabilele de aceste tipuri se numesc obiecte struct, union, respectiv class. fn continuare vor fi prezentate tipurile class deoarece sint cel mai freovent utilizate si corespund mai fidel conceptului POO de clas, revenind ulterior pentru a trata tipurile struct si union in C++. 16.2 Tipuri ‘class" Sintaxa generali a declaratiei unui tip class este similara cu a tipyrilor struct din C: 136 class < :lista_clase_baza> { }0); } I) ncrt==0 --> lista vida int nl_plina() //not lista plina { return (nert<100) } //ncrt= = 100 --> lista plina Modul in care functiile membre definite in exemplu refer datele membre poate si pari surprinzitor pentru programatorul obignuit cu structurile C: nu rezultd care este variabila fifo cu care opereazi si nu se foloseste nici un operator de selectie. Pentru membrii unei clase acest lucru este ins firesc, deoarece din punct de vedere conceptual structura de date nu reprezinta 0 entitate separata, ci formeaza impreuna cu funciile membre un ansamblu unitar. Pentru fiecare obiect al clasei se alocd in memorie spatiul necesar datelor membre, iar pentru functiile membre exist in memorie un singur exemplar al codului (in afara de functiile inline care se insereaza in codul functiilor apelante, pentru fiecare apel). Procedeul prin care o functie membra afl identitatea (adresa) obiectului fizic asupra cdruia trebuie si opereze este transparent pentru programator si va fi detaliat in par.16.4. Nu exist restrictii pentru amplasarea declaratiilor de date si functii, ins varianta din exemplu este cea uzual Modificatorii de acces pot si apara de mai multe ori si in orice ordine. Dupa specificarea unui modificator de acces, el ramine in vigoare pentru toatd seeventa de dectarajii care urmeazi, pind intervine alt modificator. Declararea unor date publice este nerecomandabila deoarece violeazi principiul incapsuliirii datelor, dar pot exista situati fn care sa fie utile functii private (metode oferite numai celorialte functii membre). Pentru definitiile funcgiilor membre aftate in afara dectaratiei clasei este necesara specificarea numelui clasei urmat de operatorul C++ de rezolutie (::). Operatorut indica faptul c& functia are acelasi domeniu cu deciaratia clasei respective si este membra a ei, desi definijia apare th afara declaratiei. Altfel compilatorul considera cA este definitia unei alte functii cu acelasi nume, externa clasei. Operatorul se extinde asupra blocul functiei, cea ce permite referirea directa a membrilor clasei (date si functii), ca si in cazul funetiilor definite tn dectarafia clasei. Functiile membre ale unei clase pot fi supradefinite si pot avea parametri implicit. Decxemplu, operatiile de adaugare siextragere a elememtelor pentru o list’ FIFO de acest tip se pot scrie in felul urmator: ex.I’: int fi { daug(int k) if(nl_plina()) { fab|(prim +nert)% 100] =k; nert++; cout <<"Lista are" << nert; cout << " elemente \n"; return 1; } else { ‘cout << "Lista plina !\n", return 0; t , int fifoextrag(int& k) { Rae} pa feria ally Le pevennain if(nl_vida()) { K=tab(prim); (prim+1)% 100; cout<<"Lista are" << nert; cout << "elemente \n"; return 1; else { ‘cout << "Lista vida tin"; return 0; y } Pentru apelul functiiior membre publice sau pentru accesul la datele publice ale unui obiect din funcyi care nu sint membre, se folosesc operatorii de setectie (.) si (->), ca in cazul structurilor gi uniunilor din C. De exemplu: ex.2: void main() { fifo ist; // declaratia unui obiect fifo fifo *pist; //'declaratia unui pointer fifo Ist.init(); //initializeaza lista Ist plst=&lst; // pointerul plst preia adresa //obiectului ist plst->adaug(10); //adauga elementul 10 fa lista + Operatorul de atribuire este aplicabil obiectelor de acelasi tip class, determinind copierea valorilor datelor membru cu membru, ca in cazul structurilor din C. Se pot folosi operatorii new si delete pentru a crea/elimina obiecte dinamice. Tata un exemplu mai amplu de utilizare a clasei fifo: ex3: void main() int i=10; fifols; ' //Is este obiect din clasa fifo fifo *Ip; _//Ip este pointer de obiecte fifo Ip=new fifo; // creare obiect dinamic fifo \sinit(Q; //initializare lista Is. Ip->init(); // initializare lista Ip cout << "Adauga un element ta Is\n"; if (\s.nl_plina()) { cout <extrag(); //arata ca Ip este vida cout << "Atribuie *Ip=is\n "5 Sipais; cout << "Bxirage toate elementele* “din Ip\n"s while({p->nl_vida()) { Ip->extrag(i); cout <0); Mncrt==0 --> lista vida int nl_plina) //not lista plina { return (nert < 100) Mert }00 --> lista plina 1...definitii identice Pentru functiile membre fifo, *Ip; /\=obiect, p=pointer MI catre un obiect fifo Utilizarea declaratiei struct pentru tipul fifo este netecomandabila deoarece este 0 situatie Uipicd pentru deciaratia class. De regula se recurge la declaratia struct n cazul in care nu este Tecesara incapsularea datelor (un tip struct se “vate considera ca un caz particular de clasa cu anembrii publici). In C++ $i tipurite union pot avea functii “embre (cu excepyia tipurilor anonime), pe linga Tpurile de date similare celor din C. Membrii unuitip union sint ins intotdeauna publici, in mod implicit (nu se admit specificatori de control al accesului). Mecanismul de derivare nu este aplicabil pentru tipuri union (nu poate fi nici clas de baza a altor clase, nici derivat din alte clase). In C++ declararea unor variabile de un tip union se oate face specificind doar numele tipului (Fir a mengiona union). 164 Autoreferinta, Cuvintul cheie "this", Clabes tenend. Pentru a defini functiile membre sint necesare Teferiri la datele membre ale clasei fara a specifica un object anume. La apelare, functia este informata asupra identitatii obiectului asupra cfruia va actiona prin transferul unui parametra implicit care reprezinté adresa obiectului, De exemplu, fn cazul apelului: Is.adaug(i); functia adaug primeste si adresa listei Is, in afard de valoarea i. + Existd situafii in care este necesar ca adresa obiectului sé fic utlizata in definitia functiei. Acest lucru este realizat in C++ de cuvintul cheie this, asociat unui pointer cAtre obiectul pentru care $-4 apelat functia (evident, cuvintul "this" are sens $i Poate apare numai in definitia unei funciii membre). Exemplul urmitor adauga clasei fifo'o functie membra care afigeazd adresa obiectului: ext: fifo::adresa() { cout << "Adresa acestei liste este "; cout << this; fifo ist; 'stadresa(); _//afiseaza adresa obiectului Ist OO Constructori si destructori 17 er 17.1 Notiuni introductive In cazul variabilelor ordinare, compilatorul asigura alocarea spatiului de memorie Corespunzator, initializarea implicita (cu 0, dar numai pentru variabilele statice) si eventual initializarea explicit, daca se specifica valori Initiale in declaratic. Pentru variabilele dinamice, compilatorul C nu dispune de nici 0 metoda de initializare si nici operatorul C++ new nu rezolva toate situatiile, fo acest caz, ramine in grija programatorului atribuirea de valori adecvate datelor inainte de utilizare, Aceasti abordare este nesatisfacdtoare in multe situafiiin cazul obiectelor. Un exemplu il constituie obiectele care necesita $i alocarea unor variabile dinamice la creare. Pe de alt parte, eliminarea unui obiect de acest tip impune $i eliberarea zonei alocate. Pentru crearea, initializarea, copierea si distrugerea obiectelor, in C++ se folosesc functti membre speciale, numite constructori gi destructori. Constructorul se apeleazi automat la crearea fiecdrui obiect al clasei, static, automatic sau dinamic (cu operatorul new), inclusiv pentru obiecte temporare. Destructorul este apelat automat la climinarea unui obiect al clasci, la incheierea timpului de vial sau, in cazul variabilelor dinamice, este solicitat prin program (cu operatorut delete), inclusiv eniru obiecte temporare. Aceste funciii efectueazé operatii prealabile utilizarii obiectului creat, respectiv elimina ui Alocarea si eliberarea memoriei necesare datelor membre ramine in continuare in sarcina compilatorului. Mai exact, din punct de vedere cronologic, ~ constructorul este apelat dupa alocarea memoriei necesare pentru datele membre ale obiectului (in faza finatd a credirii obiectului); ~ destructorul este apelat inaintea eliberarii memoriei asociate datelor membre ale obiectului (in faza initial a eliminarii obiectului). Constructorii si destructorii se dectara gi se defines similar cu celelalte funcyii membre, dar se disting de acestea printr-o serie de caracteristici specifice: 140 ee © Numele functiilor constructor sau destructor coincide cu numele clasei caireia fi apartin; destructorii se disting prin faptul ca numele este precedat de caracterul tilde =), of declaratic si definitie nu se specifica nici tun tip de reaultat (nici macar void). ‘© Nu pot fi mostenifi, dar pot fi apelati de clasa derivata. © Nu se pot utiliza pointericaitre constructori sau destructori © Constructorii pot avea parametri, inclusiv parametri impliciti, $i pot fi supradefinig Destructorii nu poseda aceste proprietati Un constructor fara parametrise numegte constructor inn Daca o clas nu dispune de constructori si destructori definigi, compilatorul va_genera automat _un_ constructor implicit, réspectiv un “destructor, care sint Tunctii publice. Acestia au fost Utilizayi pentru crearea si climinarea obiectelor fifo in programul demonstrativ din capitolui precedent. Teoretic, constructorii $i destructorii pot fi Publici sau privati, dar de regula ci se declara Publici (daca se declar constructorut privat, nu mai pot fi declarate obiecte de acel tip; acest lucru are sens numai daca clasa respectiva este destinata Sd constituie exclusiv baza unei ierarhii de clase derivate). Un obiect cu constructor sau destructor (expliciti) nu poate fi membru al unei uniuni. t Proprietatile si modul de functionare « constructorilor si destructorilor sint mat complex. sivor fi completate si detaliate ulterior, pe masur. ce se prezinta diversele aspecte POO oferite de CH, 17.2 Crearea, initializarea si eliminarea obiectelor Vom considera pentru inceput clasa fi conform declaratiei din par.16.2. Pentru a asigur initializarea automata a listei chiar de la dectarar- se poate defini un constructor care sa preiz operatile efectuate de functia init). Constructore este simplu si poate fi definit inline, astfel inc declarajia clase’ fifo devine: 17 ctor sau lasei careia prin faptul cterul tilde ecified nici id). apelayi de nstructori ri, inclusiv radefinii. > numeste, ructori si a_genera pectiv un tia au fost ctelor fifo apitolul rii pot fi > declard rivat, nu est lucru destinata de clase structor uni, mare al omplexe > masura ferite de area asa fifo asigura eclarare a preia uctorul fel inch ext: M lista FIFO de 100 de numere intregi class fifo { int tab[100}; //tabloul de 100 elemente int nert; //numarul curent de ielemente din lista int prim; /findexul primului element I din lista public: fifo() Mconstructor { nert=prim=0; cout <<"Constructor fifo\n"; b ~ fifo) M1 destructor (formal) { cout << "Destructor fifo\n"; int nl_vidaQ)// not lista vida { return (nert>0); } HM nert: --> lista vida int nl_plinaQ) // not lista plina { return (nert< 100); /inert 100 --> lista plina //adauga un clement la sfirsit int adaug(int); //extrage primul element int extrag(inté); Pentru vizualizarea apetarii constructorului, s-a adugat afigarea unui mesaj. Clasa nu are nevoie de un destructor, deoarece nu exista operatii Prealabile climinarii obiectelor fifo, dar s-a definit un destructor formal, pentru ilustrarea sintaxci si vizualizarea eliminarii obiectelor. Se poate rula un Program de test care si contind doar declarajia clasei fifo gi a unui obiect fifo si o functie maing oarecare: ex?’ fifo Is void main(){} Desi programul conjine numai declaratii si definitii de date si functi, iar functia main() este vida, dupa prezentarea din par.17.1 nu trebuie si mai constituie o surprizd faptul ca afigeaza: Constructor fifo Destructor fifo Prin mesajete inserate in functiile constructor si destructor se pot intercepta momentele cresirii $1 climindrii obiectelor. Aceasta metoda va fi folosita freevent fn continuare pentru a putea urmari actiuni care, altfel, rémin transparente pentru programator. in exemplul urmator se reia declaratia clasei fifo, pentru a obtine liste de dimensiune variabila, implicit egala cu 100. in acest scop, este necesar ca tabloul s fie alocat dinamic. Se elimina functia init() si se prevede tn schimb lun constructor care preia sarcina alocarii dinamice 8 tabloului cu dimensiunea specificata si pe cea a inijializarii adecvate a liste. Constructorul este supradefinit pentru a permite utilizarea unor liste cu un numar implicit de 100 de elemente in tablou, De asemenea este necesara definirea unui destructor pentru eliberarea tabloului alocat dinamic. Programulilustreazd declararea $i functionarea constructorilor $i destructorilor si pune in evident momentele in care acestia se apeleaza, prin afigarea unor mesaje la consol ex2: #include {Mista FIFO de nmax numere intregi class fifo { intnmax; —_// dimensiunea tistei int *tab; // adresa tabloului listei int nert; #/numarul curent de //elemente din lista int prim; —_//indexul primului element Min lista public: fifo(iny; ——_// constructor 1 0): Mf constructor 2 “fifo; destructor #/adauga un element la sfirsit int adaug(int); //extrage primul element int extrag(int&); //fanctii inline int nl_vida(Q) — //not lista vida { return (nert > 0); Incr —> lista vida int nl_plina() //not lista plina { return (nert < nmax); } //nert==nmax --> lista plina 1 // dcfinitile functiilor membre ale clasei fifo 1 constructor 1, lista de n clemente fifo::fifo(int n) { : 141 | constructor 2, implicit fifor:fifo() { Mista de 100 elemente nmax= 100; tab=new int{ 100}; nert=prim=0; cout<<"Constructor 2, lista de 100" “elemente\n"; I I] destructor ffo:: ~ fifo() { cout <<"Destructor, lista de cout < MH coordonate Pozitie(int=0, int=0); // constructor ozitie(pozitie &); // constructor de Mcopiere ~ potitie() void depjasare(int, int); // deplasare relativa yy red Foe iy jms dep // definitii functii membre / constructor ozitie(int abs, int ord) rd; ‘Constructor, * << this; afisare(); J} constructor copiere Pozitie::pozitie(pozitie & c) it x=Cx; y=cy; cout << "Constructor copiere, "<< this; afisare(); te M1 destructor Pozitie::~ pozitie() { cout << "Destructor, "<< this; afisare(): void pozitie::afisare() { cout <<" < sint care torul r prin. are se ctului Clase si Obiecte. Completari 18 — ss 18.1 Membri statici ai unei clase C++ adaugi o semnificajie suplimentara specificatorului de clas’ de memorare static. Membrii unei clase (date $i functii) care sint declarati cu acest specificator se numesc statici gi au un rol aparte in cadrul clasei. Pentru datele nestatice ale unei clase exist copii distincte in fiecare obiect; datele statice exista fntr-o singura copie, comund tuturor obiectelor. Crearea, initializarea gi accesul la aceast copie sint total independente de obiectele clasei. Funcjiile membre statice efectueaz’ operatii care nu sint asociate obiectelor individuale, ci intregii clase. Din acest motiv, la apelare nu este obligatorie indicarea unui obiect. ‘Membrii statici ai unei clase sint supusiacelorasi reguli de control al accesului ca si cei nestatici: pot fidectarati private, public sau protected. Functiile membre au acces direct la datele gifuncile statice, cain cazul celorialti membri. Un membru static poate fi referit de funcyile ne-membre prin doua metode: indicind numele clasei si folosind operatorul de rezoluie de domeniu (chiar dacd nu exista obiecte ale clasei © specificind un obiect al clasei si folosind operatorii de selectie (ca in cazul unui membru nestatic). Datoritd faptului cd exist un singur exemplar al datelor statice pentru fntreaga clas, alocarea memoriei si inifializarea se fac separat. Declaratia din cadrul clasei este fra definire si trebuie s& existe o definijie (unica) in exterior. Inijializarea se face implicit cu 0, dar se poate indica explicit o alt valoare. Pentru definire, restrieiile de acces sint abrogate. De exemplu: ext: class cls { ii declaratie membru static private static 100; // definitie (cu initiatizare) //eroare, membrul este private Funetiile membre statice nu primesc implicit adresa unui obiect, indiferent cum sint apelate, Din acest motiv, in definitia lor nu se poate folosi suvintul cheie this si membrii nestatici pot fi referiji doar specificind numele unui obiect; accesul la membrii statici este direct. De exemplu: ex.2: class cls { static int i M date statice float j public: static void fet(cls *); /ffunctie statica i int cls:i=10; _ // definite date statice (// definitie functie statica cls::void fet(cls *cp) { // acces direct la membru static // acces direct la un membru J/ nestatic, precizind obiectul } Programul urmétor ilustreazi modul de declarare, definire siutlizare a unor membristatici pentru clasa pozifie. Se adauga clasei un contor Static de obiecte (cnt), incrementat de functia constructor la fiecare creare a unui obiect si decrementat de destructor la eliminarea obiectelor: ex3: #include lass pozitie { static int cnt; // date statice, private, //contor de obiecte int x,y; M1 date nestatice, coordonate public: pozitie(in pozitie(pozitie &); // constructor copiere ~ poaitie(); i! destructor //fanctie membra statica | static void nt_obj(); - //fanctii membre ‘nestatice void deplasare(int, int); void afisare(); 5 // definitii functii membre ozitie::pozitie(int abs, int ord)// constructor t x=abs; y=ord; cout << "Constructor,"; afisare(); cat+ +; nr_obj(); 147 JJ constructor copiere pozitie::pozitie(pozitie & c) { x=0x y=ey; cout << "Constructor copiere,"; afisare(); ent+ +; nr_obj(); t 11 destructor pozitie:: ~ pozitie() { cout << "Destructor,"; afisare(); ent--; nt_obj(); void pozitie::afisare() { cout<" s"fet(). Ea poate opera asupra unui obiect care i se transfera ca parametru, dar trebuie si at de. it de ti de i ati ctele care rente oferd mbrii doar deja 1th 0 telor mite lase ate fi te cu sei gi case. face nctiel intre este fot) biect i a, foloseasc numele obiectului si un operator de seleo(ie pentrt'a Tefen date a Fano MEMIBNES - 0 functie independent este prietend a unci clase; ~ 0 functie membra a unei clase este prietend a altei clase; ~ 0 functie este prietena a mai multor clase; ~ toate funcyiile unei clase Y sint prietene ale altei clase X; in acest caz se spune c& clasa Y este Prietend a clasei X. Exemplul urmator reia dectaratia clasei pozitie (simplificata) gi adauga functia prietena independent coincid() care intoarce valoare nenula dac& povitile primite ca parameti sint identice gi nula in caz contear ex: class povitie { int x,y Hf eoordonate public: Pozitie(int abs, int ord); // constructor ~ pozitic(); // destructor void deplasare(int dx, int dy); // functie prietena friend int coincid{( pozitie &, pozitie &); ‘ int coincid(pozitie & p1, pozitie & p2) { if(plx==p2x && ply==p2,y) return 1; else return 0; } Pentru a declara o functie fy(), membra a unei slase Y gi prietena a unci clase X, trebuie si se orocedeze in felul urmator: x2: class X; J/ declaratie incompleta class Y { void fy(X, .)5 fae class X { friend void ¥::f9(X,..); void Y::6(X obiect_X,..) {..} // definitie Functia fy) opereaza cu obiecte ale clasei X acesta este seapul declaratiei friend), deci in mod cevitabil primeste parametri de tipul X. ‘ultaiea cansta in faptul ca: © declaratia friend din X trebuie s& specifice clasa Y cdrcia fi apartine fy, @cclasa Y nu poate fi declarata anterior deoarece lista de parametri a functici fy, trebuie s& refere clasa X. Ordinea declarafiilor este justificata de urmatoarele observatii: ~ fn momentul compilarii clasei X, trebuie cunoscuta structura functiei fy(); ~ la compilarea clasei Y trebuie cunoscut identificatorul X (numele unui tip), dar nu neaparat structura clasei; = la compilarea definitici functici fy() este necesar4, de obicei, cunoagterea structurii ambelor clase, pentru a permite accesul la datele membre. {in masura in care se considera necesar ca toate functiile membreale clasei Ysa aiba acces la datele clasei_X, exist posibilitatea declararii clasei Y ca prietend a clasei X, ca in exemplul urmator: x3: class Y;__// declaratie incompleta class X { friend class Y; b class ¥ {...}5 Procedee similare s¢ aplicd si altor situatii care mai apar. Se poate obiecta impotriva folosirii functillor prietene, faptul ca se incalca principiul incapsuliri §i se introduc dependene care reduc eficacitatea abstractizairii datelor. Un exemplu simplu in acest sens fl oferd cazul unei functii prietene independente, compilata separat de restul funcliilor membre. Aceasta poate fi omisa accidental din proiect si atunci se pierd calitati ale Clasei sau poate fi modificaté de un utilizator, care incalca astfel restrictile de acces. Daca functia este membri a altei clase, apar dependente intre clase, care pot afecta intretinerea si adaptarea claselor. Folosita cu discernamint, declaratia friend ofera 0 metoda suplimentara de control al accesului. Aceasta reprezinta o solutie simpla in situatii in care este necesar accesul limitat, din anumite functii, la datele unei clase. Capitolele urmatoare ofera numeroase exemple de aplicalii pentru functii prietene. 149 18.3 Obiecte constante si volatile Modificatorul de acces const este utilizat pentru a deciara date constante. Compilatorul detecteaz sisemnaleaza prin mesaje de eroare orice operatie care poate s4 conduc’ ta modificarea valorii datei. Modificatorul const poate fi utilizat si pentru a objine obiecte cu valori nemodificabile. Efectul declaratiei const se extinde asupra functiilor membre ale clasei intr-un mod particular: apelul unei functii pentru un obiect constant este semnalat la compilare cu avertisment (nu cu eroare, deci programul este executabil). Programatorul poate sa indice funcyii membre care au dreptul si foloseascd datele unui obiect constant adaugind la prototipul si definitia functiei cuvintul cheie const. O tentativa de a declara Const o functie care altereaza valoarea datelor membre este. sancjionat’ cu eroare la compilare. De exemplu, in clasa pozitiese poate declara const doar functia afisare(): ext: class pozitie { int x,y; Heoordonate public: pozitie(int=0, int=0); // constructor // constructor copiere // destructor void afisare() const; // functie constanta void deplasare(int, int); i void pozitie::afisare() const { } ‘void pozitie::deplasare(int dx, int dy) { cout<<"xa"<deplasare(); // avertisment, functie I] neconstanta O situatie simitara apare in cazul obiectetor declarate cu modificatorul volatile. Dacd pentru un astfel de obiect se apelcaz’ o functie membra pentru care nu s-a specificat in prototip si fn definitie cuvintul volatile, compilatorul afigeaza un mesaj de avertisment. 18.4 Tablouri de obiecte. Clase cu membri obiecte Tablourile pot avea elemente de orice tip, inclusiv un tip clas. De asemenea, datele membre ale unei clase pot fide orice tip, mai putin tipul clas respectiv. In aceste conditii se pot deciara tablouri de obiecte si clase cu membri obiecte de diferite tipuri. Sint necesare ins o serie de precizari in privinta constructorilor $i a initializarii. Pentru clasa pozitie se pot considera declaratiile urmatoare: ext: class pozitie { intx, 5 I coordonate public: pozitie(int abs, int ord) // constructor 1 { x=abs; y=ord; cout << "Constructor,"; afisare(); } pozitie() Mconstructor 2 { x=0; y=0; cout << "Constructor," afisare(); ‘void deplasare(int abs, int ord) x+ =abs; y+ =ord; } void afisare() Wafisare coordonate { cout <<"x="< * C+ + oferd posibilitatea de a opera cu pointeri cdtre date si functii membre ale une clast inge dé celelaite categorii prezentat ie 7 $18 prin faptul Gare asociat tipul datei sau functiei membre si, In 152 acelasi timp, tipul clasd respectiv, Pe de alta parte, trebuie remarcat ci un pointer cdtre un membru al unei clase nu este asoviat unui obiect anume, ci clasei. In particular, un pointer de date membre nu contine adfesa zonei de memorie a unei date Gintr-un anumit obiect, ci numai deplasarea datet fn cadrul oricarui obiect al clasei. Din aceste motive, pentru operatii cu astfel de pointeri cxista reguli de sintaxa distincte: = Pentru preluarea adresei unui membru se foloseste tot operatorul & si se precizeazd identitatea membrului, adicd numele clasei urmat de operatorul de rezolutie si numele membrului: pointer_membru=&clasa::membru; ~ Pentru accesul ia_un membru dupa pretuarea adresei, trebuie indicat un obiect al clasei si numele variabilei pointer folosind unul dintre urmatorii operatori dedicati © operatoral + daca se specifica un obiect al clasei sau © operatorul ->* daca se specifica un pointer de obiect. Sintaxa este: obiect.*pointer_membru sau pointer_obiect->*pointer_membru Referirca unui membru al unui obiect folosind un pointer de membru este o variant a operatici de selectie realizaté cu operatorii "punct™ si "sligeatii"; noii operator sint simbolizali in mod firese prin juxtapunerea unui operator de selectic sia operatorulut "la adres: Pentru a ilustra operarea cu pointeri de date gi funcyii membre vom reiua clasa pozitie, permitind aceesul la date (se poate folosi o declaratie struct sau 0 declaratie class cu specificatorul de acces public): imtx,y; Hi coordonate /{fanctii membre poritie(int abs=0, int ord=0)/ constructor { x=abs; y=ord; cout << "Constructor"; afisare(); ) void deplasare(int abs, iat ord) x+=abs; y+=ord; parte, mbru me, ¢i renu date datei fel de ru se eazal clasei mele varea sci $i jintre ect al sind aie’ 1" $i mod ectic te $i iting rract ces 1etor void afisare() IH afisare coordonate ; : cout<<" < *tptr(10, 10); cout< <"Noua pozitie este: "; cout< <’(’< *xptr; cout <<", "<*yptr<<")\n"; } Programul afigeaz’: Constructor: x= 1, Noua pozitie este: (11, 11) Noua pozitie este: (21, 21) Variabilele xptr si yptr sint pointeri de date de tip int membre ale clasei pozitie, iar fptr este pointer de functii void cu 2 parametri int, membr alé Clasei pozitie. Sintaxa declaratiilor se distinge doar prin specificarea numelui clasei si folosirea operatorului de rezolugie. In continuare, xptr preia adresa coordonatei x, iar yptr pe cea a coordonatei y (mai exact, este vorba de deplasarea in cadrul unui obiect pozitie oarecare). Se observa folosirea operatorului & si specificarea clasei, nu a unui object. S Variabilei fptr i se atribuie adresa funcjici membre deplasare(). Atribuirea este legala deoarece prototipul funcjiei pozitie::deplasare() coincide cu cet din declarajia variabilet pointer. Utilizarea operatorutui_& in cazul funcillor este legala, dar nu este necesar ‘Dupa preluarea adreselor, variabilele pointer sint folosite pentru accesul la membrii obiectului pozitie p(1,1), direct sau prin intermediul unui pointer. 18.6 Exploatarea claselor De obicei, pentru a facilita reutilizarea, ta crearea unei ierarhii de clase se separa in figiere distincte declarajiie clascior, definiile functilor si programul care le fotoseste. Declarayiile claselor sint grupate fntr-un fisier cu extensie ".h" care poate fi inclus fn orice program de aplicagie. Figierul cu definigiile funcyiiior membre poate fi adaugat la proiectul care constituie aplicagia in format sursd sau in format obiect. Mai multe fisier ‘Obiect cu definitii pot fi grupate intr-o biblioteca folosind utilitarul TLIB. La editarea legaturilor, ‘numai functiile apelate efectiv in program vor fi incorporate in figierul executabil. Fisicrul antet de declaratii gi figierul obiect de definitii pot fi oferite pentru utilizare si altor programatori. Acestia nu_au nevoie neaparat de figicrele sursd pentru a adauga clase noi, adecvate aplicafifior Tor, prin derivare. Pachetele Turbo C++ si Borland C+ + ofera o bogata biblioteca de clase, impreund cu exemple i fisiere sursa. In plus, biblioteca matematica este completata cu clasa numité complex care permite efectuarca unei game variate de operatii cu numere complexe (0 serie de detalii apar in anexa de functii). Pentru ilustrare, vom considera exemplul clasei fifo care se poate reorganiza sub forma: © Figierul fifo.h: 1 Declaratia clasei fifo class fifo { //lista FIFO de nmax //numere intregi intnmax; —_//dimensiunea listei int “tab; ——_// adresa tabloutui listei int nert; _// numarul curent de //elemente din lista int prim; //indexul primului element din lista: public: fifo(int); _// constructor fifo(fifo &); _// constructor copiere Oe int adaug(int); // adauga un element la I sfirsit int extrag(int&);// extrage primul element #/fanctit inline int nl_vida() — //not fista vida { return (nert>0); } inert --> lista vida int nl_plina() //not lista pina t return (nert < nmax); } //nert==nmax —> lista plina h © Figierul fifo.epp: /* definitiile functiilor membre ale clase’ fifo, declarata in fisierul fifo.h */ #ioclude #inciude "fifo.h” 153 4 1 i // pentru fifo.h in directorul curent fifor:fifo(int n=100) —_// lista de n elemente =new int{n}; cout<<"Constructor, lista de"; cout < class complex { float re, im; public: / constructor ‘complex(float r=0, float i=0) { a void afisare() { ; im=i; cout<<"re="< class comptex { float re, i public: complex(float r=0, float i=0)/ constructor { rear; im=i; } void afisare() { . cout<<"re="<0); } /inett==0 --> lista vida int nl_plina() //not lista plina { return (nert ax); } //nert==nmax --> lista plina h // constructor, lista de n elemente fifor:fifo(int n) { nmax=n; tab=new int{n}; nort=prim=0; cout< #include lista FIFO de nmax numere intregi class fifo { t int nmax; /! dimensiunea listei int *tab;' // adresa tabloului listei int nert; _// numarul curent de elemente int prim; _//indexul primului element static int ent;// contor de obiecte dinamice public: fifo(int=100); / constructor fifo(fifo &); // constructor de copiere “fifo; | // destructor void* operator new(size_t); void operator delete(void*); void* fifo: { yperator new(size_t dim) cnt++; //inerementeaza contorul de //obiecte dinamice cout < <"new: ent="< lass complex { //te=partea reala, im=partea imaginara float re, im; public: complex(float r=0, float i=0)/ constructor {re=r; im=i; cout<<"Constructor, "; afisare(); } complex(complex & c)// constructor copiere { re=crre; im=c.im; cout<<"Constructor, copiere "; afisare(); } 163 eg ‘operator float() // conversie complex -->float {. cout<<” Apel float(),re="< float rl=cl-+rl; cout<<"rl="< float a"<<12<<'’ 1/7. conversiiimplicite c2 > float -> double 12=02+4.55 cout<<"r2="<<12<<"n’; + Programul afigeaz’: Constructor, re=1, im: Constructor, re=2, im: Apel float(), re=1,im=1 6 Apel float(), re=2,im=2 12: Apel fet(1) Apel float(),r Apel fet(1) Apel float(),r rl=2 Apel float(),r Apel float(),t 12=3 Apel float(),r 12=6.55 Programul ofera exemple pentru toatesituagiile enunjate in paragraful precedent: '® conversie explicitd si conversie implicita la atribuire; © conversie implicita ta transferul prin valoare al parametrului efectiv complex cdtre functia de test fot(float); ® conversii implicite la evaluarea expresiilor. Pentru fiecare din cele 7 instructiuni de test din program corespunde 0 linie de afigare. 164 Pentru cele 2 atribuiri, se observa apelul operatorului de conversie pentru membrul drept. La apelurile functiei fot(), se pot compara cele 2 situayii. Dacd argumentul efectiv este complex, intervine apelul funcjiei operator pentru crearea valorii float cu care se initializeaza parametrul formal, inainte de intrarea in functia fct(). Efectuarea prealabilé a conversiei explicd faptul c& nu este apelat constructorul de copiere la transferul parametrului. In cazul evaluarii expresiei c1+r1, compilatorul verificd daca exista o supraincatcare a operatorului + care si admit un prim operand complex si al doilea float. in lipsa supraincarcarii, cautd conversii de tip care si permit’ efectuarea unei operatii definite. Conversia complex > float a valorii ct ii permite o adunare de numere float si o adopta. analizd similard se poate face pentru expresia c1+02. Pentru ambele valori, 1 gi 02, se face conversia complex --> float. In expresia c2+4.55, constanta este memorata (implicit) in reprezentare double astfel incft pentru valoarea C2 este necesara o dubla conversie complex --> float --> double. Deoarece in acest lant de conversii succesive intervine o singura conversie definité de programator, operatia se poate efectua. In caz de ambiguitate (existenta mai multor variante de conversii) compilatorul refuz expresia sisemnaleaza eroare. O procedura similara se poate aplica pentru conversia unui tip clasé in alt tip clas. De exemplu, pentru conversia tipului-pozifie in tipul complex se adauga clasei pozitie funcjia membra operator complex(): ex2: class complex; class pozitie { int, ¥; public: // declaratie incompleta Operator complex()s/ prototip operator b class complex { float re, im; public: friend pozitie::operator complex(); no 1/definitia functiei operator pozitie::operator complex() {complex ¢; e.re=x; c.im=y returnc; } Se remarcd faptul cd functia operator trebuie s aiba acces la datele membre ale clasei complex, i apelul ul drept. ara cele -omplex, crearea ametrul ia fot. faptul ch viere la pilatorul atorului lex si al onversi operatii alorii O1 adopt. expresia se face -morat’ el incit mnversie in acest singura rajia se multor xpresia pentru emplu, >mplex perator ompleta ator deci trebuie declaraté prietend a acestei clase. Ordinea in care apar declaratiile $i definitia functiei operator rezulta din analiza prezentatd in subcapitolul dedicat functiilor prietene. 20.3 Conversii de tip folosind constructori Pentru clasa complex din exemplul precedent, definigia constructorului existent permite conversii float -->complex, datorit4 faptului c& accept& parametri cu valori implicite. Practic, specificarea unui singur parametru efectiv in locul unei perechi, determina atribuirea valorii respective partii reale a obiectului complex. Se poate scrie urmitorul program de test: ex. void cfct(complex); {_cout<<" Apel cfet," void main() {complex cl(1,1), ©2(2,2); float r1=10, r2=20; 1) functie de test -afisare; } J] conversie explicita cl=complex(r!); cl.afisare(); /} conversie implicita la atribuire c2=12; cl afisare(); H conversie implicita la transfer de parametri fet(r1); Pe ecran vor apare mesajele: Constructor, re=1, im: Constructor, re=2, im: Constructor, re=10, im=0 re=10,im=0 Constructor, re=20, im=0 re=20, im=0 Constructor, re=10, im=0 Apel cfct(), re= 10, im: Pentru ambele atribuiri se observa crearea unui obiect temporar de tipul complex, in care variabila re preia valoarea float, iar variabila im cste initializata implicit cu 0.’ Obiectul temporar este apoi copiat membru cu membru in variabila destinatie. Acelasi fenomen se constata la transferul prin valoare al parametrului functiei de cfct(complex).. : Trebuie observat ca se poate folosi constructorul definit si pentru alte conversii de tip, de exemplu int --> complex, prin intermediul conversiei implicite prealabile int -> float la transferul parametrului float al constructorului. Conversia tipului pozitie in tipul complex se face in mod similar prin supradefinirea constructorului complex: ex2: class pozitie; J} declaratic incompleta class complex { float re, im; public: Complex(pozitie); _//prototip constructor friend complex::complex(pozitie); ‘ I definitie constructor complex::complex(pozitie p) { re=px im=py; Pentru accesul la datele membre (private) ale obiectului pozitie, constructorul trebuie deciarat ca functie prietend a clasei pozitie. Trebuie remarcati ordinea in care apar declaratiile claselor gi definitia constructorului. 20.4 Concluzii. Interdependenta dintre conversii si supradefinirea operatorilor Din exemplele prezentate se pot delimita urmatoarele situatii de aplicare a celor doua metode: — Supradefinirea operatorului cast nu poate realiza conversii dintr-un tip fundamental intr-un tip clas. Functia operator trebuie sa fie membri a clasei, deci primeste implicit ca prim parametru adresa obiectului. Operatorul cast este unar (functia operator poate avea un singur parametru) $i fntoarce implicit (si obligatoriu) ‘un rezultat de tipul operatorului. fn conctuzie prin supradefinirea operatorului cast se pot defini numai conversii dintr-un tip clas4 intr-un tip de baz sau alt tip clas. - Supradefinirea constructorului nu permite conversia unui tip clas fntr-un tip de baz. Functia constructor are ca rezultat in mod implicit (si obligatori) un obiect de tipul clasei. Prin urmare, prin supradefinirea constructorului se pot defini numai conversii intr-un tip fundamental sau un tip clasa intr-un tip clasa. Se poate utiliza oricare dintre cele doud metode pentru conversiile dintr-un tip clasd in altul. 165 Recapitulind exemplele date, rezultd cA pentru 0 conversie din clasa X in clasa ¥ se poate alege intre: © supradefinirea constructorului Y sub forma Y::Y00); © supradefinirea operatorului cast in clasa X, cu functia X::operator YO. in ambele cazuri trebuie acordatd atentie ordinii in care sint plasate dectaratile claselor si definitia functiei de conversie. Evident, trebuie optat pentru una dintre solutii; definirea ambelor functil conduce la ambiguitate si este sanctionata cueroare. Deoarece conversiile definite sint utilizate de compilator la evaluarea expresiilor gi la transferul de parametri, este inevitabil s4 apara interdependente intre conversii si supradefinirea operatoritor. Pentru observarea acestor relafii se poate analiza urmétorul exemplu care reia clasa complex. Clasa este completatd cu o functie membra operator=(), pentru atribuirea la un obiect complex a unei valori double. Clasa dispune de supradefinirea operatorutui de adunare cu 0 functie prietend. Constructorul clasei complex permite efectuarea conversiilor double --> complex. Pentru conciziune, s-au folosit functii membre inline: ex. class complex { » float re, ins; public: complex(float r=0, float i=0)/ constructor {. re=r,im=i; cout <<"Constr.cmplx."; afisare(); cout<<"\n’; } M constructor copiere ‘complex(complex éc) { re=cre; im=cim; cout <<"Constr.cmplx.cop."; afisare(); cout<<"\n'; } void afisare() const {cout <<’’< complex definita de constructor, urmata de o atribuire standard membru cu membru. Exemplul de mai sus urméreste si ofere compilatorului doua variante pentru atribuirea c=10.5, $i anume: folosirea operatorului de atribuire sau a conversiei. Se observa ci solutia aleasa este folosirea operatorului de atribuire. In general, supradefinirea operatorilor este prioritara fata de conversiile de tip definite (indiferent de metoda). Acestea sint utilizate numai in masura in care sint strict necesare, Cele doud operajii de adunare ilustreaza avantajul pe care il ofera in cazul de faa definirea funcjiei operator+ ca functie externa prietena. Datorita simetriei ci, ambele operatii sint posibile (insojite de conversia implicita double --> complex efectuata de constructor), Dac funciia ‘operator+ () era membra a clasei, operatia: 25+e echivalenta cu: (2.5).operator +(c) ar fi fost rejectata. Supradefinirea operatorilor cu functii membre este preferabilé atunci cind functia operator modifica primul parametru. Parametrul implicit al unei funciii membre (obiectul asociat apelului) nu este supus nici unei conversii. Acest lucru evit erorile mai greu de detectat datorate unor Wersii care pot apare la apelul unei functii void main() rator prietene. Untimut exemplu ilustreaza posibilitatea de a complex cl(1.1,1.1), 5 inde prin conversii functionarea unui operator pozitie p1(10, 10), p2(20,20) n\n radefinit pentru o altd clasi. Se reiau clasele cout<<"\n’; ; zitie gi complex si se adauga clasei pozttie r=pi+p2; ~clia membra operator complex: Hoomplex=pozitie + pazitie : cout<<"r="; rafisare(); cout<<"\n\n"; ex: r=pl+22; J! complex=pozitie+double lass complex; _//declaratie incompleta rafisare(); ass pozitie { } srotected: int x,y; Mcoordonate Programul afigeaz’: public: Constr.cmptx. 1.14j1.1 Pozitic(int abs=0, int ord=0)//constructor __Constr.cmplx. 0+j0 { x=abs; y=ord; Constr.poz. cout << "Constr.poz."; afisare(); } Constr.poz. pozitie(pozitie &p)// constructor copiere X=PXY=py; Op.cmplx <-poz.Constr.cmptx. 0+j0 cout << "Constr.poz.cop."; afisare(); } 20+ 20 <-- (20, 20) void afisare() Constr.cmplx.cop. 20+j20 { coute<*x="< concluzie, constructorul de copiere al clasei ate este responsabil de inifializarea corecti a zor mogiente de casa de bark ta pst fe bavi. In paragraful “este prezentata gi justficatd solujia pentru «al constructorului de copiere al clasei de baz. ciru moment, se poate utiliza definigia “Atoare a constructorului de copiere pentru <3 punet: onli Sunet::punct(punet & pet) : pozitie(pet.x, pet.y) { vizibil=pet.vizibil; culoare=pet.culdare; cout << "Constructor copiere, "< refering pozitie. Pentru analiza acestor conversii se foloses¢ clasele punct si pozitie. In programul urmétor| apar ultimele versiuni ale definigilor pentru clas punct, dar afisarea adresei obiectului a fost : mutati in functia afisareQ: ex. /clasa pozitie, clasa de baza class pozitie { protected: int x, ¥; I] oordonate ; public: pozitie(int=0, int: J] constructor pozitie(pozitie &); [/constructor J) copiere ~ pozitieQ; [/ destructor void afisare(); void deplasare(int, int); b // definitii functii membre, clasa pozitie povitie::pozitie(int abs, int ord) // constructor { x=abs; y=ord; cout << "Constructor"; afisare(); + Meonstructor copiere pozitie::pozitie(pozitie & p) { X=PGY=Py ‘cout << "Constructor copiere"; afisare(); } pozitie:: ~ pozitie() 1 destructor { cout << "Destructor"; afisare(); } void pozitie:safisare() { cout<<" pozitie: "< unde B1, B2, ... Bi sint clasele de baza, iar specif_acces este unul dintre specificatorii de acces: Public sau private. Un exemplu simplu il constituie ctasa strpoz, reprezentind un text asociat unei anumite pozitii $i avind 0 anumité culoare. Clasa strpoz poate fi derivata dintr-o clasa string, reprezentind un sir de caractere in general sidin clasa pozitie. In exemplul urmator, dotarea cu functii a claselor este minimal. fn particular, definirea unei clase string devine mai interesanta in cazul in care se supraincarcd 0 serie de operatori pentru a oferi operatii de baza cum ar fi atribuirea (=), concatenarea (+) si compararea (==, <, >) pentru doua giruri. fn principiu, utilizarea terminatorului de sir nu mai este neaparat necesard, deoarece se memoreaz chiar numarul de caractere. Utilizarea lui a fost adoptatd pentru a simplifica definirea funciiilor membre, folosind funcjti standard pentru siruri din biblioteca C Pentru clasa pozitie se utilizeaz declarajia si definigiile din ex.1, par.21.3.1. ex: #include #include i! clasa pozitie, clasa de baza (par.21.3.1.) i/clasa string, clasa de baza class string { protected: int near; char * str; public: Meonstructori string(int n=0) { near=n; str=new charjn+1}; str[0]=0; cout<<*Constructor 1"; afisare(); Mungimea sirului //adresa sirului } 175 EE EE ye? RE F string(char *s) Constructor 2 strpoz: TEXT, x=5, y= { ncar=strlen(s);str=new char[ncar+1]; culoare=A strepy(str,s); strpoz: TEXT, x=5, y=5, culoare=A cout<<"Constructor 2"; afisare(); } Constructor copiere pozitie: x=5, string(string & s) Constructor copiere string: TEXT ncar=s.ncar;str=new char[ncar+ 1}; Constructor copiete strpoz: TEXT, strepy(str,s.str); y=5, culoare=A cout<<"Constructor copiere *; strpoz: TEXT, x=5, y=5, culoare=A afisare(); } Final main() I. destructor Destructor strpoz: TEXT, x=5, y=5, ~string() culoare=A { cout<< "Destructor" Destructor string: TEXT afisare(); delete str; } Destructor pozitie: x=5, void afisare() Destructor strpoz TEXT, x=5, y { cout<<"string: "< poate azul in acelagi rut de in care dul lor iasa D plicate. te este utorul ex. class BB { protected: x, ; class B1 : public BB {..}; class B2 : public BB {. class D : public BI, pu ie B2 tacky Membrul x provenind din ctasa B1 poate fi ~eferit cu expresia: BB::Bi:xx + cel din B2 cu: BB::B2:x De obicei aceastd duplicare nu este necesara si vate deveni deranjanta. Exist insd posibilitatea = a solicita compilatorului sa includa un singur afisare(); ppet->afisare(); ppoz=ppet; ppoz->afisare(); ; ppoz-> depiasare(4, 4); Programul afigea Constructor pozitie: : Constructor pozitie: Ox8f4 1ffe9 x=5, Constructor punct: Ox8f4 1ffe2 culoare R invizibil pozitie: Ox8f410110 x= 1, unct: Ox8f4 ffe2 culoare R invizi punct: 0x84 lffe2 x=5, y=5 culoare R invizib Deplasare punct: Ox8f4iffe2x=9, y=9 culoare R invizibit catre punct: Ox8f4 ffe2 invizibil Destructor punct: Ox8f41ffe2 x=1, y=1 , y=9 culoare R Destructor pozitie: Ox8f4 Iffe9 x= 1, Destructor pozitie: 0x84 1fff0 x= 1, y Programul ilustrea74 posibilitatea de a utiliza unterul ppoz de tip pozitie, pentru a adresa ~ectul pet de tip punct. Se’poate observa ca ssajele afigate de apelurile ppct->afisare() si 790z->afisare() sint identice. Prin urmare, :clarind virtual functia afisare( in clasa pozitie, centificarea versiunii functiei se face corect, respunzator tipului obiectului pet adresat de inter. Mesajul afigat de apelul ppoz->deplasare() -ouie interpretat mai atent. Trebuie observat, in “imul rind, ca versiunea apelata este szitie::deplasare) si nu punct::deplasare() arianta definita in clasa punct conditioneaza satea mesajului de vizibilitate a punctului, ori teste invizibil!) Rezultatul se explicd prin faptul cA, in mod plicit, compilatorul utilizeaza legatura static’, iar retia pozitie::deplasare) nu a fost declarati wala, Pe de alt’ parte, trebuie observat cA funcyia ‘ozitie::deplasare() apeleaza functia afisare(). ‘ajul care apare pe ecran apartine, insa, “siunii punct::afisareQ si nu pozitie:-afisare), <1 -a aplicat $i aici legatura dinamica, Modificarea functionarii — functiei ztitie::deplasare() este ulterioara credrii clasei 2itie si nu s-a produs efectiv nici o schimbare in er (de altfel definitiile puteau sa fie sompilate). In cele ce urmeaza vom exploata asta observatie pentru a evidentia importanja ~s{illor virtuale in crearea ierarhiilor de clase. Functia deplasare() se poate declara si ca ual. Pentru prezentarea care urmeazi, vom ssidera cd in clasa pozitie apare declaratia: s } functiilor je test cL virtual void deplasare(int, int); Se poate considera in continuare adaugarea la sele pozitie $i punct a clasei cerc derivata din sa punct, lata o prima solutie: -lass cere: public punct { int raza; public: ‘cere(int, int, int, char="A’); ~ cere(); invizib| void afisare(); Fev cere:scerc(int abs,int ord, intr, char °: unet(abs, ord, ¢) { raza=r, vare R cout<<"Constructor’; afisare(); } cere:: ~cere() { cout<afisare(); c.deplasare(4,4), } Programul afigeaz: Constructor pozitie: Ox8t4 ffea x=5, y Constructor punct: Ox8f4 Iffea x=5, y=5 culoare=A invizibil Constructor cere: Ox8f4 ffea cere: Ox8f4 Iffea centrul (5,5), raza=2 vizibil cerc: Ox8{4 Iffea centrul (5,5), raza=2 vizibil Deplasare cere: Ox8f4 Iffea centrul (5,5), raza=2 vizibil catre cere: Ox8f4 Iffea centrul (9,9), raza=2 vizibil Destructor cere: Ox8f4 Iffea centrul (9,9), raza=2 vizibil Destructor punct: Ox8f4 fea x=9, y=9 culoare=A vizibil Destructor pozitie: Ox8f41ffea x=9, 9 179 Programul ilustreazd doua aspecte. fn primul rind, utilizarea pointerului de obiecte pozitie, ppoz, pentru a adresa obiecte cerc. Apelul ppoz->afisare() utilizeazé versiunea corecté a functiei si anume cerc::afisare(). in al doilea rind, se observa aplicarea legaturii dinamice in apelul c.deplasare). Este selectaté versiunea punct::depiasare() (ultima variant definité in ierarhie), iar aceasta apeleazd la rindul ei functia cercz:afisare(). Procedeul utilizat de C-++ pentru realizarea asocierii dinamice cons’. th crearea cite unui tabel de adrese ale funciilior ~i*aale pentru clasa de baz si toate clasele deriv. .. \in ea. Fiecare obiect apartinind uneia dintre aceste clase contine, in plus fafa de datele membre, adresa tabelului functilor virtuale asociat clasei sale. in particular, pentru cele trei clase din exemplu, se realizeaza tabelele urmétoare: pozitie: ie::afisare &Xporitie::deplasare &cere::deplasare Trebuie observat cA in clasa cero, functia deplasare() nu este redefinitd, astfel incit adresa &cerc::deplasare este egala cu adresa &punct::deplasare. Pe de alta parte, informatiile necesare pentru construirea acestor tabele se objin din declaratii, deci sint disponibile la crearea figierului executabil Declararea unei functii virtuale se poate face pe orice nivel al ierarhiei si adresa ei se inscrie in tabelul clasei respective gal descendengilor ei. Apelul une functii virtuale consta din: © preluarea din obiect a adresei tabelului asociat clasei sale, © citirea din tabel a adresei funcyici apelate, © executarea functiei respective. Prin urmare, tipul obiectului determina fntotdeauna varianta de functie care se executa, indiferent cum este adresat obicctul. Impedimentul evidenfiat fn par.21.3.1 dispare 180 pentru functiile virtuale. In particular, pentru clasele din exemplu rimin valabile o serie de limitari. Se poate considera de pilda secventa: ex3: cere ¢(10,10,3); pozitie *ppo: ppoz->arata(); ppoz->coloreaza(’V’); &c; Pentru ambele apeluri va rezulta eroare de compilare cu mesajul cd functile respective nu sint membre ale clasei pozitie, lucru care este evident si firesc, deoarece pozitia nu are decit coordonate. Din acest motiv, ppoz nu poate fi folosit pentru a avea acces la anumite calitaji ale obiectelor din clasele descendente. In particular, apelurile pot fi efectuate cu un pointer de obiecte punct, dar colorarea unui cere poate ingemna o alté operayie dacd il interpretim ca disc. In plus, toate figurile| care se mai pot deriva au culoare si vizibilitate, Solujii posibile ar fi declararea funcjici de colorare cavirtuald in clasa punct sau odeclarare ca virtuala in clasa pozitie, cu o definitie formala (vida). In general, functiile care efectueazd operatii valabile si pentru clase descendente gi pentru care este probabild redefinirea in acele clase pot fi luate fn considerare pentru a fi deciarate virtuale. Optiunea devine mai ferma dacd este necesar cal acele definii sd fie accesibile din clasele parinte. In particular, afigarea este necesar& pentru toate| figurile care pot fi derivate. Functia trebuie redefinita de fiecare data, deoarece figurile aul aspect diferit. In plus, daca diversele coordonate| ale unei figuri sint relative la 0 poritie de refering pe ecran deplasarea oricarei figuri poate fi efectuati de aceeasi functie, definita in clasa pun {este cea mai simpli figura gi are atributul de vizibilitate: o figura vizibilé pe ecran, trebuiel stearsd si redesenata in noua poziie, iar unc invizibild suport o simpla modificare coordonatelor) Funciille care nu necesité redefinire in clasel: descendente, fie pentru c4 ramin valabile (folosing| eventual alte func(ii virtuale), fie pentru c nu sint necesare, se declard ca functii ordinare. De| exemplu, declararea functiei deplasare() cal virtuala nu era justificata, deoarece, beneficiind de| functia virtuali afisareQ,versiunea| punct::deplasare() rimine valabité pentru orice| figura (definitia din exemplu este ins formal), Datorit’ procedurii de apelare, functiile virtuale introduc o serie de penalizari, in special ic cazul aplicajiilor complexe: cresterea (intr oarecare masura) a timpului de execute « programului, consum suplimentar de memori: (fiecare clas are un tablou de func $1 fiecare pentru serie de nla: oare de enu sint evident rdonate. pentru a lor din ile pot fi net, dar operatie -figurile virtual ii). opera tru care t filuate virtuale. cesar ca irinte. In ru toate trebuie rile au ordonate refering’ poate fi sa punct putul de trebuie iar ung| icare @ n clasele (folosine special i intr cutie « memori: i fiecar| obiect conjine adresa tabloului corespunzitor). Folosirea lor nejustificata trebuie deci evitata In concluzie, C+ + ofera prin declaratia virtual posibilitatea selectarii unor functii care si Seneficieze de legatura dinamica. In mod normal, > funcjie virtual are diverse variante fn ierarhia de clase gi varianta care se apeleaza este determinata de tipul obiectului. Functiile virtuale permit o flexibilitate -emarcabild fn reutilizarea si completarea claselor zxistente (de exemplu dintr-o biblioteca de clase). °rin intermediul lor, functii deja definite (gi -ventual precompilate) sint capabile s& se adapteze nor clase noi fara a fi necesara modificarea gi nici ‘ecompilarea lor. 21.6 Clase abstracte in C+ + existd posibilitatea de a defini atft clase. estinate crearii de obiecte, cit si clase destinate xclusiv crearii de clase noi prin derivare, numite clase abstracte. In principiu, acestea sint utilizate ca baz pentru etarhii de clase gi sint extrem de generale (permit urm, iar in sensul invers crt-> prec (vom conveni sa numim sensul direct de la stinga la dreapta). Primul element al liste se afl laadresa cap.urm, iar ultimul la cap. prec, iar daca lista este vida, cap.urm=cap,prec=&cap. Cu aceste precizari, setul de functii de pozitionare si de test nu mai ridicd probleme. Constructorul si functiile de citire, addugare $i extragere se pot defini astfel: x22: {I constructor Idi:sidi() { crt=cap.urm=cap.pree=∩ } void" Idi:citesc() '/citire element curent {if (crt!=&cap) return ert->data; else return 0; } J/adaugare la stinga void" Idi:adaug_st(void* d) { Inod *j p=new Inod(d); ip) { p->urm=crt; p->prec=crt-> prec; } return 0; } //adaugare la dreapta void* Idi::adaug_dr(void* d) { Inod *p; p=new inod(d); ap) { P->pree=crt; p->urm=ert->urm; crt->urm=ert->urm->pree=p; crt=p; return p;, i return 0; } //extragerea elementului curent void* { Inod *p; void* d; alistei) invers rect de sean ar daca i de pleme. sare $i nt st n; P. crt->urm->prec=crt->prec;, ort->prec->urm=crt->urm; ert->urm; p->data; delete p; "return d; y return 0; } Dupa fiecare adaugare, pointerul crt indicd sodul introdus, iar dupa fiecare extragere nodul armator devine nodul curent. In acest fel se pot ‘ace convenabil secvente de adaugari si extrageri, ra fi necesare pozijionari. Functiile intore dresa elementului curent fn caz de succes sau NULL=0 in caz de egec. Pentru parcurgerea gi eventual citirea elementelor listei se pot folosi funcyile: ex.2.3: {/ parcurgere in sens direct void Idiz:spre_dr() { if(nl_vidaQ) && ert->urm! Git=crt->urm; return crt->data; } return 0; } i parcurgere in sens invers void* Idi::spre_st() if(nl_vida() && ert->prect=&cap) { Grt=crt-> prec; return ert->data; } return 0; } Funciile avanseaza indicatorul crt si intore adresa elementului curent. La incheierea unei arcurgeri a listei, indicatorul ert rimine blocat pe Ultimul nod si functiile intore valoarea NULL=0. diferenja de principiu faja de exemplele din par.9.1.2. se poate observa in cazul functilor de addugare si extragere: clasa nu asigura alocarea gi cliberarea dinamicd a memoriei pentru obiectul care contine valoarea din lista. Acest lucru este mai dificil din mai multe motive: tipul componentelor listei este nedeterminat; — nu se cunoaste dac adresa primita este a unui obiect dinamic; — crearea gi eliminarea obiectului pot impune apelul unui constructor, respectiv destructor, nu numai alocarea si eliberarea memoriei. solutie care ar incerca si depiigeasca aceste probleme ar reduce intr-un fel sau altul domeniut de aplicare. Crearea si initializarea separata a componentelor poate fi utilé, daci acestea sint complicate, dar faptul c& eliminarea obiectelor imine in grija programatorului constituie un Gezavantaj. Destructorul clasei nu elimina decit nodurile listel, ca $i functia de extragere: ex.2.4: Iai) Inod *p; p=cap.urm; Pentru testarea clasei vom efectua citeva operatii cu o lista de valori intre; ex3: void main() Idi list; int *ip, i /Jinscrie ta inceput valorile: 0.9 for(i=0; i< 10; i++) list.adaug_dr(new int(i)); /Hinscrie ta sfirsit vatorile: 19..10 list.capat(); for(i=9; i> =0; i--) list.adaug_ st(new int(i+ 10)); 1 citeste primul clement list.primQ; cout<<*(int *)list.citesc()< 1/ dreapta) si afiseaza list.capat(); while( (ip=(int* list spre_dr()) ) cout<<*ip<<’ ’; cout< //stinga) si afiseaza list.capat(); while( (ip=(int* list.spre_st()) ) cout< <*ip< <’; cout<

You might also like