I I I

I I

\

,.

Editat sub egida Comisiei Nationale de Informatica

INITIERE IN C+ +

-

PROGRAMARE ORIENTATA

. PE OBIECTE

(Editie revizuita)

Cluj ... Napoca· 1993

Autor

Mu§lea Ionu]

Redactor

Poenaru Codrutn

Designer

Derve§teanu Liviu

Editor

prof. univ. Muntean Emil

Tehnoredaetare eomputerizata Poenaru Codruta

© Copyright Mierolnfonnatiea SRL, 1993

CUPRINS

CuWNT INAINTE

7

1. INTRODUCERE IN O.O.P. • . . . . . . . . . . . . . . 9

1.1. ScopuIO.O.P.. . . . . . . . . . . . . . . . . . . . . . . . . . 9

1.2. Seurt istoric . . . . . . . . . . . . . . . . . . . . . . . . . .. 10

1.3. ~vantajeO.O.P.. . . . . . . . . . . . . . . . . . . . . . 12

1.4. Inainte de inceput 13

2. SCURTA INCURSIUNE IN C+ + . . . . . . . . . .. 15

2.1. Intrari ~i iesiri, Stream-uri , 15

2.2. Functii cu parametrii implicip. Functii cu acelasi nume ., 16

2.3. Refennta . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 19

2.4. NEW §i DELETE , 24

2.5. Clase. . . . . . . . .. '" . . . . . . . . . . . . . . . . 25

2.6. Constructori ~i destructori . . . . . . . . . . . . . . . . .. 30

2.7. "Redefinirea'ioperatorilor . . . . . . . . . . . . . . . . . .. 35

2.8. FRIENDS. . . . . . . . . . . . . . . . . . . . . . . . . 37

2.9. Clase derivate . . . . . . . . . . . . . . . . . . . 39

2.10. Polimorfism . . . . . . . . . . . . . . . . . . . . . . . . 45

3. CIA.SE " " . .. " " Gr • .. • " 111 " • " • " • • • " • .. * .. ." 48

3.1. Introducere . . . . .. . . . . . . . . . . . . . . . . . . . .. 48

3.2. Declararea claselor . . . . . . . . . . . . . : . . . . . . . ., 48

3.3. Membrii unei clase. "Scope access operator". . . . . . . .. 52

3.4. Accesarea membrilor unci clase. Pointeri la metode . . .. 58

3.5. AIle amanunte privind declararea claselor. Functi! "inline" . 62

3.6. Membriistatici .. . . . . . . . . . . . . . . . . . . . . . 68

3.7. Constructori . . . . . . . . . . . . . . . . . . . . . . . . . 71

3.7.1. Initializareastructurilor dcdate . . . . . . . . 71

3.7.2. Propnetapspectficeconstructonlor . . . . . . 74

3.7.3. Tipuri deconstructori, Ambiguitau . . . 76

3.7.4. Conversii . . . . . . . . . . . . . . . . . . 79

3.7.5. Apelulexplicit alconstructorilor . . . . . 80

3.7.6. Reducerea numarului dc constructori . . 82

3.7.7. Constructori ~i obiecte statice . . 82

3.7.8. Constructorii claselor lncuibarite . . . . . 83

3

3.8. Destructori .. . . . . . . . . . . . . . . . . . . . . . . . . .

3.8.1. Definire §i caractcristici .

3.8.2. Destructorii claselor incuibarite .

3.8.3. Erori deosebite . . . . . . . . . . . . . . . . . . . . . .

3.9. Friends : . '.' '.' .

3.10. Cornportarea constructorilor §i destru~tonlor III situatu

necesitind alocare dinamica de memone .

3.10.1. Crearca §i distrugerea sirurilor de ?~iccte . ' .

3.10.2. "Scurtcircuitarea" mecanismului implicitde gesnonare

a memoriei alocate dinamic . . . . . . . . . . . . . .

3.11. Probleme rezolvate. Probleme propuse sprc rezolvare .

3.11.L Array-uri de dimcnsiuni variabile .

3.11.2. Hash-table pcntru numere de telefon .

3.11.3. Mecanism perfectionat de gestiune a memonei .

4. MO~mNIREA .,,""!II. lit • , " " ••• , " ••• " •

4.1. Introducere . . . . . . . . . . . . . . . . . . . . . . . . . . .

4.2. Clase derivate .

4.3. Drepturi de accesare a membrilor "mostenip" ~ . . . . ~ .

4.4. Constructori §i destructori .

4.5. Metodevirtuale. Utilizarea listelor eterogene .

4.6. Mostenire multipla . . . . . . . . . . . . . . . . . . . . . . 4.7. Problema rezolvata. Exercitii propuse spre rezolvare ....

5. ALTE FACILITATI ALE LIMBAJULUI

c+ + " . " ...... , . " . " .. , IS " • , "

5.1. FunctH pure §i clase abstracte . . . . . . . . . . . . . . . .

5.2. OverioadingOperators. ~... . . . .

5.3. Opera tori unari §i binari .

5.4. Operatorii "new" ~i "delete", , , .

5.5. Operatorul "0· .

5.6. Operatorul "::" .

5.7. Operatorul "Il" .

5.8. Operato.~ul "->" :

5.9. Conversii .

5.10. Problema propusa spre rezolvare " .

6. STREAM-URI . 6.1. Introducere

4

99 109 109 113 118

127 127 128 130 138 145 157 167

178 178 180 182 190 193 196 201 204 206 211

213 213

87 87 89 90 91

95 95

6.2. Operatii de intrare §i iesire . . . . .'. . . . . . . . . . . .

6.2.1. Introducere '. . .

6.2.2. STREAM-uri §i upuri de date predefinite .

6.2.2.1. STREAi\1-uri de intrare .

6.2.2.2. STREAM-uri de iesire .

6.2.3. AIle metode de manipulare a STREAM-urHor .

6.2.4. Extractori §i insertori dcfinili de utilizator .

6.3. Tratarea eroriIor aparute In opera Iii cu stream-uri .

6.4. Opera Iii de intrare §i iesire cu format .

6.4.1. Indicatorj] de stare ai formatului . . . . . . . . . . . .

6.4.2. Manipulatori .

6.5. Fisiere §i stream-uri : .

6.6. Probleme prop use spre rezolvare . . . . . . . . . . . . . . .

ANEXAA .

. . . . '"

• 41 • If 0 '" '" • • .. • • • ... .,

Lumea minunata a OOP

..........

BIBLIOGRAFIE

• • • • .. • • • • • • • • III • • ..

215 215 2]6 216 218 219 222 224 226 226 229 232 236

237 237

240

Pentru Codrula

"Obiectele sint reprezentiiri naturaralecare [aciliteazii intelegerea Ii modificarea unui program. "

Joseph Weizenbaum

CUVINT INAINTE

Desi teoria programarii orientate pe obiecte era bine fundamentata inca de acum 20 de ani, ideea nu a prins cu adevarat decit In ultimii ani. Ce se ascunde in spatele conceptului de programare obiectuala? Ideea de a oferi programatorului un instrument performant de reprezentare a lumiiin calculator. Cum se poate realiza acestlucru? Foarte simplu: in loc sa separe irernediabil DATELE de COD, programarea orientata pc obiecte nu face alteeva decit sa contopeasca cele doua entitati, Apropiindu-ne acum de C+ +, putem spune cil. acesta imbina calitatile limbajului C eu avantajele programarii obiectuale. 0 foarte buna caracrerizare a rezultatului objinut 0 facechiar BjarneStroustrup.omul ce a pus bazelenoului limbaj de programare general, conceput astfel inch sa-i Iaca pc programatorii seriosi sa programeze lntr-o maniera cit mat placuta.

Prezenta lucrare se adreseaza in primul rind cunoscatorilor limbajului C dornici sa-§i modernizeze stilul §i concepjia abordarii proiectelor in formatice. Avind in vedere faptul cii Iimbajul c++ este un superset al Csului., pe intreg parcursul lucrarii se vor presupune cunoscute notlunile ec stau la baza programarii in C. 0 parcurgere exhaustiva in cadrul unci singure carJi a ambelor limbaje ar avea doua neajunsuri: pe de 0 parte expunerca ar fi prea lunga (vezi cele 4 volume TURBO C+ + scoase de BORLAND), iar pe de alta, informatia specifics C++ s-ar dilua §i raspindi nepermis de mull.

Incepatorilor Ie recomandam aceasta carte, cu mentiunea ca, Inainte de parcurgcrea ei, este necesara studierea unei cil.f1i de C clasic,

a nota speciala: avind in vedere di limba oficiala a Informaticii este engleza ~i ca - eel put in pe moment - marea majoritate a termenilor de specialitate nu au 0 traducere scurta §i satisfacatoare in limba romana (Ex:

MOUSE, SCOPE, BINDING) pc parcursul lucrarii vor n utilizaji §i termenii englezesti.

Lucrarea de fala descrie pc Iarg eel mai raspindit dialect al limbajului C++ in lumea PC-urilor: TURBO C++.

Doresc sa le multumesc pe aceasta calc prietenilor mei Adrian Frits, Calin Cascaval, Marius Greere ~i Horajiu Ouja pentru observatiile §i sfaturile

7

pretioase pc care mi le-au dat, Fara ajutorul lor, aceasta cartenu ar fi existat in forma ei actuala.

AUTORUL

1. INTRODUCERE iNO.O.P.

1.1. Scopul O.O.P.

Termenul de OOP provine dinenglezescul "Object Oriented Programming" , care desemneaza disciplina programarii obiectuale (sat( orientataobject). Domeniul OOP este deosebit de larg, §i are la ba71i ideea generoasa de a unifica doua concepte de bazii: datele aplicatiei §i codul necesar tratarii lor. Pentru a se ajunge la acest deziderat, se creaza utilizatorului facilitali deosebite in directia definirii unor tip uri de date proprii §i a unor operatori destinati manipularii lor. Acestea se vor comporta aidoma tipurilor de date §i operatorilor standard (lntregi, numere reale, caractere, respectiv adunare, scadere §.a.m.d.). Astfel, dupa necesltati, un programator poate crea tipul , COMPLEXsauMATRICE,caresaoperezecunumerecomplcxe.saumatrici. o secvenja banala in cazul numerelor reale, de genu}

complex a,h,c; matrice m,n,o;

c=a+b; m=n*o;

poate deveni 0 realitate, chiar daca cele §ase variabile reprezinta numere complexe §i matrici. Nolle tipuri de date ("complex· §i "matrice") se numesc CLASE, variabilele "a" , "b" , "c" , "m" , "n" §i "0" poarta numele de OBIECTE (sau mstantierl ale claselor "complex" §i "matrice"), iar operatorii redefiniti "+" §i "*" se numesc METODE ale claselor in cauza, Prin unificarea datelor eu codul se inldege ca 0 CLASA contine atlt structurile de date necesare descrierii unui OBIECT, cit §i METODELE (functiile) cu ajutorul carora obiectele in cauza vor fi manipulate.

Este evident ca, in acest mod, gradul de abstractizare e mult mai ridicat, iar programele devin mai usor de in,te1es, depanat §i lntretinut,

Datorita modului de abordare fundamental diferit, nu trebuie sa va sperie Iapiul ca.Ia inceput, yeti avea unele dificulta]i de adaptare, Cei care au facut treccrca de la FORTRAN la PASCAL cunosc acea inerenta senzajle de nesiguranta la primclc contacte eu reeursivitatea §i backtracking-ul, In scurt limp. veti remarca faptul cii munca de plna acum - in sensul de adaptare a unei probleme la mndul de lucru al calculatorului - va fi facilitata de marea flexibiluate §i maleahilitatc a abordarii oblectuale,

9

1.2. Scurt tstorlc

In primele limbaje de programare evoluate, cea mai marc parte a atcntiei era acordata descrierii structurilor de date deoarece, practic, prelucrarile cc se faceau asupra lor erau destul de simple §i nu necesitau un efort deosebit. Spre exemplu, putem cita cazul COBOL-ului (§i azi foarte raspindit), in care circa 70% din lungimeacodului sursaestcocupata deo descriere astructurilor de date -, in limp, odata cu cresterca complexitajf! programelor, a aparut necesitatea unei "ordonari" riguroase a muncii, Ca 0 treapia superioara, capabila sa rezolve in buna masura problernele ivite, s-a impus PROGRAMAREA STRUCTURAT A. De-a lungul anilor, in special datorita cresterii dimensiunii produselor software, §i acest instrument a devenit la rlndu-i depaslt. Sporirea complexitatii programelor aduceadupa sine dificulta]i reale legate de rigiditatea §i intretinerea unor programe mamut, Cu alte cuvinte, dc§i pretul produselor era in crestere, calitatea lor tindea sa scada,

,

in cautarea unei solujii care sa scoata informatica din situatia dificila in care se afla, s-a po mit de la ideea ca "0 buna reprezentare a temei de rezolvat este deseori cauza transformarii unei problemei complexe intr-una deosebit de simpla" (P.I:I. WILSON, director al laboratorului de intellgenja artificiala de la M.I.T.). In urma diversificarii cautarilor, au lnceput sa apara limbaje destinate solutionarii problemelor specifice anumitor domenii de activitate. Dintre tendintele novatore merita sa amintim eel puttn doua: limbajele avind la baza notiunea de "CADRU" ("FRAME") §i cele ee pomesc de la ideea de "ACTIUNE". Inciuda faptului caambele sint destinate inteligentei artificiale, exista totusi 0 deosebire ce merita relinuta. Primele implementeaza OPERATn asupra unor MODELE DE ENTITATI, pc cind celelalte postuleaza faptul ca OBlECI'ELE nu sint simple clemente pasive asupra carora se fac anumite prelucrari, ci, dimpotriva, rnenirea OBIECTELOR re,zida in a ACTrV A prelucrarile ce le vor suporta ele insele.

In fine, pornind de la notiunea de CLASA, 0 alta tendinpl a dat nastere asa-zlselor LIMBAJE ORIENTATE PE OBIECTE. Primele dintre ele cu adevarat demne de acest nume au fost SIMULA (1965) §i SIMULA-2 (1967). In anii '70 a aparut §i celebrul SMALLTALK. Cel mai mare dezavantaj al lor (din punct de vedere al penetrarii pietei) a fost, din cite se pare, lnsusi faptul cii au aparut ca limbaje de sine statatoare, avind 0 raspindire relativ red usa. Din aces! motiv, putini programatori erau dispusi sa abandoneze limbajele consacrate in acele momente, doar de dragul de a lucra obiectual.

~i, cum diferentele intre punctul de vedere al cercetatorilor §i eel al marilor industria§i au la baza insa§i deosebirea diotre ARTA§i RENT ABILIT ATE, 0 buna bucata de vreme moda programarii obiectuale a fost uitata. in anii 80, in urma acccptarii definitive a Iimbajului C, un colectiv

10

condus de Bjarne Stroustrup incearca introducerea conceptului de CLASA intr-un dialect numit HC with Classes". Ideea prinde contur §i, in 1983, ia nastere prima versiune a noului limbaj: C+ +. Aproape irnediat apar §i partizanii fanatici ai OOP-u!ui, aflat acum la 0 a doua tinerete. Profitlnd deci de marea sa popularitate in rindul softistilor §i de multitudinea domeniilor de aplicatie (de la grafica interactiva la proiectarea interfetelor utilizator §i de la exploatarea retelelor de calculatoare la tehnicile de proiectare a com-

,pilatoarelor) "moftul" devine rnoda, iar moda certitudine,

De ce acest succes extraordinar? Mai ales din cauza faptului cii limbajul C+ + nu faee nimic altceva decit sa-! dea un nou avint unuia dintre cele mai la moda limbaje ale momentului ( este yorba de "C" ). Cind ne gindim cii toti cei implicati in lumea UNIX-ului, §i nu sint putini, pot trece direct la OOP profitind de pastrarca -practic neschimbata- a sintaxei §i regulilor de constructie, lucrurile incep sa se mai lamureasca.

Cu toate acestea, problemele nu lipsesc, 0 declaratie foarte sugestiva ar n cea a "inventatorului" limbajului C+ +: <Utilizatorii au "sarit" pe C+ + inainte ca specialistii sa aiba timpul necesar sa-i instruiasca de a§a maniera lncit sa-! poata folosi cu randament maxim! >. Ce ascunde aceasta marturisire? in principal doua aspecte:

a. Lipsa timpului fizic a facul ca formarea cadrelor de specialitate sa nu urmeze ciclul optim. Acesta, se presupune cii ar csnsta in a incepe invatarea programarii cu un limbaj PAUR obiectual, dupacare. s~ se faca trecerea la programarea structurata, In acest mod, ambele tehnici ar putea fi perfect stapinite. Va yeti intreba care este dezavantajul? Raspunsul este destul de simplu. Daca OOP-ul nu va fi bine inteles, vor fi inevitabile situajiile in care, de exemplu, un programator ce nu a lnvaiat corect PROLOG-ul este tentat tot timpul sa simuleze in acest limbaj procedeele de baza lnvatate in C. Sau, analog, sa utilizeze ~i in PASCAL schemele de simulare a recursivitatii deprinse in FORTRAN.

b. Ca 0 consecinta directa a punctului "a.", in numarul din octornbrie 1991 al revistei "INFO PC" este semnalat faptul eli peste 60% din compilatoarele de C+ + existente pe piaia nu sint tolositc decit pentru dezvoltarea de software structural, §i nu de tip OOP (altfel zis, se lucreaza in C pe un compilator C,+ +).

Nu se pot face anticipari asupra modului in care vor evolua tendintele in "inaltele sfere ale software-ului", dar, nu ar fl de mirare, ca, in citiva ani, larga popularitare a limbajului "C+ +" sa nu duca la 0 reintoarcere la punctul de plccare: SMALTALK sau, de ce nu, SIMULA.

. ~i ce alta confirmare a importanlei programarii obieclllale ar fi mai semnificativii, dedt faptul eli bro§urile de angajare (in domeniul software) la IBM sau MicroSoft cer tuturorviitorilor candidati cunoa~terea limbajului

11

c++?

1.3. Avanta]e OOP

Yom inccpe prin a defini principalele co neep te ce stau la baza OOP,dupa care ne vom putea da seama mai bine de avantajele lor.

Prin noul concept de INCAPSULARE se ating mai multe objective: "contopirea" datelor cu codul (in cadrul a§a numitelor CLASE) , facilitatea deosebita de localizare a erorilor (intotdeauna cauza se ana in "interiorul" unei singure clase) §i de modularizare a problemei de rezolvat (fiecare clasa va rezolva 0 anumita problema).

Un alt terrnen introdus de OOP, MO~TENIREA, aduce inca un avantaj specific. In urma definirii unci clase, eu un minim de efort §i timp, se pot preciza seturi de clase asemanatoare, avind totusi 0 trasatura distinctiva. Un exemplu foarte sugestiv ar fi 0 clasa a mamiferelor, din care fac parte clasa ciinilor §i eea a primatelor. Ciinii se impart la rindul Ior in cockeri, boxeri, dogi, fox-teneri §.a.m.d. Practic, ciinii §i primatele au atit unele trasaturi comune (mostenite ca mamifere), cit §i altele specifice, Pe de alta parte, dogii §i cockerii au trasaturi comune atit in calitate de ciini, cit §i ca mamifere. Daca delimitam corect c1asa mamiferelor (nasc pui vii, au singe' cald, i§i alapteaza puii), in mornentul in care definim ciinii, e.suficient sa spunem ca sint mamifere pentru ca toate caracteristicile de mai sus sa fie subintelese. Ramin prin urmare de specificat acele trasiitl;!,ri ce le deosebesc de celelalte mamifere (carnivore, anim~le domestice, etc.), Jntocmai se petree lucrurile §i in universul claselor C+ +. In paginile cartii veti gasi dovada clara a facilitattlor ad use de catre MO~TE~IRE: postbllitatea de cxtindere a unui . program printr-un minim de efort.

La actualul nivel de cunostinje vom defini termenul de POLIMORFISM doar intuitiv, urmlnd ca la mornentul potrivit sa dam toate amanuntele necesare. Ideea de baza consta in faptul ca, intr-o ierarhie de clase obtinute prin mostenire,o metoda poate avea forme diferite de la un nivel la altul (specifice rcspcctivului nivel din ierarhie), Pcntru a lamuri cit mai bine aceasta, problema vom lua un exemplu concludent tot din lumea animala. Cosideram lumea vic drcpt 0 clasa generala avind printre altecaractertstici §i functia (in limbaj OOP am spune METODA.) de HRA..NIRE. Indiferent ca evorba de o Ialea, un parameci, 0 balena sau un jaguar, toate aceste vietatl poseda functia de HRA..NIRE. Alita doar ca fiecaredintre eleo realizeaza altfel, Din aeest punct dcvcderc, HRA..NIREAeste un proces abstract, dar care in momcntul asocierii lui cu 0 fiinla vie devine concret. Niciodata cind spunem ca DC hranim diode sau pisica ~u ne gindim sa-I expunern la soare ~i sa-i stroplm, ca pc doua lale1e,.

12

Exista §i alte avantaje ce lin de mecanismul claselor, dar slnt destul de greu de explicat acum. Ele vor fi prezentate pc parcursul cartii, in cadrul capitolelorcorespunzatoare,

Vom incheia aceasta sectiune prin a sublinia avantajele de ordin practic ale programarii obiectuale.

In momentul de faJa, piata informatica este invadatade adevarate biblioteci §i colectii de CLASE. Menirea lor este de a perrnite un coeficient cit mai ridicat de reutilizare a codului scris. Pe de alta parte, pornind de la acesteclase, utilizatorul poate crea prin mostenire alte clase, care sa-l ajute sa-§i rezolve problema in mod optim. in plus, programatorul are garantia folosirii unor proceduri scurte, inteligibile §i, nu in ultimul rind, CORECTE.

Un alt domeniu de utilizare a bibliotecilor de clase este eel al realizarii PROTOTIPURILOR unor produse software. Prin PROTOTIP se intelege un program functional, simplificat §i care sa dea 0 imagine clara a produsului final. Utilitatea lor consta in faptul ca, intr-un timp foarte scurt §i cu un efort minim din partea producatorului, clientul poate avea 0 imagine destul declara a produsului final. De ce am aflrmat ca bibliotecile de clase pot fi utile in realizarea de protitipuri? Pentru ca, daca in cadrul unci aplicajii oarecare, avem nevoie de un algoritm de recunoasteri de forme (de exemplu), nu este neeesar ca prototipul sa conjina un algoritm optim. Este preferabila utilizarea unci metode deja implementate in cadrul unci biblioteci, §i care sa faca acelasi lucru, In acest mod, prototipul poate fi prezentat lntr-un interval de timp foarte scurt, dupa care se vor inlocui doar acele "module" care sint deficitare din anumite puncte de vederc (consum excesiv de memorie, viteza nesatisfacatoare, §.a.m.d.).

Nu trebuie sa neglijam nici facilitarea de catre OOP a cooperarii intre diversele echipe §i/sau persoane lucrind la unul ~i acelasi proiect.

1.4. inainte de lnceput ...

... trebuie facuta 0 prcoizare: cartea de fata nu va aborda TURBO C+ + din punct de vedere al mediului de programare (IDE, modele de memorie, diverse utilitare, ... ), ci doar ca dialect C+ +. Toate programele prezentate ill carte au fos, testatecuajutorul unuicompilatorTURBO C+ +versiunca2.00, sub sistemul de operare MS-DOS 5.0.

~i inca trel observatii:

a. Implicit, orice fi§ier care contine cod "C+ +" va trebui sa aibii extensia ".CPP" pentru a f compilat corect.

b. Toate programele ce utilizeaza biblioteca grafica BORLAND contin un

13

apel de funcjie de genul

ini tgraph (gd, gIn, "c :\\tcpp\\bgi ") ;

Pentru ca aceste exemple sa poata fi executate corect pe calculatorul dumneavoastra, va trebui sa avejt grijli sa modificati eel de al treilea parametru de a§a maniera lncit sa exprime calea corecta spre directorul ce contine fi§ierele specifice modului de lucru grafic,

~ A

2. SCURTA INCURSIUNE IN C+ +

Scopul acestui capitol este de a face 0 trecerc in revista a celor mai importante caraeteristici §i conceptc introduse de limbajul C++. Nu se doreste 0 discutie exhaust iva, cidoar 0 vedere de ansamblu asupra a ceca ce se va discuta in amanunt in urmatoarele capitole. Prin citeva exemple destul de simple se va inijia un prim contact al cititorului cu nolle concepte. Explicatiile Iurnizate in cadrul acestui capitol vor fi de natura sa incite la 0

"parcurgereatenta a celorlalte sectiuni, in care gradul de rafinare al informatici va creste substantial.

Practic, din cuprinsul acestui capitol nu sint "obligatoriu" de citit decit primele patrusectiuni, Celelaltecontin dear inlormatii cevor fi prezentate mai pe larg in cadrul capitolelor urmatoare, Ideea de a scrie un capitol "jumatate facultativ" vin~jn intlmpinarea celor mai cunoscute doua tehnici de lnvatare:

"BOTTOM-UP" (se incepe de la zero, §i se va cladi incer, dar sigur, un edificiu ale carui dimensiuni vor fi percepute doar in finalul acuvitati! de invalare) §i "TOP'~DOWN" (se lncepe printr-un prim contact cu problema in toata cornplexitatea ei, dupa care sc va trece Ia aprofundarea diverselor componente), Adeptii primului stil vor putea sari lini§tiJi peste a doua jumatateacapitolului. Cu toate.caoferim posibilitateaalcgerii, noi recomandam cea de a doua abordare, care are avantajul dea permite 0 corecta lncadrare a fiecarui arnanunt in contextul general.

,

2.1. lntrarl ~i ie~iri. Stream-uri

Aproape toate carjile de C dau drept prim exemplu de program celebrul "Hello world l", care afiseaza acest text pe ecran folosind Iunctia "printf", 0 prima dovada de non-conformism a limbajului C+ + este insusi faptul ca reuseste cu mult succes inlocuirea clasicelor funcjii "printf" §i "scanf" (dli incepatori nu au avut difieuWili cu argumcntele - pointer! - ai acesteia <fin urmal) cu 0 bil1liotcea ce arc la baza conceptul de STREAM. Avind ill vedere ea acest concept fundamental este descris pc larg 111 capitolul o, sa vedem doar cum se opereaza cu el:

4t include <iostream.h> II header-ulbibliotecii de stream-uri

void main(void){ int a;

/» declaratia de mai jos "float b;" va fi comentata conform celeilalte posibili ta ti C + +rde a scrie 0 linie de cornen tari u * /

float b r I I aceasta este cea de a douamaniera de comentariu C+ +

15

char Ci

cout«"\n\nTastati un intreg : "; cin»a;

cout«"\nTastati un numar real cin»b;

cout«"\nTastati 0 litera

" . ,

. ". . ,

cin»c;

cou t-c-cv vn vnat.L tastat "«a«" r n«b«" r ..

«c«". \n";

}

In urma executiei programuJui pc ecran se va afisa:

Tastati un intreg : 12

Tastati un numar real: 12.34

Tastati 0 litera: a

Ali tastat 12, 12.34, a.

A§a cum specified §i comentariul, "iostream.h" este header-ul unei bi-

blioteci de stream-uri de intrare §i iesire, EI vine sa inlocuiasca pe _

"stream.h",header-ulbiblioteciidestream-uricorespunzatoareprimeiversiuni a limbajului C+ +. 0 alta observatie referitoare la aceeasi prima linie ar fi legata de noua posibilitate de comentariu, §i anume cea utillzlnd doua slash-uri. Cele doua caractere "/" semnaltzeaza inceputul cornentariului, el continuind plna la sflrsitul liniei respective.

Probabil ali intuit deja ca "cin" §i "cout" desemneaza dispozitivele de intrare §i iesire (implicit tastaturasi ecranul), Operatorii "< <" §i "> >", folosiji in C pentru shift-ad, slnt utilizati de asta data drept "pune la ... " §i "preia de Ia .. .". Dupa cum vom vedea mai tirziu, limbajul C++ permite 0 astfel de redefinire a operatorilor existenti, Chiar daca un operator este redefinit, se vor pastra §i vechile lui sensuri. Alit ca, in functie de operanzii care-l apeleaza, se va efectua 0 operatic sau alta.

2.2. Functii cu parametrll lmpncln, Functii cu acelasl nume

lncepem aceasta sectiune prin a relua 0 afirmatie fiicuHi in cuvintul introductiv, DEOARECE LlMBAJUL C+ + ESTE UN SUPER·SET AL

16

LIMBAJULUI C, TOATE DECLARATIILE ~I DEFINITIILE DE VARIABILE ~I FUNqn,. CARACTERISTICE DJALEcrtJLUI TURBO C,VORRA.MINEINCONTINUAREVALABILE.Cumne-ampropusdoar prezentarea caracteristicilor C+ +, nu vom explica in cele ce urmeaza nici un fel de instructiuni care, asemeni definirii functiei "exemplu", nu necesita declt cunoasterea limbajului C:

int exemplu(int p) {

int i;

for(i=O;i<p;i++) printf("\n\t i=%d",i); return p/2i

}

Yom continua prin a analiza cu atentie exemplul de mai jos: t include <iostream.h>

void functie(int a=123,double b=456.789, char * c="programtf)

{

cout<"\na="«a«" b="«b«" C="«Ci

}

void main(void)

{ functie(321,987.654,"subrutinan); functie(321,987.654);

functie (321) ;

functie();

}

In urma executieisecventei de instructiuni.peecranvorapareau rmatoarele

~iruri de caractere:

a=321 b=987.654 c=subrulina a=321 b;=987.654 c=program a= 321 b= 456.789 c= program a=123 b=456.789 c=program

Probabil ca intulji deja despre ce este vorba: prin declaratia void functie(int a=123,double b=456.789, char * c::;:;ltprogram");

compilatorul este atentionat ca orice apel al functiei 'functie" poateavea Intre

17

zero §i trei parametri actuali, tn caz cii numarul lor este mai mic de trei cei lipsa vor lua IMPLICIT valorile specifica te in lista de parametri formali. Dar atentic! Aceasta facilitate are §i ea limitele el: Indiferent de numarul parametri1?r act~ali ai ~uncli~i, ordin~a §i tipul lor slnt lntotdeauna aeeleasil B~ chlar, rna! mu~t~ m~~md cu .pnm~1 parametru transmis implicit" TOTI eel care-r urmeaza In hsta nu mal pot fi transmisi DECIT cu valori implicitel

. ·F~nctio~s over~?ading· - in traducere libera "suprapunerea" ori "supr~In~.rcarea funqnl?r - este un alt concept fundamental in C+ +. Oralie 1~1, u.tlhzatorul poate sa declare functii cu acelas] nume, dar avind prototipuri d!fer~te (altfel spus, este vorba d~ functii cu acelasi nume, dar cu semnatun diferite). ~ceste functii pot fi distinse prin faptul ca vor diferi fie prin tipurile parametrilor Iormali, fie prin tipul valorii returnate. Vom incepe prin a da un exemplu, pe care-l vomcomenta in continuare,

# include <iostream.h> # include <string.h>

Nu exista parametri 1

II in TURBO C++ declaratia ce urmeaza nu este obllgatorie

overload a: '

Parametrul este pointer la stringul: asdfe Lungimca stringutui = 5

La versiunea ini!iala (AT&T 1.0) a lui Bjarne Strostroup, in C++ era necesara utilizarea cuvintului cheie "overload", urmat de numele functiei respective. In noile implcmentari, acest lucru nu mai este impus. Totusi, recomandam folosirea unci astfel de declarajii, in ideea cii un program trebuie scris cit mai explicit. Pentru ca, nu uitaji, programele trebuie sa fie u§or de injeles alit pentru eel care le-a facut, cit §i pentru 0 alta persoana ee ar urma sa-i continue muncal

. In situatia de fala, utilitatea unui astfel de exemplu poare fi lndoielnica.

Daca tnsa dorim largirea bibliotecii rnatematice a limbajului C+ + ell noi functi! (de exemplu ridicarea la putere), poate prezenta un cert in teres existenia unor functii eu acelasi nume'''(''PUTERE"), dar care opereaza cu diversi parametrii (numere complexe sau rationale),

Exemplu:

typedef struct rationall{ float numarator,numitori } rationall;

II prototipurile eelor doua functii avind acelasi nume

void a t vo i.d j i int a (char .. ) i

II definitiile celor doua functii

typedef struct complexl { float p_reala , p_imaginara; } complexl;

void a(void){

cout«"\n Nu exista parametri !\n";

}

overload PUTERE;

rationall .. PUTERE(rationall .. , int )i complexl .. PUTERE(complexl *,int);

int a rchar "s){

cout«"\n Parametrul este pointer la stringul : "<x s i

return strlen(s);

2.3. Referinta

}

In absolut toate dorneniile informaticii se pune foarte acut problema eflcientei. Ea comporta doua aspecte: unul legat de spajiul de memorie utilizat §i un altul legat de timpul de executie al programelor. In legatura cu acosta din urma, este unanim cunoscura preferinja pentru limbajul de asamblare a tuturor programatorilor interesati de cconomisirca ficcarei fractiuni de secunda. Dar, de la acest aspect pina la dimensiunile pe care le ia astazi fenomenul, este 0 cale foarte lunga. Vom da doua exemple elocvente in acest sens,

void main(void) { a();

CQut«"\n Lungimea stringului ; "«a("asdfe"); }

Pe ecran se vor afi§a urmatoarele texte:

Specialistii de la MicroSoft au tras concluzia cii folosirea spcciflcatorului

18

19

"pascal" in (ala Iunctiilor scrise in C scurteaza codul ge~erat cu 7% '. Aecs! cistig se obtine prin faptul ca, in conventia PASCAL, suva este gestionata de catre rutina apelata, nu de catre cea apelanta,

AI doilea exemplu este rezultatul studiilor lui Bjarne Strostroup. Acesta afirma ca timpul consumat de un apel de functie de tipul "alloc" este atit ~e mare incit este mai rentabil ca, in cazul unor alocari repetate de memone pentru date de dimensiuni red usc, sa se proiecteze un "gestionar" at memcriei. Acosta va aloca la un moment dat 0 cantitate mai mare de memorie, din care sa "livreze", la comanda, cantitatile neccsare.

Rcvenind, sa amintim ca, in cazul unor apeluri de functii care au drept parametri obiectc de mad dlmensiunl, este foarte necconomica punerea aceslora pc stlva la apclul functiei, 0 economie semnificativa se poate obune prin utilizarea pointerllor. Aceasta are, insa, doua dezavantaje:

a. este destul de putin elcganta deoarece, in uncle cazuri, este mult mai

. sugestiva operarea asupra obicctelor decit cea asupra pointerilor la ele;

b. exista cazuri spccialc in C+ + cind 0 astfelde solutio este contraindicata.

De aceea s-a facut apel la un nou tip de data, numit tipul REFERINTA.

Acosta implementeaza perfect conccptul de "transmitere a parametrilor prin referin Iii". 0 referin Ia rcprezin ta, de fa pt, n urnele un ui obiect, Daca avem tipul de data "T", prin "T&" se va intelege 0 rcferinlii la un obiect de tipul "T". Orice rcfcrinta trcbuic initializatil, dupa care devine pur §i simplu un alt nume (un "alias") al obiectului cu care a fost initializatii.

Pentru a exemplifica cit mai bine cficienja utilizani referintelor, vom analiza doua exemplc: unul care foloseste ~i unul care nu foloseste tipul referinta. Programul de mai jos nu va folosi tipul referinta, pe cind eel care-i urrneaza il va utiliza:

#: include <iostream.h>. # define MAX NUME 25

typedef struct persoana {

char nUme[MAX NUME],prenume l[MAX_NUME], prenum;_2[MAX_NUME],Initiala_tatalui~ int virsta,cod;

float inaltime;

} I?ersoana;

.

void tipareste persoana(persoana p){

cout«p. nume-cc ' "<xp . prenume _1« II "«p. prenume _2;

cout«" virsta; "«p.virsta«" , cod ="«

p.cod«" , inaltimea = "«p.inaltime«"\n\n";

20

}

void main(void) {

persoana JameS_Bond={"BOnd","James","",'K',40,

007,1.88}; tipareste persoana(James Bond); persoana Ion~IOn;JameS_BOnd; tipareste~perSOana(IOn_Ion);

}

Pe ecran se va tipari:

Bond James virsta : 40, cod = 7 , inaltimea ::: 1.88

Bond James virsta : 40, cod = 7 , inaltimea = 1.88

Pen tru in [elegerea programului, trebuicsu bliniat ca instructi unite degenul persoana Ion_Ion=JameS_Bond;

realizeaza initializarea printr-o copie bit cu bit a cclei de a doua structuri in cadrul primeia.

Structura "persoana" ocupa un numar semnifi~liv de octeti, Daca se doreste tiparirea unui §ir de 1000 de p~rsoane, Ump?l ocupat de t~ansmitcrea succesiva a celor 1000 de structun ca parametn creste nepernus de multo Pentru a scapa de accst inconvenient, vom folosi un al doilea program, care utilizeaza drept parametri referinte la structuri de tip "persoana",

lata acum eel de-al doilea program, in care se va utiliza tipulreferinja: # include <iostream.h>

# define MAX NUME 25

typedef struct _persoana {

char nume[MAX NUME1,prenume_1(MAX_NUME)i char prenume 2[MAX NUME],initiala_tatalui' int virsta~cod;-

float inaltime;

} persoanaj

persoana sir_persoane[20];

void tipareste_persoana(persoana& P){ cout«p.nume«" "«p.initiala_tatalui«". "« p.prenume_l«" .. «p.prenume _2«"\n";

cout«" virsta : "«p.virsta«" , cod = ,,« p.cod«" , inaltimea :::: "«p.inaltime«"\n\n\n";

21

}

void actualizeaza_virsta(persoana& refPersoana) {

refPersoana.virsta++;

}

persoana& cauta_persoana_de_50_ani(void) {

for(int i=0;i<20;i++) if(sir_persoane[i].virsta==50) return sir_persoane[i]i

}

void main{void) {

cout«"\n\n\n\n\nprogram demonstrativ :

REFERINTE\n\n\n"i

persoana James Bond={ .. Bond", .. James .. , .... , 'K' ,40,

- 007,1.88};

persoana& .ref persl=James Bond; tipareste_persoana(ref_persl);

persoana Ion Ion=James Bond; persoana& ref pers2=Ion Ioni tipareste_persoana(ref_pers2);

actualizeaza_virsta(ref_pers2)i tipareste_persoana(ref_pers2)i

tipareste_persoana(JameS_BOnd)~

persoana aa={"Iftode","rlie","",'K',40,007,1.88};

for(int i=0;1<20;i++) {

/ / initializare referinta

persoana& ref pers3=sir persoane[i];

- -

sir persoane[i]=aaj / / Inirializare elernentdinsir

for(int j=-lij<i;j++) actualizeaza_virsta(ref_pers3);

}

22

cout«"\n\nPersoana de 50 de ani : \n"; tipareste_persoana(cauta_persoana_de_50_ani()i }

in urma executiei programului pc ecran se va afisa:

Program demonstrativ: REFERINTE

Bond K. James '

virsta : 40, cod = 7 , inaltimea == 1.88

.

Bond K. James

virsta : 40, cod = 7, inaltimea == 1.88

Bond K. James

virsta : 41 ,cod == 7 , inaltimea = 1.88

Bond K. James

virsta : 40, cod = 7 , inaltimea = 1.88

Persoana de 50 de ani:

Iftodc K. Hie

virsta : 50 , cod = 7 ,inaltimca = 1.88

De asta data, in functia "tipareste persoana" nu va Ii transmisa intreaga structura, ci doar adresa ei. Parametrul "refl'ersoana" va referi structura de tipul "persoana" care aparea pc post de paramelru formal. De remarcat ca membrf structurii se refera prin ".", nu prin ". >" (sa nu uitam eli 0 referinla la un obiect nu este altceva decit un AL T NUME desemnind exact aceeasi instantiere Cll care a fast initializata).

Dupa cum se va vedea In urrna rularii programului, actiunile ce se vor executa in "actualizeaza , virsta" vor fi pastrate in cadrul obiectelor ~i in urma parasirii acestei funetii. Acesta este un al doilea aspect at utilizarii referintelor: se lucreaza direct asupra obiectului referit. De remarcat ca §i in cazul utilizarii referintelor, Initializarile de obiecte prin atribuire au loc exact in maniera descrisa rnai sus (adica tot prin copiere bit cu bit).

In cadrul ultimelor doua proceduri se remarca un lucru foarte interesant, Restricpa din limbajul C, legata de obligatlvitatea declararii tuturor variabilelor locale la inceputul procedurii, nu mai c valabila (vezi locul in care sint declarate "aa" ~i "ref_pers3"). Mai mult chiar, se vede ca, in doua rind uri, contorul intreg "i" este definit in cadrul instructiunii "for", Totusi se mai cuvine facuta 0 observatie: instructiunile de genul

for (int i=O j ••••• )

23

sint echivalcnte cu

int i; for(i=O; ••. )

Altfel spus, 0 secvenja

for (int i=O; ... ) {

}

for(int i=O; .•• )

va provoca eroarea de "redefinirea variabilei i".Am atrasatentia asupra acestui fapt deoarece multa lume este tentata sa considere ea declararea lui "i" in cadrul unui "for" il face sa devina 0 variabila locala acestuia, Deci nu uitati: domeniul de existenta al lui "i" ia sfir§it la tnchlderea blocului ee-l contine pe "for".

2.4. NEW ~i DELETE

Programele scrise in TURBO C neobiectual foloseau pentru alocarea dinarnica de mcmorie familiile de functii "free" §i "malloc", care erau destul de greoaie in folosire, Ele au fost pastrate in TURBO C+ +doar pentru men Ii nerea com pa tibili tatH eu verst unile precedente, in locul lor, se pot folosi cei doi opera tori creati in acest scop, Primul, "new", serveste la alocarea dinamica a memoriei, iar eel de al do ilea - "delete" -la eliberarea ei.

Operatorul "new" va returna un pointer la zona de memorie alocata dinamic, In cazul in care nu exista suficienta memorie disponiblla, alocarea nu va avea loc. Acest fapt estesemnalizat prin returnarea unui pointer NULL. De aceea, so recomanda ca, dupa fiecare utilizarc a lui "new", sa se testezevaloarea returnata. In caz de c§ce al operatieide alocare, este bine sa se tipareasca un mesaj de avertizare §i sa se incheie executia programului. Altfel exista riscul de a se obtiae efccte dezastruoase,

Operatorul "delete'va elibera zona de memorie la care pointeaza argumen-

tul sau, .

in programul de mai jos se poate vedea un exemplu de testare a coreetitudinii alocarii dinamice de mernorie.

Exemplm

t include <iostream.h> # include <process.h>

24

i define nl "\n"

typedef struct _pereche { int a,b;

struct _pereche *next;

} pereche;

II Testeaza valoarea parametrului actual. Daca el este NULL

II Inseamna ca ultima opera tie de alocare dinamica de memorie , II s-a soldat eu un esec, Din acest motiv se aflseaza un mesa]

.II de avertizare si se provoaca oprirea executiei programului. void testare pointer(void * pointer){

if (pointer=;;NULL)

.{

cout«"\n\nMemorie insuficienta 111\n"; exit ( 1) ;

} }

void main(){ peracha *pl,*P2i

p l=naw par ache ; II se aloca memorie pentru 0 structura testara pointer«void *) pI); Iitestalocarecorecta p2=new paracha [ IO]; II se aloca memorie pentru un

II sir ell 10 elemente

tastare pointer ( (void 1<) p2); II test alocare corecta p2[2].a.-;;;10;

cout«nl«p2[2j.a«nl;

delete pI; II se elfbereaza memoria alocata pentru 0 structura

1/ •• -

}

o mare parte din exemplele earlii vor utiliza oyeratorul Knew·: i.n cele mal multe din cazuri nu s-a testat valoarea returnata de acesta. Omisiunea este INTENTIONATA §i arc doua scopuri: .pe de 0 pa~te se evWi inearcar~ excesivaa exemplelor, tar pede alta nuse distragcatenpa de lascopul secvente!

de instructiuni. '

2.5. ctase

in cadrul acestei sectiuni vom descrie ~hccint una dintre cele mai impor- 25

tante noutajl aduse de limbajul C+ +, ~j anume conceptul de clasa. Cu ajutorulclaselorserealizeaza un vechi dezideratal programatorilor: unificarea datelor §i a procedurilor,

Pentru a inlelege cit mai bine ideea ce sta Ia baza conceptului de clasa, vom analiza pentru inceput ideea de tip de data, sub forma in care exista ea in limbaju! C. Spre exemplu, daca ne referim la numerele lntregi ("int"), vom remarca faptul ca, acestea sint descrise de doua trasaturi: in primul rind spatiul de memorie in care numerele sint reprezentate (§i care variaza de la un calculator Ia altul), iar in at doilea rind un set de' opera tori cu ajutorul carora intregii pot forma expresii aritmetice sau Jogice: Programatorul nu este interesat de solutia ,folosita de compilator pentru-rezolvare a celor doua aspecte, ci doar de modul in care poatc utiliza respectivul tip de data.

In ciuda facilitatilor de limbaj eu totul remarcabile, C-ul nu poate (~i nici nu are cum) sa fie dotat cu toate tipurile de date §i operajii pe care §i le-ar putea dori diversi! utllizatori. 0 rezolvare eleganta aacestui inconvenient este adusa de C+ + prin conceptul de clasa. Cu ajutorul claselor, un programator i§i poate defini orice tip de data dorit, impreuna cu setul necesa! de operatii, Toate aceste intormajf vorfi reunite (incapsulate) lntr-o CLASA.Astfe], prin clasa vomlntelege un nou tip de date, definit de utilizator. Clasele pot fi folosite lntocmai ca §i tlpurile de date predeflnite, Faclnd abstracjie de modul de realizare a celor spuse ptna acum, ne putem Inchipui foarte bine urmatoarea secventa de mstructiuni C+ +:

complex q(lO,I),b(2,3),c; rational d(4,3)i

c=a*b;

d""d*2i

1n acest mod utilizatorul va opera eu nurnere complexe §i rationale exact ca §i cum ar folosi tipurile predefinite "int'sau 'float",

Inainte de a da un exemplu concret deimplementare a unei clase, vom explica sumar modul de creare a unui PROJECT in mediul TURBO C+ +. Facem acest lucru deoarece la noul produs al firmei BORLAND 0 astfel de operajie nu mai are Ioc manual, ci.se efectueaza .cu sprijlnul unor functii speciale ale meniului. Celor care nu cunosc tehnica de lucru cu PROJECT-uri Ie vom recomanda sa studieze.un manual de utilizare TURBO C. In citeva cuvinte totl.l~j, despre ce este vorba.lncazul programelor de dimensiuni mai mari, scrierea codului in cadrul unui singur fisier e neplacuta §i neeconornicoasa: de fiecare data se vor compila toate liniile programului, chiar daca s-a rnodificat un simplu comentariu. De aceea, serecomanda tmparttrea programului in module mai scurte, care sa fie scrise in fisiere ' separate. Aceste flsiere VOT fi cornpilate independent, iar prin link-editarea lor in com un (in cadrul unui PROJECT) va lua nastere un singur program execu-

26

.' . . arte un astfel de produs softwar~.este

tabil, Avantajele si~t m~l!lple. pe de? Pede alta, in cadrul actiunn d:

mal usor de gindit §l mtretlflut, Jar r ecomptlat decit modulul in cauza.

modificare a unuia dintre module, nu va I r .

. r /actualizare a PROJECT-unlor

Revenind la mecams~ul de crea ~ drul unor Iunclil din meniul de

TURBO C+ + acesta este Implementat in ca . . .. PROJECT se

, ~...' I prtnctpal a oppunu '

bazav Astfel in urma selectaru din memu . OPEN PROJECT),

. , . - PROJECT (opllunea

vaputeafiecrea,fiemcarcaunno~ losi runile INSERT §i DELETE, eu

In cadrul acestuia se vo~ putea ~ OS) ~Pd;n PROJECT-ul selectat. 0 data ajutorul carora se adau~a/§terg ~j~e~J~ se vor compila §i Hnk-edita toate

creat un PROJECT, pnn coma bil

• ' . r program executa 1.

fi~ierele sale, cretndu-se un smgu . .'

. deos .e in cadrul acester secuuni vom

Am facut paranteza de m~1 sus ,eoarec servi drept obiect de studiu

prezcnta un prim exemplu scns_ m~du~ar, ~;~ ~:a de PROJECT.

atit pentru nOliunea de CLASA, Cit §l~en

di 'u:ei fi§iere: STTNG_ CL.DAT,SETIncepem cu. un progr~~~~~~~sp:~tr~ a ob!ine din cele t~ei. fi~iere. un TINGS.CPP §I MAIN_ . w, n i PROJECT (sa-I zicern SETprogram cxecutabil este neccsara crcarea u u t- a lUI' SETTINGS CPP §i

-' li k editarea coree a .-

TINGS.PRJ), care sa asigure tDAT f indus ca header in celclalte doua

MAIN SET.CPP (STTNG_C t::': ~a 1 G CL DAT

fi ')- Sa- vedern deci ce conpne .fl§lcrul srrx -' .

1§lcre . • .

II DECLARAREA C LASEI "settings"

i include <graphics.h>

class settings {

private: . f d

. l' 1/ culoare de scriere SI de on

Lnt, cul,bkcu , II trib teledescriere

.' type tv+"' a.n u

struct textsett1.ngs, "-1 II atextelor

tll II atributelede umple-

Struct tillsettingstype t

II re a figurilor

public: . • void put_settings(vo~d): void get_settings(vOl.d),

}; "'_ .x ~ entuamdouadefinitii.OCLASA.

Odatafiicutacestlucru,samcercamsaacc, t U'n OBI,ECTesteo

. d f tr de catre programa Of.

este un tip abstra~t de ~ate, e .llli CLASA. Cu alte cuvinte, un OBIEer este variabim declarata ca fiind de tipul

o INST ANTIERE a unei CLASE. .

. _ oarta nurnele "settings" (acesta este

Clasa pe care tocmat a~ declara~ ~ p d finit) Ea are doua seqiuni: cea

numele noului tip de data pc care -am e .

27

privata ~i cea publica. In cazul nostru (Ioarte particular, de altfel) prima contine doar declarajli de variabile, iar a doua numai prototipuri de functii, Acest fapt se datoreaza unei pure intimplari, in ambele sectiuni putind figura, lara restricjii, aut variabile cit §i funcjii, Sectiunea privata pastreaza (invizibil pentru utilizatorul clasei) informatti folosite de catre implementato~ul cla_sei,. pc clnd cea publi~ repryezinta interfata dintre clasa ~i potentialul ei utilizator, Pentru 0 mal buna intelegere, vom face 0 analogie eu tipul de date Mint" dinC, In acest caz atit octejii in care e memorat intregul, cit §i operatorii existenji (+, -, '" §.a.m.d. ) se afia la dispozitia utilizatorului deci ele sint pub lice. '

,.

o precizarccareva lam uri multe neclaritati: un "struct" din limbajul Cclasic poate fi privit ca 0 clasa cu toate variabilele publice §i neavind nici 0 funqie rnernbru. Am definit astf~l §i functiile membru: acele functii ce sint declarate in cadrul unei clase. ~i fiindca tot a venit yorba de structuri, sa spunem ca, in C++, §i lor li se pot adauga functii membru. In'acesr moment esteclar ca 0 secv<:nla ca cea de mai jos nu mai reprezinta 0 enigma pentru dumneavoastra,

class clasa {

I I .. -:

publj"c : int i;

void do it(void) { cout«i; };

1/ -

} ;

//

clasa my_class; // declarareaobiectului tmy ..... class"

my c Las s, i=5; // seta rea membrului Hi" Ia 5

my:= c las s. do _it ( ); !! apel~rea functiei membru "do _it"

Sa reluam in cuvinte celeciteva "idei puse in evidenta de acest scurt exemplu, Alit variabilele cit ~i Iunctiile membru ce apar in sectiunea publica a unei clase pot fi utilizate in orice punct al programului, Exista totusi 0 restrictie: ele pot fi accesate doar prefixate de catre un nume de obiect

,(instantiere a clasei respective). Operatorul ce face posibila selectarea membrilor unui obiect este punctul (","), intocmai ca in cazul structurilor din C. in schimb, toate variabilele §i metodele private nu vor putea fi accesate DEcIT de catre alte metode ale clasei in cauza (trebuie sa subliniem ca in terminologia OOP "metoda" este sinonima cu 'functie membru").

In continuare, vom analiza fisierul ·SEITINGS.CPP·, care conjine des-

crierea functillor membru ale clasei "settings". '

28

1* Salveaza/restaureaza atrlbutele modului de lucru grafic */ # include "st~ng_cl.dat" //eincluscaheader

/ / RcstilUfeaza atributele modului de lucru grafic salvate cu

/ / get_scltingsO .'

void settings::put_sett~ngs(vo~d)

{settextstyle(txt.font/txt~direction/txt.charsize);

settextjustify(txt.horiz,txt.vert); setfillstyle(fll.pattern,fll.color); setcolor(cul);

setbkcolor(bkcul);

}

II Salvea7.a atributele curcnte de lucru in mod grafic void settings::get_settings(void)

{

gettextsettings(&txt);

getfillsettings(&fll)~ cul=getcolor(); bkcul=getbkcolor();

}

Din cite se vede, fi§ierul STING_CL.DAT este indus s~b f.0.rma de header. EI a fost conceput ca fi§ier separ~t t~cmai p~ntru a fen .utlhZ?toru~ de munca plicticoasa de a tasta informatiile mclus~ .tn acesta on de Clt~ on

. Intr-unul dmtre modulele unui PROJECTeste. utilizat un astfel de ?blect.

Pentru loti cei care cunosc nucleul gr~fical firmei ~OR~D, SCOpUll~ care a fost cream c1asa SETIINGS este eV1dent.Ea reahzeaza un mecarusm sl~plu §i comod de salvarc/restaurare a unor carac.teris~id spec~fice m.odulu.1 de lucru grafic (culoare de scriere/tond, font, directie de scnere, dtmenstune caracter, §ablon de umplere a figurilor etc.).

Vom mai sublinia ca.la detinirea funqiHor membru, n~me1eaces~o~a e~t~ prefixat de numele clasel urrnat de operator~l "::". Aceasta constructie l~dl~ faptul cii este yorba de functia membr~ a unet clase ?nu~e. 0 astfel de masur~ de prevedere este necesara deoarece III cadrul un~1 program putem avea mal, multe clase care poseda funclii membru cu acelasi ?ume. I?~ asemenea, poate exista 0 funclie membru avind acelasi nume ca §l 0 functte ne~e~bru. ".

in fine, vom prezenta fi§ierul MAIN_SET.CPP, care prezmta demon-

strativcalitalileclasCiSEITINGS.

i include <conio. h> ' ',' .' i include "sttng cl.dat" / /headercedescriedasaSE1!INq-S;

- / /prezenta lui este necesara III caz

29

/lca se doreste foJosirea de obiecte Iide acest tip

void main() {

int gd,gm;

detectgraph(&gd,&gm); 4:nitgraph(&gd,&gm,"\\tcpp\\bgi")i outtextxy(50,lOO,"HELLO I");

getch() i cleardevice()i

settings my_settings; my_settings.get_settings();

settextstyle(GOTHIC_FONT,HORIZ DIR,4):

outtextxy(lOO,lOO,"HELLO 1"); - j

settextstyle(SMALL_FONT,VERT DIR,7); outtextxy(500,lOO,"HELLO !")~

my_settings.put settingS()i

getch ( ) ; ! - ,

cleardevice ( ) ;

o'u:ttext.xy (100 ,100, "HELLO 1");

getch() i

c Lo ae qr aph ( ) ; }

2.6. Constructorl ~i destructorl

Sa revenil_111a analogia intre 0 clasii oarecare §i tipul de date "int", In cele expuse a~uncl, aI_? preci~t? ser!e?e asem~nar.i. D~asta data ne vom pune 0 problema~ de alta natura: cine §Icmd aloca cei.dcioctet] (in general acesta e.ste~ n~mar.ul ~e ~ytes pe care se reprezinta un intreg) neccsari memorarf flecar~1 va~I~~II~ m~regl? Raspunsul nu ne intereseaza din punct de vedere al corectlt~dml!, .Cl pnn pnsm~ s~g~s~~vita1~i sale; D~ aceea ne putem inchipui foarte bine ca In momcntul mUlnlf1i unci dcclaratii de genul:

int i;

30

exista ofunctiespeciala "intO"care rezerva numarulde octeti necesari.In cazul in care se intllneste 0 forma de genul:

int j==9;

se va apela "int(9)", care, pe linga alocarea octetilor necesari, Ii mai §i initializeaza cu valoarea 9.

Un mecanism absolut similar trebuie sa existe §i pentru eliberarea memoriei respective in momentul in care se paraseste domeniul de existenta al variabilclor, Daca celor doua functii le yom da numele generice de "construetor" §i respectiv "destructor", putem spune ea am definit alte doua concepte esentiale ale programarii obiectuale.

De rcmarcat ea 0 clasa poatc avea mai multi constructori, De exemplu, chiar in analogia noastra eu numerele lntregi, avem de-a face eu doi construetori. Unul nu are nici un parametru (rezerva doar spatiu), pc dud al doilea are un parametru cc constitute valoarea iniliala a variabilei, De notat ca, daca o clasa este prevazuta cu constructori, la fiecare declarare a unui obiect de

, ac~st tipvaavea locapelul unuiadintreconstructori, Totacum trebuicsubliniat ca INTOTDEAUNA constructorul va purta numele clasei in cauza. La fel §i destructorii, alit ca numele respectiv va fi precedat de un caracter "- ",

In continuare, vom da un exernplu de clasa avind alit constructor, cit §i destructor.

1/ - clasa STACK irnplernenteaza 0 stiva ale carei clemente sint / / siruri de caractere

II -stiva este simulata printr-o lista simplu inlantuita

# include <iostream.h>
# include <process.h>
# include <string.h>
# include <stdio.h> class stack {

struct stiva {

char- info[20J ~ struct _stiva *nextj

} *head; / / pointer la inceputul stivei de stringuri

public:

a t.ac kj vo i.d] ; -stack(void) ;

/ / CONSTRUCTOR !l! / I DESTRUcrOR!ll

31

void pop (void) ; void push(char *); void print_stack();

} ;

II Descarca un element de pe stiva (elimina primul element din lista) void stack::pop(void)

{

struct stiva *a; if (headl=NuLL) { a=head; head=head->next; delete at

II Inittalizeaza lista ca Iiind vida inline stack::stack(void) {

head==NuLL; }

}

else cout«"Nu se poate executa \"pop\" dintr-o stiva gO,a1a I";

II Distrugcstiva (Iistasimplu lnlantuita) stack::-stack(void)

{

}

II Pune un element pe stiva (adauga la inceputul listei un nou II element)

void stack::push(char *info) {

struct stiva *a; II var, tampon if (headl=NuLL)

do

{

struot stiva*a=new struct _stiva; if (a===NULL)

{

a=head->next; delete head; head::::a;

cout«"Memorie insuficienta in STACK::PUsH\n"i exit( 1);

}

while (al=NULL); },

} strcpy(a->info,info); a->next=head;

he ad=aj

}

II Tiparestestiva

void stack::print_stack(void) {

struct _stiva *a; II var, tampon a= head;

if(headt=NULL) {

cout«"\n STIVA CONTINE :\n\n"; do { .

cout«a->info«"\n"; a=a-c-next s .

void main(void) {

stack my_stack;

my stack. push I " Aceasta If) ; my-stack. push ( "este" ) ; my-stack.push("stiva")~

- . '.

}

while (al=NULL); . cout«"\n";

my_stack.print_stack( )',f my stack.pop(-.); my=stack.print_stack();

}

else cout«"\n STIVA ESTE VIDA I";

my _ st,ack. J?Op ( ) ; my stack.pop(); my:stack.pop·() i

}

32

33

my stack.print stack(); my-stack. push (7."Sirsi t") ; my:stack.print_stack();

}

in urma executiei programului, pe ecran se vor tipari urrnaroarele date:

STIV A CONTINE: .

stiva este Aceasta

STIV A CONTINE:

este Aceasta

Nu se poate executa "pop" dintr-o stiva goala ! STIV A ESTE VIDA!

STlVA CONTINE:

Sfirsit

La implcmentarea constructorului s-a putut vedea urmatoarea constructie:

inline stack::stack(void) {

head=NULL;

}

Cuvintul cheie "inline" este Iolosit pentru a indica faptul cit functia definita astfel va fi expandata in toate locurile unde este apelata. Altfel spus, e Yorba de 0 modalirate moderns ~i eleganta de a defini macrouri-Iuncpe. Prefixarea cu "in line" aresens doar pentru functiile foartescurte, la careeste mai costisitor (in timpi de executie) mecanlsmul de apel al procedurii decit executa rea corpului propriu-zis al accsteia. Un astfel de caz este §i eel de fala: corpul functiei nu conjine decit 0 simpla initializare. De remarcat §i lipsa oricarei spccificari de tip returnat de catre constructor.

Procedura "stack.: - stack" este destructorul stivei, in momentul in care domeniul de existcnta al variabilei este depasit, se va apela aceasta functie in scopul eliberarii memoriei alocate dinamic,

Ar rnai fi de analizat urrnatoarea structura:

34

void stack::print_stack(void) {

struct stiva *a; Ilva~tampon a= head;

J/ •.•

}

in care functia membru "printstack" opcreaza cu variabila privata nhead".ln caz ca avem mai multe stive S1, S2 ~i S3, pentru fiecare din apelurile

Sl.print stack();

s2.print-stack();

S3.print:stack();

a "head" trebuie sa alba valoarea specifici stivei in cauza. Va veti intreba, pe buna dreptate, cum se realizeaza acest lucru, R~spunsul este foarte si~plu: la fiecare ape I de functie mernbru se maitransmite un p.arame~m supl!mentar (invizibil utilizatorului), ~i anume un pointer. la o~JCctul_l? cauza, Aces~ pointer sc numeste "this", ~i poate fi utilizat ~I de ~atre utilizator Cel.~al simplu exemplu de utilizarc a lui "this" poate fi urmarit in exernplul de maijos:

void stack::print_stack(void) {

struct stiva *a; II va~tampon

a== this->head; 1/ pentru referirca variabilei membru se uti/ I Iizeaza operatorul "-> "; in acest exemplu / I folosirea lui "this" este redundanta

1/ }

2.7. "Redeflnlrea" operatorllor

Termenul consacrat pentru redefinirea operatorilor este eel de "operator overloading". in cele ce urrneaza, vom analiza rnodul prin care se .perrnite schimbarea sensului (redefinirea) operatorilor limbajului C. Utilizarea unci astfel de facilitajti duce la scrierea unor programe deosebit de inteligibile. Dupa cum am mai ararat, faptul cit se pot scrie intrucpuni de gcnul

complex a/b,c,d/e;

e=(a+b)*(c-d) ;

in locul greoaielor complex a,b,c,d,e;

35

e=inmulteste{aduna(a,b),scade(c,d»; este un avantaj care nu mai necesita alte comentarii.

lnatnte de a trece la prezentarea exemplului propriu-zis, vom mai face 0 observatie interesanta. Putem defini 0 clasa "MA TRICE" care implementeaza operatii cu matrici (adunari, scaderi, inmultiri, transpuse, inverse etc.). Daca faccm acest lucru in mod "cinstit" c foarte bine, Daca insa dorim sa objlnem programe greu de descifrat, putem denumi operatiile complet aleator. 0- peratorul "+' va efectua inmultirile, "_" inversa, iar "*" adunarea. Dar atentie: desi acest lucru ducc la scrierea unor programe nelizibile, insus! programatorul are §ansc mari sa nu-sl mai poata ocoli proprile-i capcane.

In continuare vom analiza redefinirea operatorului de adunare de catre 0 clasa "complex".

Exemplui

~ include <iostream.h>

class complex {

private:

float real,imaginar;

public:

complex(int r=O,int i=l) { real=r;imaginar=i;}

cpmplex& operator+(complex&);

void afiseaza_complex(void);

} ;

II creaza un nou numar complex care va fi initializat cu suma II partilor reale, respectiv tmaglnare

complex& complex::operator+(complex& 52)

{

complex& t= 1< new complex (real+s2.real,imaginar+

, 132. imaginar) ;

II dupa cum am mai spus, in cadrul unci metode este permisa II accesarea tuturor membrilor clasei; din accst motiv se pot II face si accesari de genul "s2.real"

return·t;

}

II Tipareste partile reale si imaginare ale unui numar complex

void complex::afiseaza_complex(void)

{

cput«"\ncomplex : "«real«"+i1<"«imaginar;

}

36

void main(void) {

complex a(lO,ll),b(l,l); complex& c=a+b;

a.afiseaza complex()i b.afiseaza-complex(); c.afiseaza=complex();

}

Dupa execujia programului, pc ecran se vor afi§a mesajele:

Complex: 10+i*11 Complex: 1 +i*l Complex: 11 +i*12

Analizind corpul operatorului redefinit "+", remarcam cii el efectueaza adunar~a a doua numere complexe, Sin! insumate succesiv partile reale, respecnv lrnaginare. Sa facem §i citeva sublinieri:

- Pen tru a pu tea fl returnat, rezultatului i se aloca dinamic memorie (daca s-ar aloca static, obiectul ar fi distrus la parasirea functiei),

- Apelul operatorului • +. poate avea doua forme echivalente: complex a,b,c;

a=b+c; b.operator+(c);

Se remarca atlt comoditatea, cit §i forta sugestiei primei exprimari. - La crearea dinamlca a obiectului "complex" se va efectua §i apelul constructorului (cu parametri actuali specificaji in lista).

2.8. FRIENDS

Exact ca in viala, §i in programare "prietenul la nevoie se cunoaste".

Dar des pre ce este verba? Uneori, se face simlita nevoia ca, in afara functiilor membru, §i alte functii sa poata accesa varabilele private ale unei clase. Acest !u~ru se_poa_te re~li~ prin. mecani~mul declararii respectivelor functi! drept

friends (pricteni) at clasei respective. Astfel de declaratii de functii sevor face intotdeauna in cadrul declararii clasei in cauza, In urma unel declaratii de acest gen, "prietenii" au dreptul sa acceseze.datele private ale clasei respective. Facilitatea ca 0 functie sa fie "prietena" cu mai multe clase deschide un orizont foarte larg acestei ldel, lata §i un exemplu care rezolva exact aceeasi problema a adunarii numerelor complexe, folosind de asta data 0 functie

37

"friend".

* include <iostream.h>

class complex

{ .private:

float real,imaginar;

public:

complex(int r;O,int i-I) { real=r;imaginar=i;} friend,complex& operator+(complex&,complex&)i void afiseaza_complex(void);

} ;

complex& operator+(complex& sl,complex& s2) {

complex& t=*new complex(sUreal+s2.real,

s 1 •. imaginar+s2 . imaginar) ;

return t;

. }

void complex::afiseaza_complex(void)

{

cout«"\nComplex : "«real«"+i*"«imaginar;

}

void main(void) {

complex a(lO,ll),b(l,l); complex& c=a+b;

a.afiseaza_complex()i b.afiseaza_complex()i c.afiseaza_complex()i

}

Dupa execujia programului, pc ecran se va afisa:

Complex: lO+i*ll Complex: 1 +i*l Complex: 11 +i*12

In programul de mai sus, "prietenul" nu e 0 functie oarecare, ~i chiar un operator redefinit, Ca §i in sectiunea "2.7", este vorba tot de un operator binar. Diferenta falii. de sectiunea precedenta consta in faptul ca, in acel

38

exemplu, binaritatea operatorului era oarccum mascara de catre parametrul ascuns "this", pe cind acum ea este cum nu se poatc mai evidenta.

Ca ultim sfat, nu uitati cealalta cunoscuta zicala romaneasca: "Fereste-rna doamne de prieteni, ea de dusmani rna Ieresc eu §i singur!", Este cum nu se poate mai adevarat, mai ales ca funcjiile "friend" crcaza 0 bre§a considerabila in conceptul de incapsulare,

2.9. Clase derivate

Un concept important al programarii OOP este eel de MO~TENIRE. La baza lui sta un principiu Ioarte simplu: avind 0 clasa oarecare "B" §i dorind sa obtinem 0 alta clasa "0", asemanatoare cu prima, putem sa declarant pe "0" ca fiind DERIV AT A din "B". In acest mod, "0" va mosteni atit variabilele cit §i functiile mcmbru ale lui "B", pe Iinga care i se pot adauga §i altele noi. Prin definitie, "B" se va numi CLASA DE BAZA, iar "D"va purta numele de CLASADERIVATA.

Sa luam spre.exemplu clasa "stack", care implementeaza o stiva dcstringuri.

Modelarea se face cu ajutorul unor liste simplu Inlantuite, Practic, excmplul din seqtunea "2.6" este copiat identic, atit ca nu intr-un fisier, ci in doua,

Primul dintre ele se numeste "stack.h" ~i conjine declararea clasei: class stack {

protected :

struct stiva {

char- info[20]; struct _stiva *next;

} = he ad j / / capul stivei de stringuri

pUblic:

s t ackrvo.i.dj ; -stack(void) ;

void pop(void)j void push(char *);

void print_stack(void);

} ;

Unica schimbare aparuta in aceasta zona a programului este prefixarea declararii lui "head" cu "protected" in loc de "private". Scopul acestei modificari sc va vedea in curind.

Cel de al doilea Iisier va purta numele "stack.cpp" ~i va arata astfel:

39

# include <iostream.h>
# include <process.h>
# include <string.h>
# include <stdio.h>
• include "stack.h" stack::stack(void) { head=NULL;

}

stack::-stack(void) { struct _stiva *ai

II var, tampon

if(headl=NULL) do

{

a=head->next; delete head; head=a;

}

while (a I =NULL) i

}

void stack::print_stack(void) {

struct stiva *a;

II var, tampon

a'" head;

if (headl=NULL) {

cout.c-cv \n STIVA CONTINE : \n" ; do {

cout«a->iqfo«"\n"i a=a->next;

}

while (a 1 =NULL) ; cout«"\n";

}

else cout«"\n STIVA E VIDA I";

}

void stack::pop(void) {

40

struct _stiva *ai

if (head I =NULL) { a=head; head=head->next; delete a;

}

else cout«"Nu se poate executa \"pop\" dintr-o stiva goala I";

}

void stack::push(char *info) {

struct stiva *a=new struct _stiva;

if (Ia) {

cout«"Memorie insuficienta in STACK::pusH\n";

exit(l);

} strcpy(a->info,info)i a->next=head;

head=a;

}

Aceasta rescriere a exemplului anterior este necesara deoarece vom specifica un nou tip de sUva, care sa permita pc linga operatiile prezentate anterior §i operatia de INDE~E. Prin indexare se va intelege accesarea directa a elementului "i" al stivei. In cazul in care pestiva nu exists "i" clemente, se va da un mesa] de avertizare,

Daca am lucra in TURBO C neobiectual, fie am cauta vechiul fi§ier §i am incerca sa-i aducem modificarile de rigoare, fie am res erie pur §i simplu totul, Din cite vom vedea, limbajele obiectuale permit abordari rnult mai elegante §i mai eficiente.

Vom incepe prin a erea un nou fisier, pe nume "idx_stk.h" : # include "stack.h"

class i _stack;public stack { int max;

public:

i stack(void); void push(char *); void pop (void);

41

} ;

In noua clasa "i_stack", care mosteneste cia sa "stack". Ce lnseamna mostenirea in cazul nostru? Pe tinga structura lui "stack", vom mai beneficia de 0 variabila privata "max" (care va pastra numarul de ~lemente'de p~ stiva) ~i de un nou operator "nil (care va realiza accesarea indexata astivei). In plus, clasa "i_stack" va redefini procedurile "pop" §i "push", §i va avea un nou constructor, "i_stack". Prefixul "public" existent in fala lui "stack" specifica modul in care se mostenesc datele din clasa de baza "stack"; to!i membrii vor ramine in continuare "public" sau "protected", dupa cum au fost declarati in clasa de baza. Daca "public" ar f lipsit, informatiile publice §i protejate ale clasei de baza deveneau private in cea derivata.

Tn acest moment este timpul sa facem 0 precizare. Toate informatiile declarate "privlltp" in cadrul unei clase vor exista §i)n clasa derivata, dar nu vor fi accesibile, Intr-un fel, acest lucru este normal. In caz contrar ar insemna ca insa§i ideea de data privata - accesibila dear functiilor membru ale c1asei respective - este compromisa.

Unica diferenja intre rnembrii "private" §i "protected" consta in aceea ca ultimii vor Ii accesibili ~i in eventualele clase derivate.

Sa analizam in continuare fisierul "idxstk.cpp", in care sint implementate noile proceduri ale lui "i_ stack".

# include <iostream.h>

# include "idx stk.h"

void i_stack::push{char *elem) {

max++ ; II s-a mai adaugat un element pe stiva

stack: s puah t e Lemj j II apelulvechiului'push"

r

}

void i_stack::pop(void)

{

max--i

stack: :pop(); }

/ I s-a eliminat un element de pe stiva / / apelul vechiului "pop"

i stack::i_stack(void)

{

/ / nu mai trebuie facuta atribuirea "head == NULL;" deoarece / / inainte de a se executa constructorul clasei derivate, se va / / apela IMPLICIT constructorul clasei de baza; acesta va

42

/ / efectua atribuirca respectiva max=O i

}

struct stiva* i_stack::operator[] (int index) {

if (index<max) {

struct stiva *temp=headi / / variabilatcmporara 1/ folositq la parcurgerea II stivei

for(int i=O;i<index;i++) temp=temp->next; return tempi

}

else {

cout«"Indexul "«index«" depaseste dimensiunea indexului maxim al stivei ( " «(max-l)«" j"i

return 0;

} }

Trebuie sa subliniem ca noile proceduri "push" §i "pop" le apeleaza pe cele vechi de indata ce §i-au indeplinit obligajiile suplimentare. Modul in care se face apelul (prefixarea cu "s~ack::") estc obligatoriu dad se doreste evitarea unei recursivita]i infinite. In lipsa prefixului "stack::", compilatorul nu are de unde sa stie ca e verba de metoda clasei de baza, §i va efectua un nou apel al metodei clasei derivate. Cum conditie de oprire nu exista, apelurile recursive vor continua la infinit.

In final, sa studiem ultimul Iisier ce face parte din "PROJECT", §i anume "main.cpp":

# include <iostream.h>

• include "idx stk.h"

void main(void) {

i_stack my_stack;

my_stack.push ("ACeasta"); my stack.push("este"); my=stack.push( "stiva");

cout«"\n\n\n"«my stack[O]->info«"\n"« my_stack[1]->info«"\n"«my_stack(2)->info«"\n"i

43

my_stack.print_stack();

my stack.pop(); my=stack.print_stack();

cout«"\n"«my_stack[O]->info«"\n"« my_stack(1]->info«"\n"«my_stack[2]->info«"\n";

II exprimarea "my_stack[2] " va da nastere unui avertisment de

II indice prea mare ..

my_stack.pop() ; my_stack.pop() : my_stack.pop();

my stack.print stack(); my-stack. push (T.'Sfirsit") ; my=stack.print_stack();

}

Procedura "main" nu are alt seep decit sa utilizeze noul tip de sliva in mai muite situajii. Modul in care are loc executia programului reiese din analiza mesajelor afisate pe ecran:

stiva

este Aceasta

I

STIVA CONTINE:

stiva estc Aceasta

STIV A CONTINE:

este Aceasta

Indexul Z depaseste dimensiunea indexului maxim al stivei ( 1 ) este

Aceasta

Nu se poate executa "pop" dintr-o stiva goala !

44

I I

I

I I

STIVA E VIDA! STIVA CONTINE:

Sfirsit

Poate vi soar parea normal eli avertizarea privitoare la depastrea de index sa fie tiparita DUP.Aafi§area celor douaelemente alestivei, Lucrurile nu stau a§a deoarece tiparirea tuturorvalorilor dintr-o Inlanpiire de operatori l-c <" nu are loc decit in urma evaluarii fiecarel expresii, Cum .incercarea de evaluare a lui "my_staek[2]->info" va provoca tiparirea mesajului de avertizare, atentionarea va fi afi§ata INAINTEA valorilor de pe stiva.

2.10. Polimorflsm

Exemplul dat in sectiunea precedents a evidential un lucru interesant, §i anume ea atlt clasa de baza ( "stack" ) cit §i cea derivate ( "i_stack" ) yoseda doua metode (functi! membru) avind acelasi nume: "push" §i "pop", In plus, fiecare dintre ele va fi apelata DOAR de obiectele de tipul corespunzatcr. Marcie avantaj rezida in faptul eli redeclararea unci metode nu duce la pierderea posibilWi\ii de a 0 accesa pe cea iniliaia. Teate aceste proprietati reunite poarta numele de polimorfism,

o alta problema direct legata de polimorfism va reieti din exemplul de mai jos, Fisierul care ll contine se va numi "poli.cpp" §i va Inlocui in PROJECful din sectiunea "2.9" fi§ierul "main.cpp".

I include <iostream.h>

I include "idx stk.h"

void ma i.n t ) {

stack

stackl; stack2;

i stack

stack .,.

my_stack[2];

my_stack[O]=&stackl; •

my_stack I 1] =&stack2; II un pointer la ° clasa de baza B poate II pastra pointeri lactase derivate din B

II incarcarea primei stive

my stack [ 0] -opush ( .. Ac:easta" ) ; my-stack[O]->push("este 0"); my=stack[O]->push("stiva simpla");

45

/ / Incarcarea celei de a doua stive; de notat obligativitatea

/ / operatorului Weast", daca se doreste apelul metodei "push" / I a clasei derivate

«i stack *) my stack[l])->push("Aceasta")i «i-stack *) my-stack[l])->push("este 0"); «(::stack *) my=stack[l])->pushC"stiva indexata");

cout«"Al doilea element al stivei indexate : .. «(* «i_stack *) my_stack[l}»[l]->info;

cout«"\n\nPrima stiva :"; my stack[O]->print stack()i cout«"A doua stiva :"i my_stack[l]->print_stack()i

}

Dupa executia programului pc ecran vor fi afisate urmatoarele date:

Al doilea element al stivei indexate : este 0

Prima stiva :

STIVA CONTINE:

stiva simpla este o Aceasta

A doua stiva :

STIV A CONTINE:

stiva indexata este o Aceasta

o prima concluzie trasa .in urma studierii acestui nou exemplu: intotdeauna un pointer la 0 clasa de baza poate pastra un pointer la 0 clasa derivata din ea. Alit ca, dad! nu pastram intr-o maniera oarecare informatiile referitoare la tipul pointer-ului, nu vom putea opera corect eu metodele disponibile. Din cite se vede, atit in cazul in care se folosesc metode redefinitc, cit §i in eel al metodelor noi aparute in cadrul tipulul derivat, este nevoie ea asupra pointer-ului sa se aplice operatorul.tcast", care sa-l trans forme in pointer la entitatea reala, In schimb functiile ramase neschimbate in cadrul ambelor clase -Ia noi "print_stack" - pot fi utilizate fara "casting". Mai exista 0

46

categoric speciala de metode - numite VIRTUALE - care sint capabile sa deosebeasca poinrerii la clasa derivata de cei la clasa de baza, Gratic acestei proprietati, ele nu vor necesita efectuarea unui "casting", nici macar in cazul apelarii unor metode redefinite in clasele derivate. Dar desprc aceste rnetode virtuale vom vorbi in capitolul4.

Iata-ne la sfirsitul pariii introductive. Pin a acum, am tacut 0 sumara trecere in revista a notlunilor §i conceptelor de baza ale limbajului C++. Ea ne-a perm is sa avem 0 vedere de ansarnblu a ceca ce inseamna de fapt acest limbaj, In capitolele ce urmeaza, vom analiza fiecare tehnica §i concept in parte, pornind de la 0 abordare mai gcnerala ~i ajungind sa cunoastem cele mai ascunse secrete.

3. CLASE

3.1. Introducere

Numele purtat de una din versiunile anterioare limbajului C+ + a fost HC with Classes", Acest nume se datora nouHilii introduse: conceptul de "clasa". Pe baza claselor s-au pututdczvolta, maiapoi, toate nciletehnici de lucru. Fara clase, mostenirea ~i polimorfismul nu ar fi fost decit simple speculatii teoretice,

Conceptul de class este novator deoarece permite implemenrarea unui mecanism de definire ~i utilizare a noilor tipuri de date, descrise de catre utilizator, Acest lucru se face de 0 maniera absolut identica ell cea a tipurilor de date predefinite,

in plus, clasele permit separarea detaliilor de implementare (mecanismul intern) de cele absolut necesare pentru utilizarea noului tip de date. 0 excelenta analogie s-ar putea face cu modul de constructie a unui UNIT in TURBO PASCAL: utilizatorul nu are acces declt la functiile ~i procedurile ce apar in cadrul sectiunii INTERFACE. Cele incluse doar in IMPLEMEN· TATION nu pot fi utilizate de catre programator, Ele pot fi apelate numai de catre functiile din sectiunea INTERFACE, in cadrul "eorpurilor" lor. 0 alta cornparatie sugestiva ar fi cea cu bibliotecile din TURBO C. Programatorul poate apcla orice functie de biblioteca dad ii cunoaste prototipul ~i efectul. El nu are insa acces la codul sursa al respectlvelor functii.

3.2. Declararea claselor

Vom in cepe prin a da 0 serie de trei exemple, din care va retest un prim avantaj al utilizarii c1aselor.

Sa presupunem ca scriem un program care lucreaza cu corp uri in trei dimensiuni. Daca vom mernora coordonatcle unui punet in trei variabile X, Y ,Z §i avem 0 procedura ce realizeaza 0 translatie a punctului respectiv eu TX,TY,TZ putem scrie:

Exemplul I

int X,Y,Z;

void translateaza(int TX,int TY,int TZ) {

X+=TXi

48

Y+=TY; Z+=TZ; }

DC§i 0 astfel de exprimare este corecta, ea nu reflecta nicidecum existenja unei legaturi Intre cele trei variabile X,Y,Z Ntmic nu da de lnteles ci ele au o trasatura comuns: descriu pozitia unui punct in spatiu. Mult mai sugestiva ar fi 0 exprimare de genul:

Exemplul2

struct coordonate_3D{ int X/Y,Z~

};

void translateaza(etruct coordonate 3D punct, int Tx,int Ty,int~Z)

{

punct.X+=TX; punct.Y+=TY; punct.Z+==TZ;

}

. Cu toate ci este mutt mai sugestiv, §i acest mod de abordare are un

inconvenient. E yorba de faptul ca legatura existenta tntre STRUCTIJRA definita §i FUNCfIA de translatie nu este pusa in evidenta in nici un mod. Cu alte cuvinte, se simte nevoia unui formalism care sa specifice dar ideea ca functia "translateaza" nu poate fi apelata DECIT pentru a opera asupra unor puncte, Aceasta problema este usor de remediat in C+ + prin folosirea claselor.

Exemplul S

class coordonate 3D{ / / declararea clasei "eoordonate_3D"

private : -

int X, Y, Z; / I declarare variabile membru public :

void translateaza(int TX,int TY,

int TZ); / / declarare functie membru

};

/ I definire functie membru

void coordonate 3D::translateaza(int Tx,int TY,

- int TZ)

{

X+=TX; Y+=TY:

49

/ / declararea a trel obiecte de tipul"coordonate _3D"

class coordonate_3D obiectl,obiect2,obiect3;

Pentru a putea continua expunerea, e necesar sa dam citeva definiti].

o CLASA - este un tip de date definit de catre utilizator, dar care

se comports lntocmal ca un tip predefinit,

o INSTANTIERE - a unei clase "CLASA" (sau un OBIEC1) este 0 variabila declarata ca Iiind de tipul"CLASA".

o VARIABILA MEMBRU

o METODA

- este 0 variabila declarata in cadrul unei clase.

- este 0 functie declarata (sau definita) in cadrul unei clase, §i care poate accesa toate variabilele declarate in cadrul clasei, Metodele se mal numesc §i "FUNCTIl MEMBRU".

Prin MEMBRII unei clase vom lntelege totalitatea metodelor §i variabilelor membru ale clasei,

In exemplul de mai sus avem:

a. trei obiecte de tipul tcoordonate _3D" (obiectl,obiect2,obiect3 );

b. trei variabile membru (X, Y,Z);

c. 0 singura functie membru ("translateaza").

Sa incercarn, deci, sa vedem in cc consra 0 clasa. 0 sintaxa mult simplificata arata astfel:

class Nume clasa {

[ [ private Lista membri 1 [ [public :] Lista-membri-2 } ;

Cuvintul cheie "class" indicii faptul ca urmeaza descrierea unei clase avind numele "Numeclasa" (in exemplul de mai sus: "coordonate 3D"). Ca 0 precizare suplimentara, numele clasei poate fi orice identificator, unic in cadrul domeniului deexistenta respectiv.

Descrierea propriu-zisa a clasei consta in cele doua liste de membri, prefixate eventual de cuvintele cheie "private" §i "public".

In caz ca listele de rnembri exista, elc pot contine orice tipuri de declaratii §i definilii de date §i functii. r;>upa cum am spus deja, toate aceste dale §i

50

funcjii vor constitui MEMBRII clasei.

Cuvintcle chcie "private" ~i "public" impart clasa in douii scctiuni: un~ privata §i una publica. Sa vcdem care cstc rolul ficcarcia dintre cele doua

sectiuni:

public:

mcmbrf aparjinind acestei sectiuni pot fi accesati de catre utilizator din orice punct al domeniului de existenta al respcc-

tivei clase.

membrii apartinind acestei sectiuni (atit date cI.t §i func.Iii) nu pot fi accesa]i DECIT de catre mel?del~ cla~el. respectl~e.

Utlizatorul clasei nu va avea acces la ei decit pnn intermediul

metodelorpublicc.

Reluind analogia cu conceptul de.IUNIT" din Pascal, se pot echivala sectiunile "PRIVATE" cu "IMPLEMENTATION" §i "PUBLIC" cu "INTERFACE".

private:

in acest moment, dispuncm de suficiente informajii pentm a ne da seama ca, in C+ +, incapsularea are la baza doua idei:

3. cu ajutorul clasclor sc vor lcga structurilc de date §i Iunctiilc destinate manipulariilor;

b. sectiunile private §i publice Iac posibila separarca mecanismului intern de interfa la cu u tiliza torul.

o sin taxa mai rafinata a claselor - §i deci mai apropiata de cea reala - ar fi urmatoarea:

specificator_clasa Nume_clasa { [ [private ] Lista_membri_l [ [public : J Lista_membri_2 };

Din cite se vede, unica diferen!a ra1a de definitia prccedenta consta in tnlocuirea lui "class" eu "Specificator _ clasa". Acesta din urrna poale fi until dintre urrnatoarele cuvinte cheie: "class", "struct" sau "union". Ultimele doua descriu structuri de dale avind aceleasi propricta]! ca §i in limbajul C neobiectual, cu doua modificari:

a. li se pot atasa functii membru;

b. pot fi compuse din doua scctiuni (una publica ~i una privata).

DIFERENTA intre "class" §i "struct" consta in Iaptul ca, daca lipsesc specificatorii "public" ~i "private", toti mcmbrii lui "class" vor fi IMPLICIT privati, iar cei ai lui "struct" publici. ;;i in cazul specifi~t~rului "union", implicit, mernbrii vor fi tot de tip public. Aceste observatii lamurcsc~ multe semne de intrebare §i dau 0 imagine clara a ceca ce inseamna 0 clasa, Cele

51

spuse pina acum vor fi subliniate de secventa de mal jos:

struct i1 { int a;

double b; float c (void) ;

I I a.b.cr) sin! IMPLICIT publici } ;

union i2 {

int ai

double b; float c (void);

I la,b,cO sint IMPLICIT publici } ;

class i3 {

int a;

double b; float c (void) ;

I I a.b.ct) sint IMPLICIT privati } ;

3.3. Membrii unel clase, "Scope access operator"

Este cazul sa revcnim asupra celor trei exemple prezentate in sectiunea precedents, Daca aruncam 0 privire de ansamblu, observam ca, in primul ext;,mpIu, procedura "translateaza" opereaza asupra variabilelor globale X, Y, Z. In al doilea exemplu, ea va opera asupra membrilor X, Y, Zai unci structuri "coordonate Bl)", transmisa ca parametru, Aceste GOUa cazuri erau foarte simple, apelul procedurii avind loc in maniera obisnuita a Cvului. Ultimul exemplu aduce un aspect nou: metoda "translateaza" nu poate "actiona" independent, ci doar ca functie apelata de un obiect de tipul "coordonate _3D".

Din cite se vede, nu mai este necesar ca, in cadrul functiei, sa sc precizeze "identitatea" lui X, Y §i Z. Ele vor fi considerate implicit variabile membru ale unui obiect "coordonate Bl)", specificat explicit in momentul apelului, Desi maniera exacta in care se face acest lucru 0 vom vedea in sectiunea urrnatoare, dam totusi un exemplu care sa clarifice problema. Apelul metodei "translateaza" se poate face astfel:

obiectl.translateaza(2,3,4);

In acest mod se precizeaza ca obiectul "obiectl" apeleaza metoda "translateaza" cu parametrii actuali 2,3 §i 4. Efectul acestui apel consta in adunarea

52

valorilor 2, 3 §i 4 la variabilele membru corespunzatoare ale obiectului "obiectl".

Sa revenim la definirea lui "translateaza":

void coordonate 3D::translateaza(int Tx,int TY, int TZ)

{ II }

Seremarca imediat "prefixarea" numelui proceduriicu numeleclasei urmat de "::". Acest din urma simbol poarta numele englezesc de "scope resolution operator" sau "scope access operator", §i este utilizat in operatii de modificare adomeniului devizibilitate implicit. Unexemplu tipicde utlllzarea sa ar fi urmatorul:

Exemplul I int i;

void fl(void) {

int i; I I ...

i++; II ... }

II se va incrementa "j"-ullocal

void f2(void) {

int ii I I .•.

: : i++;

I I se va incrementa "i"-ul global

II operatorul "i:" a facilitat accesarea variabilei cu numele "in II aflataintr-undomeniudevizibilitatemaiinalt.si.

II in consecinta, mascara de catre variabila locala "i"

II ...

}

Privind exemplele de mai sus, trebuie sa mai fa~m 0 subliniere: in stlnga lui "r;" nu poate fi declt un nume de clasa sau nimic. In al doilea caz, variabila prefixata trebuie sa fie globala!

Vom continua cu un sfat al lui Bjarne Stroustrup ([SmO], p.15l): de cite ori este UiU, se recomanda folosirea unor nume de functii din bibliotecile

53.

standard ("read", "write", "open" ~.a.m.d.) drept nume de Iunctii utilizator, urmind ca primele sa poaia fi rcutilizate in urmatoarea maniera:

I I definirea functiei utilizator

int clasa::fopen(char * pNume",,"fisier.dat" char -It pAtr="rt")

{ II

I I apelul functiei de biblioteca

::fopen(pNume,pAtr); I I .. ,

}

In continuare, prezentam un exemplu in careseva utiliza operatorul'xcope access" ("scope resolution") pentru a opera in cadrul unei metode alit cu variabile mernbru, cit §i ell variabile globale.

Exemplul2

int Xl=lO i

struct

clasa

{

int Xl; void fctie(void); } cl,c2;

void _clasa_::fctie(void) {

Xl++i

: : Xl++;

}

I I se va incrementa variabila membru I I. se va incrementa variabila globala

Daca avem 0 clasa "CLASA" §i un mernbru al ei numit "MEMBRU", 0 expresie de genul "CLASA::MEMBRU" se va numi "qualified name". Scopul lui "qualified name" este de a perrnite accesarea oricarui membru, chiar daca este ascuns de 0 alta declaratie de variabila.

Exemplul J

class ex { in_t contor; public :

void fct (void) ; } ob l j

void ex::fct(void) {

54

II variabila locala diferita de membrul"contor" int contor;

ex: :contor=IOO; I I variabila-membru for(contor=O;contor<10;contor++)

I /"contor"-ullui "for" este eel local

e~: : contor+=2; 1/ variabila-rnembrul!l

}

Ineheind discutia legata de "scope access 'operator", vom sublinla ca prezenta numelui clasei in fata Iunctiei mcmbru este absolut necesara. In lipsa ei nu s-ar putea face dlstinctia intre divcrsele metode purtind nume idcnticc §i apartinind unor clase difertte.

o aWi motivatie a prezentei numelui clasci cste legata devariabilelecu care se opereaza in cadrul functiei-membru. Daca analizam ultimul cxemplu rcferitor la clasa "coordonate_3D", se rernarca faptul ca se opereaza direct cu variabilele mernbru X, Y, Z, lara a-mal da indicatii suplimentarc privind identitatea lor reala. Toatevariabilele utilizate in cadrul unci functii-rnembru a clasei "CLASA" ~i nedeclarate in cadrul ei, se presupun implicit a fi membri ai lui "CLASA". Daca, in urma compilarii, presupunerca se adevereste, totul va fi in regula. In caz contrar se incearca identificarea lor ca variabile globale. Esecul acestei identiflcari va provoea erorile de compilare corespunzatoare ..

Distingerea variabilelor membru ale obiectelor din aceeasi clasa se realizeaza gratie unui mecanism invizibil programatorului, EI are la baza ideea de a furniza oricarei functil-mernbru un parametru ascuns, care sa reprezinte un pointer la obiectul asupra caruia s-a aplicat metoda. Astfel, in Exemplul3 al sectlunii precedente, pc Hnga cei trei parametrii declarati TX, TY, TZ, Ia fiecare apel de genul:

pI.translateaza(lO,-2,7)i

se mai transmire si un pointer la obiectul lip 1" (care apeleaza metoda). In acest fel expresiile de tipul

X+=TX;

devin

this->X+=TX;

unde, prin "this", in C+ +, se desemneaza pointer-ul transmis ca parametru ascuns. Aces! cuvint cheie "this" este un nume de variabila (Iocala Iunctiilor membru), accesibila programatorului, continind un pointer la obiectul care a apelat metoda.

De fapt, rolul lui "this" nu se margineste la identificarca membrilor unui obiect, Dupa cum vom vedea in capitolele ce urmcaza, exista situatii de

55

impl:mentare in care utilizarea lui "this" este salvatoare. Sa consideram ur~ato~ul e~emplu:.intentionamsa Cream 0 clasa "Usta dublu in1anluita". Daca donm sa asiguram inserarea unui element in caput listei clasa poate arata

astfel: ' .

Exemplul4

# define NULL 0

class lista { int numari

lista '" urm,'" prec; static lista'" capLista;

public :

void init(int i) { nurnar=i;

}

/ / inserare in capul listei dublu inlantuite

void insert(void){ urm==capLista; prec=NULL;

if(capLista) capLista->prec=this'

.. '

capLl.sta=thl.s;

} } ;

/ / p~ntru a se putea face initializarea de mai jos este necesara declararea / / IUI"~p_lista"cafiind"STATIC";pentrualteamanuntedespremembrii / / statict se va studia sectiunea "3.6"

lista'" lista::capLista=NULL;

void main(void) {

1ista 11; lista 12; lista 13;

l1.init(I); l2.init(2)i 13.init(3) ;

/ / obiectul "11" apeleaza functia membru "init"

11 • insert ( ) i 12.insert()i

1/ obiectul Tl" va fi inserat in lista / / obiectul "12" va fi inserat in lista

56

13. insert ( ) ; / I obiectul "13" va fi inserat in Iista }

Alte exemple importante de utilizare a lui "this" vom regasi in sectiunea . "3.10.".

Cu obiectele apartinind unei clase, se poate opera lntocmai ca §i cu orice tipuri de date predefinite. In secventa de mal [os se vor da citeva exemple de

astfel de utilizari,

Exemplul5

coordonate 3D p l , p2, p3; 1/ declararea a 3 obiecte coordonate- 3D "'ptr; II declararea unui pointer la obiect

coor'donace 3D sir [20] ; II declararea unuisirde20deobiecte coordonate-3D'" forrneaza(int,int,int)i coordonate-3D '" aduna(coordonate_3D,coordonate_3D);

pl=p2; II atribuire corecta

coordonate 3D=p3; II EROARE: operatie fara sens

ptr=s ir; - 1/ corect ; "ptr" va pointa la ·sir[O) "

Din acest ultim exemplu reiese 0 noua facilitate C+ +. in urma definirii

unei clase "B", nu mai este necesara prezenja specificatorului clasei ("class", "struct" sau "union") la tiecare declarare a unui obiect de aeest tip. Va fi suficienta specificarea numelui clasei, Exista insa §i 0 exceptie de la aceasta

regula:

Exemplul6

class litere { II

} ;

class nurnere {

/I };

II A TENTIE: aceasta functie NU ESTE metoda a c1asei cu acelasi n ume int nurnere(void)

{

/I }

void rnain(void) {

57

li tere 11,12; I I CORECT: prezenta specificatorului "class" I I este optionala

numere n 1; I I EROARE: clasa "numere" se suprapune ca domeniu

I I domeniu de existenta cu functia ne-mernbru "numerc" class numere pl,p2; II CORECT

int numar=numere ( ) ; I I CORECT: este un apel de functie! }

Cu alte cuvinte, domeniul de existenta al unui nume de class Incepe in momentul declararil ei §i se terrnina la sfir§itul blocului in care a fost declarata. Unicul caz mai ciudat este eel prezentat in Exemplul o, cind numele clasei cst~ identic Cll eel al unui alt identificator (in cazul nostru, un nume de functie). In astfcl de situatii, e necesara prefixarea lui "numcre" cu "class", pentru a se putea face deosebirea lntre clasa §i functia cu acelasi nume,

Urmarind exemplul de mai jos, vom descoperi 0 aha situatie speciala.

Exemptul7

struct nr complex; I I DECLARATIE INCOMPLETA nr complex * adunare(nr complex nl,nr_comlex n2);

11--:.. -

I I DECLARA TIE COMPLETA

struct nr_complex

{

I I ..• } ;

Din cite se vede,_pri!! DECLARATIE INCOMPLETA se inlelege declararea unci clase FARA a i se specifica lista de membri. 0 DECLARA'fIE COMPLET A a unci clase va contine §i lista mernbrilor ei. Atenjie: la declararea completa a unci clase trebuie specifica]i TOTI rnembrii ei, Adaugarile ulteriore nu stnt posibile. Declararile incomplete sint deosebit de utile cind sc doreste refcrirea unor clase inca nedefinite.

~i inca 0 observatie, care, chiar daca pare banala, este foartc importanta.

Toate metodcle unei clase vor avea exact acelasi domeniu de existenta ("scope") ca §i clasa careia Ii apartin.

3.4. Accesarea mernbrllor unei clase.Polnterl Ia metode

In lirnbajul C, utilizarea operatorilor nu are nici un sens daca nu li se asociaza ~i variabile asupra carora sa opereze,

58

int i=O,j=l,k==O;

i ++ ; I I corect

__ j; I I coreet

++ ( __ ) i I I EROARE: nu are nici un sens, I I operatorii nu au operand

Anal "C+ + la Iiecare apel de functie membru tre~ui~ _:>pccifieat

obiect:I~~~;~apele;za.Deasemenea,la~iecareutilizarcdcvanahllamcmhru trebuie speciftcat obiectul caruia li aparlme.

Exemplull

struct cll { int a,b;

void init(int,int)i void inc (void) ;

} obiect_l,* pObiect_2;

void cll::init(int al,int bl) {

a=ali b=bl; }

void cll::inc(void) {

a++; b++j

}

void main(void)

; I obiectului pointat de lip _ obicct_2" i se va aloca dinarnic memorie pObiect_2=new cll;

I I membrii lui "obiect_]" vor fi initializati obiect 1.init(lO,11);

/ I membrii lui ,,* p _ obiect , 2" vor fi tnltializati pObiect_2->init(lO,11);

I I mcmbrii lui "obiect_l"vor fi incrcmentati Obiect_l.inc()i

59

II seva actualiza valoarea membrului "a" obiectului "obiect I"

obiect l.a=pobiect 2->a+Obiect l.b- -

- - -'

}

Din cite se vede, modul de accesare a variabilelor membru publico se face folosind opcratorii "." sau "->", dupa cum obiectul de care apartin este desemnat prin nume sau printr-un pointer.

Variabilele mcmbru private nu pot fi accesate decit in cadrul metodelor clasei respective. Exemplul de mai jos va lamuri modul in care se realizeaza acest lucru.

Exemplul Z

class exernplu {

int nurnar; public :

void init{int nr=O);

void aduna(exernplu& obiect,exemplu* pObiect);

} ;

II Metoda "init" initializeaza membrul "numarvlavaloarea II parametrului "nr"

void eXernplu::init(int nr) {

nurnar=nr;

}

II Metoda "aduna" actualizeaza valoarea membrului "numar" void exernplu::aduna(exernplu& obiect, exernplu* pobiect)

{

nurnar=numar+Obiect.nurnar+pObiect->numar;

II Membrul tnumar" al obiectului ce a apelat metoda poate fi accesat diII recr, fara nici 0 prefixare. In cadrul metodei mai potfi accesate si variII abile membru private ale altor obiecte de acelasi tip,cu unica menti- 1/ une ca trebuie precizat si obiectul in cauza. Specificarea acestuia se va II face printr-un nume sau printr-un pointer.

}

void main(void) {

exemplu obiectl,obiect2,obiect3;

obiectl.init(2);

~

i

60

obiect2.init(3}; obiect3.init(4);

obiect 1. numar=6; 1/ EROARE: "numar" este privat , deci

/ / inaccesibil in functii ne-membru

obiect 1. aduna (obiect2 r &obiect3 ); / / "obtecn" apeleaza / / metoda ad una

}

Sin taxa apelului unci functii membru poate avea doua forme clasice:

nume obiect.nume metoda(lista parametri);

- - -

nume pointer_la_obiect->nume_rnetoda -(lista_parametri)i

o a treia metoda de apelarea Iunctiilor membru estecea Iolosind "pointerii la metode", Este yorba de un concept relativ nou (chiar §i in scurta istorie a C+ + ). care nu aparuse in prima versiune AT&T a limbajulu~ Compilatorul firmei BORLAND ia insa in considerare lmbunatatirea adusa de Stroustrup in versiunile urmatoare. Iata-ne, deci, fala-n fala cu 0 metoda eleganta §i eficace de manipulate a functiilor membru cu ajutorul pointerilor. Daca precizam ea definirea unui "pointer la 0 tunctie membru a clasei B" se face prin "B::*", exemplul de mai jos nu mai are nevoie de explicatii,

Exemplul J

class clasa { int contor; public:

void init(int nr=O) { contor=nr; }

int increment(void) { return contor++; }

} ;

II tipul"pointerLaMetoda" este un pointer la 0 metoda a clasei 1/ "clasa" , metoda ce nu are parametri si care returneaza Hint"

typedef int (clasa::* pointerLaMetoda) (void);

void main(void.) {

clasa cl,* pcl=&cl;

pointerLaMetoda pM=clasa::inc~ement;

c 1. ini t ( 1 ) ; pcl->init(2);

61

/ / operarorii It. *" si "-> *" sint utilizati la / / manipularea polntenlor la metode

int i=(cl.*pM)(); i=(pcI->*pM) ();

}

Noul tip de data ("pointer la metoda") desemneaza adresa unci Iunctii membru "M". Reamintim ca metodele unei clase au IMPLICIT un parametru tip "pointer la obicct". EI se va transmite "ascuns", ~i va reprezenta acel "this" existent in cadrul oricarei metode. Din acest motiv, parametrul in cauza nu va mai apare in lista de parametri ai functiei desernnate de pointer.

Acum, avind inforrnatii suficiente despre conceptul de clasa, sa vedem cum ar trebui sa arate 0 clasa idealii, in concepjia lui Bjarnc Stroustrup ([STRO], p.142):

a. sa aiba un set restrins de met ode publice;

b. sa poata fi considerata drept 0 "curie neagra", care in locde borne de intrare §i iesire are un set de metode pub lice;

c. sa permita ruodificarea structurii datelor fiira a schimba §i interfata (asazisele borne de intrare §i iesire din cutia neagra),

I

3.5. Alte arnanunte privind declararea claselor.

Functll "lnllne"

Sa incercam acum sa sintetlzarn cele spusc pina in acest moment printrun exemplu.

Exemplul l

# include <iostream.h>

class nr_rational { private :

int numitor , numarator;

void tip (void) { cout«"\nNumitor="«numitor«",numarator=" «numarator;

}

public double val;

void init(int,int); } i

void nr_rational::init(int a,int b) {

62

numarator=a;

numi tor= . b i =0 ? b: 1 i I I se evita impartirile cu 0

val=numarator/numitor;

tip ( ) ;

}

void main(void)

{

nr rational rl;

r 1 . ini t ( I7 I 3 ) i / I corect

r L. tip ( ) ~ / I EROARE: functie inacccsibila utilizatorului

r t . val ++; / I corect

r I. numi tor--; / I EROA,RE: variabila rncmbru privata

}

Din acest exernplu reies dar proprietatile sectiunilor private §i publicc~ Utilitatea lor este deosebita in cazul in care 0 lucrare mare cste abordata modular fiecaremodul fiind tratatdecatreoalta persoana. Astfel.odata pusa la punct'structura interfetclor (sectiunilc "public"), fiea:rc programa.tor i~i poatea1cgesau schimba forma de reprezentare a datelor din cadrul ~nel.c.lase, tara ca acest lucru sa-l afectezc pe ccilalti (deoarece clasele vor fi utilizate DOAR prin intermediul scctiunilor publicecorespunzatoare).

Specificaroril "private" §i "public" pot sa apara in cadrul declararii claselor de oricite ori, §i in orice ordine. Domeniul de existenta al acestor cuvinte cheie este din momentul tnrilnirii lor, pina in eel al aparitiei urmatorului specificator sau al rerminarii declaratiei clasei,

Exemplu Z

class ex {

int iii/implicit "i" este privat public :

int ji

float j Ii / I explicit "j" si "jl" sint publice

private :

int ki

float k l ; II explicit"k"si"kl"sintprivatc

public

int 1 i 1/ explicit "I" este public

} ;

Exista 0 deosebire fundarnentala intre "cfass" ~i "struct" pe de 0 parte, §i "union" pe de alta parte. Instantierile primelor doua ~o_nlin i~ fiecar~ moment valorile TUTUROR variabllelor din Iista de mernbri. In schimb, obiectele de

63

I

tip "union'vor pastra la un moment dat valoarea UNUISINGUR membru din lista specificata.

Aceia dintre dumneavoastra care au folosit intensiv'uniont-uri In limbajul "CO §tiu prea bine ce greoi era mecanismul de setare §i accesare a unui "union". Era nevoie de un "struct" care sa contina un "union" §i un cimp de identificare a valorii actualmente pastrate in acesta. In c+ +, totul poate fi inlocuit foarte elegant de catre 0 clasa in care:

a. mai multiconstructori (vezi secjiunea "3.7.") seocupa de inijfalizarea diverselor cimpuri;

b. un set de functii de accesarc a diverselor tipuri de valori va verifica in prealabil corectitudinea operatiei.

Ar putea sa vi se para ca totul este ca §i in C, atit ca, in loc sa fie prezentat oarecu~ dezordonat, este incapsulat intr-o clasa. Nu aveti dreptate decit partial. In C+ + beneficiern in plus deo mtertaja eleganta (metodele publice), care ascunde toate detaliile §i eforturile de implementare ale mecanismului (ceca ce nu e pujin lucru).

In acest moment incepem sa avem 0 imagine destul de clara despre ceca ce inseamna unificarea conceptelor de date §i cod. Intr-un program C++ scris corect (DIN PUNCfDE VEDEREOBIECfUAL), nu au voie sa existe date tara cod sau invers. Totul va fi Incapsulat in cadrul claselor. Utilizarea variabilelor globale sau a functiilor ne-membru va insemna, in cele mai multe din cazuri, fie 0 concepjie grc§ita a programulul, fie 0 utilizare ineftcienta a programarii orientate object. Conform manualului de utilizare al TURBO C+ + ([BORI], p.130), "datele directioneaza fluxul codului, iar acesta din urma modeleaza forma §i valoarea datelor",

~i inca 0 observatie: deoarece folosirea variabilelor membru publice

. distruge ideea de ascundere a datelor, se spune cii este hine sa se evite utilizarea lor. Cei care se supun-acestci restricjii implcmenteaza un set de metode care se ocupa doar cu setarea §i accesarea valorii datelor private.

Exemplul J

class ex { int i; public :

int get i(void) { return i; } void set=i(int a) { i=a;}

I

t !

/

l !

} ;

Putem face 0 scurta paranteza, in care sa aratam ca unul din dezavantajele invocate de adversarii Csului era slaba lui tipizare. Acest lucru a dus la aparitia ANSI C, care inlatura slabiciunea versiunii clasice, Totusi, marea diferenta

64

intre ANSI C §i C+ + consta in aceea ca primul p.u este capabil decit sa semnalizeze eventualele incompatibilitaji ce apar. In C+ + insa, gratie metodelor de setare §i accesare a variabilclor private, aces! gen de inconveniente nu numai ca este lndepartat, dar putern spune chiar ca este tratat de 0 maniera calitativ superioara,

o alta chestiune esre legata de problema incuibaririi claselor. Daca urmarim exemplul de mai [os, vom vedea ca astfel de incuibariri sint permise in limbajul C++.

Exemplul d

class dreptunghi { struct punct { int x,y;

void init punct(int xl=O,int yl=O); } stinga_sus~dreapta_josi

public :

void init dreptunghi(int xl,int yl,int x2,

- int y2)

{

stinga sus.init punct(xl,yl); dreapta_jos.init_punct(x2,y2);

}

} ;

void punct::init_punct(int xl,int yl)

{

x=x Lj y=yl; }

punct p l r II CORECT: tipul "punct" este "vizibil" in tot fisierul dreptunghi dreptunghi_l;

void main(void)

{ dreptunghi_1.init_dreptunghi(lO,11,l2,l3);

}

Este nccesara 0 precizare deoscbit de importanta. Desi ar parea normal ca tipul de data "punct" sa fie inaccesibil utilizatorului (fiind definit in sectiunea "private"),.situatia e de fapt cu totul alta. Regula spune astfel: definirea in cadrul unei clase B a unui nou tip de date D nu va n influentata de sectiunea (publica sau privata) in care are loc aceasta operatic.

65

Se mai observa ca, in consccinta, la definirea mctodei de initializare a clasel "punct", preflXar~a numelui functiei sc face doar eu numele clasei "punct", nu §l cu cel al clasei "dreptunghi",

Exista ~nsa 0 excep!i~ ce merita amintitli. In cazul declararii unui tip enumerat III cadrul unci clase, acesta va respecta regulile de accesare a s~Cli~n~i in care a fost?ec!~rat: Cu alte cuvmte, o declarare a unui tip enumerat facuta III cadrul secuunu private nu va putea fi utilizata .DECIT de catre mcmbrii respectivei clase.

Exemplul S

class ex{
enum tl { a r b } ;
public:
enum t2 { c , d } i
} ; void main(void) { int variabila;

variabila=ex: :a; 1/ EROARE:"ex::a"nueaccesibil

var Lab i.La=c i // EROARE: "c" nu poate fi identificat decit // daca e prefixat cu "ex.:"

variabila=ex: :Ci /1 COREer }

Exe~plul de mai s~s subliniaza ca valorile publico de tip enumerat nu vor putea [1 utilizatc DECIT prefixate cu numele clasci in care au fost declarate.

Titlul acestei scctiuni continea §i termenul de "Iunctii inline", Sa vedern despre.ce este yorba:

Exemplulji

• include <iostream.h>

class coordonate_3D{

int X, Y, Z i // variabile mcrnbru ale clasei

I I Prin DEFINIREA rnetodei in interiorul declararii clasei / I functia membru devine IMPLICIT "inline" j!! '

void translateaza(int TX,int TY,int TZ)

{

X+=TXi

66

Y+=TYi Z+=TZi

}

void tipareste (void) i 1/ DECLARAREA unci metode } ;

/ I Prcfixarca DEFINITIEJ mctodei cu cuvintul chcie "inline" este

/ I echivalenta eu definlrea funct iei mernbru in cadrul declaratiei clasei inline void coordonate_3D::tipareste(void)

{

cout«"\n\tX="«x«"\ty="«y«"\tz="«z«"\n";

}

Care cste noutatea introdusa de accst cxcmptuv In loc sa ne multumim sa declarant proccdura "translateaza" in cadrul structurii "coordonale_3D", 0 vom §i dcfini tot in cadrul clasei,

Care sint consccintclc Iaptului ca proccdura "translatcaza" va Ii de tip "inline"? Acest lucru inseamna ca, practic, ea nu va fi apelata, ci expandata la locul fiecarui apel. Dupa cum am rnai spus si in capitolul Z, este verba de un mecanism elegant de definite a unor macrouri de lipuJ"# define".

Definirca unci Iunctii in cadrul unci clase se mai numestc dcclarare "inline" implicita. Exista §i 0 declararc explicita, care se face ca in cazul metodei "tiparestc": functia mernbru e doar dcclarata in cadrul clasci, iar in locul in care e definita va fi prefixata de cuvintul cheie "inline", De subliniat di oricc functie, indifcrcnt ca este sau nu metoda, poale Ii declarata inlinc. Alit eji procedurile cc nu sint membru a unci clasc nu pot fi dcclaraic "in line" DECrT EXPLICIT.

Ohservatii:

a. Expandarca functiilor "inline" va fi fficula de 3§U manicra incit dorncniul de existenja £11 variabilclor sa flU fie nicidccum afcctat.

b. Fala de cazul in care se foloscsc directivcle "#dcfinc", la ut ilizarea mecanismului "inline" se va executa §i verificarea tipului parametrilor!

e. Nu intotdcauna este posibil ca o Iunctic sa fie "inline". Dcclararea implicit.i sau explicita a accstei optiuni estc ncccsara, dar nu §i suficicnia pcntru ca ea sa dcvina realitatc, Estc exact situatia dcclariirii variabilelor de tip "register": daca nu mai sin! registri! disponibili, aceasra opjiune raminc pur tcoretica.

d. ESTE INTERZISA FOLOSIREA in cadrul functiilor "inlinc" a STRUC TURILOR REPETITIVE DE CONTROL ("for", "while", "elo while").

Va vet! intreba, poate, la ce sint bune functiile "inline"? Ele sint dcoscbit de eficiente in cazurile in care transmitcrca pararnetrilor prin stiva (operatic

67

lerna) este mai costisitoare (in limp de executie) decit cfcctuarea operatiilor din corpul functici.

Facem 0 parantcza pcntru a va aminti di utilizatorii limbajului de asamblare care ezila intre folosirca macro-urilor §i cea a procedurilor, sint ajutali in luarca dcciziilor de 0 formula matematica destul de simpla, Astfel, daca notam cu "X" numarul de instructiuni din rutina §i cu "C" numarul de apelari a ei, putem calcula

F = X * C / ( X + 4 * C ).

Decizia optima seva lua astfel: rutina seva expanda ca macrou sau nu, dupa cum F este mai mic sau mai mare decit 1.

Analog, firma BORLAND face cltcva recomandari, in functic de care sa se decida utilizarea sau nu a mecanismului "inline". As 1 fel, 0 functie merita sa fie "inline" daca vor Ii indcplinitc simultan conditiile:

a. corpul proccdurii trcbuie sa contina eel mult trei instruqiuni;

b. numarul parametrilor §i dimcnsiunea lor trebuie sa fie semnificativa,

Ultima recomandare a BORLAND-ului ([BORl}, p.140) suna cam asa: utilizarea functiilor "inline" este eficienta doar in cazul in care codul generat de cornpilator pcntru corpul functiei este mai scurt dedi celnecesar apelului ei. Avind in vedere ca 0 astfcl de comparare e imposibil de estimat printr-o simpla privire, ni se sugereaza sa incercam ambele solutii §i sa 0 alegem pe cea optima. Dccizia optima nu se poate Iua decit in urma analizarii codului

obiect, . .

3.6. Membrii statlcl

Spre deosebire de "auto", "register" §i "extern", cuvintul cheie "static" poate fi utilizat in prefixarea membrilor unci clase, Evident ca, odata declarat "static", membrul in cauza va avea proprietati diferite de mernbrii obisnuit! (ne-statici). Aceste proprictaji apar datorita faptului ca membrii statici NU AP ARTJN UNUI OBJECT ANUME,cisinl COMlJ,NI tuturor Instantierilor unei clase.

Exemplul l

class exemplu { int i;

// ...

public:

1/ variabila membru "contor" este declarata "public" doar in scop didactic I I (pentru a fi accesibila in functia "main")

68

static int contor;

// variabila membru statica

void inc (void) { i++i }

void inc_contor(vold) { cont?r++; } void init(void) { i=Oi }

I I ...

static void functie (exemplu *); 1/ metoda statica } obl,ob2,ob3;

int exemplu:: contor=O; 1/ _initializarea variabilei statice

void exemplu:: func t i.e (exemplu *ptrEX) {

i +=76; I I EROARE: nu se cunoaste obiectul ee-l "poseda" pe "i" ptrEx->i ++; I I COREel: cstcspecificat "proprietarul" lui "i"

corrt o r-t+j // COItECT: "contor" este 0 variabila membru II statica.ea OU apartine unui obiect anume

}

void main(void) {

o~l.init() i ob2 . ini t ( ) ; ob3 . ini t ( ) ;

ob I , inc (); /1 obl.i=l; ob2 . inc (); // 002.i= 1; ob3.inc(); /lob3.i=1;

ob I , functie (&obl) ; II CORECT

exemplu: : functie (&ob2 ); I I CORECT

functie ( ) i I I INCORECT (in afara de cazul ca exista 0 // procedura ne-membru cu acest nume)

obl.inc_contor(); ob2.inc_contor()i ob3.inc_contor();

// contor= 1; // contor» 2; / I contor= 3;

exemp Lu e : contor+=6 i I I contor=9; }

69

Din exemplul de mai sus se poate vedea modul de declarare al membrilor statici, precum ~i eel de initializare a variabilelor membru l'tatice. Pentru Iiecare instanjierc a c1asci "exemplu" (ob 1, ob2,ob3) vaexista CITE Ovariabila "i" DISTINcrA ~i 0 SINGURA variabila "'contor", COMUNA tuturor celor trei obiecte. Comportarea variabilei "con tor" poate Il eel mai bine in[eleasa analizind functia "main",

Pentru 0 variabila-membru statica se rezerva 0 singura zona de memoriei, COMUNA TUTUROR INSTANTIERILOR CLASEI. Toate operatiile ce se efectueaza asupra ei au drept operand acea UNICA variabila, COMUNAtuturorinstan!ierilor.

In ultima linie a lui "main" sc vede un alt mod de accesare a variabilelormembru statice. Deoarece ele nu apartin unui obiect anume, pot fi prefixate doar de numele clasei, urmat de "scope access operator" ("::"). Deci, prefixarea variabilei membru statice cu "ob1." , "oh2." sau "ob.I," nu este necesara. Ea poate fi }nsa folosita pentru a indica apartenenja lui "con tor" la clasa "exemplu". Intotdeauna, indiferent de pretixare, va fi incrcmentata acceasi unica va- riabila "contor".

Din cite se vede in exemplu, apelul metodelor statice se face exact ca ~i accesarea variabilelor-rnembru sratice, Deoarece metodele stance nu slnt apelate de un obiect anume, nu li sc va transmite acel parametru ascuns "this". Deci, daca intr-o metoda statica dorim sa operam cu variabile-membru nestatice, vom fi obligaji sa furnizarn un parametru explicit de genu} obiect, pointer sau referinta la obiect,

o trasatura specifica variabilelor-membru stat ice apartinind unor clase declarate globale este aeeea ca TREBUIE inijializate in cadrul flsierului in care clasa a fost declarata.

Exemplul Z

class ex {

static float aj , / / DECLARARE public :

static float b; / / DECLARARE

} ;

float ex::a==10i float ex: :b=12;

/ / INITIALIZARE / I INITIALIZARE

In cazul in care domeniul de existen!a al clasei este local, initializarea variabilelor-membru statice nu mai este permisa.

To]i membrii statici sint DOAR DECLARATI in cadrul clasei, ei urmind a fi obligatoriu INITIALIZATI. In acest moment faccm o observatic

70

care line mai degraba de istoricul produsclor BORLAND decit de limbajul C+ +.In versiunileanterioare produsului BORLAND C+ + 2.0, initializarea variabilclor membru stance nu era obligatorie, ea Iacindu-se IMPLICIT cu valoarea O. La compilatoarele mai noi initializarea devine OBLIGATORIE, Iipsa ei fiind semnalizata ca 0 eroare de compilare.

Principala utilizarc a variabilelor-membru statice consta in eliminarea Cit mal multorvariabile globale ale unui program.

3.7. Constructorl

3.7.1. lnltlallzarea structurllor de date

Pentru a lntelege cit mai exact conceptul de "constructor" vom face 0 scurta paranteza, legata de un domeniu Ioarte bine conturat: initializarca datelor. Este bine cunoscut ca orice programator arc grija sa nu utilizeze variabile neinitializate. Legal de acest lucru, sa vedem cum se poate face 0 lnltializare in C.

Exemplul I

# include <string.h>

# define MAX 20

struct adresa {

char nUme[MAX],prenume[}ffiXj,strada[MAXj; int numar;

} adrl={"Caragiale",dlon Luca","pacientei",13};

void main(void) {

struct adresa adr2i

strcpy(adr2.nurne,"Matei"); strcpy(adr2.prenUrne,"BaSarab"); strcpy(adr2.strada,"Postei")i adr2.numar=21i

}

Din acest exemplu reics foartc clar doua moduri de a initializa structuri de date: fie in mornentul declararii lor (cazullui "adr I "), fie prin accesarca dirccta a divcrselor cimpuri ale variabilei ("adr2"). Ccl putin al doilea mod de lucru este deosebit de incomod, in sensul di 0 serie de instructiuni se cer repetatc de un numar deranjant de mare de ori, pentru a cfectua 0 aceeasi succesiune

71

de operatii. In plus, cxista riscul ca, din neatentie, in procesul de initializare sa fie ornise uncle cimpuri.

Sc poate invoca ~i 0 alta obiectic, rnult mai serioasa: clasele care au variabile membru private nu admit initiallzari prin lisle! Acest fapt este bine ilustrat de exemplul de mai jos.

Exemplul Z

struct 51 { int a;

char c [101 ; }el={10,"abcdef"};

II CORECT

struct s2 { private : int a; double b;

} e2={1,2}; II EROARE

class 83 { int ai char c;

} e3=={12, 'c'}; II EROARE

De aceea, este preferabil ca initializarile sa se execute intr-o procedura, a§a cum se vede in cxempul urmator:

Exemplul J

# include <5tring.h>

* define MAX 20

struct _adresa {

char nume[MAX],prenUme[MAX],strada[MAX]i int numar;

} adrl;

void init(struct adresa* pa,char* nume, char* prenume,char* strada,int nr)

{

strcpy(pa->nume,nume); strcpy(pa->prenume,prenume); strcpy(pa->strada,strada); pa->numar=nr;

}

72

void main(void) {

struct adresa adr2i

init(&adr1,"Ion","Marin","pesterii",11); init(&adr2,"Bordoc","Ecaterina","Plaiului",22)i }

Dar §i in acest caz exista 0 obiectie: nu ~xista nici 0 legatura implicita intre procedura "in it" §i structura "_adresa". Intr-o abordare obiectuala, am putea elimina inconvenientul declarind respect iva procedura ca fiind 0 metoda a clasei "_adresa". Cu toate acestea, s-ar face in continuare simtita lipsa unui mod elegant de initializare de genul:

int a=0,b=7;

float z=67.87;

Pentruaeliminaaecsteneajunsuri,afostnecesaraimplemcntareaunuinou

mecanism, §i anume eel al constructorilor. Sa incepem eu un exemplu, dupa care vom incerca 0 definite riguroasa a conceptului:

Exemplul4

• include <string.h>

# define MAX 20

class adresa {

char nume[MAX],prenume[MAX],strada[MAX]i int numar;

public :

I I CONSTRUCTOR!!!

_adresa{char *,char *,char *,int);

} ;

adresa::_adresa(char* numel="",char* prenumel="", char* stradal="",int nr=O)

{

strcpy(nume,numel); strcpy(prenume,prenurnel)i strcpy(strada,strada1)i

numar=nri

}

void main(void) {

73

adresa adrl(HBordocn,nEcaterina~);

adresa adr2; II daca se doreste apelul constructorului eu toti II parametrii luind valori implicite, se pot omite II chiar si parantezele

}

3.7.2. Proprietali speclflce constructorllor

In exempl ul anterior observam eli un constructor este 0 procedura care are acelasi nume ca §i clasa careia it apartine, Deoarece servesc Ja crearca ~i initializarea obiectelor, constructoril vor Ii apelatiori de cite ori sc creaza noi instantieri ale unci clase. Utilitatea constructorilor este evidenta eel putin sub doua aspecte:

a. constructorul asigura initializarea corccta a tuturor variabilelor mcmbru ale unui obiect;

b. constructorul of era 0 garanjie in plus di initializarea unui obiect se va efcctua 0 singura data.

Sii notam §i Iaptul di 0 clasa poate avea mai multi constructori. In principiu, acestia se declara, se definesc §i se utilizeaza intocmai ca orice metoda uzuala. Totusi, exista clteva sublinicri absolut necesare:

- Constructorf poarta numele clasei careia ii apartin.

- Constructorii nu pot returna valori, In plus, 0 conventie face ca nici la

declararca §i nici la definirca lor sa nu poata fi specificat "void" ca tip returnat.

- Adresa consrructorilor nu este acccsibila programatorului, Expresiile de gcnul "&X::XO" sint ilegale.

- Consrructorii sint apela]i IMPLICIT de cite ori cstc nevoie.

- In caz eii 0 clasa nu are nici un constructor declarat de catrc pro-

gramator, compilatorul va genera implicit unul. Acesta va fi public, fara nici un parametru, §i va avea 0 Iista vida de instructiuni.

- 0 clasa.al caret constructor a fost generat implicit nu poate Ii utilizata ca mernbru intr-un "union".

- In cadrul constructorilor se pot utiliza operatorii "new" §i "delete".

- Constructorii pot avea parametrii luind valori irnplicite,

NOTA: Teare aceste observatiisintetizatc mai sus sint valabile §i pentru destructori. Acestia din urma vor fi analiza]i in sectiunca urmatoare ("3.8.").

Aserneni Iunqiilor obisnuite, un constructor poare efectua orice fel de actiuni. Tot eu titlu de observatie arnintim di rnodalitatile de obtinere a unui

74

constructor "inline" sint [dentice eu cclc de la tuncplle-membru obisnuite.

Trebuie sa subliniem ca, la orice crcare dinarnica a unui obiect, compilatorul va apela IMPLICITconstrucloru! potrivit.

Cu riscul de a ne repeta, reamintim ca 0 clasa poate avea orici]l construetori. Acestia vor avea acelasi nume, dar VOf diferi prin tipul §i numarul pararnetrilor. Cu toate aces tea, indifcrcnt de modul in care se c~eaz[' l~n obiect, intotdeauna comptlatorul va apcla constructorul corespunzator (dill punct de vedcre al potriviril parametrilor).

Exemplul l

# include <string.h>

# define MAX 20

struct adresa { ~

char nume[MAX],prenume[MAX1,strada[MAX];

int numari

adresa(char *,char *,char *,int); -adresa( adresa&); }-adrl("IOn","Marin","pesterii",13);

adresa:: adresa(char* numel=H",char* prenumel=n", char* stradal;"",int nr=O)

{

strcpy(nume,numel)i strcpy(prenume,prenumel)i strcpy(strada,stradal)i numar=nri

}

adresa~:_adresa(_adresa& ar)

{

strcpy(nume,ar.nume)i strcpy(prenume,ar.prenUme)i strcpy(strada,ar.strada); numar=ar.numar;

}

void main(void)

{

adresa adr2("BOrdoc","Ecaterina")i adresa adr3=adrl; /1 scapcleazaconstructoru!

struct struct

/ / "_adresa(adresa&)"

adr3=adr2;

/ / se executa 0 copie bit cu bit a tuturor / / mcmbrilor clasei " adresa"

}

Acest cxcmplu iI reia pe precedentul, atit ca ii mai adauga un constructor.

Noul constructor cstc utilizat la initializarea unui obiect (in cazul nostru "adr3") cu 0 valoare de acclasi tip (rcspcctiv "adr I "). Practic, instructiunea:

struct _adresa adr3=adrl;

va Ii interpretata sub forma cchivalenui

struct _adresa adr3(adrl);

La cxecutarca instructiunii "adr3=adr2;" se va folosi un procedeu de copiere bit cu bit, care nu trcbuic confundat cu constructorul mai'sus amintit. Altfcl spus, de§i cele doua instructiunl par a se executa identic, in primul caz se va apela constructorul "adresa(adresa&)", pe cind in al doilea nu. Acest lucru poate fi Ioarte binc urrnarit cuajutorul Dcbugger-ului incorporat, prin Iolos irea tr asa r ii pas cl! pas. In primul caz se va intra in funcjia "_adresa::_ adrcsa(_ adresa& )", iar in al do ilea nu.

Este esenjiala retinerca unei resiricjli: un constructor nu poatc avea ca parametru 0 instanticrc a clasei respective. Acesta cste motivul pcntru care noul constructor adaugat de noi arc prototipul tI_adrda(_adresa&)" §i nu

"_adrcsa(_adrcsa)". .

De notat cii apclul constructorilor variabilelor globale se face lnainte de exccutia functie' "main". 0 astfel de observane este deosebit de lmportanta la efcctuarea unor operatii care nccesita anumite condijii initiale, De exemplu, sa prcsupunem ca in cadrul constructorului clasei "grafic'' avem 0 functie din biblioteca grafica. Daca modul grafic de lucru e initializat doar in "main", existenta unci varia bile globale de tip "grafic" va provoca 0 eroare de executie, Tot ca observatie, amintim caapelul constructorilor variabilelor globale VA PRECEDA chiar §i pc eel al Iunqiilor decimate a sc executa inainte de "main" (printr-o directiva de lip "#pragma").

3.1.3. Tlpurl de constructorl, Ambiguitc'i1i

Inexemplul de mal jos, vom declara doua tipuri de constructori: class X {

/ / .....

X (X& ) ; / / COpy CONSTRUCTOR: constructor de copiere

X (void) ; / / 'DEFAUef CONSTRUCTOR: constructor implicit

76

} ;

CO NSTR U croa UL IMPLI CIT ( "defau It cons tructor" ) poa te fi obtin u t in doua moduri:

a. rrin definirea de catre utilizator a unui constructor rara nici un pararnetru.

In acest caz corpul constructorului poate contine orice fel de instructiuni admise intr-un context dat (in sensul ca, de exemplu, daca e vorba de un constructor "inline", el nu poate contine instructiuni repetitive).

b. Prin genera rea sa IMPLICITAdecatrecompilatoL Un astfel de constructor va fi creat ori de cite ori programatorul declara 0 clasa care nu are nici un constructor. In acest caz corpul constructorului nu va con line nici 0 instruqiune,

Am crezut necesar sa insistam asupra constructorului implicit deoarece deseori au loc uncle confuzii, Cita vremc prin acest termen va fi desemnat un constructor fara nici un parametru, indifcrcnt ca a fo~t creat de programator orl generat de compilator, totul va fi in regula. In general, se greseste Intelegind prin CONSTRUCTOR IMPLICIT doar una din cele doua categorii amintite,

.

Un alt lucru deosebit de important este legat de Iaptul ca orice clasa trebuie

sa posede un constructor de copierel Pentru a creste flexibilitatea limbajului, compilatorulvageneraIMPLICITunastfeldeconstructorpentrufiecareclasa la care programatorul nu a facut-o explicit. Constructorul de copiere generat implicit (a nu se confunda cu constructorul implicit) va copia membru-cumembru toate variabilele argumentului in cele ale obiectului care apeleaza metoda. El va fi apelat in situatii de genul

x obiect2=obiectl~ sub forma echivalenta

x obiect2(obiectl);

Se recomanda 0 mare prudenta in imbinarea prezentei constructorului implicit cu cea a unor constructori eu parametrii luind valori implicite. In caz contrar, compilatorul va semnaliza crori datorate unor ambiguitati de genul celor din exemplul de mai jos.

J

Exemplul I

class nr {

int numari

public :

nr (void) ; nr(int=O); nr(int,int=O);

77

} ;

pUbiic:

complex(int r,int i) { p_r=ri p_i=i; }

nr: mr r vo i.d ) {

II

}

} ;

nr: :nr(int nn) {

II

}

Exemplul.I

class complex { double p r,P i;

public: --

complex(int r,int i)

p_r(r),p_i(i) { }

} ;

nr::nr(int nnl,int nn2) {

II

}

3.7.4.Conversii

In continuare, va fi studiata 0 situatie aparte, ES1C vorba de un hibrid intrc cazurile indicate in observatia de mai sus. Sa considerarn cxemplul care urmeaza,

void main() {

nr nJ; I I AMBIGUU: constructor implicit (fara parametrii) I I sau cu un paramctru luind valoare implicita?

Exemplul l

. # include <string.h>

# define MAX 20

nr n2 ( 1) i I I AMBIGUU; constructor eu un pararnetru sau cu II doi, din care unulluind valoare implicita?

struct persoana {

char numele[MAX],prenumele[MAX]; } i

nr n3 ( 1, 2 ) i I I corect

struct adresa {

char nUme[MAX] ,prenume[MAX] ,strada[MAX]; int numari

_adresa(persoana&);

} ;

}

Din acest exemplu, pc Iinga problema amhiguitii!ii, mai trcbuie retinut un aspect. Parametrii unci functii-rnembru pot fi dcclara]i ea Iulndvalori implicite fie la declararea metodei, tic la definirea ei. Nu se accepra insa specificarea acestui lucru in arnbele locuril

Revenind la problema initializarii obiectelor, vom face 0 noua observatic.

Instantierile claselor ce au constructori ne-implici]l pot Ii initlalizate in doua moduri:

- Cu constructori de copiere.

- Cu constructori apelap Cll 0 lista de parametrii. In acest caz exista doua

posibilitati echivalcnte de specilicarca actiunii de inljializare:

adresa::_adresa(persoana& p)

{

strcpy(nume,p.numele); strcpy(prenume,p.prenumele); strcpy(strada,"Ploiesti"li numar=5;

}

Exemplul2

class complex { double p_r,p_i;

void main(void) {

persoana persl={"Balota","Ion"};

78

79

_adresa adr2=persli

programului, Am recurs la aceasta metoda fiindca este deosebit de expresiva din punctul de vcdere al urmaririi actiunii constructorilor. Deoarece 0 sa mal folosim astfel de notajii §i in aile exemple, vom face 0 conventie: prin "linia 12" vom injelege linia ce arc etichcta 12, nu a 12-a linie a programuluil

Exemplul l

struct elem { int nr;

} ;

in accst program sevor crea doua instantieri ale clasei "obiect", pentru una dintre de alocindu-se dinamic memo ric. In ambele cazuri se va apela §i constructorul clasei respective.

In urma analizei exernplului de mai sus se impun citeva observatii:

a. in cadrul constructorului se poate aloca dinamic memorie (linia 4 §i 9);

b. constructorul poate fi apelat explicit in doua moduri (vezi liniile 2 §i 7);

c. memoria alocata pcntru obiectele create dinamic (vezi linia 2) se va elibera eu "delete" (linia 12).

}

Se observa ca, in cadrul Iunctici "main", se inccarca initializarea lui "adr2" cu ajutorul unui constructor de copierc, Alit ca dupa sernnul de atribuire nu urrneazaobiect de tipul"_ adresa", ci unul de tipul "pcrsoana". In astfelde cazuri nu sc poate recurgc la constructorul de copiere (deoarece TIPURILE NU COINCID), Din acest motiv se va tnccrca un apel de constructor cu lista de paramctri, la care primul parametru sa fie de tipul "persoana&", iar rcstul fie ca nu exista, fie ca iau valori implicite. Daca exista un astfel de constructor, eI va fi apelat, Aceasta opera lie poarta numele de "CONVERSIE UTILIZATOR" intre tipurile "pcrsoana" §i "_adrcsa",

Exemplul z

class complex { int p r,P i; public :

class clasa { int numari

struct elem * pElem; pUblic:

clasa( int);

} ;

II se presupune ca operatia de adunare intre 2 numere

II complexe este implementata ca metoda a clasei "complex"

clasa::clasa(int param=O) {

pElem=new elem; numar=pElem->nr=parami

}

complex(int i=O ,int j=l){ p _r=ii

.,<. p_i=j;

}

void main(void)

}

{

void functie(void)

clasa * pObiect; pObiect=new clasa(5); clasa obiect=clasa(9); delete pobiect;

}

{

complex a,b(1),c(2,3);

a=h+l ;

II se interprcteaza ell "a= b+ complexi l);"

}

lata deci inca un tip de conversiedctinita de utilizator: din intreg (tip predefinit) in complex (lip dcfinit de utilizator). Altc amanuntc dcspre convcrsii vor Ii prezcntate in capitolul S.

3.7.5. Apelul explicit al constructorllor

Inaintc de a analiza secventa de mai jos, se impune 0 observatie legata de coi.icntartile ce 0 insotesc. Ficcare comcntariu contine una sau doua etichete. Ace tc etichete sint numcre care indica ordinea de-parcurgere a instrucliunil~'r in cazul unci trasari pas cu pas cu ajutorul Debugger-ului, Altfel spus, este vo.ba chiar de ordinea in care calcnlatorul va executa instructlunile

80

II 3 si 8

I I 4 si 9 I I 5 si 10 1/ 6 si 11

II 1

1/ 2 // 7 1/ 12 /1 13

81

3.7.6. Reducerea numarulul de constructorl

Exemplul I

class ex {

/ / ...

public : ex(int,double);

Exista situajii in care 0 class poseda un numar excesiv de constructori. De cele rnai multe ori, aceste probleme pot fi evitate prin utilizarea [udicioasa a mecanismului parametrilor luind valori implicite.

Exemplul l

/ / VARJANTAI

/ / NUMAREXCESIVDECONSTRUCTORl!!!!

} ;

int f1(int a,double b,const int c) {

class ora_8xacta_1 {

int ora,minut,secunda; pub l.i.c s

ora exacta l(int o,int m,

- -int s){ora=oiminut=m;secunda=s;}

ora_exacta_l(int o,int m){ora=o;minut=m; secunda=O;}

ora exacta_l(int 0) {ora=ojminut=secunda=O;} ora_exacta_l(void) {ora=minut=secunda=Oi}

static ex ex1(2*c,2.3*c/5}i// CORECT:"c"econslant

static ex ex2 (2, b) i / / INCORECT: "b" e variabila

static ex ex3 (a, b) i / / INCORECT: "a" si lib" sint variabilc

}

} i

b. Fie un program in care e definita 0 functie "FI ". Sa presupuncm ca:

1. in Iunctia "Fl" este declarat un obiect-static "01 ";

2. in cursul unei executiia programului nuscva apela niciodata Iunctia "FI" (deoarece, sa ziccm, nu am select at niciodata una din opjiunilc mcniului principal).

Se pune atunci problema: va fi sau nu va fi apelat constructorul obiectului static declarat in "FI" ? Conform specialistilor firmei BORLAND, nu exista o regula clara care sa specifice acest lucru. Ceca ce se stie cu ccrtitudinc cste faptul dl, in caz de efectuare a apelului, acesta va avea loc DupA eel al constructorilor corespunzind variabilelor globale.

/ / VARIANTA2

class ora 8xacta 2 {

int ;ra,minut,secunda; public:

ora_exacta_2(int o=O,int m=O,int 9=0) { ora=o;

minut=m;

secunda=s;

3.7.8. Constructorli claselor incuibarite

}

In exernplul de mai jos vom gasi 0 situajic in care 0 clasa are ca membrii instantieri ale altor clase. 0 astfel de sit uatie de incuibarire trebuie tratata Cll atenjia cuvenita, deoarcce ini\ializii.rile nu sint permise in cadrul declararii claselor. Din acest motivexista () conventie care impuncapelul constructorilor obiectelor membru.

Sa consideram 0 clasa "CLASA" ce contine un obiect incuibarit dc tip "01", care, la rindul sau, are un mernbru "M" de lip pointer. Presupuncm ca In cadrul constructorului clasci "OI" sc aloca dinamic 0 zona de memorie la care sa pointczc "M". Daca in cadrul construcrorului clasei "CLASA" se incearca initializarea zonci la care pointeaza "M", §i constructorul lui "01" nu a Iost apelat,pointcr-ul "M" va avea 0 valoare aleatoare, iar initializarca va Ii ineficienta, In plus, prinaceasta inipalizare a unci zoneoarecare.s-arputeadistruge Informatii semnlficatlve din memoria calculatorului, Pentru a evita astfel de riscuri, limbajul C+ + prevede un mecanism extrern de simplu §i cficicnt,

} ;

void main() {

ora exacta 2 01,02(10),03(10,10),04(10,10,10);

}

3.7.7. Constructori ~i oblecte statlce

In cadrul acestei sectiuni trebuie Tacute doua observatii:

a. Este obligatoriu ca parametrii constructorilor corcspunzind unor obiecte statice sa fie EXPRESII CONST ANTE!!!

82

83

Inaintede a analiza exernplul de, mai jos,sa vedem in citeva cuvinte despre ce este yorba. Au fost declarate trci clase: una pastreaza coordonatele unui punct §i doua care definesc tipuri de veetori (definili prin cite doua puncte semnificative: origine §i virf). In continuare, vom urmari modul in care se vor apcla -construcrorii eorespunziitori celor doua puncte sernnificativc din

cadrul Iiecarui vector. .

Exemplul I

class punct {

. 1/ un punct este caracterizat de 0 abscisa si de 0 ordonata int X,Yi

public:

1/ cei doi constructori vor difcri doar prin tipul argumenteJor;

II ei vor seta valorile absciseisi ordonatei

punct(float a,float b)

{

x=ai y=b;

}

punct(int a=O,int b=O)

{

x=a; y=b;

}

} ;

class vectorl {

/ I un vector cste caracterizat de doua puncte: originea si virful

punet origine, virf;

II variabila cc va contoriza numarul de instantiert ale clasei "vector!" static int numar vectori;

public :

vectorl(float,float)i

vectorl(int,int,int,int);

} ;

class vector2 {

1/ originea si virful vectorului vor fi specificate de cci doi pointeri

II, ("pOrigine" si tip Virf')

punct * porigine, * pvirf; public :

84

vector2(int,int,int,int); }i

int vector 1: : numar _ vectori=O; /Iinitializareamembruiuista/ /tic privat 'numar vectori"

vector1::vectorl(float xl,float y1).

; origine(x1,yl)

1/ - "origine"este initializat prin apelul explicit 3.1 constructorului avind doi II parametrii "float";

II - "virf" este inltiallzat prin apelul implicit al constructorului avind dol / I parametril "int", ambii luind valoareaimplicita 0

{ .

numar _vector i ++; 1/ se incrementeaza numarul de instantieri a II clasei "vector l"

}

vectorl::ve<;:torl(int xl,int yl,int x2,int y2) : origine{xl,yl) I virf(x2,y2)

II ambele puncte sint initializate prin apelul explicit al constructorului II corespunzator

{

numar _vector i ++; / / se incrernenteaza numarul de instantieri a II clasei "vector l"

}

vector2::vector2(int xl,int yl,int x2,int y2) {

II se aloca rnemoric pcntru cele doua puncte si se apeleza constructorul / I corcspunzator

porigine=ne~ punct(xl,y1); pvirf=new punct(x2,y2)~

}

void main(void) {

vectorl vI l(10,10),vl 2(0,0,1,1); vector2 v2(16,16,17,19)i

}'

Orprimaobservatie ar fi legata de moqul de apelare 3' constructorului "puncr'. In cadrul clasei "vector2", apclul constructorului "punct" se va face IMP-LICIT in momentul in care se va ajunge la fiecare din cele doua "newt-uri. Deci, in acest, caz, totul decurge conform asteptarilor,

85

In cazul lui "vector l" situatia este cu totut alta. In cadrul construcrorului avind 4 parametri, se remarca modul de apelare al constructorilor pentru cei doi membri "punct". Forma este Ioarte asemanatoare cu cea a initializarii membrilor ce nu sint obiecte. Atit ca, in cazul claselor incuibarite care nu au nici constructori implicit! ~i nici constructori la care to!i parametrii pot lua valori implicite, este OBLIGATORIE accasta forma de initializare. Asa cum se vede ~i in cazul constructorului "vcctor l (float.float)", daca obiectul membru are un constructor la care toti parametrii pot lua valori implicite, aceasta forma de "initializare" nu mai este obligatorie, compilatorul fiind capabil sa decida apelul constructorului la care loti parametrii iau valori implicite, Analog s-ar petrece lucrurile ~i in cazul existentei unui constructor implicit.

Dupa ce am inteles ceca ce se intlmpla, sa-incercam sa dam 0 regula generals. In cazul in care intr-un program exista 0 clasa "CLS" care are ca mernbrii obiecte aleunoralte clase "CLSO", "CLSl", ... , pot aparea urmatoarele siiuatii:

a. Daca clasele incuibarite in cadrul lui "CLS" nu au constructori, nu va exista nic! 0 deosebire intre constructorii clasei "CLS" §i un constructor de clasa ce nu arc obiecte-membru incuibarite.

h. Dad clascle incuibarite nu au un constructor implicit ~i constructor la care tali parametrii pot lua valori implicite, este obligatorie.specificarea EXPLICITAa numelui fiecarui obicct urmat de lista parametrllor actuali.

c. Dad! clasele Incuibarite au un constructor implicit sau unul cu TOTI parametrii avind valori implicitc, se poate proceda fie ca in cazul "a." (situatie in care se vor apela IMPLICIT constructorii respectivi), fie di in cazul"b." (se vor specifica explicit doar paramctrii doriti), De exemplu:

vectorl::vect~rl(float xl,float yl)

: origine(xl,yl),virf(lO)

/ I. 0 •

unde virf este apelat cu un singur parametru.

Cu titlu de obscrvatie, subliniem eii I_rTOTDEAUNA apelul construetorilor obicctelor incuibarite se va face INAINTE de executa rea construetorului obiectului "cuib", Ceca ce nu se poate prcciza cu exactitate este ORDINEA de apelare a constructorilor obiectelor incuibarite. Astfel, nu se rccomanda scrierea unor sccven]c de genul

vectorl::~ectorl(int xl,int yl,int x2,int y2) : .origine(xl++,yl--) , virf(xl+x2,yl+y2)

{ 1/ }

86

deoarece nu se poate stabili cu certitudinc care dintre cei doi constructori "punct" va fi apelat primul,

Ca 0 regula, vom preciza ca specificarea constructorilor claselor incuibarite se va face OBUGATORIU in cadrul DEFINITIEI constructorulul "cuib", nu al declaratiei lui.

In concluzie, putern spune c:1 intotdeauna sc vor apela construcrorii claselor Incuibarite, indifcrcnt daca modalitatea de apel va fi implicita sau explicita. In cazul in care apelul nu e explicit §i nici nu se poate face implicit (de catre compilator), va fi sernnalizara 0 eroare de cornpilare.

3.8. Destructori

3.8.1. Definlre §i caracterlstlcl

Destructorul poate fi privit ca §i complement al constructorului. Spuncam ca, in general, constructorii sint folosi]i pcntru a aloca mcmorie ~i pentru a efectuaanumiteoperatii (de exemplu, incrementa rea unui con tor al numarulu i de instantieri ale clasei). Destructorii se utilizeaza indeobste la eliberarea memoriei aJocate de primii §i la efectuarea unor operatii inverse celor amintite mai sus (de exernplu, decrementarea aceluiasi contor). Un alt amanunt ce indica aceasta complcmentaritate line de modul in care sc denumesc destructorii: ei poarra numele clasei precedat de un caracter "- '',

class B { / / ... public

B (void); / / constructor -B (void); 1/ destructor

} ;

~a cum am afirmat §i Ia inceputul sectiunii "3.7.2.", toate observatiilc Iacute in Iegatura cu constructorii ramin va labile §i in cazul destructorilor.

Exemplull

# define NULL 0

struct s{ int nr;

struct s * next; } i

class B { int i:

87

struct s * ps; public:

B( int); -B (void) ;

} ;

B: :B(int ii=O) {

I 13 si 7

ps=new s; ps->next=NULL; i=ps->nr=ii; II 4si8

} I I 5 si 9

B: :-B(void) II 11 si 14
{
delete pSi II 12 si 15
} II 13 si 16
void main(void) II 1
{
B '* pb;
B b=9; II 2
pb=new B(3) t II 6
delete pb; II 10
} II 17 Sa faccm citeva sublinieri pe marginea programului prezentat:

a. Destructorii se apeleaza implicit in doua situatii: la eliberarea cu "delete" a memoriei alocate dinamic pentru memorarea unor obiecte (linia 10) sau la parasirca domeniuluidcexistenjaal uncivariabilerlinia 17;variabila lib").

h. Acest al doilea caz menta 0 mentiune specials: dad estevorba de variabile globale sau definite in "main'vdistrugeree lor se va face DupA ultima instructiuncdin "main", dar INAINTEdc incheierea executiei programului.

c. In cazul obiccrelorcreate dinamic, destructorul nu se va apela DECIT dad sc incearca eliberarea (cu "delete") zonei de memorie (linia 10).

d. Daca avcm clase incuibarite, ordinea de apelare a destructorilor este inversa celei de apelare a constructorllor respectivi,

e. Ordinea apelurilor de functii executate la sfirsitul unui program TURBO C+ + este urrnatoarea:

- functiile "atexit" (in ordinea inserarii lor);

- Iunctiilc "#pragma exit" (in ordinca codului prioritatii);

- destructoriivariabilelorglobale.

Deoarcce sint mal rar Iolosite, soar putea ca primele doua categorii de

88

~uncJ.ii sa nu va fie familiare, In acest caz puteti gasi informatii amanuntite in once manual de TURBO C.

f. Daca i.n timpul executiei unui program se va apela functia "exit", nu vor fi apelati destructorii variabilelor locale, ci doar cei ai variabilelor globale. In caz de apel a functiei "abort" nu va fi apelat nici un destructor!

g. Avind in vedere cele d~ mai sus, nu se recomanda utilizarea lui "exit" in cadrul destructorilor. In caz contrar se poate intra intr-o recursivitate infinita,

h. Utilizatorul dispune de doua moo uri de ~ provoca apelul unui destructor: - prin specificarea numelui sau;

class B {

I I ••.

pub Li.c r -B ( ) ;

} ;

void f(void) { B b;

b. B: : -B ( ) ; / / APEL DIRECf: e obligatoriu prefixul liB::" }

- prin folosirea lui "delete" (este 0 metoda indirccta; vezi linia 10).

"

3.8.2. Destructorii elaselor inculbarlte

In aceasta subsectiune se va Incheia discujia despre clasele incuibarite !neeputa in cadrul secttunii "3.5" §i continuata apoiIn subsectiunea "3.7.8/ In problema apelulut destructorilor claselor incuibarite se impun doar doua observajii, pc care le vom discuta pc exemplul ce urmeaza.

class baza {

class incuibl obI; class incuib2 ob2; I I •..

public : 1/ .••

"baz aj vo Ldj ; } obO;

Care ar Ii sublinierile ce se impun:

a. Apelul dcstructorilor obiectelor Hob I" §i "ob2" se va face doar DUP.A executia destructorului" - baza".

b. in cazul in care se prefers ca in loc de obiecte incuibarite sa sc foloseasca

89

membrii de tipul "pointer la object" (sa-i zicem incuibarire indirecta), va fi necesara atit alocarea dinamica, cit §i eliberarea EXPLICITAa memoriei necesare pentru obiectele la care vor pointa membrii in cauza. De obicei astfel de actiuni au lac in cadrul constructorului, respectiv destructorului clasei "cuib". Dar despre acest caz particular vom dlscuta in sectiunea "3.10.".

3.8.3. Erori deoseblte

Un prim caz care.se cere studiat ell atentie este cel din exemplul de maijos:

Exemplul l class ex {

double * pNumar; public : ex(double);

-ex ( ) i

} ;

ex::ex(double nr) {

pNumar=new double; *pNumar=nr;

}

ex: :-ex(void) {

I I .•.

delete pNumar;

}

void main(void) {

ex bl(lO.66); ex b2=bl;

ex b3(20.101)i bl=b2=b3;

}

Liniile etichetate cu 1 §i 2 sint generatoare de erori subtile §i greu de detectat, La baza generarii lor sta mecanismul de copiere bit cu bitaobiectelor. Acesta poate fi utilizat in doua situatii: la atribuiri.sau la apeluri ale construetorului implicit de copiere. Daratentie: eroarea nu apare la copiere, ci Ia apelul destructorilor, care vor fi silili sa elibereze de mai multe ori una §i aceeasi

1/ 1

II 2

90

I

zona de mcmorie, Acest fapt se datoreaza copicrii in rnembrii lui "b l" §i "b2" a adrcsci mcmoratc in "b3".lnaslfcldcsi I uatii comportarcaprogramului poatc dcvcni bizara, de obicei provocind blocarea sistcmului, Practic, cazul exemplului de rnai sus esre echivalent cu eel din sccvcnta care urmeaza:

Exemplul z

void f(void) {

int* pInt = new inti delete pInt;

de lete pInt; / I GRESEALA: acecasi zona de mcmorie c / I eliberata de doua ori

}

3.9. Friends

In general, se considcra di cxistil patru faze ale concepcrii unui program: intocmirea caietului de sarcini, realizarca proiectului conceptual, implementarea practica ~i testa rca produsului final. Daca prima Iaza estc corect executata, va fi imposibil ca proiectarc"a conceptuala sa nu dea nastcrc unor ierarhii de clase binc puse la punct. In cele mai multe cazuri, 0 astfel de nereusita sc datorcaza ncpriccperii programatorului, nu inabordabilitatii obiectuale a problemei. Cu roate acestea, Bjarne Strou~trup s-a gindit sa ofere solutii elegante pentru rezolvarea tuturor situatiilor. In aces! scop, el a creal mecanismul de "friend", care sa permits abateri controlate de la ideea de protectie a datelor prin incapsulare, Inainre de a intra in detalii, repetam un avertisment: in caz ca sinteli tentat sa folosi]i acest mecanism, va recomandarn sa mai meditati inca 0 data asupra modului in care ali modularizat problema prop usa spre rezolvarel Avejl sanse mad fie sa gasili un decupaj mai bun, fie sa putcti cxprima "prictcnia" printr-o rclatie de "mostenire" (vezi eapitoluI4.). Daca tot nu ali izbutit, puteti fi :.lproape sigur ca inca nu stapinit: perfect conceptele OOP.

Mecanismul de "friend" porncstc de la imposibilitatca ca una §i aceeasi metoda sa fie mcmbru a mai multor clase. Avind in vcdcre aces! lucru, putcm examina cele doua aspcctc ale problemci:

a. ,FUNCTII FRIEND

. Este yorba de Iunctii care nu sint mctodc ale unci clasc, dar care au totusi acees la membrii priva]i ai acestcia. Orice functie poaw fi "friend" al unci clase, indiferent ca e yorba de functiilc ordinate, mctodelc altor clase sau opera tori rcdefiniti (vezi capirolul S).

91

Exemplul l

class B {

int a; intf(void); friend int fl(B&);

public :

friend int M::f2(B&,int);

}

int fl(B& rB)

{

return rB.f();

}

int M::f2(B& rB,int j=O){ if(rB.a>7) returnj++; else return j--

}

Asa cum se vede §i in exemplul de mai sus, nu are nicio importa.nta da~ o functie cste declarata "friend" in cadrul secjlunii "private" sau "public" a unet clasc. lata deci 0 exceptie: declaratia racuta in cadrul sectiunii "private" este accesibila "in extcriorul" clasei.

b. CrASE FRIEND

In cazul in care dorim ca toli mcmbrii unci clase "M" sa aiba acces la partea privata a unci c1asc "B", in locsa enumeram metodcle lui "M" ca fiind "friends" ai lui "B", putern declara direct clasa "M" drept "friend" al lui "B". De regula, aceste situatii se pot evita declarindu-l pe "MO ca fiind derivat din "B" (adica "ll mo§tene§tc"). Amanunte des pre modul de realizare al unor astfel de derivari vom vedea in capitolul4.

Exemplul2

class M {
II
} i
class B {
II ...
friend class M; } ;

Observatle: relatia de "fricnd"NU ESTETRANZITIV A! Adica.dacaclasa Bl estc "friend" al lui B2, §i B2 este "friend" al lui B3, NU inseamna

92

cit Bl este Implicit "friend" al lui B~.

Dupa cum reiese §i din Exemplul 1, functiile "friend" nu au parametrul ascuns "this" (deoarece nu sint metode). Aceasta carenta se va suplini prin transmitcrea unor parametrii obisnui]! de tip pointer, obiect sau referinta la

obiect. -

In incheierea sectiunii, dorim sa va prezentam un exemplu in care folosirea functiilor "friend" este foarte utila din punetuldevedere al diminuarii timpului de executie.

Exemplul J

class rational;

\

I I declarare incompleta

class complex {

double p reala,pimaginara;

friendcomplex& ponderare(complex&,'rational&); public :

complex(doub.re r,double i) : p reala(r), p=imaginara(i) {}

double get real(void) { return p reala;}

double get=imaginar(void) {return p_Imaginarai} } ;

class rational {

int numarator,numitori double val;

public :

friend complex& ponderare(complex&,rational&);

rational(int n1,int n2) : numarator(n1) {

numitor= n21=O? n2:1; val=«double)numarator)/numitor;

}

double get_valoare(void) { return val;} };

II fiind "friend", "ponderare" are acces la membrii privati ai II claselor "complex" si "rational"

complex& ponderare(complex& c,rational& r) {

complex* t=new complex(c.p reala*r.val, c.p=imaginara*r.val);

93

return *ti

}

/ / nefiind "friend", "ponderare _ineficienta" nu are acces la / I membrii privati ai claselor "complex" si "rational" complex& ponderare_ineficienta(complex& c,

rational& r)

{

complex* t=new complex(c.get_real() *r.get_valoare(), c.get_imaginar()*r.get_valoare(»i

return *ti

}

void main(void)

{

complex a(2,4),b(6,9); rational d(l,2),e(l,3);

a=ponderare(a,d); b=ponderare(b,e)j

a=ponderare_ineficienta(a,d); b=ponderare_ineficienta(b,e); }

in .exemplul ales sint definite doua Iunctii de ponderare a unui numar complex prin unul rational. Ele fac practic acelasi lucru, ins a prin metode difcrite, Funcjia "ponderare" fiind "friend" a celor doua clase, le poate accesa mernbrii privati, deci este foarte rapids. Cea de a doua, "p~nderare _ineficicnta", nu accescaza mcmbrii privati ai color doua clase DECIT prin intcrrncdiul metodclor publice. Din acest motlv timpul de acccs estc mailung, iar Iunctia mai lema. Totusi, la unul sau doua apcluri de acest gcn, avantajul nu e deloc evident. Daca am dori lnsa ponderarea unuisir de 100000 de numere complcxe, Iucrunle sevor schimba net. De aceea se recornanda ca.in aplicatiile in timp real (deci la care orice fracriune de secunda contcaza: conducere de proccsc industrialc, recunoasteri de imagini, etc), sa se Ioloscasca calea cea mai rapida, chiar daca aceasta nu este pur obicctuala.

94

3.10. Comportarea constructortlor ~i destructorilor in situa1ii necesltind alocare dlnamlca de memoria

3.10.1. Crearea ~I dlstruqerea ~irurilor de oblecte Incepem prin a reaminti ca un CONSTRUCTOR IMPLICIT CSIC un constructor cu lista de parametrii vida.

o restrictie oarecum suparatoare a limbajului C+ + ar Il urmatoarca: nu putem declara un tablou de obiecte DECIT daca clasa respectiva arc un CONSTRUCTOR IMPUCIT. Aceasta rcstrictie este necesara daroritii imposibilitatii de a furniza (in cadrul declaratiei sirului) argumcntele necesare constructorilor. Pentru a nu da nastere Ia confuzii, s-a dccis ca, la initializarea obiectelor unui ~ir, sa NU poata fi folosit.nici macar un constructor cu lOli parametrii Iuind valori implicite,

Deoarece clasa care va da tipul tabloului poatc avea ORIelTI construetori in alara celui implicit, ea nu-si va pierde generalitatea. Aut ca, in cazul in care respecriva clasa nu posed a un constructor implicit, trebuie facuta una din urrnatoarele modificari:

a. cvcntualul constructor cu lOli paramctrii luind valori implicitc va Ii "rescris" (vom vedea imediat in ce mod) ca un constructor rara nici un parametru;

h. in lipsa constructorului implicit ~i a celui cu tali parametrii luind valori implicite, seva crea un constructor implicit care sa asigure minimul neeesar de Initializari pentru fiecarc obiect.

~i acurn, sa analizam dous exernple care vor lamuri pc dcplin Iucrurile, ExemplulI

/ / EXEMPLU ERONAT : clasa "punct" nu are constructor Iara / / parametril!l!

/ / In exemplul urma tor vom vedea 0 rcscrierc corecta a clasei "punct ",

class punct { int X,Yi public :

punct(int xl=O,int yl~O) { x=x l j

y=yl;

}

} i

95

punct polilinie[lO];

I I aid aparc eroarea: desi exista un

I I constructor cu toti parametri luind I I valori implicite compilatorul C+ + I I refuza sa-l foloseasca

void main(void) {

II

}

Exemplul Z

I I accst exemplu cste rescricrea co recta a clasei "punct" I I din cxcmplul precedent

* include <iostream.h>

class punct { int X,y;

void pct(int xl=O,int yl=O) { x=xliy=yl; } public :

punct(int xl,int yl) { pct(xl,yl)i }

punct(void) { pct(); }

I I metoda de mai jos estc necesara pentru initializarea I I sirurilor de obiecte d~ tip "punct"

void set_xy(int xl,in~ yl)

{

pct(xl,yl) ;

}

} ;

punct polilinie[lO]i

void main(void) {

for(int i=O;i<5;i++) {

cout«"\n\n TASTATI DOl INTREGI :\n ";

int X,Yi

cin»x»y;

polilinie[i].set_xy(x,y);

} }

Sa analizam, pc pasi, actiunile efectuate in cadrul rescrierii:

96

:I. vcchiul constructor pnmcsie numele de "pet" §i estc declarat privat;

b. sc creaza un CONSTRUCTOR IMPLICIT, care-l apeleaza pc "pet" (fiira a-i furniza vreun parametru):

c. se creaza un constructor avind doi pararnetrii intregi, care vor fi utilizati ea parametrii actuali la apclul lui "pel";

d. se creaza metoda "scl_xy", care va scrvi la initlalizarca sirurilor de obiecte de tip "punct".

Din cite se vede, functionalitea clasei "punct" nu este afectata, atit ca li crcstc - oarceum redundant - numarul de constructori.

In inchcicrc, vom analiza crcarca dinarnica a iablourilor de obicctc, Pcntru a cvidcntia problcmclc cscnjialc, am dccis implcmcntarca unci no! clasc ("POLJUNIEH), CMC sa gcncralizczc idcca de §ir de puncie,

Excmplul3

• include <iostream.h> # include <process.h>

class pun<?t { int x,y;

void pct(int xl,int yl) { x=xl;y=yli } public :

punct(int xl,int yl) { pct (xl,yl), } punct(void) ( pct(O,O); }

} ;

class PoliLinle { lnt nr_pcte;

punct * ppoliLinie; pub I i.o e

poliLinie (int) ; -PoliLinie(void);

} ;

PoliLinie::PoliLinie(int nr) {

ppoliLinie=new punct [ nr 1; I I alocarea memorici ncccsare I I unui sir de "nr" obiecte

I I vcrificam daca alocarea de memorie s-a facut corect if (PPol"iLinie==NULL)

{

cout«"\n\tMemorie insuficienta !I I";

97

exi t (1) ;

}

nr_pcte=nr;

}

poliLinie::-PoliLinie(void)

{

delete [nr _pcte] ppoliLinie; / / cliberarea memoriei necesa/ / re pastrarii unui sir de "m_pete"

/ / obiccte

}

void main(void) {

poliLinie polil(5);

}

In primul rind trebuie remarcat modul de construire a unci polilinii: se aloca dinamic memoria necesara unui ~ir de "nr" puncte. Acest fapt atrage dupa sine 0 consecinta eu totul aparte, pc care 0 vom discuta In cele ce urmeaza. Indiferent ca e yorba de 0 instructlune

pointerPunctl=new punet;

sau

pointerpunct2=new punct[lOO];

inlotdeauna "new" va returna un pointer la "punct". Din acest motlv, la intilnirea unor instrucjiuni de genul

delete pointerPunctlj

delete pointerPunct2j

mecanismul de gestiunc a memoriei alocate dinamic nu arc de unde sa sue dad! "delete" se refera la eliberarea memoriei aferente unui punct sau a 100 de puncte,

Pentru a rezolva dilema, C+ + of era 0 solutio eleganta (vezi destructorul ,,_ polilinlc'j.Astfel, prin constructii de tipul:

delete pointerpunetl;

delete[lO) pointerPunct2;

se va specifica EXPLICIT cantitatea de memorie de eliberat.

98

3.1 0.2. "Scurtclrcuttarea" mecanlsmulul implicit de gestionare a mernorlel alocate dlnarnlc

Spunearn di un compilator nu este capabil sa deosebeasca un pointer la un obiect de un pointer la un §ir de WOO de obiecte, V-ali intrebat, poate, de ee n-ar fi in stare? Raspunsul esre foarte simplu: nefacind parte din limbajul propriu-zis, mecanlsmul de gestiunc a memoriei alocate dinamic n u econtrola t direct de compilator. Din acest motiv pot sa apara erori de genul:

- initializarea unor zone de memorie "prin" pointeri pentru care nu s-a alocat inca memo ric;

- eliberarea de mai multe or: a accleiasi zone de mcmoric.

La prima vcdcre, astfel de fenorncne ar putea f intcrpretatc drept slabiciuni ale limbajului. Dar, sa nu uitam, acestc aspccte lin de faza de exccujie a programului, In plus, nu pot fi ignoraie avantajele unci libcrta]] cvasi-totale de gestionare a memorici. Eadevarat cil accasta libertate implica multa arenjie din partea programatorului, dar, in acclasi limp, permite profesionistilor sa exploateze un calculator in maniera cea mai eficienta.

Avantajul rnodului deschis de a aborda problema mcmorici alocate dinamic consta in faptul ca permite programatorului sa-~i creeze mecanisme proprii in acest domeniu. Astfel, vor putea f eliminate slabiciunile mecanismului standard, a carui prca marc gencraliiate duce la scaderca vitezci de cxecutie a programelor.

Sa vedem deci, concret, despre ce este Yorba. In limbajul C ncobiectual, pentru gestlonarea memoriei alocate dinamic, se utilizeaza functii din familia "rnalloc" §i "free". Modul lor de Iolosirc va este dcsigur cunoscut, precum §i Iaptul ca, 0 data apclare, programatorut picrde com pie! controlul a ceca ce sc intlmpla in "interiorul" ior. Tot cc §tim este ca, din punct de vedere £II utilizatorului, comportarca acestor Iuncjii respects principiilc cutiei negre, Adica, desi nu-i cunoasrcm continutul, putcm gasi 0 combinatie a valorilor bornelor de intrare care sa produca la borncle de lcsirc valorile dorite, In cazul nostru, curia neagra va fi functia "malloc", dimcnsiunea in octeji a zonei de alocat va reprezenta valoarea inrrare, iar pointerul Ia zona alocata va juca rolul de valoare regasita la borncle de iesire. Acest mod de abordarc a problemei poate fi tnsa nesatisfacator in uncle eazuri.

Pentru aplicatiile in limp real, ar fi uti! un mccanism care sa pcrmita un mod mai rapid de alocare a memoriei. Dcoarccc fiecarc apel 31 unui "malloc" sau "free" este mare consurnator de limp, in caz di se aloca frecvent memorie (sa zicem de 5000 - 10000 de ori), s-ar putea cistiga un limp pretios daca ar exista un mecanism propriu de gestionare a mcmoriei. Scopul lui ar fi sa evite alocarea rcpetata a memoriei necesare fiecarei ernitati 111 parte. Acest lucru

99

sar face prin alocarea simultana a mcmorici nccesare pcntru "N" entitiili, ci§tigindu-se astlcl timpul consumat la alte "1'!-1" apcluri. Pinii la cpuizarca color "N" entita!i alocatcsimultan, mccanismul nostruvaactiona astfcl: de cite ori va Ii ncccsar, va returna cite un pointer la una din cntitatile disponibile. In plus.clibcrarca uncicntitat! nuva inscmnaoliberarca memorieialocate pentru ca, ci adaugarea ci la sctul de cntiUili disponibilc, Astfel, prin scurtcircuitarea mccanismului implicit de gcstlunc a mcmoriei, se injclegc inlocuirea lui cu un altu I, funcjionind pe principiile araiatc mai sus.

Vcti intreba care ai Ii avantajul adus de C+ +? Gratic coustructorilor, dcstrucrorilor §i operatorilor "new" ~i "delete" totul poate decurge Ioarte elegant §i cficacc, in eadrul accsrci subscqilmi vom discuta doar citcva principii de baza, care ncvor pcrrnitc sa prezentam mai tirziu implcmentarca unui adevarat mccanism de gestionarc a mernoriei.

Yom lnccpc cu citcva obscrvatii importantc legate de variabila "this":

a. In cadrul mctodclor ESm PERMISA ATRIBUIREA DE VALORI POINTERULUI "this",

b. La intrarea intr-un constructor, "this" va fi difcrit de "NULL" DOAR DACAS-AALOCATDEJA ME~·10RJE PENTRU ACELOBIECT!

c. Daca in cadrul unui constructor 58 ATRlBUIE 0 V ALOARE V ARIABILEI "this" §i, in plus, la intra rea in eonstructor,"this" are valoarea "NULL", inscamna eil ACEL CONSTRUCTOR A FOST APELAT DATORITA UNEI CREARI DINAtvilCE DE OBJECT (dcci cu ajutorul 0- peratorului "new"). AsHel, testind la intrarca in procedure valoarea lui "this", se va §ti eu exactitate tipul apelului.

clasa::clasa(void)

{

II pentru ca tcstul de maijos sa dca rezultate corecte e OBLIGATORIU II ca in cadrul constructorului sa cxiste 0 atribuirc de tipul "this= ... "

if(this==NULL) /1 APELDIN"NEW" II

else II

II OBiECTUL NU A FOST CREAT "DINAMIC"

}

lnainte de a trcce mai departe, sa amintim un lucru pe care, pina acum, l-am trecut cu vcderea: alocarca memorici necesare unui obiect cu ajutorul lul "malloc" NUADUCEDUPA.SINEAPELUL IMPLIClT ALCONSTRUCTORUUJI CLASEl RESPECTIVE I!! Accst Iapt cra de prcsupus dcoarcce familia de Iunctii "alloc" (SiC pastratii doar pentru compatibilite cu versiunile de C neobiectual, Deci atentic la secvente ca cea de mai jos:

100

Exemplul 1

# include <alloc.h>

class ex{

int i,j; pUblic: ex(void){ i==O; j=l;

}

} ;

II ATENTIE: se lucreaza cu modelul de men~orie HUGE! void main(void)

{

ex *pl,*p2;

pl= (ex *) farmalloc (sizeof (ex) ) ; II nu se va apela

II constructorul, ci se II va aloca doar memorie

p2=new e}}.; II se aloca memorie si se apelcaza constructorul }

. ~evenind la ideea preccdcnta, sa mal facem 0 observatie: dad! in cadrul unui constructor se face 0 atribuire catre "this", ORICE MAN IPULARE A UNOR MEMBRI AI CLASEI INAINTEA ATij.IBUIRII POATE A YEA EFECTE DEZASTRUOASE!

Exemplul j;

class clasa { int i,j; public :

clasa (void);

clasa::clasa(void) {

i=l6; II POSIBIL DEZASTRU: in caz ca "this=o=Nl.ll.L" nu se II stie cc zona de mcmoric va fi initializatal

if(this==NULL) this=aloca_memorie(),

101

i=20; / / In aces! loc "this" difera sigur de NULL. Zonele de memoj =2 3; / / ric initializate vor fi cdc rczervate pentru membrii clasei. }

Din pacate, limbajul C+ + nu ofera implicit vreo metoda prin care destructorul unci clase sa poata deduce daca obiectul a fost sau nu creat dinamic. In caz eli aceasta informatie este necesara, clasei i se poate adauga un membru care sa indice acest lucru (vezi cxernplul 5). De asemenea, nu exista nici 0 modalitate implicita de a afla dad un destructor este apelat datorita unui "delete" sau datorita parasirit dorneniului de existenja a unci variabile,

Inschimb, existao metoda graticcareia poatefi evitat mccanismulstandard de elibcrare a memoriei: ASIGNAREA VALORII "NULL" POINTERULUI "THIS" !!l! Astfel, dad intr-un destructor apelat de "delete" vom face asignarea "thise Nl.Il.L", zona de memorie la care pointeaza "this" nu va mai Ii eliberata de catre gesuonarul standard al memoriei.

Un alt aspect dernn de retinut esre legat de modul de alocare ~i eliberare a memorici in cazul unor obiecte create dinamic ~i care au ca membrii pointeri la diverse entitap. Trebuie precizat ca, in momentul crearii obicctului in cauza, se va aloca memorie DOAR pcntru membrii sai, NU ~i pentru eventualele entitati la care ar putea pointa accstia, Pentru accste entitali este necesara 0 alocare EXPLICITA de memo ric, eventual chiar in cadrul eonstructorului. Din acest motiv, eliberarea necesarului de mcrnorie corespunzator unor astfel de entita]! se va face tot EXPLICIT, de obicei in cadrul destructorului clasei, Sa analizam un exernplu practic: .

Exemplul J

class CLASA { int i;

CLASA * pCls; public :

CLASA() i -CLASA( ) ;

} i

CLASA: : CLASA ( ) {

/ / pCls=new CLASA;

/ / EROARE: soar provoca un apel recursiv infinit al constructorului

/ / urmeaza varianta corecta de alocare a memoriei pentru / / entitati "pointate" de membri ai unui object

pCls=(CLASA *) new char[sizeof(CLASA»);

102

1/ s-au mai alocat 6 octeti pentru obiectul pointat de "pCls" i=O;

pcls->i=6i

}

CLASA: : - CLASA ( ) {

1/ delete pCIs;

/ / EROARE: s-ar provoca apclul recursiv infinit al des! ructorului

1/ urrneaza varianra corecta de eliberare a memoriei nccesare

/ / unor enutau "pointatc" de membri unui obiect

delete[sizeof(CLASA)] (char *) pCls; II s-all eliberat cei 6 octeti la care pointa "pCls"

}

void main()

{

CLASA * pc;

pC=new CLASA, / / se aloca 6 octen pentru obiectul nou crcat delete pC; / / sc elibcreaza cei o octer] ocupati de obiect

}

. E~iS~~ t?lll§i 0 posibilitate de ~ ~I?ca simultan mcmorie alit pentru un ob~?ct, cit ~l pcntru evcntualele entitap Ia care pointeaza unii membrii ai sai lata cum se poate realiza acest lucru: ' .

Excmplul4

# include <iostream.h> # include <process.h>

class CLASA { int i;

CLASA * pcls; public :

CLASA() ;

}i

CLASA: : CLASA ( ) {

103

if(this!=NULL) {

cout«"EROA.RE : constructorul nu a fost apelat

ell NEW";

exit(l);

}

/ / scva aloca memoria nccesara pastrarii a doua obiecte de tip "CLASA". / / "This'lva poinia 13 primul dintrc ele.

this=(CLASA *} new char[2*sizeof(CLASA)];

pCls=this+ 1; / / "pCls" va pointa la eel de al doilca obicct de / / tip "CLASA"

i=O; pCIs->i=5; }

/ / sc sctcaza "this -» i" / / sc sctcaza "pCls - > i"

void main() {

CLASA * pc;

pC=new CLASAi / / la apelul construcrorului se vor aloca 12 octeti

delete pC; / / se vor elibcrat TOTI cei 12 octeti alocati

}

Dcoscbirca estc usor scslzabila.In Exemplul3 lie utiliza alocatorul standard de mcrnoric de doua ori: mal intf pcntru <J.biectul propriu-zis, §i apoi pefitm cella care va pointa mernbrul sau "pCls". In Exemplu14 se va utiliz~ 0 singura data gcstionarul mcmorici. Dar, de data aceasta, se aloca memo ric IN ACELA~I T1MP atit pcniru obicct, cit §i pentru cntitatea la care pointeaza "pCls". Datorita acestci stratagcme, nu va mai fi nccesara prezenta unui destructor, dcoarccc se va §li exact cWi memorie a fost alocata in cadrul constructorului. In morncntul intilnirii lui

delete pC;

sc va clibera intreaga cantitate de memorie,

In incheicrca aces lei subsectiuni, vom prczenta un excmplu de utilizare a majoritati] tchnicilor desprc care s-a vorbit pina acum, Vom incepe prin a exp lica pe scurt ce se inrirnpta in cadrul programului, S-a implemcntat 0 clasa "pcrsoana", ale carei instantieri pOI Ii create in trci moduri diferite, Pentru Iiccare mod in parte, constructorul va executa actiuni spccifice, dupa cum urrncaza:

104

a. OBIECT_CREAT_STATIC:

Este vorba de crearea unor obiccte pentru care nu se va aloca dinamic memorie.

b. OBIECT_CREAT_DINAMIC_FARA_A_ALOCA_MEMORlE:

Estcvorba dc o simularc a tipului de data "REFERINTA.". Pointer-ul "this" al unui obiect "OB" va fi setat la 0 valoare rcprezeptind un pointer la un obiect "OBI" de acelasi tip, crcat deja. In acest mod, orice operatic efectuata asupra unui membru al lui "OB" va modifica rncmbrul corcspunzator al obiectului "OBI", referit de catre obicctul "OB". Cu alto cuvintc, "OB" va fi un nou numc al obiectului "OBI ".

c. OBIECT_CREAT_DINAMIC_CU_ALOCARE_DE_MEMORIE:

Acest mod de creare corespunde situatiei in care memoria necesara obiectului in cauza nu va fi alocata decit in cadrul constructorului in cauza,

Exemplu15

/ / Programul de mai jos este compilat cu modelul de memorie "HUGE", / / Pe parcursul lui se va utiliza Iunctia de biblioteca "farcorelett" pentru 1/ a se cunoaste canritatea de memorie disponibila in "heap".

# include <iostream.h>

# include <process.h>

# include <string.h>

# include <alloc.h>

enum mod_de_creare { OBIECT CREAT STATIC (

OBIECT CREAT DINAMIC FARA A ALOCA MEMORIE , OBIECT_CREAT_DINAMIC_CU ALOCARE DE MEMORIE"

} ;

class persoana { char nume[20]; char a[lOOO]; int virsta;

mod de creare indicator; public :-

persoana(mod de creare,char *,int,persoana *): -persoana(vold);

} ;

105

I I Creaza in unul din cele S moduri posibile un obiect de tip persoana persoana::persoana(mod_de_creare modcreare,

char *pNume,int _virsta, persoana *pPersoana=NULL) {

I I acestei operatii, this=(persoana *)

new char[sizeof(persoana)]i if (this==NULL) {

cout«"MEMORIE INSUFICIENTA\n"; exit(l);

switch (-modCreare) {

case OBIECT CREAT STATIC

Ilinitializari -

if(thisl=NULL) {

. strcpy(nUme,pNUme); virsta=_virsta;

}

else {

cout«"EROARE: obiect static cu this=;::

NULL" i exit(l);

}

I I initializari strcpy(nume,pNume); virsta=_virsta;

}

else {

I I CONSTRUCTORULNUAFOSTAPELATDIN "NEW" cout«~EROARE : this nu este NULL\n"j exit(l);

}

break;

} break;

case OBIECT_CREAT_DINAMIC FARA A ALOCA MEMORIE

if (this==NULL) - - - _

if(pPersOana!~NULL)

{

this=ppersoanai

}

strcpy (nume, pxums j i I I initializari virsta==_virsta;

/ / NOTA: - initializarea cc urmcaza nu se puiea face la inccputul merodei

1/ deoarccc in doua din cazuri "this" ar urrna sa-si rnodifice va-

1/ loa rea

/ I - de rcmarcat ca in cazul in care obicctul estc creal ca

II OBlECT CREAT DINAMIC FARA A ALOCA MEMORIE,

II optiunea respect iva va IT inscrisa in-cimpul"indicator" al

1/ obiectului indicat de "pPersoana" 1

indicator=modcreare;

}

else {

/ I NU EXISTA "PERSOANA" LA CARE SA SE FACA / / "LEG ATURA"

cout«"EROARE : this ramine NULL\n"i exit (1);

}

}

persoana::-persoana(void)

{

II Nu se permite eliberarea mcmorici tlecit pentru obiectele la care nu II exista "referinte", In cazul obiectclor "refcritc" de alti pointeri, s e 1/ scurtcircuiteaza mecanismul standard de elibcrarc a memorici (prin II "this=NULL").

else {

/ / CONSTRUCTORULNU A FOSTAPELATDE UN "NEW" c:out«"EROARE : this nu este NULL\n"i exit( 1);

} break;

if (indicator:=;;:

OBIECT CREAT_DINAMIC_FARA_A_ALOCA_MEMORIE) this=NULLi

case OBIECT_CREAT_DINAMIC CU ALOCARE DE MEMORIE

if(this==o){ - - - -

II Urrncaza alocarea memoriei necesare si testarea corectitudinii

}

unsigned long m;

106

107

void mn(void) {

m=farcoreleft ( ); I / m=~62368; mn () ;

persoana pl(OBIECT CREAT_STATIC,"ion",24);

m==farcoreleft()i }

II r~ == 61328;,

persoana *p2 = new persoana(

OBIECT CREAT DINAMIC FARA A ALOCA HE110RIE

"p"~34,&pT); - -- - ,

II m::::60288;

Ui ilizarca unor ar tificii de genu) "OBIECT _ CREAT_FARA_ ALOCARE DINAMICA DE MEMORIE" trebuie faeula eu Ioarte mare atcntie, pcniru sirnplul motiv (Ii de pot scipa usor de sub control, avind spectaculoasc cfcctc distructivc. De cxcmplu, un obiect "01" refcrit de "02" §i ~03" poatc Ii sters din grescala, mra ;1 cfcctua () nccesara operatic de "rcserare" a refcrinjelor Ia el ("02" §i "03"). Daca, rnai (irziu, vom Iolosi obiecrul "02", Iicscvor citi datc cronatc, Iic scvor modifica locatiidc mcmorie de la adrese aleatoarc,

In cadrul unor programe scurte, scrisc de un singur programator, riscurile sint destul de red usc. La proiecrc de dimcnsiuni mai mari §i la care eolaboreaza mai multt programatori, lipsa de coordonare §i consccvcnta poate duce la erori greu de localizat.

Avind in vedere cdc enumerate, va sugeram sa luati exernplul de mal sus drept 0 simpla gimnasuca a minti]. In caz de nevoie, recornandam utilizarea tipului "referinta" existent in limbajul C++, Dar aceasta nu inseamna sa I1U utiliza]! tchnicile prczentatc in cadrul subscctiunii ori de cite or! vi se pare ncccsar.

persoana *p3 = new persoana(

OBIECT CREAT DINM1IC CU ALOCARE DE MEHORIE

- ~- -- -- '

"v",55)i

persoana *p4 = new persoana(

OBIECT CREAT DINAMIC FARA A ALOCA HEHORIE

- - ._ -- - ,

"a",43,p3);

persoana *p5 = new persoana (

OBIECT CREAT DINAMIC CU ALOCARE DE MEHORIE

- - -- -_ ,

"0",79);

m=farcoreleft(),

delete p2; m=farcoreleft();

delete p3; m=farcoreleft();

delete p4; m=farcoreleft();

delete p5i m=farcoreleft()i

}

void main(void) {

108

II m= 60288; nu s-a elibcrat memoria II aiocata deoarecc "p2" refera pe "p l"

3,,11. Probleme rezolvate.Probleme propuse spre

. rezolvare

II m = 602&~; nu s-a eliberat memoria II alocata dcoarece "p3" e referit de "p4"

3.11.1. Array-uri de dimeneiuni varlablle

Multi dintrc cci care au rilcut trcccrca de la BASiC la C au avut 0 vremc nostalgia declarant .unor §iruri de dimensiuni variabile, in functie de necesitat]. lnainte de a studia modul de abordare a unci astfel de probleme in C+ +, trcbuicsa v~i Iaccm 0 recomandarc: ar fi binc ca, in urma parcurgcrii intregli diqi,sa revcniti asupra acestui cxernplu. Am [inutsa vaatragem atentia asupra acestui Iapt deoarecc doar in momentul in care veti staplni §i concepct ele de polimorfism §i rcdefinirc a opera tori lor va vqi putea da seama de diferenta reala intre programul de mai jos §i echivalentul sau neobicctual, Noul tip de data sc va numi BASIC_ARRAY (in amintirea limbajului dupa care ne-am inspirat) §i va avea trei meiode:

- un constructor (crcaza §i intializeaza array-urile);

- un destructor (clihcreaza memoria alocata pentru un array);

I /'m~ 60288; nu s-a eliberat memoria II alocata deoarecc "p4" rcfera pc "p3"

II 111= 61328; s-a eliberat memoria

II alocata dcoarece "pS" nu e refcrit si nu II rcfera pc nirneni

109

- 0 functie de concatenate a doua array-uri (exemplu: daca avem .

a= {1,2,3} si b={4,5,6}, in urma concatenarii lui "a" cu "btl vom obtine a= {l,2,3,4,5,6} §i. b::::{4,5,6}).

Sa analizam acum programul: # include <iostrearn.h> # include <process.h> # include <conio.h>

{

/ / mesajexplicativ cout«nl«nli

cout«"Tastati "«nrElem«" intregi 1"«n1;

/ / alocarea memoriei nccesare array-ului dimension=nrElern; pointer=new int[nrElem);

# define nl "\n"

test«void *) pointer);

class BASIC ARRAY {

int * pointer; / I pointer la array

int dimension; / / dimensiune array

void print (void) i I I tiparire array

void test (void *) i I I testeaza daca s-a alocat corect memorie public:

BASIC_ARRAY (lnt); I I constructor

I I initializare array

for(int i=Oji<nrElem;i++) cin»pointer[i];

I I tiparirc array print() ;

}

I I Tiparcstc un array

void BASIC_ARRAY::print(void) {

cout<nl«nl«"sirul este : n;

-BAsrC_ARRAY(void) II destructor {

delete[dimension] pointer;

}

for(int i=O;i<dimension;i++) cout«pointer[i]«" Hi cou t-c-cn Lr

II procedura de concatenate

void concatenate(BASIC_ARRAY&);

} ;

cout«"Apasati 0 tasta J"«nl; getch () i

}

I I Testeazavaloarca unui pointerpentrucares-aaloeatdinamicmemorie II Daca pointer-ul estc setat la NULL inseamna ea operatia de alocare / I dinamica a mernorici nu s-a efcctuat corect.

void BASIC_ARRAY::test(void * ptr)

{

if (ptr==NULL) {

1/ Concatencaza array-til rare a apclat metoda cu rei primit ca

I I pararnetru ( PRIN REFERINTA ! ); rezultarul se va pastra "in" / I obiectul care a apelat metoda

void BASIC ARRAY::concatenate (BASIC_ARRAY& ref ARRAY)

cout«"\n\n\t MEMORIE INSUFICIENTA",

exit(l);

{

int *tmpi

int final dimension;

} }

/ I Creaza un array de intregi avind "nrElcm" clemente. / I Initializeaza array-ul eu valori citite de la tasiarura. BASIC ARRAY::BASIC_ARRAY(int nrElem)

I I ealculareanoiidimensiuniarezultalului

final dimension=dimension+refARRAY.dimension;

110

11 J

/ / alocarc mcmoric ncccsara

tmp=new int[final_dimension];

test«void *) tmp);

/ / copicrca celor doi "operand' in cadrul "rezultatului" for(int i=0;i<fina1 dimension;i++) if(i<dimension) tmp[i]=pointer[i];

else tmp[i]=refARRAY.pointerli-dimension];

delete [dimension] pointer; / / clibcrarca mcmorici lulosnc / / de prirnut operand

dimens Lo n=f inal __ dimens ion; II setarc (j j n.cns: une corccta

pointer=tmp; / I sctarc "pointer 13 array"

/ / tiparirc rczultat pr i .. rt; ( ) i

}

void main(void)

BASIC ARRAY a(3); BASIC ARRAY b(4);

int i;

CQut«"Tastati dimensiunea siru1ui cin»i;

cout«nl;

" . ,

BASIC ARRAY c(i);

a.concatenate(b); a.concatenate(c); }

Ca excrcitii propusc sprc rczolvarc, va sugeram sa complctati lista meiodelor clasci cu urrnatoarc!« Iuncui:

- "Int findt BASIC_/\RRA '{&mudcl),,:

Ca uti in array-ul initial prima ap3ri!ieaarray-ului "model". Ret urncaza 1 sal! 0 dup:; cum modclul existil sau nu in cadrul arrav-ului

iniua]. ,.I

112

- "Int replace(BASI C __ ARRA Y S: model , vee hi,

" BASIC_ARRAY&model_nou),,:

Inlocuicstc in cadrul array-ului initial toate apantiilc lui "model_ vech itt ell "modcl_ nou".Rcturneazal1umaruldcsubslitulii efectuate.

- "Int eliminate(BASIC_ARRA Y& model)":

Elimina din array-ul initial toateaparitiile array-ului "model". Returneaza 1 sau 0 dupa cum climinarea a Iost sau nu posibila.

3.11.2. HASH~TABLE pentru numere de teleton

In aceasta sectiune vom prezenta un eoneep~ utilizai dcseori de catre informaticieru: "hash tables" ("tabelc de hashing"). In citeva cuvintc, dcsprc ce este yorba: fie () multimc de clemente pastraia in memoria calculaiorului ca 0 !ista neordonata, A ani) daca 0 valoare anume este inclusa in accasta mul ~i me inseamna a parcurge SECVENTIAL lista §i a testa clement eu clement. Daca sc folosesc divcrsi algoritmi de sortare a multimii, cautarea se poatc face mull mai rapid. Daca nici astfcl viteza nu cstc suficicnt de marc, sc va imbina sortarea cu "hashing't-ul.

Idcca de baza in "hashing" cstc de a incerca sa imparum clcmcntclc multimii intr-un nurnar oarecare de clase de cchivalenta (avind Iiecarc cam acclasi numar de clemente - statistic vorbind). Daca rcusim accst Iucru, inseamna ea la cautarea unui clement in cadrul muljimii vorn stabili in prima faza carei clase de c;:hivalenlii ii apartine, iar apoi il vom cauta doar in cadrul acelci subrnultimi. In cazul in care clasele de echivalcnta vor fi serrate, vitcza crcstc §i rnai mult.

Acestea fiind spusc, sii vcdcm ce-si propune programul de mai jos, Sc doreste implcmentarca unci agende telelonice in care cautarca numcrclor de tclcfon sa se faca printr-o tabela de hashing. Criteriul imparjirii in.clase de echivalcnta va fi cifra finala a numarului de (eldon. In concluzic, vorn avca 10 clase de cchivalcnta, avind aproximativ acelasi numar de clemente. Tabela de hashing cstc consutuita dintr-un ~ir de 10 clemente, Iiccarc continind un pointer la 0 lis La simplu inlanluita. Toate elementele ce aparun accleiasi listc au in comun faptul ca sc sfirscsc cu acceasi cifra, Deoarccc accst excmplu are doar un scop didactic, clasele de cchivalenta nu vor fi senate. Deci, a cauta in "agenda" un numar de telefon cc se termina cu cifra "i" lnscamna a-l cauta secvential in cadrul listei la care pointcaza al "iff_lea element din tabela de hashing.

Caexercitiu, va propunern sprc rezolvareordonarea clasclor dccchivalenta §i cautarca (inserarea) in cadrul acestora prin metoda "divide and conquer" (metoda "injumatatirii intcrvalului"). De aserncnea, va propuncm scricrea

113

unui destructor al clasei "hasharray",

if include <stream.h>
if include <process.h>
:# include <string.h>
:# include <conio.h> if define ON 0 if define OFF 1

if define n1 "\n"

class hash_array {

struct elem {

char nume[20] i long nr_telefoni e1em *urm;

II nume persoana

II numar de telefon atasat

II pointer la urmatorul element

elem ( long, chaz e ); II constructor element

} * sir[lO];

II tabela de hashing

void tipareste(void);

/ I tiparire agenda telefonica

void test (void* ); 1/ testeaza ccrectitudinea alocaril / I dinamice de rnemorie

pUblic:

hash_array (void) ; / I constructor

/ / cautare element in agenda

e1em* cauta_numar(long numar telefon,

char *nume=n~,int in~ereaza=OFF);

1/ inserare element in agenda

e1em* insereaza(long numar_telefon,char * nume) {

1/ in urma cautarii, elementul se va insera (optiunea ON) e1em* pE1em=cauta numar(numar telefon

- - ,

nume, ON) i

tipareste(); return pE1emi

114

} } i

/ / lnitializare element multime e1em::e1em(long n,char * p) {

nr_te1efon=ni strcpy(nume,p); urm=NULLi

}

Illnitializare tabela de hashing (nu exista inca clemente) hash_array::hash_array(void)

{

for(int i=0;i<10;i++) sir(i]=NULL; }

/ I Testeaza valoarea unui pointer; daca acesta este NULL inscamna ca II ultima operatic de alocarc dinarnica de mernorie nu s-a efectuat coree! void hash_array::test(void * pointer)

{

if (pointer==NuLL) {

cout«"\n\n\tMEMORIE INSUFICIENTA II l"i exi t (1) i

} }

/ I Cauta in agenda un numar de telefon . Daca nu-l gaseste si II parametrul "insereaza" arevaloarea "ON", il va insera

e1em* _hash_array::cauta_numar(long numar_telefon, char * nume, int insereaza)

{

e1em* tmp,*cap_lista;

int f lag~oFF; II numarul nu a fast inca gasit

/ I determinate "intrare" in tabela de hashing

int indice=numar telefon-(numar telefon/lO)*lO;

cap _lista=tmp=sir (indice J , --

/ / cita vreme numarul nu a Iost gasit si mai exista clemente 1/ netestate sc continua cautarea

while (tmp!=NULL && flag!=oN)

115

tmp=tmp->urm;

getch () ; }

.

if (tmp_>nr_te1efonl=numar_telefon)

else {

f 1ag==ON; / / nurnar gasit

if (insereaza==ON) { cout~<n1«n1«n1

«"Numarul "«numar_te1efon«"nu poate fi"

«" inserat de doua ori 1 "«n1«n1«n1; return NULL;

void main(void) {

hash_a~ray agenda;

sir[indice]->urm=cap lista; return sir[indice]; -

agenda.insereaza(1110,"Manu"); agenda.insereaza(1111,"Condas"); agenda.insereaza(1112,"Gatu"); agenda.insereaza(1113,"Farna"); agenda.insereaza(1114,"BOrdu"); agenda.insereaza(1115~"Ionescu"}1 agenda.insereaza(1116,"Popescu"); agenda.insereaza(1117,"Ion"); agenda.insereaza(1118,"Vasile"); agenda.insereaza(1119,"Mi1as"); agenda.insereaza(6060,"MocanU"); agenda.insereaza(2524,"Vinti1a"); agenda.insereaza(2610,"Zeda"); agenda.insereaza(4753,"Istrate"); agenda.insereaza(7421,"panait"); agenda.insereaza(3112,"Ghergu"); agenda.insereaza(9428,"pana"); agenda.insereaza(9694,"Dinu"); agenda.insereaza(4743,"BUrca"); agenda.insereaza(8231,"DUmitru",;

}

r

II daca inscrarca cstc ceruta sl posibila, ca sc va face in

II capul listei (ultimul adaugat va fi chiar Tntrarca" in tabcla) if( insereaza==ON ) { sir[indice]=newe1em(numar_te1efon,nume);

test«void *} sir[indice});

}

return tmpi

}

/ / Tipareste intreaga agenda telcfonica void hash_array::tipareste(void)

{

cout«n1«nl«nl«"AGENDA DE TELEFON

"«nl«n1;

/ / Urmeaza incercari de inscrarc a unor clemente deja existente, 1/ Ele nu vor mai f cfcctuate.

cout«"Numere ce se sfirsesc eu "«~I «" :"«n1;

elem* tmp=sir[i];

agenda.insereaza(9428,"pana"); agenda.insereaza(9694,"Dinu"); agenda.insereaza(4743,"BUrCa"); agenda.insereaza(8231,"DUmitru");

for(int i=Oii<10ji++) {

/ / pcnlru ficcarc "i nt rare" in tabela de hashing .,.

while (thlP I =NULTJ) { 1/ ... sc tipareste clasa de echivalenta cout«tmp->nr _.te1efon«" "«tmp->nume«nl, tmp=tmp->urm,

/ I sc efectucaza 10 cautari de numcre citite de la tastatura for(int i=Oii<10;i++)

{

}

long nr; elem k tmpi

cout«"Tasteaza un numar de telefon cin»nri

" . ,

117 .

116

if«tmp=agenda.cauta numar(nr))I=NULL) cout.c-cvam gasit :- "«nr«" "« tmp->nume«nl«nli

else cout«"Numarul "«nr«" nu exista in agenda !"«nl«nlp

} }

3.11.3. Mecanism pertectlonat de gestiune a memorlel

Dupa cum am maisubliniat, unul din aspectcle limbajului C+ + ce.suporta imbunatatiri seriose din punct devedere a vitezei de cxecutie, este mecanismul de gestiune al memoriei. In cartea "The C+ + Programming Language" ([STRO] p.162-164), Bjarne Stroustrup da un exemplu de imbunatatire a performantelor mecanismului standard. Alit doar ca acel program este prezen tat ca o aplicatie particulars, legata de 0 clasa anume, Gratie tehnicii de lucru cu "PROJECT"-uri, vom generalizaexemplul lui Stroustrup.aducindu-i §i sensibile imbunatatiri calirative. Prograrnul de mai jos poatc fi utilizat cu succes §i in practica, generalitatea lui facindu-I uti! in orice situatie de folosire intensiva a alocarii dinamice de memorie. Ca performante atinse de accsta, putem preciza ca, fala de mecanismul implicit, aduce 0 scadere a timpului consumat cu alocari/eliberari de memorie de circa 19-20%. In cazul unci liste simplu lnlantuite cu 10 clemente cistigul nu este deloc evident, dar la aplicatii cu nurneroase opera Iii de alocare/eliberare cistigul devine cu adevarat substantial.

Sa vedem care ar Ii principiile de baza, dupa care sa analizam programul propus. Ideea novatoare consta in a aloea STMUL TAN un numar mai mare de entitiiti (100 - 1000, in functie de aplicajie), care saJie gestionate apoi de un mecanism propric. Cese intelege prin "gestionate"? In principiu este yorba de:

a. Un alocator: el aloca la un moment dat 0 IisUi de N entitati, De cite ori programul aplicativ are nevoie de 0 noua entjtate, se va returna un pointer la urrnatorul element disponibil din lista, In momentul in care lista se goleste, se va aloca memo ric pentru ° noua lista, tot de N clemente.

b. Un eliberator: in IDe sa elibereze memoria folosita de 0 entitatc, obiectul in cauza se va adauga ca !_lou clement in capul Iistei de enuta]! disponibile (gestionatadealocator).ln aces! mod sevor evita secventcdeoperatii "new" §i "delete".

Un alt avantaj al metodei propuse consta in modul de organizarc a clasei "inventar". Un obiect al acestei clase nu estc altceva deS-it un pointer Ia 0 lista simplu inlanluHii. Care ar fi inovatia ad usa de noi? In cadrul clasci se va

118

introduce un membru static cc va conioriza numarul de instanticri (in cazul nostru numarul de liste simplu inlaruuite]. Astlcl, nu va Ii ncccsara eliberarca efectiva a tuturor entitatilor alocate DECITin momentul in care nu mai cxistii nici 0 instantiere a rcspcciivci clase. ~i asta pentru ca atita vrcrnc cit rnai existaobiectede acest tip, memoria ut ilizatadc instantieriledistrusenu trebuie eliberata, ci poate fi adaugata in lista de clemente disponibilc,

Ca dezavantaj al metodci am putca putea aminu consumul rnai ridicai de n:emorie. Dar, cum dezideratul consta in cresterea vitezei, era de presupus ca ea sc va face pc seama aitui pararnetru de functionare a programului.

Ca lema, propunem 0 analiza serioasa a genurilor de aplica!ii in care un astfel de gestionar de memorie este eficient (atit ca limp de execujie, cit ~i ca memo ric consumaia).

In cele ce urmeaza, vorn prezenta pc rind Iisierele programului: a) ALOCA_M.H

II contine structurilc de date folosite de catre gestionarul de memorie

struct LISTA { OBIECT * Obi LISTA * next; } ;

II "OBJECT" e dcfinit in "datc.h"

class gestionar memorie { LISTA * pLista;

LISTA * pRecuperate;

pUblic:

gestionar memorie(); -gestionar_memorie(); OBIECT * aloca memorie();

void elibereaza_memorie(OBIECT *)i } ;

b) DATE.H

II este folosit ca "include" in "GEST MElV!.CPP"

i include "inventar.h"

II "elemtva fi definit in "inventar.h" i define OBIECT elem

119

# define NUMAR OBIECTE 4

c) GEST_MEM.CPP

!! coniine dcfinitiile mctodclor clasei "gestionar jnemorie"

# include "date.h"

# include "aloca m.h"

# include <stdio.h>

# include <process.h>

! ! Nu cxisra inca celc doua lisle

gestionar memorie::gestionar_memorie()

{

pLista=NULL; pRecuperate=NuLL; }

gestionar __ memorie: :-gestionar_memorie()

{

LISTA 1< tmpi

while(pListal=NULL) { delete pLista->ob; tmp=pLista; pLista=pLista->next; delete tmp;

}

/ ! lista data de "plcccupcratc" nu mai trcbuie eliberata / / dcoarcce in aces! moment CSiC vida

}

/ / Daca mai cxista obiectc disponibilc, va returna un pointer la primul II dintrc clc; in caz contrar face acclasi luc ru.dar alocind in prealabil / I mcmoric

OElEcr 1< gestionar _ memorie ~ : aloca._:memorie ( )

{

LISTA 1< valDeRetur.nat=pLista,

if (valDeReturnat===NULL)

II nu exista memo ric disponibila: se va aloca memorie pcntru un nou "sir"

120

{

LISTA * temporar=(LISTA *)

new char[NUMAR_OBIECTE*sizeof(LISTA)];

OBIECT * tmpl=(OBIECT *)

new char[NUMAR_OBIECTE*sizeof(OBIECT)];

II Se tcsteaza si "temporar" si "tmpl" deoarece soar putea ca Illungimea lui "OBIECT" sa fie mai mica dccit a lui "L1STA"; I I in astfel de situatii exista posibilitatea (redusa, cc-i drept) I! ca sa fie suficicnta mcmorie pentru a-crea un "OBIECT", nu

! I insa si a "LlSTA", si atunci "temporar" va fi NULL, iar "tmp l" nu. I I In toate celelalte situatii ar fi suficient de testat doar "tmp l".

if( temporar==NULL I I tmpl==NULL ) { printf("MEMORIE INSUFICIENTA\n"); exit(l);

}

I! urmeaza ca fiecarui clement din sirul "temporar" sa i se

! I ataseze memoria necesara cite unui "OBJECT" (NU UITATI :

I! "LISTA" eo lista simplu inlantuita de pointeri la instantieri ale clasei !! "OBJECT")

valDeReturnat=temporar++; valDeReturnat->next=temporar, valDeReturnat->ob=tmpl++i

for(int i=1;i<NUMAR_OBIECTE-1ii++,tmpl++){ te:mporar->ob=tmpl;

t.empoz ar - >ne xt=t.empor ar+ 1 ; temporar=temporar->nexti

} I I SFIRSIT "FOR"

temporar->next=NULL, temporar->ob=tmpl;

} I! SFIRSIT"IF"

II primul clement al listei initializate se va folosi imediat; de aceea el I I va fi pus chiar acum in "capul" listci cu clemente "de recuperat"

pLista=valDeReturnat->nexti valDeReturnat->next=pRecuperate,

121

pRecuperate=valDeReturnat; return valDeReturnat->ob;

}

/ / Va asocia memoria folosita de obiectul disponibilizat unui element din / / lista "de ree~perat:'. pe ~a~e-I ~a elimina din aceasta lista si-I va adauga / / la cea eu obiecte disponibile (Ill loc sa se elibereze memoria nefolosita / / ea va fi pusa cu prima ocazie la dispozitia utilizatorului ' void gestionar_memorie::elibereaza_memorie(OBIECT*

pObiect)

{

LISTA * tmp=pRecuperate;

/ / eliminare din lista "de recuperat" pRecuperate=pRecuperate-nexti

/ I reinitializare si adaugare in Iista eu clemente disponibile tmp->ob=pobiecti

tmp->next=pListai

pLista=tmp;

} .

d) INVENTAR.n

/ / descrie structurile de date folosite in clasa "inventar"

struct e1em {

char nume_obiect[20Ji int numar_inventar; e1em * urmator;

} i

class gestionar_memorie;

class inventar {

elem * cap_lista;

static gestionar memorie* gm; static int nr_oblecte;

void distruge_inventar(void)i void tipareste(void)i

122

public :

inventar (void) ; - inventar (void) i

void adauga in lista(char *,int); void elimina_dIn_lista(int),

} ;

c) INVENTAR.CPP

/ / continedcfinitiilemetodclorclasci"invenlar"

# include <iostream.h>
# include <process.h>
# include <string.h>
# include <conio.h> # include "date.h"

# include "aloca m.h"

# define nl "\n"

# define DA 1 # define NU 0

int inventar::nr_obiecte=O; gestionar_memorie*

inventar::gm=new gestionar memorie;

/ / Initializari inventar::inventar(void)

{

cap_lista=NULLi

if (gm==NULL) {

cout«"MEMORIE INSUFIECIENTAn«nl;

exit (1) i

}

nr obiecte++;

}

inventar::-inventar(voidj {

nr obiecte--;

123

I

I

I'

I

I

distruge inventar(); if (or_obIecte==O) { delete gmi cout«"GATA \n";

} }

void inventar::distruge_inventar(void) {

while (cap _lista! =NUJ~L) elimina_din_lista(cap_lista->numar_inventar),

}

II Atcntic: in metoda "adauga_in)ista" nu sc face valida rea noului / I introdus (nu se veri fica unicitatea numarului de invcnrar) l!!

void inventar::adauga_in_lista(char* nm,lnt nr) {

elem * tmp=gm->aloca_memorie(); strcpy(tmp->nume obiect,nm), tmp->numar inventar=nr; tmp->urmator=cap_lista; cap_lista=:tmp;

tJ.pareste ( ) ; }

1/ Tiparcstc intrcaga lista de clemente void inventar::tipareste()

{

elern * trnp=:cap_lista;

cout«nl«nl«"INVENTARUL COMPLET :"«nl;-

while (tmpl=NULL) { cout«trnp->nume obiect«n "« trnp->numar_Inventar«nl; tmp=tmp->urmator;

} getch() ; }

124

1/ Elimina din Iista un obicct idenuficat prin numarul de invcntur void inventar::elimina_din_lista(int numar)

{

elem *, tmp=cap_lista,* pDeEliberat; int ExistaElernent=NU;

if(cap lista!=:NULL) ,

if ("3ap lista->numar _ inventar==numar) { II cflminarc cap de lista

ExistaElement=DA; pDeEliberat=cap_lista; cap_lista=cap_lista->urrnator;

} else

while(tmp->urrnatorl=NuLL && ExistaElernent==NU ) {

II sc cauta secvential elcrnentul dorit

if (trnp->urmator->nurnar_inventar==numar){ ExistaElernent=DA; pDeEliberat=trnp->urmator; tmp->urrnator~trnp->urmator->u~rnator;

} else trnp~tmp->urrnator;

}

if(EXistaElernent~=DA) { "grn->elibereaza_memorie(pDeEliberat); tipareste()i

} }

1) M_PRJ.CPP

1/ Programul principal

# incl~de <iostrearn.h> # include <process.h>

# include "inventar.h"

void rnain(void){ inventar il,i2, * i3;

125

i3=new inventar;

if(i3==NULL) {

cout«"\n\n\tMEMORIE INSUFICIENTA I !I"; exit(l);

}

cout«"ADAUGARE : \n" i

for(int i=Oji<1000;i++) { il.adauga_in_lista("qwertY",i)i

}

cout«"ELIMINARE : \n" i for(i=Oii<lOOOji++) { il.elimina_din_lista(lOOO-i);

}

cout«"ADAUGARE : \n" i for(i=O;i<lOOOii++) {

i2. adauga_in_lista ("qwerty", i) ;

}

cout«"ELIMINARE ! \n"; for(i=Oji<lOOO:i++) { i2.elimina_din_lista(lOOO-il;

}

cout«"ADAUGARE : \n"; for(i=Oii<lOOO;i++) { i3->adauga_in_lista("qwerty",i);

}

cout«"ELIMINARE :\n"; for(i=O:i<lOOO;i++) { i3->elimina_din_lista(lOOO-i);

}

delete i3; }

126

4. MO~~ENIREA

4.1. lntroducere

Vom Incepe explicarea conceptului de MO~TENIRE printr-un exernplu legal de familia de functi! "SCANF" ("seanr', "fscanf" §i "sscanf"). Intr -0 scurta paranteza, vom reaminti citeva caracteristici ale lor:

- int scanf'(Format.Arg 1,Arg2, ... ,ArgN)

Citeste de la dispozitivul standard de intrare "N" valori, in conformitatccu informatiile din "Format".

- int fscanf'(Pointer _La _Fisier,Format,Argl ,Arg2, ... ,ArgN)

Citeste din fi~ierul dcsemnat de "PointerLaFisier" "N" valori, in conformitate eu informa[iile din "Format",

- int sscanf(Pointer _La_ Buffer.Format.Argl ,Arg2, ... ,ArgN)

Citeste din buffcrul desemnat de "Pointer_La_Buffer" "N'valori, in conformitatc cu inforrnatiile din "Format",

Dupa cum probabil ali remarcat deja, modul de utilizare a cclor trei Iunctii este cvasi-identic, variind doar sursa din care vor Ii citite valorile in cauza: dispozitivul standard de intrare, un Iisicr sau un buffer de caractere. 0 astfel de asemanare frapanta ne poate duce cu gindul la faptul ca, in fond, ar putea exista Ioarte bine 0 Iunctie generala "_SCANF _", ale carei proprietaji sint "mostenite" de catre cele trei func1ii de biblioteca. ~i, pentru c.1 Iiecare dintre procedurile "mostenitoarc" arc 0 proprietate caracteristica (sursa de unde preia valorile), sintem de fapt in fala a trei lip uri de functii apartinind uncia §i aceleiasi FAMILU.

lata, deci, in citeva cuvinte la ce sc reduce intrcg mecanismul de 1\10$-

. TENIRE. Avind 0 CLASP.. oarecare "B", putcm defini 0 aWl CLASP.. "0" care sa MO~TENEAscA (sau, daca prcfefcrati, sa PREIA) TO ATE caracteristicile primeia, la care sa poata adauga ALTELE Nor, proprii doar accsteia din urma. Prima clasa se va numi "CLASA DE BAZA", iar cea de a doua "CLASA DERIVATA".

Conform spccialistilor Iirmci BORLAND, "mecanismul de mostcnire cste, poate, singura mare diferenja intre C §i C++" ([BORl], p. 141). Cu riscul de a ne repeta, dorim sa sublinicm inca 0 data un lucru deosebit de important: de§i MO~TENIREA sta la baza alter concepte novalo3fe (cum ar fi polimorfismul), cheia de bolta a programarii obiectuale ramine INCAPSULAREA. Pentru ca, sa nu uitam, in lipsa accsteia din urma, MO§TENIREA nu ar fi avut nici un suport practic,

127

4.2. Clase derivate

In cclc cc urrncaza vom da un prim cxemplu de derivate a unci clasc "hard _ disl<_ 2" din clasa "(1oP(lY _ disk":

Exemplul l

enum stare_operatie { REUSIT , ESEC }i

enum stare_protectie_la_scriere { PROTEJAT , NEPROTEJAT };

class floppy_disk {

protected:

II cuvintul cheic "protected" pcrmuc declararea unor mcrnbrii ne-puII blici, care sa poata fi acccsati de catrc cventualcle clase derivate din 1/ "floppy _ disk"

stare_protectie_la_scriere indicator protectie; int capacitate,nr sectoare,

public : -

stare_operatie formatare(void); stare_operatie citeste_pista(int drive. int sector de start,

int nl!-mar_;ectoare,void * buffer); stare_operatie scrie_pista(int drive,

int sector de start,

int numar_;ectoare,void * buffer); stare_operatie protejeaza_la_scriere(void); } i

/ I clasa "hard _ disk _1" dcfincstc un hard-disk FARA a profita de

II avantajclc mostcnirii '

class hard disk 1 {

stare_protecti~_la_scriere indicator protectie; int capacitate,nr sectoare;

int numar partitIi;

pUblic: _

stare_operatie parcheaza_disc(void) , stare_operatie formatare(void); stare_operatie citeste __ pista(int drive,

int sector_de_start,

12S

int numar sectoare,void * buffer); stare operatie serie pista(int drive, int-sector de start,

int numar ;ectoare,void * buffer); stare_operatie protejeaza_la_scriere(void); } ;

/ / clasa "hard disk 2" defineste un hard-disk; se utilizeaza 1/ avantajelemostenirii proprictatilor clasei "floppy _disk"

class hard_disk_2 : public floppy_disk{ int numar partitii,

pub Li,c : -

stare_operatie parcheaza_disc(void)j } ;

/ / din dcfinirea metodelor de mai jos reies drcpturile de / / accesare ale membrilor clasei derivate

stare_operatie hard_disk_2::parcheaza_disc(void) {

/ / CORBCT: accesare membru "protected" indieator_protectie=PROTEJATi / / ....

return REUSITi

}

void functie(void) {

hard_disk_2 hd l r

hdl. format are ( ) i / / CORBCT

hdl. indicator_protectie=NEPROTEJAT; / / EROARE: / / incercare de

/ / accesare a unui

/ I mernbru protejat

}

Incontestabil, cea de a doua metoda de a dcfini un hard-disk cste mull mai eleganta, mai eficace §i mai user de lntcles. Sa analizam cu atentie secventa de mai sus. Prin enuntul

class hard disk_2 : public floppy_disk se indica cornpilatorului urrnatoarele informatii:

129

_ clasa "hard_disk_2" va fi DERIVATAdin c1asa 'floppy disk";

_ t0li. membrii d~ tip "~u.blic" ai clasei "floppy_disk" vor fi mosteniti (§i dec] vor putea Ii [OIOSI!I) ca "public" de catre c1asa "hard_disk_2";

_ toti membrii "protected" ai lui "floppy _disk"vor putea Ii utilizatica fiind "protected" in cadrul clasei "hard_disk_2".

~ste eviden t ~a eficacitatea nu inseamna doar faptu! ca in declaraua clasei derivate nu mal apar Informatiile mostenire (de fiind automat luate in considerare de catre compilator).ln plus, nu mai trcbuie rescrise nici macar functiilc-mernbru ale clasci de baza, ele putlnd fi rcfolosite EXACT in ~~niera in care au lost definite. Dar, a§a cum vom vedca in secpumtc v~lt?are, acest lueru nu inseamna ca, dadi dorim, nu putem sa le redefinim, gasindu-le 0 eu totul alta funcuonalttate.

4.3. Drepturi de accesare a membrilor "rnostenltl'' In secjiunea "3.2." of ere am 0 sinraxa simplificata a dectararn unei clase: specificator_clasa nume_clasa {

[ [private ] lista membri 1

[ [public :] lista=membri-2

} ;

De data aceasta vom rafina acea forma, §i vom obtine: specificator clasa nume clasa derivata

[: [ modificator acces 1 J nume clasa baza 1

[ , [ modificator acces 2 T nume cl~sa baza 2 T [ , ... ]

] ]

{

[ private

[ protected

[ [ public ]

} [lista_obiecteJ;

Pentru inceput, ne vom aeupa de cazul in care exists un singur modificator de aeces ("Illodificatof_acces_l") §i 0 singura clasa de baza ("nume cla-

sa_baza_l "). Insecjiunea "4.6."vom tratacazulgeneral (mai mullimodifieatori de acces §i c1ase de baza), numit §i MO~TENJRE MULTIPLA.

lista membri 1 J lista-membri-2 ] lista -membri- 3 ]

In sintaxa prezentata exista trei noutati:

130

!

I I

exprirnarea relatiei de "MO~TENIRE" ("numc_c1asa_derivata" Iiind DERIVATdin "numc_clasa_haza_l");

- prezenta lui "modilicator _ acccs"; aparitia sectiunii "protected".

in functie de tipul lui "specificator~~lasa" ("class" sau "struct"), "modificalor _ acees" va lua valoarca IMPLICIT A "private" sau "public". Rolul aces I ui "modificator acces'' este ca impreuna cu specificatorii "pu~lic". "private" sa~ "protecredv tntilniti in cadrul declararii clasei de baza, SA HOTARASCA drcptul de acccsarc a mcmbrilor mo~tenili de ~c~lrc 0 clasa derivata, ATENTIE: modificatorul de acces NU MODIFICA IN NICI UN FEL dreptul de accesare a membrilor clasei de haza!

Nu am amintit nimic dcspre "uniorr'-uri, dcoarece ele NU POT FI NICI CLASEDE 8AZA,NICI CLASEDERlVATE !!!

Tn continuare, vom prezenta un tabel care sintetizeaza drepturile de accesare a mernbrilor unei clasc derivate "0" in Iunctie de drepturile de accesare a membrilor clasci de baza "8" ~i valoarca lui "moduicator acces".

Drept de acces in Morliflcator de acces Drept de ac£es in
clasa de baza clasa derlvata
PUBLIC PUBLIC PUBLIC
PRIVATE PUBLIC inaccesibil
PROTECTED PUBLIC PROTECTED
PUBLIC PRIVATE PRIVATE
PRiVATE PRIVATE inaccesibil
PROTECTED PRIVATE PRWATE Vom incepe explicarea acestor relati: prin doua observatii legate de cuvintul cheie "protected":

el NU poare fi folosit ca "modiflcatorncces" in cadrul unci rclatf de mostcnire;

_ rolulsau sc limireaza laa pcrmite aecesarca in cadrul unci clase.derivate a unor membrii no-publici ai clasci de baza.

Continuam comcntarca tabelului de mai sus cu o alta remarca. Indifercnt de modificatorul de acccs Iolosit, MEMBRII PRIVATI A1 UNEI CLASE DE BAZA NU VOR PUTEA Fl ACCESATI DIRECT IN CLASA DE-

131

RIVATA! Estc 0 rcstrictic Ioartc logica, in lipsa careia s-ar Incalca insa§i ideca de data privata (adica accesibila NUMAl metodelor rcspectivci clase). Dar, NOTA BENE, in ciuda imposibilitatii de a-i aceesa direct, ei continua sa EXISTE in cadruI ficciirci instanticri a clasei derivate. La mornentul potrivit, vorn vedea ca ci pOL fi utilizati PRIN INTERMEDIUL METODELOR MO~TEN ITE tic la clasa de baza (vczi cxcrnplul din scctiuuca "4.7.").

§i Inca 0 observatic: orice "FRU~ND" al unci clasc derivate are exact acclcasi drcpturi ~i posibilita]: de ? accesa mcmbrii clasci de baza ca oricc alt rncmbru al clasei derivate.

Rest ul intormatiilor continutc ln tabcl nu VOl Ii corncntatc, ci cxempllficate in cclc cc urmcaza:

Fxcmplul I

class baza { private : int a;

int f Lj vo i dj : protected :

int bi

int f2(void); public

int c;

int f3 (void) ;

} ;

class derivata 1 int al; protected :

int a2i public

int a3;

int fdl (void) ;

public baza {

} ;

class derivata 2 int ali protected :

int a2; public

int a3;

int fd2(void);

private bald

} i

132

int baza::f3(void) {

a=b=o=O ) / / CORECT: "f3" poatc accesa orice membru al clasei / / "baza"

return a;

}

/ / OBSERVATIE:

/ / In cadrul celor doua mctode descrisc mai jos ("fd 1" si "fd2"),

/ / DREPTlJRILE DE ACCESARE a mcmbrilor clasci de baza sin: / / IDENTICE. Diferentelc apar doar in mornentul accesarii

/ / acestor mcmbri in cadrul unor functii ne-membru.

int derivata_l::fdl(void) {

al=a2=a3=O; / / CORECT

b=f3();

/ / CORECT: "b'' si "{3" sint mostenite cu drept de acces

~=f2 ( ) ;

/ I CORBCT: "c" si "f2" sint rnostenite cu drept de acces

a=f l, ( ) ;

/ / EROARE: atit "a" cit si "flO" sint mostenite, / / dar nu pot fi accesate in clasa derivata

return b+c; / / CORECT

}

int derivata_2::fd2(void) {

al=a2=a3==O; / / CORECT

b=f3();

/ / CORECT: "b" si "13" sint mostenite cu drept de acces

c=f2();

/ / CORECT: "c" si "1'2" sint mostenite cudrept de acces

a=fl ( ) ;

/ / EROARE: atit "a" cit si "flO" sint mostenite, / / dar nu pot fi accesate in clasa derivata

return b*f2()+c*f3()i // CORECT

}

133

void functie(void) {

baza ob_baza; derivata I ob_derl; derivata-2 ob_der2i

ob baza.c=ob baza.a-ob baza.b;

/ I EROARE: doar"ob_bal'.a.c" esteacccsibil in functii ne-rnembru

ob derl.a=ob derl.b*ob derl.c/ob derl.fI()

- +ob-derl.f2()=ob derl.f3();

II EROARE: doar "e" si "f3" sint acccsibile in functii ne-membru

ob der2.b=ob der2.f3()-ob der2.a*ob der2.cl

- ob der2.f2()*ob der2.fl();~

II EROARE: nlci unul din membiii mosteniti ("a","b","e","f1 ", "f2","f3") II nu este accesibil in functii ne-membru (in cazul nostru "functie")

}

Deoarece 0 clasa derivata poate fi folosita la rindul ei ca ~i clasa de baza, este necesara 0 atentie deosebita in alegerea celei mai bune cornbinatii "tip membri"-"modificator acccs", In cal'. contrar, exista riscul ca cventuala class "nepot" sa piarda mutt din elasticitarc.

o remarca foarte interesanra line de lstoricul C+ +: versiunea inilialli "AT&T 1.00" nu con linea cuvintul c":.liIie "protected". Stroustrup argumenta imposibilitatea de a accesa direct dintr-o c1asa derivata un membru ne-public al clasei de baza prin incalcarea statutului de membru privat (adica accesibil DOAR clasei ee-l poseda), Totusi, in cele din urma, necesitarea de a mosteni §i manipula membri ne-publici ai claselor de baza a dat nastere compromisului numit "protected". Din cxemplul de mai jos reies c:iteva caracteristlci deosebit de importante in utilizarea claselor derivate,

Exemplul z

class BAZA { II ... public :

int contorl,contor2;

void fl (void) { II

}

void f2 (void) { II ...

134

}

void f3(int a) { II

}

} ;

class DERIVATI II ...

public :

int contor 1; II definirea unui nou mernbru, purtind acelasi II numc cu eel mostenit

public BAZA{

II urrneaza redefintrea mctodelor clasei de baza

void fl(void) { II

BAZA: :fl ();

II apelul metodci "fI" a clasci "BAZA"

}

int f2(void) { II

BAZA: :f2 ();

II apelul merodei "fl" a clasei "BAZA"

return 0;

}

void f3(void) {

BAZA::f3(lO)i

/ / apelul metodei "f3" a clasei "BAZA"

} } ;

class DERIVAT2

BAZA{ / / implicit, modificatoruldc acces II Iolosit este "private"

1/ .•. public

BAZA: : contor 1 i

BAZA: : f 1; II cei doi membri mostcniti sint FORT ATi sa II REDEVINA "public"

} ;

void main(void) {

135

DERIVATl dl; DERIVAT2 d2;

dl.fl(); d1.f2()i

II apclul merodclor redefinite

dl . BAZA: : f 1 ( ) ; II apelul mctodei clasei "BAZA" d 1 . contor 1==0; II initializarca noului mcmbru

d I . BAZA: : contorl=l; II innializarea membrului mostcnit

az , fl ( ) ; d2.contorl==0;

I I utilizarea mcrnbrilor mostcniti si

I I FORTATI sa REDEVINA "public"

I I EI~OARE; mcmbru prlvat inaccesibil I I EROARE: membru privat inaccesibil

d2. f2 ( ) ; d2.contor2=1; }

in primul rind vorn analiza clasa "DERIVATl ". Se obscrva ca, in cadrul aces lei clase, vor fi rcdefinite ("OVERLOADED") toare cele trei metode ale clasei de baza. 0 facilitate de limbaj pcrmite lnsa accesarea de catre orice rncmbru al clasci "DERIV AT1" a tuturor aces tor metode apartinind clasci "BAZA" §i redefinitc in "DERIV AT1". Modul de Iolosire cste foarte simplu: . Iuncjia in cauza va fi pr~efixata cu numcle "clasci posesoare" (in cazul nostru "BAZA") urrnat dc ''::''.In lipsa prefixarii cu "BAZA::", efectul va fi ori un apel recursiv infinit (in caw! merodelor "I l" si "(2"), ori 0 eroarc la compilarc (este cazul lui'B", care e apelata cu un parametru lntreg, desi a fost redefinita ca neavind nici un parametru).

Trebuic amtnrit inca un lucru: ~qi in clasa ;;DERIVAT1" am adaugat inca 0 variabila membru "contorl ", in functia "main" pot fi accesate AMBELE variabile "contor l" (atit cea mostcnita, cit §i cea adaugata), Difcrcntierea lor se va face prin prcfixarca variabilei mostenitc cu "BAZA::",

Yom mai sublinia ca prefixarea cu numclc clasei de haw este valabila §i in afara corpului mctodclor (vezi cazurile instructiunilor "d l.BAZA::f1 0;" §i

lid l.BAZA::contorl = 1 ;"). '

A doua noutate este legata de clasa "DERIV A TI". Exprimarea, oarecum ciudata

public :

BAZA: :contorl; BAZA:: fl;

136

nu ascundc nimicaltccvadcclt posibilitatea dea forta unii membriisa redevinii de tip "puhlic". Acest lucru va fi facut IN CIUDA REGULILOR DE MODIFICARE A DREPTULUI DE ACCES din cadrul mecanismului de mostenire (deci nu estc nicidecum yorba de adaugari de noi mcmbrii). A§a cum se vcde, atit in cazul variabilelor cit §i in eel al metodelor, este suficienta simpla prccizare a numclui-si a clasei de provcnienta, De altfel, inccrcarea de a da mai multe arnanunte desprc respectivul mernbru (tipul variabilei, respcctivvaloarca returnatasau tipul parametrilorjva Iisernnalizata cacroare de cornpilare. De rcmarcat Iaptul ca, in acelasi mod, se poate efectua §i fortarca membrilor ce si-au picrdut calitatea de a Ii "protected" (datorita unci mostcniri de tip privat). Exista ~i () rcstricue: indifcrcnt de modilicatorul de acces utilizat, NU ESTE PERMISA transforrnarea unui mcmbru PRIV ATal clasci de baza intr-unul PUBLIC sau PROTECTED al cclci derivate.

o alta chestiune cc trcbuie discutata consta in posibilitatea de a se efectua convcrsii de tipul "B*"->"D*" §i "D*"->"B*", unde prin "D" am notal 0 clasa dcrivata din clasa "B". Excmplul de mai jos nc va lam uri pe dcplin asupra problemei,

Exemplul J

class baza{ II

} ;

class derivat 1 II

} ;

class derivat 2 II

} ;

void main{void)

{

baza "I< derivat 1 * derivat-2 *

pb==&odl;

pb=&od2,

pdl=&ob; pd2=&ob;

public baza{

private baza{

pb,ob; pdl,odl; pd2,od2;

II CORECT: conversie implicita

II EROARE: nu sc poatc face conversie implicita

/ I EROARE: nu se poatc face conversie implicita II EROARE: nu se poatc face conversie implicira

137

pb= (baza*) &od2; II CORECT: conversie explicita

In cazul destructorilor, problema se va pune exact invcrs: m~i lntii v~ fi apclat destructorul clasei derivate §i abia dupa accea eel al clasei de baza.

Exemplul I

* include <iostrearn.h> * include <conio.h>

pd l e (derivat _1 *) &ob; / / CORECf:convcrsieexplicita pd2=(derivat_2*) &ob; / / CORECf:convcrsieexplicita }

Reluind, in cuvinte, cele prezentate in cadrul exemplului, conversia "DERIVAT*"->"BAZA*" se poate face:

IMPLICIT: -daca "DERIVAT" mosteneste pe "BAZA" in rna-

niera publica;

EXPLICIT: - daca "DERIV AT" se obtine din "BAZA" prin mos-

tenire in rnaniera privata.

Conversia "BAZA *"->"DERIVAT*" nu poate fi faeula DEelT EXPLICIT, prin operatorul"east".

Sa analizam urmatoarea siruatie: 0 variabila publica a clasei "baza" devine privata in urma mostenirii ei dc catre clasa "derivat 2". Facind 0 conversie "derivat , 2*" -> "baza*".;printr-un pointerla "baza'vom: pureaaccesa respectiva variabila ea §i cum ar fi publica. Cu reate ca acest lucru este POSIBIL, nu este DELOC RECOMANDABIL. Il!.aint~ de a rrece rnai dcparte, vom relua avertismentul lui Bjarne Stroustrup: INCALCAREA DREPTURILOR DE ACCES PRIN CONVERSII EXPLICITE lntre poinreri 11;1 tipuri de date legate Intre ele prin relajia de mostenire risca sa duca la erori aproape imposibil de detectat. De aceea, in astfel de situatii, se recomanda 0 atentie deosebita,

class BAZA

{

protected int conti public :

void SET CONTOR(int val) { cont=vali } void TYPE CONTOR(Void) {

cout«--;;\n\n CONTOR == "«cont;

}

BAZA(int a=O) {

SET CONTOR(a)i

cout« "\nConstructorul cl.asei BAZA;\t CONTOR~"«cont«n\n"i

}

4.4. Constructori ~i destructorl

-BAZA(Void) {

cout « "\nDestructorul clasei BAZA;\t

CONTOR="«cont«"\n";

Avind in vedere ea scopul clasclor derivate este acela de a permite adaugarea unor noi caracteristici unei clase de baza, este evident ca, in general, clasele derivate vor aveanevoie de constructori mai complecsi decit cele de baza, Pentru a usura cit mai mull munca programatorului ~i pentru a avea 0 cornportare cit mai naturala, constructorti claselor de baza vor fi apela]i IMPLICIT, daca acest lucru este posibil.In caz contrar, va fi neccsara specificarea EXPLICIT A a parametrilor in cauza (vczi exemplul de mai jos). Specificarea explicita a parametrilor constructorului se poate face §i daca programatorul doreste apelul unui constructor anurne din setul de construetori at rcspectivei c1ase. Deoarece 0 clasa derivata nu este altceva decit 0 constructie "in jurul" unci clase de baza, este logic ea mai lntii sa se apeleze constructorii claselor de baza, ~i doar dura aceca sa se execute actiunile specifice constructorului clasei derivate.

}

} ;

class DERIVAT

public BAZA

{

public :

DERIVAT(Void) {

/ / se va apela IMPLICIT "BAZA(O)" !

cout « "constructorul clasei DERIVAT\n";

DERIVAT(int b)

BAZA(b) 1/ apelexplicitaleonstructorului / I clasei de baza

{

138

139

cout « "Alt constructor al clasei DERIVAT\n";

}

void TYPE_CONTOR(Void) {

CQut«"\n\n contorul clasei derivate este " «~conti

}

-DERIVAT(void) {

}

cout « "\nDestructorul clasei DERIVA'l';";

} ;

void main(void)

{

c l.r s c r ( ) i

BAZA bl,b2=1;

DERIVAT dl,d2(2),d3(3);

BAZA * pbl; DERIVAT * pd l :

pbl=&b2; pdl=&d2;

pbl->TYPE CONTOR(); pdl->TYPE=CONTOR();

cout«"\n"i

}

I

p'ult.~m u~mari ordin~a excc~tarii proccdurilor programului ghidlndu-ne dupa succcsiunea mesajclor afisatc pc ccran:

Constructorul clasei BAZA; CONTOR= 0

Constructorul clasei BAZA; CONTOR= 1

Constructoru! clasei BAZA; CONTOR= 0 Constructorul clasci DERTV AT

140

Constructorul clasei BAZA; CONTOR= 2 AU constructor al clasei DERIVAT

Constructorul clasci BAZA; CONTOR= 3 All constructor al clasei DERIVAT

CONTOR = 1

Contorul clasci dcrivate cstc 2

Destructorul clasei DERIVAT; Destructorul clasei BAZA; CaNTOR = 3

Destructoru I clasei DERIV AT; Dcstructorul clasei BAZA; CONTOR= 2

Destructorul clasei DERIV AT; Destructorul clasei BAZA; CONTOR= 0

Destructorul clasei BAZA; CONTOR= 1

Destructorul clasei BAZA; CONTOR= 0

La al doilca constructor al clasei dcrivate se vede foarte bine modul in care se vor specifica parametrii nccesari constructorului clasei de baza. De notat doar ci exprimarea

DERIVAT(int b) : BAZA(b)

nu era necesara, ca putind fi inlocuita de mai simpla DERIVAT(int b) : (b)

In acest moment trebuie sa atragem atentia asupra unui aspect prezentat in exernplul de mal jos.

Exemplul Z

class B

{

B ( ) ; -B ( ) i } ;

II EROARE: constructor inaccesibil clasei D II EROARE: destructor inaccesibil clasei D

class D B {

} ;

141

Analizind cele doua struciuri de date, tragem urmatoarea concluzie:

ESTE INTERZIS CA IN CADRUL UNEI CLASE BAZASA A VEM TOTI CONSTRUCTORB SAU DESTRUCTORII PRIVATI. Avind in vedere gravitates unor astfel de siruajii (in care clasa derivata NU POATE ACCESA CONSTRUCTORUL SAU DESTRUCTORUL clasei de baza), erorile de acest gen vor fi semnalizarc in faza de compilare.

Desi pare rara sens, existcnta unor constructori ~i destructori privati nu este lipsitade interes, Nu uirajl.acestemetodevor putea fi Iolosite-deexcmplu - in cadrul unor functii "friend".

Cu titlu de observajie, sublinicm ca destructorii ~i constructorii clasclor de baza NU SE MO$TENESC. Cu toate acestca, accesibilitatea lor este esenjiala in procesul crearii §i distrugerii instanticrilor clasei derivate.

Incheiem accasta sectiune prin analiza crearii dinarnice de obiccte fara a utiliza mecanisrnul implicit de gestionarca mernorici. Pentru injelcgerea exemplului ce urrneaza trebuie sa mai Iacern 0 sublinierc. Daca in cadrul constructorului unci clasc derivate se face 0 atribuire de genu I "this= ... ", constructorul clasei de baza nu va fi apelat pina in momentul in care atribuirea a fost facuta. Lucrurilese petrec astfel deoarccevaloarea lui "TI-IIS" in cadrul constructorului clasei de baza va fi una §i aceeasi eu cea atribuita lui in constructorul clasei derivate. Reciproc, daca in cadrul constructorului clasei de baza i sc atribuie lui "THIS" 0 valoare, ea va fi folosita in continuare ~i in constructorul cia sci derivate!'!

Exemplul J

# include <iostream.h> * include <process.h>

exit(l);

}

else t.h.i e=t.h i.s j / / Nu cste inutil: vezi obscrvatia de / / la sfirsitul exemplului !

cout«"\n\t\t La iesirea din BAZA() this=" «this;

} } i

class DERIVAT public :

public BAZA{

DEIUVAT (void * pointer=NULL) {

cout«"\n\t\t La apeJul constructorului DERIVAT() this=" <this;

if (this==NULL)

if(pointer!=NULL) this=(DERIVATk) pointer; else

{

cout«"\nEROARE: in \"DERIVAT(}\" se forteaza this=NULL";

exit (1) i

}

else this=this i / / Nu cstc inutil: vczi obscrvatia de / / la sfirsitul exemplului !

class BAZA { public :

cout«"\n\t\t La iesirea din DERIVAT() this=" «this«"\n\n",

BAZA(Void * pointer=NuLL) {

} } ;

cout«"\n\t\t La apelul constructorului BAZA() this="«thisi

void main(void)

if (this==NULL) if(pointerl=NuLL) this=(BAZA *) pointer; else

{

{

cout«"OBIECTE CREATE FARA ALOCARE DINAMICA DE

MEMORIE ;\n\n", cout<" \tBAZA" i

BAZA bl; cout«"\n\tDERIVAT\n"; DERIVAT dl i

cout«"\nEROARE in \"BAZA()\" se forteaza this=NULL";

142

143

CQut«"OBIEC'fE CREATE PRIN ALOCARE DINAMICA DE MEMORIE : \n \n" ;

CQut«"\tBAZA" ;

new BAZA( &bl) i

DERIVAT

CQut«"\n\tDERIVAT\n"; new DERIVAT(&dl);

La apclul constructorului DERIV ATO this;::OOOO:OOOO La apclul constructorului BAZAO this=8FD3:0FFC La icsirca din BAZA() this::::SFD3:0FFC

La iesirea din DERIV ATO this=8FD3:0FFC

Ceca ce merita intr-adevar analizat in accst exemplu este constructorul clasei DERIY AT. Sc observa cli "IF"-ul care testcaza valoarea lui "this" arcoarecum surprinzator - doua ramuri. Pc una se face atribuirea necesara in caz ea "TIIlS" este NULL, iar pc cealaltil se.va face 0 atribuire de tipul

this::::this;

Desi nu are nici un scop aparent, aceasta atribuire este FUNDAMENTALA in cazul in care constructorul poate Ii apelat atit eu "THIS!:::: NULL", cit §i cu "THIS::::;:: NULL". Yom justifica afirmajia prin Iaptul ca, daca in cadrul constructorului clasei derivate se va detecta (LA COMPILARE) 0 atribuire catre "THIS", apclul constructorului clasei de bazase va face DOAR DupA EFECTUAREA ATRIBUIRII. Ei binc, de aid apare ~i problema. Soar putca ca atribuirea unei valori catre "this" sa se faea DOAR PE· 0 SINGURA"RAMURA"APROGR1MULUI (de exemplu.Intr-un IFsefacc doar pc "THEN", nu §i pe "ELSE"). In cazul in care executia va avea loc pe o alta ramura, apclul constructorului clasci de baza nu se va mai executa DEOARECE NU S-A ATINS INSTRUCTIUNEA CONTININD ATRIBUIREA CATRE "THIS". lata deci 0 problema cu totul nea~teptata,~i care poate da multa bataie de cap programatorului ncatcntl

/ / Ficcare din cdc patru crcari dinamice dc obiecte ce apar in continuare / I ar provoca oprirca cxecutiei programului. Aces! fapt s-ar datora

/ / atingerii apclurilor Iunctiei "exit" din cadrul celor dol constructori

/ I ("BAZA"si"DERIVAT").

/ / new BAZA(NULL);

/ / new BAZAO;

/ / newDERIVAT(NULL); / / new DERIVATO;

}

in urma cxccutici accstui program, pc ccran se vor tipari urmatoarclc rnesaje:

OBIECTECREATEFARAALOCAREDINAMICADEMEMORIE:

BAZA

La apclul construciorului BAZAO lhis=8FD3:0FFE La icsirea din BAZAO this=8FD3:0FFE

4.5. Metode virtuale. utnlzarea llstelor eterogene

DERIYAT

La apelul constructorului DERry ATO this==8FD3:0FFC La apclul constructorului BAZAO this=8FD3;OFFC

La iesirea din BAZAO this=8FD3:0FFC

La icsirea din DERIVATO lhis=8FD3:0FFC

Prin definipe, un tablou omogcn este un tab lou conjinind elemente de acelasi tip (de cxernplu, un sir de lntregi). Dupa cum am spus, un pointer la 0 clasa "B" poate pastra adresa oricarei instanjieri a vreunei clase derivate. din "B". Deci, avind un sir de polnteri la obiccte de tip "B", insearnna ca, de fapt, putem lucra §i cu tablouri neornogcne. Astfel de tabla uri neornogenc sc VOl' nurni ETEROGENE. Una din caracteristicile limbajului C+ + consta tocmai in Iaptul cii MECANISMUL FUNCfIILOR YIRTUALE PERMI1E TRATAREA UNIFORMA (sau, daca prefcrati, IN ACEEA~I MANlERA) a tuturorclementclorunuimasivdedatcETEROGENE! Acestlucr u este posibil datorita unor Iacilitii]! ce tin de asocieri Iacute DOAR IN MO,', MENTUL EXECUTIEI PROGRAMULUI, nu in momentul compilariil

Yom face 0 scurta parantcza pcntru a explica mal pe larg aceste principii gcncrale. Sa presupcm ca avern 0 clasa "B" care poseda 0 metoda publica "M", Din clasa "B"vom deriva mai multe clasc "01 ", "D2", "D3", ... , "Dn", Daca claselc

OBIECTE CREATE PRIN ALOCARE DINAMICA DE MEMORIE ;

BAZA

La apelul constructorului BAZAO Ihis=OOOO:OOOO La iesirca din BAZAO this=8FD3:0FFE

144

145

{

pB=Pi t=tPi

}

void tipareste_valoare(void) {

if( t== BAZA ) PB->tipareste_valoare(); else

if( t== _DERIVAT 1 )

«DERIVAT_l *) PB)-> tipareste_valoare()i

}

else «DERIVAT_2 *) PB)-> tipareste_valoare()i

} i

void main(void)

{

BAZA af2] i DERIVAT 1 bI; DERIVA'() b2;

LISTA_ETEROGENA p[4];

a{O].set_val(l); a [ 1] • set val (2) ; bl. set_val (3) ; b2.set_val(4);

p[O].set(&a[O]);

p [1] • Bet (&bI, _DERIVAT_l) ; II se face () conversie IMPU~

II CJTA "DERIVAT_l"-> "BAZA*"

p[2).set«BAZA *) &b2,_DERIVAT_2)i II sefaceoconverII sic EXPUCITA "DERJVAT_2"-> "BAZA*"

p [ 3] . set ( &a ( 1 ] ) ;

for(int i=O;i<4;i++) p[ij.tiparests_valoare(},

148

}

In urma executici programului, pe ecran se vor afi§a urmatoarele mesaje:

Element BAZA eu VALOARE = 1 Element DERIV A'T' 1 eu V ALOARE = 3 Element DERIV AT 2 cu V ALOARE = 4 Element BAZA cu V ALOARE = 2

Pentru a reusi 0 tratare uniforma a cclor trei tipuri de obiecte a Iost necesara crearea unci a patra clase, nurnita LlSTA_ETEROGENA. Aceasta va pastra atit pointer-ul la obiectul respcctiv, cit §i 0 informatie suplimcntara, referitoare la tipul obiectului reterit de pointer. Tiparirea informatiilor semnificative ale Iiccarui obiect sc va face in metoda "L1STA ETEROGE-

NA::tipareste_valoarea". in aceasta Iunctie membru sin! ncccsarc 0 seric de teste pentru apclul mctodci corespunzind fiecarui tip de obiect. Va dati seama ca, daca in loc de 3 tlpuri de obiectc am lucra cu 10 sau 100, solutia ar deveni mult mal greoaie §i ne-elcganta.

Reamintim cil in instructiunea p[2].set({BAZA *) &b2,_DERIVAT_2);

operatorul "CAST" este absolut necesar. Conform ector alirmatc in sec]i unea "4.3.", daca dintr-o clasa "B" derivam in maniera privata 0 alta clasa "D", convcrsia "D*"- > "B*" nu se poate realiza dccit de maniera EXPLlCITA.

In Exemplul 2 vom vedea 0 modalitate mull mai simpla §i eleganta de a trata OMOGEN un masiv de date eterogene. In Exemplul l tratarca omogena se referea la instructiunea "for", in care, indiferent de tipul elernentului curent, se va apcla una ~i acceasi Iunctie-actiunc. Se vcde Ioarte binc ca, in aces! C.1Z, cste yorba de 0 pseudo Ira tare uniforrna, deoarece decizia de tratare difercntiata estc aminatii de la nivelullui "for" !a eel al mctodei "UST A_ ETEROGENA::lipares1C_valoarea". lata acurn 0 rezolvare tipic obicctuala a problcmci,

Exemplul Z

# include <iostream.h>

class BAZA { protected :

lnt valoare; public :

void set_val(int a) { valoare=a; }

virtual void tipareste_valoare(void) {

149

cout«"Elernent BAZA cu VALOARE = «valoare<"\n"i

}

} ;

class DERIVAT_l : public BAZA { public :

void tipareste_valoare(void) {

cout«"Elernent DERIVAT 1 cu VALOARE := "

«valoare«"\n";

}

} i

class DERIVAT 2 : BAZA { public :

BAZA: :set_val;

void tipareste_valoare(void)

{

cout«"Element DERIVAT 2 cu VALOARE = II «valoare<"\n";

}

} ;

class LISTA ETEROGENA { BAZA * pBi

public:

void set(BAZA * p) { pB=Pi }

void tipareste_valoare(void)

{

pB->tipareste_valoare();

}

} ;

void main(void)

{

BAZA a [2]; DERIVAT 1 bI; DERIVAT_2 b2;

LISTA ETEROGENA p[4];

150

a[O].set_val(l); a[1].set_val(2); bl.set_val(3) ; b2.set_val(4);

prO] .set(&a[O]); p[I].set(&bl); p[2].set«BAZA *) &b2); p[3] .set(&a[l]);

for(int i=0;i<4;i++) p[iJ.tipareste_valoare()i

}

~i de asta data, pe ccran vor fi afisatc acelcasi inlorrnatii ca §i in cazul

exernplului precedent:

Element BAZA cu V ALOARE = 1 Element DERIV AT 1 cu V ALOARE = 3 Element DERIV AT - 2 cu V ALOARE = 4 Element BAZA eu V ALOARE = 2

Marea diferenta intre cele doua exemple consta in aceea ca, acum, clasa LIST A ETEROGENA are 0 metoda "tiparcste-valoarea" mull simplificata. in nouafunctie mernbru nu mai cstc nccesara nici testarea tipului de pointer memorat §i nici conversia pointcrului memorat la tipul original. ~i toate acestea doar din cauza prcfixaril metodei "BAZA::tiparestc _ valoarea" eu cuvintul cheie "virtual". Practic, in urma intilnirii lui "virtual", compilatorul va lua AUTOMAT§i FARANICI UN ALT AJUTOR DINPARTEAPROGRAMATORULUI toatc dcciziile neccsare pentru electuarea unui "LATE BIND IN G" corect.

Sa incercam sa intclcgcm modul de rcalizare al unui mccanism alit de puternic. Prefixarea unci metode "F" a clasei "B" cu cuvintul cheie "virtual" are urmatorul efect: toate clasele ce 0 mostenesc pe "B" §i nu REDEFINESC functia "F", 0 vor mostcni lntocmai. Sa prcsllpuncm aeum ca 0 clasa "0", derivata din "B", va REDEFINI metoda "F". In aceasta situatie, COMPILATORUL VA AVEA SARCINA DE A ASOCIA UN SET DEINFORMATH SUPLlMENTARE, CU AJUTORUL CARORA SE VA PUTEA DECIDE ~IN MOMENTUL EXECUTIEI) CARE METODA "F" VA FI APELATA

Observatil

a. Deoarece Iunctiilc vir! uale nccesita memorarea unor informatii suplimentare, instanticrilc claselor care au mctodc virtuale vor oeupa in mcmorie mai mult loc dedi ar Ii neccsar in cazul in care nu ar exista dedi metode

151

nc-virruale,

Trebuie arnintite §i doua restricjii: nici FUNCTIILE NE-MEMBRU §i nici METODELE STATICE NU POTFI DECLARATE VIRTUALE!

b. Pentru ca mecanismul merodelor virtuale sa poata tunctiona, metoda in cauza NU POATE FI REDECLARATA in cadrul claselor derivate ca avind aceiasi parametrii §i returnind un ALT TIP de data. In schimb, este permisa REDECLARAREA Cll all set de argumente, dar ell ACELA~I tip de data ret urnat. ATENTIE: in acest caz noua functie NU VA MAl PUTEA FI MO~TENITA MAl DEPARTE CA FUND METODA VIRTUAL~!

e. Evitarca mecanisrnului de ape! uzual pentru functiilevirtuale se face foarte u§or prin prefixarca numelui Iunctici cu numelcclasei apartinatoare urmat de II;:".

Exemplul J

void DERIVAT::fl(void) {

BAZA: :tipareste valoare ( ) i I I apclul metodei din clasa II BAZA

tipareste_ valoare (); I I apelul metodei din clasa DERIVAT }

d. Cu ioate cii nici un constructor nu poate fi declarat virtual, destrucrorii accepts 0 astfel de prefixare, Ea poate Ii deosebit de utlla in cazul in care se doreste distrugerea unfforma a unor masive de dale ererogene, 0 situatie clara de utilizare a unor destructori virtuali vom obtine transformind Exemplul 2 in urmatoarea maniera:

Exemplul-t

# include <iostream.h>

class BAZA { protected :

int valoare; public :

virtual void set_val(int a) { valoare=a; }

virtual void tipareste_valoare(void)

{

cout«"Element BAZA cu VALOARE = .. «valoare«"\n",

}

152

virtual -BAZA(Void)

{

cout«,,\t\t DESTRUCTOR BAZA\n\n";

}

} i

class DERIVAT : BAZA { public :

BAZA: :set val;

void tipareste_valoare(void) {

cout«"Element DERIVAT cu VALOARE :::: "

«valoare<" vn " ;

}

-DERIVAT(Void) {

cout«"\t\t DESTRUCTOR DERIVAT\n"j

}

} ;

class LISTA ETEROGENA { BAZA * pBi

public:

BAZA* get (void) {

return pBi

}

void set(BAZA * p) {

pB=p;

}

void tipareste valoare(void) {

pB_>tipareste_valoare()i

}

} ;

]53

void main(void) {

LISTA ETEROGENA p[4];

p[OJ.set(new BAZA); p[lJ.set«BAZA *) new DERIVAT); p[2J.set«BAZA *) new DERIVAT); p[3J.set(new BAZA);

for(int i=0;i<4;i++) {

(p [ i J • get ( ) ) ->set val ( i ) ; p[i].tipareste valoare(); delete p[i].get();

}

}

In urma exccutici accstui program se VQr afi§a pc ecran urmatoarcle date:

Element BAZA ell VALOARE = 0 DESTRUCTOR BAZA

Element DERIVATcu VALOARE = 1 DESTRUCTOR DERIVAT DESTRUCTOR BAZA

Element OER1VATeu VALOARE = 2 OESTR UCTOR OERIV AT DESTRUCTOR BAZA

Element BAZA cu VALOARE::= 3 DESTRUCTOR BAZA

In acest mod avcm conflrmarca clara a corectitudinii distrugerii uniforme a listci ctcrogcnc de obiccte.

e. 0 lunctic virtualii "F", rcdcfinita intr-o clasa dcrivata "0", va Ii vazuta in noua sa forma de catrc toate clascle derivate din "0". Accasta redefinirc nu alecreaza cu nimic faptul ca ea estc virtuala, metoda raminind In eontinuarc virtuala ~i pcntru eventualele clase derivate din "0".

Exemplul S

# include <iostream.h>

class Bl { pUblic:

virtual void tl(void) { cout«"Bl\n"; };

154

} ;

class B2 : public Bl { public :

void tl(void) { cout«"B2\n"; }

} ;

class B3 } ;

public B2 {

void main(void) {

Bl a; B2 bi B3 Ci

a.tl(); b.tl(); c.tl();

}

Efectul executiei acestui excmplul va fi tiparirea pe ecran a urmatoarelor §iruri de caractere:

Bl B2 B2

Asadar, in clasa "B3", functia virtuala "t l" este "vazuta" a§a cum a fost ea redefinita in clasa tlB2". Vechea forma (cea corespunziitoare clasei "B1") poate fi utilizata asa cum am ararat la punctul"c." al observatiilor,

f. Saconsiderarn excmplul de mal jos:

Exemplul ti

# include am.h

class BAZA { protected :

virtual void tipareste ~esaj(void)

{

cout«"\tMetocia apartine clasei BAZA\n";

}

public

void functie(void) {

155

cout«"\n\n URMEAZA UN MESAJ :\n"; tipareste_mesaj();

}

} ;

class OERIVAT : public BAZA {

void tipareste_mesaj(void)

{

} } ;

cout«"\tMetoda apartine clasei OERIVAT\n";

void main(void)

{

BAZA a; OERIVj\T bi

BAZA * pBi

a. functie (); b.functie();

pB=&a; pB->functie()i

pB::::&b; pB->functie(); }

In urma executiei sale se va afi§a urmatorul text:

URMEAZA UN MESA] :

Metoda apartine clasei BAZA

'URMEAZA UN MESAl :

Metoda apartine clasei DERIVAT

URMEAZA UN MESAl :

Metoda apartine clasei BAZA

URMEAZA UN MESAl :

Metoda apartine clasei DERIV AT

Ce se observa in acest exernplu? Metoda"BAZA::funclie" va fi mostenita

156

class B1
{
/1
} i
class B2
{
1/
} ;
class B3
{ I
II
} ;
class 01 Bl I
{
B2 bl;
157 I de catre clasa DERIVAT. Mecanismul de "LATE BINDING" permite setcctarea corecta a lui "DERIVAT::lipareste _valoare"la tlecare apcl al lui "functie" pentru cite 0 instanjiere a clasci DERIV AT.

g. Cind se pune problema de a decide daca 0 metoda sa fie sau nu virtuala, programatorul va Ilia in considcrarc urmatoarele aspecte:

- In cazul in care conteaza doar perforrnanjele unui program ~i sc exclude intentia de a-l mai dezvolta in continuarc, SE VA OPTA PENTR U METODE NE- VIRTUALE.

- In cazul programelor ce urrneaza sa suporte dezvoltari ulrerioarc, situatia este cu totul alta. Vor fi declarate virtuale toate acele mctodc ale clasei "CLASA" care se considers ca ar putca fi redefinite in cadrul unor clase derivate din "CLASA" ~i, in plus, modiflcarca va trcbui sa fie sesizabila la "nivclul" lui "CLASA"! Toate cclelaltc rnerode pot rarnine ne-virtuale.

4.6.Mo~tenire multlpla

Una din marile noutilii ale versiunii AT&T 2.0 a limbajului C+ + consta in posibilitatea ca 0 clasa sa fie derivata din rnai multcclase de baza. In versiunile preccdente, rclatla de mostcnirc era limitata la doua clasc: cca de BAZA ~j cea DERIVATA. Cu toate acestea, mostenirea multipla se putea sirnula destul de usor:

ExempJul1

I

I

I

B3 b2; } ;

Din punet de vcdcre al structurii de date, clasa"Dl" din exemplul de mai sus este echivalenta cu forma:

class 01 : B1 , B2 , B3

{

/ I ... } ;

Din punct de vedere functional, exista totusi destule diferentc (madul de apel al mctodelor claselor mcmbre, ditlculta]! in lucrul eu masive de date ctcrogenc etc.), Tocmai de aceca, noua vcrsiunc elim)nii accstc neajunsuri, acceptind existents relatici de mostcnirc multipla. In acest mod, o clasa dcrivata va pUlca avea SIMUL T AN mai multe clase de bazal

Exista, totusi, §i uncle restrictii;

a. 0 clasa derivata "0" nu poatc mosteni direct de doua ori aceeasi clasa "B". Motivul este foarte simplu: nu s-ar putca face distincjia Intre mcmbrii purtind acelasi nume!

Exemplul z class B {

/1 } ;

class D

{ } ;

/ I EROARE II!!

B , B

b. 0 clasa dcrivata "0" nu poate mostcni simultan 0 clasa "B" §i 0 alta "01 It, dcrivata (direct sau nu) tot din "B". De asernenea, nu pot fi mostenite simultan doua clasc "01" ~i "02" aflate in rclatia de mostenire "B"->". _> "Dt"- > ... ->"02".

Exemplul J class B {

1/ } ;

class c1 {

} ;

B

158

class C2
{
} ;
class 01
{
} ;
class 02
{
} ; c1

B I C2

1/ EROARE

C1 r C2 I I EROARE

In schimb, 0 mostenire de gcnul

class B {

1/

} ;

class 01
{
} ;
cl~ss 02
{
} ; B

B

class 0

{ } ;

D1 , D2

cste gasita corccta de catre compilatorul C+ +.

Pentru a realiza 0 mostenire multipla, esre suficient sa specificam dupa numele clasei derivate 0 !isla de clase de baza separate prin virgula (evident, aceste nume pot fi prefixate cu modificatorii de acces "public" sau "private"). Dar atentie: ordinea claselor de baza nu este indiferental Apelul construetorilor respectivi se va face EXACT in ordinea enumerarii numelor claselor de baza! Relulnd observatia de la mostenlrea simpla, apelul constructorilor tuturor claselor de baza se va efectua INAINTEA eventualelor initlalizar! de variabile-membru, Mai mult, asa cum vom vedea in exemplul de mai jos, aceasta ordine nu poate fi modlficata nici chiar de ordinea apclului explicit al constructorilor claselor de baza in cadrul eonstructorului elasei derivate.

159

Exemplul4 class Bl {

char Ci

public :

Bl(char cl)

c(cl) {}j

} ;

class B2 {

int c;

public

B2(int cl)

c j c L) {};

} ;

class D : Bl I B2 {

char a,b;

public :

D(char al,char bl) : a t a L) , bj b L) B2(int) all , Bl(bl)

{

/ / ...

} } ;

void main(void) {

o d(3,3)j }

Dupa cum am mai afirmat, indifcrcnt de ordinca initializari! variabilelor ~i de eea a constructorilor specifica]i explicit, in definirea constructorului clasei derivate

o(char aI,char bl) : a(al) , b t b L) B2«int) a Lj j Bl(bl) 0;

succesiunca operatiilorva fi urma toarea.apetul constructorului "Bt''.a poi "B2" si, doar la sflrsit, inijializarea lui "a" ~i "b".

o situatie cu totul particulars 0 au asa-zisele CLASE VIRTU ALE,

160

Pentru a avea 0 imagine cit mai clara asupra problernci, sa analizarn exemplul de mai jos:

Exemplul S
class B
{
} i
class Dl
{
}i
class 02
{
} ;
class Ml
{
} ;
class M2
{
} ; virtual 01 , virtual public 02

B

B

01 , public 02

In urma mostenirii claselor "DJ" §i " D2", clasa "M1" va ingloba doua obiecte de tipul "B" (cite unul pentru Iiecare din celc doua clase de baza). In practica, ar putea exista situatii in care cste de dorit mostenirca unui singur obiect de tip "B", Penrru aceasra se va proceda ca §i in cazul clasei "M2": se vor prefixa cete doua clase de baza cu cuvintul chcic "virtual", In urma acestei decizii, clasa "M2" nu va contine decit 0 SINGUR1\ INSTAl''lfTIERE A CLASEI"B",

Desi 111 aparcnta totul e dcosebit de simplu, se mai p031e pune 0 intrebare: carei clase li va apartinc acea unica instantiere? Lui D1 sau lui D2'! INSTANTIEREA IN CAuzA VA APARTINE LUI "Dl", adica PRIMEI fLASE VIRTUALE (din Iista claselor de baza) care a fost derivata din "B". In urrna analizel exernplului de mai jos vc{i remarca un fapt dcstul de ciudat, pc care-l vom cementa in coruinuare.

Exemphrl e

class B

{

public: int i;

} ;

16]

class DI
{
} ;
class D2
{
} ;
class 1-11
{
};
class M2
{
} ;
class M3
{
} ; public B

public B

public D1 , public D2

virtual public Dl , virtual public D2

virtual public D2 , virtual public 01

void main(void) {

MI mI; M2 m2; M3 m3;

mI.DI:: i==O; m l . 02: : i=1;

m l . i=O; II EROARE: exprimare arnbigua (de care din cei II doi membri "i" e vorba ? )

m2.01::i=4;

m2 • D2 : : i=5;

m2.i=6;

m3.Dl::i=7;

162

/ I CORECT

II ATENTIE: desi nu se semnalizcaza croare la I I compilare, accasta insrructiune NU

II ARE NYC} UN EFECT I!!

II CORECT: e cchivalent cu "m2.Dl::i= 6;"

/ I ATENTIE: desi nu se semnalizeaza eroare la / I cornpilare, aceasta instructiunc NU

II ARE NICI UN EFECT I!!

r

m3.D2::i=8;

II CORECT

m3.i=9;

II CORECT: e echivalent cu "m3.D2::i= 9;"

}

Concluzia este urrnatoarea: de§i conform regulii enuntate mai sus variabila mernbru apartine instantierf primei c1ase din lista clasclor de baza, cele doua farFle "m2.D2::i" §i "m3.Dl::i" nu vor Ii tratate de catre compilator ca §i erori, In schimb, efectul lor Uu va fi eel scontat, adica atribuirile in cauza nu vor fi cfectuate (instruqiunlle respective neavind deci nici un cfect). Atentie deci la aceasta sursa de erori! Cea rnai simpla modalitate de (1 evita astfel de ncplacen consta in a renunja la prefixarea variabilci mcmbru eu clasa de care aparpne. In acest mod nu potaparca ambiguitaji.deoarecevarlahtlaesn, unica.

Definirea elasclor virtuale n'l obliga sa modificiim regulile de apelare a construcronlor claselor de baza. In cdc ce urmcaza vam prezenta noul set de reguti;

Rl. Constructorii claselor de bazd virtuale vor fi ape/ali iNAINTEA celor corespunzind unor clase de bazii ne-virtuale.

R2. in caz cii a clasd posedii mai multe clase de bazd virtuale, constructorii lor

. vor fi ape/ali in ordinea in care clasele au fast declarate in lista claselor de haul. Apoi vor fi ape/a!l (tot in ordinea dcclardrii] constructorii claselor de bazd ne-virtuale, §i abia in cele din urmd constructorul clasei derivate.

R3. Chiar dacd In ierarhia claselor de bazii exisui mai mutt de 0 instaruiere a unei clase de bazd virtu ale, construetorul respectiv va fi ape/at doar a singurii data.

R4. Dacd, in schimb, in aceeasi ierarhie existd attt instaruieri virtu ale eft §i ne-virtuale ale unci aceleiasi clase de bazii, constructorul va fi ape/at:

o SINGURA DATA - pcntru toate instaupentevtrtuate;

DE"N"ORI

- pcntru cdc ne-virtuale (daca exista "N" instanticri de aces! tip).

Pcntru a cia ri fica lucrurilevom oferi un cxemplu mai complex, pccarevom studia modul de aplicare a acestor reguli,

Excmplul7 int i=O;

class Bl {

pUblic:

B1 (void) { i++;

163

}

} ;

class B2 { public:

B2(void) { i++i

}

} ;

class 01

public 81 , virtual public B2

{ public:

01 (void) { i++i

}

} i

class 02

public 81 , virtual public B2

{ public:

02 (void) { i++i

}

} ;

class M : public 01 , virtual public 02 {

public:

M(void) { i++i

}

} ;

:; void main(vol.d)

{

M m l j

}

On.linea in care VOl' fi apcla]i construcrorti cste urrniitoarea:

B2 (); Bl t ) i

164

02 ( ) ; 81 ( ) ; D1 ( ) ; M();

Ea corcspunde intru totul regulilor en un late mai sus. In incercarea de a construi 0 instanjiere a clasei "M", se vor analiza cele doua clase de baza. Deoarece "02" este clasa de baza virtuala, se va incepe prin a crea un obiect de tip "02" (regula "RI "). Pentru aceasta va f necesara crearea unor obiecte de tip "B 1 " §i "B2". Se va incepe cu "B2" deoarece estc yorba lot de 0 clasa de baza virtuala (aceeasi rcgula "Rl"). Din aceste motive, primul constructor apelat va fi "B2", urmat apoi de "BI". In acest moment va putea fi apelat §i constructorul "02", deoarece au fost deja create instanjierile claselor sale de baza (regula "R2"). In fine, pentru a crea un obiect de tipul "D 1" ar trebui sa se inceapa cu crearea unui obiect de tip "B2". Cum clasa de baza "B2" este virtuala, §i, in plus, exista deja 0 instanjierc a acestei clase, sc va renunta la crearea obiectului de tip "B2" (regula flR3"). Se va continua, deci, Cll apelul constructorului clasei de baza !lBI ", urmat apoi de constructorul "D1" (regula "R2"). Deoarece in aces! moment sint create instantierile claselor de baza "01" §i "02", se va putea apela §i constructorul clasei "M" (tot regula "R2"),

in inchcicrea sectiunii prezentam inca un cxemplu de functionare a mecanismului de mostenire multi pla.

Exemplul S

* include <graphics.h> # include <conio.h>

class arc de cerc

{

int xc,yc,raza,unghi start,unghi sfirsit;

public: --

arc_de_cerc(int x,int y,int r,int ul, int u2) {

xc==x; YC=Yi raza=rj

unghi start=ul; unghi=sfirsit=u2;

arc(xc,yc,unghi start,unghi_sfirsit,raza);

}

}

165

class linie

{

int xl,x2,yl,y2j public:

linie(int a,int b,int c,int d)

{

xl=a; yl=b; x2=c; y2=d;

line(xl,yl,x2,y2}i

}

} ;

class cap

public arc de_cerc

{

public : 'f . t

cap(int xc,int yc,int u_st,l.nt u_s ,1n

arc de_cerc(xc,yc,u_st,u_sf,raza)

raza)

{ }

} ;

I

class ochi_stinga

public arc de_cerc

{

public : " t u sf

ochi stinga(int xc,int yc,l.nt u_st,l.n f- ' )

int-raza) : arc de_cerc(xc,yc,u_st,u_s ,raza

{ }

} ;

class ochi dreapta

public arc de_cerc

{

public : 't f

ochi dreapta(int xc,int yc,int u_st,l.n u_s ,

- arc de cerc(xc,yc,u_st,u_sf,raza)

int raza) :

{ }

} ;

166

, i !

class gura public arc de cerc {

public :

gura(int xc,int yc,int u_st,int u_sf,

int raza) : arc_de_cerc(xc,yc,u_st,u_sf,raza)

{ }

class fata umana

cap,ochi stinga, ochi_dreapta,gura,linie

{

public:

fata umana(void) : cap(lOO,lOO,50,O,360),

- linie(lOO,lOO,lOO,llO),

ochi stinga(80,90,3,O,360)~ ochi=dreapta(120,90,3,O,360), gura(lOO,lOO,40,240,300)

{}

} ,

void main(void)

{

int gd,gm;

detectgraph(&gd,&grn); initgraph(&gd,&grn,"\\compiler\\t_pascal\\bgi");

fata umana f;

getch ( ) ; closegraph(); }

Neputind forta clasa "fata_umana" sa mosteneasca de patru ori clasa "are_de _cere", vom reeurgc la un subterfuglu: dineavomderivaalte patru clase t ("cap", "oehi_stinga", "ochidreapta" §i "gura"), pc care Ie vom utiliza ca §i clase de baza ale clasei "fata umana",

4.7. Problema rezolvata, Exercilii propuse spre rezolvara

In cclc ce urmeaza, vom prezenta un exemplu care are drept scop fa-

167

miliarizarea cu situajli de 0 complexitate mai apropiata de cea reala, Ideea programului este de a implementa 0 familie de Ierestre graficc, care sa faciliteze §i sa faca mal placut dialogul cu utilizatorul,

In esenta, programul consta intr-un PROJECf campus din 4 module: clasele "settings", "general , window", "error window" §i fi§ierul "main.epp" (contine programul principal care utilizeaza celc trei clase). Vom incepe sa analizam pc rind fi§ierele in cauza, primul fiind "headert-ul clasei "settings";

/*FISIERUL STING_CL.DAT */

class settings {

int cu L, bkcul; I I culoare de scriere si de fond

struct textset:.tingstype txt; I I atributele de scriere ale I I textelor

struct f illsettingstype f 11; I I atributele de umplere

protected:

void put_settings(void)i /1 setare atribute mod grafic void get_settings(void)i II salvare atribute mod grafic

} ;

Acest "header" va fi indus in t1~ierul "SETIINGS.CPp·, care contine definitiile metodelor declarate in "STTNG _ CL.DAT". Clasa "settings" arc ca seop implementarea unor rutine de salvare/restaurare a caracteristicilor modului de lucru grafic (culoare de desenare §i fond, caracteristlci de scriere §i de umplere).

j*FISIERUL SETTINGS.CPP */

# include <graphics.h>

# include "sttng_c1.dat"

I I Restaureaza atributele modului de lucru salvate anterior void settings::put_settings(void)

{

settextstyle(txt.font,txt.direction,txt.charsize); settextjustify(txt.horiz,txt.vert)i

setfillstyle(fll.pattern,fll.color); setcolor(cul)i

setbkcolor(bkcul);

}

168

1

~,

j

f

I

I

I

I I Salveaza atributele curente de lucru in mod grafic void settings::get_settings(void)

{

gettextsettings(&txt)i getfillsettings(&fll); cul=getcolor(); bkcul=getbkcolor();

}

In continuare, vorn analiza descrierca unci clase numita "GENERAL_ WIN DOW",dinearesa putern deriva maiapoi toate tipurile de ferestre doritc, Yom inccpe eu "header'-ul care coniine declaratia clasei:

/*FISlERUl.. G_WIN_CL.DAT */

1

# include "sttng_cl.dat"

# define MARGINE 8

class general_window

settings {

private:

I I VARIABILE MEMBRU .

int x , y, dx , dy; I I coordonatcle colturilor stinga-sus si 1/ dreapta-jos ale Icrestrci

void * store_image; 1/ pointer la imaginea peste care I I va fi descnata Iereastra

int image_size;

II lungimea (ocicti) zonei de mai sus

. char * window_name; I I pastreaza nurnele fcrestrei

int name_size; II lungimeanumcluifereslrci(nr.caracterc)

// METODE .

void set _ xy (void) i I I initializeaza coordonatele colturilor / I ferestrei

void get_image (void); / I salvare imagine de sub Iereastra void draw_window (void), I I descnare fcreastra

169

I

int bad_coord (int, int, Lrrt , int); / / verifica coord. colturi

protected :

void my_settings (int, int); / I scteaza caracteristici mod / / de lucru grafic

settings::get_settings; settings::put_settings;

/ / Iorteaza la "protected" cele / / doua metode mostenite

public :

void create window(char *,int=-l,int=-l,

- int=-l,int=-l)i

void destroy_window(void)i

} ;

Fiserul "GNRL _ WIN.CPP" va continc definijii]« metodclor clasei "general , window".

J*GNRL_WIN.CPP*/

# include <iostream.h>
# include <graphics.h>
# include <process.h>
# include <string.h> # include "g_win_cl.dat" / / coniine dcclaratia clasei

/ I Salvare imagincde "sub" Icreastra

void general_window::get_image() {

image slze=imagesize(x,y,dx,dY)i store-image=new char[image size]; if (store image==NULL) { -

cout«'7.out of memory in GENERAL_ WINDOw\n"; exit(l);

}

getimage(x,y,dx,dy,store_image); }

/ / Setarca caracreristtcilor modului de lucru grafic

void general window::my settings(int bk color,

- - int color )

170

; I APELMETODA MOSTENITA "get_scHingsO" !tt,

. • / / salvarea caracteristici mod grafic

get_sett~ngs(),

settextstyle(DEFAULT_FONT,HORIZ_DIR:1); settextjustify(LEFT_TEXT,LEFT_TEXT),

setfillstyle(SOLID_FILL,bk_color); setcolor(color)i

}

/ I Desenare fereastra ,

void general~window::draw_window()

{ .'

/ / afisarea numelui ferestrei

bar(x,y,dx,y+9); t 'dth(window name)/2,

outtextxy«dx-x)/2+x;tex W~ _

y+9,window_name);

II desenarea corpului ferestrei setcolor(WHITE); setfillstyle(SOLID_FILL,BLACK): bar(x,y+9,dx,dy); rectangle(x,y+IO,dx,dy); rectangle(x+2,y+12,dx-2,dy-2);

}

II Seteaza absciscle si ordonatcle col~ur~lor s.t~~ga-sus / I (x.y) si dreapta-jos (dx,dy)la valorile Im~hclte

1 'dow' . ·aat- vv Ivn1.d \

void genera _w~n ., ~ ~_ •. .J. \. - -,

{

x=getmaxx{)/2-150; y=getmaxy ( ) /2-80;. dx=getmaxx()/2+150; dy=ge~axy()/2+80;

}

II Verifica daca ambele colturi sint in cadrul. ecra~ul~~ ~i II daca intr-adcvar coltul s,tinga-sus cste mal sus SI mal m

II stinga dccit eel dreapta-jos coord (int xl, int y1, int general_window::bad_

int x2,int y2)

{

171

ii( xl<O I I yl<O I I x2<O I I 2 0 I I

II y2>getmaxy() .11 xI>=x2YI1 yl>::: x;>getmaxx()

return I; Y )

return 0; }

I I Va crea 0 fereastra Cll numele dar de "wind ' .

I I coordo t I I" ow name I st cu

. na c c co llInlorstmga-slis si drea ta- 'os lui

I I valon1eparametrilor"il" "i4"( I P J ind

I I. valorilcimplicite); - cventus sCpotuliliza

vo~d general_window::create_wi~dow(char

*win_name,

~nt x l , int y i ,

{ lnt x2,int y2)

if( bad_coord(XI,yl,x2,y2)

I I setare coord, colturi la val. implicite

set_xy();

else {

I I init. coordonatelor la valorile transmise ca parametn

x=xl; . . I

y=yli

dX=X2i

dy==y2i

}

get_image ( ) i

I I salvare imagine de "sub" fereastra my_set tings (RED, BLACK) ; I I seteaza caracteriStici!e de I I lucru in mod grafic

int lung=textwidth(win_name)i

II d<.l~a.numel~ fcreastrei nu incape in fcreastra fereastret I I nu I sc va afisa numele ,I

if{dx-~-2*~RGINE>=lung) {

I I numelclllcapc In Iercasrra

n~me_size~strlen(win name)+l; w~ndow_n~me=new charTname size]; strcpY(Wlndow_name,win_name);

}

else {

172

1/ numele nu incape in Iereastra name size=2:

window narne"'new char[name size); strcpy(window_name," "); -

}

draw_window( ) ; 1/ descnare fereastra

put_settings (); / / restaurarc optiuni utilizator }

/ / - rcstaurare caracteristici mod de lucru grafic folosite de utilizator; / / - refacere imagine de "sub" Icreastra;

/ / - eliberare memorie consumata pen tru pastrarea imaginii salvate. void general_window::destroy_window(void)

{

put_settings();

putimage(x,y,store image,COPY PUT); delete[image size]-store imag~; delete [name_;ize] window=name;

}

In fine, clasa "ERROR_WINDOW" va avea urmatorut'ficader": 1* E_WIN_CL.DAT */

# include "g_win_cLdat"

class error window: general_window { public :

void create error window(char *)i void destrov_error_window(void);

}i

Definirea metodelor respective se va face in fi§ierul "ERR_ W[N.CPP": r ERR_ WIN.CPP */

# include <graphics.h>
# include <process.h>
# include <conio.h>
# include lie win cl.dat" /1 coniine declaratia clasei # define MARGINE 8

173

char * mesaj,msg[]="Mesaj de eroare prea lung lnt lung,inalt,x=getmaxx()/2,y=getmaxy()/2;

r MAIN.CPP */
# include <conio.h>
# include <stdio.h>
1 " • # include <process.h>
,
# include <graphics.h>
# include "e win cl.dat" / / Asigura initializarea modului de lucru grafic si furnizarea / /. mesajelor de avertizare si eroare corespunzatoare

void init_graph(void)

{

void error_window~:create_error_window(char * error_msg)

{

/ / set are caracteristici de lucru my_settingS(BLACK,WHITE);

lung=textwidth(error msg)/2; inalt=textheight(error_msg); mesaj=error_msg,

/ / daca mesajul este prca lung va fi inlocuit cu unul de avcrtizare if (lung+MARGINE>X) {

lung=textwidth(msg);

inalt=textheight(msg)i

mesaj=msg;

int gd, gm, e r r r

}

detectgraph(&gd,&gm);

initgraph (&gd, &gm, .. \ \l'()~":dndc \ \bgi" ) ; err=graphresult();

if (errl=grOk) {

printf ("GRAPHICS ERROR : %s\n", grapherrormsg(err»);

exi t ( 1) ;

/ / inainte de a crca Icrcast ra se rclac vcchilc caracteristici 1/ (pcntru a nu fi pierdute prin supra-insericre) put_settings();

} }

create window("EROARE",x-lung-MARGINE,y-2*inalt,

_ x+lung+MARGINE,y+3*inalt);·

/ / Pe ecran se va crca un dcsen oarecare (pentru a se arata ca / / u tiliza rca ferestrelor nu distruge imaginea exislenta) void creaza_scena()

my settingS(BLACK,WHITE)j outtextxy(x-lung,y+2*inalt,mesaj)i put_settings();

}

{

char txt(] ="Este";

char txtl[]="un program";

char txt2[]="demonstrativ al cartii de C++";

void error_window::destroy_error_window()

destroy_window(); }

circle(50,50,50); circle(80,80,BO); circle(120,120,120);

{

Refcritor la metoda "error _ window::destroy _ error_ windowj)", cste clar ca ea putea lipsi, in locul ci utilizindu-sc "dcstroywindowtj't-u: mostenit. S-a prefcrat aceasta solutio deoarcce ni se pare mal elegant ca numele metodei sa fie cit mai sugcstiv.

In fine, fisierul "MAIN.CPP" arata astfel:

set-', extjustify(LEFT_TEXT,LEFT_TEXT);

settextstyle(TRIPLEX_FONT,HORIZ_DIR,lO); outtextxy«getmaxx()/2)-(textwidth(txt)/2), (getmaxy()/3)+(textheight(txt)/2),txt)i

174

175

settextstyle(TRIPLEX FONT,HORIZ DIR,2); outtextxy«getmaxx()72)-(textwidth(txtl)/2), getmaxy()-2*textheight(txtl),txtl);

settextstyle(GOTHIC FONT,HORIZ DIR,4); outtextxy«getmaXX()/2)-(textwIdth(txt2)/2), getmaxy()-2,txt2);

}

1/ Aceasta procedura va crea si manipula diverse tipuri de ferestre,

1/ incercind sa surprinda situatii cit mai complexe (suprapuneri partiale 1/ sau totale, etc.)

void actiune(void)

{

general window gwli general-window gw2; general=window gW3j

gw1. create _ window ( "Fereastra .numar u L 1"); outtextxy(500,30,"A"); gw2.create_window("Numaru12",10,10,100,100); outtextxy(500,60,"B"); gw3.create_window("NUmaru13",50,50,150,150); outtextxy(500,90,"C")i

getch() ;

gw3.destroy_window()i getch() i gw2.destroy_window(); getch (); gw1.destroy_window()i getch ( ) ;

outtextxy(SOO;120i"O");

gw1.create window("Fereastra numarul 1"); gw2.create-windowC"Numarul 2",10,10,100,100); gw3.create:=window("NUmaru13",50,50,150,150);

error window ew1;

eW1.create error window("Disc protejat la

- - scriere I");

176

outtextxy (500,150 A,",E") ; getch ( ) ;

error window ew2;

eW2.create_error_window(ItMemorie insuficienta I"); outtextxy(500,180,"F");

getch() ;

ew2.destroy_error_window(}i getch(); ewl.destroy_error_window()i getch() ;

}

void main(void) {

init_graph();

creaza_sCena()i actiune();

closegraph()j }

Ca problemepropuse sprc rezolvare, am sugera extinderea acestui set de Ierestre, dupa cum urmeaza:

a. crearea unui mecanism de redimensionare §i schirnbare a pozijiei Ierestrei generate pe ecran;

b. crearea unei ferestre de prompt (citire de caractere, numere lntregi §! reale);

c. crearea unei ferestre de cautare pe disc a unor clase de Iisiere (Ex: ". *, *.txt,a?n.*, ... ).

177

~

5. ALTE FACILITATI ALE LIMBAJULUI

C+ +

5.1. Functll pure ~i clase abstracte

!nccpem cu aceasta tema deoarece CSIC direct legala de unul dintre subiectelc precedcntului capitol, ~i anume de mccanisrnul de mostenire a mctodclor virtualc, Yom reaminti in citcva cuvinte desprc cc era vorba. Daca o metoda "M" a unci clase de baza "B" cstc preflxata cu cuvint ul chcie "virtual", ca va fi utilizata identic de toate clascle "MB" cc 0 mostenesc pc "B" carli a redcfini metoda "M", Dad! 0 clasa "MB I", derivata din "B", va rcdefini metoda "M", compilaiorul va §Ii sa utilizeze noua metoda indiferent daca ea va fi manipulata:

a. de un obicct de tipul MBJ;

b. prin intcrmcdiul unui pointer la obiectc dc tipul B, care memoreaza adresa unui obiect de tip MBI.

Oricum, estc dar ca din moment cc pol fi apclate, metodele virtualc vor fi alit declarate, cit §i definite.

In unelcsituatii practice se poatc gasi 0 clasa "B" din carc sc dcriveaza toate celelalte clasc ale ierarhiei, Uneori clasa "B" este a tit de generals, lncit uncle mctode cc rcprczinta caractcristici fundamentale ale ci nu pot fi descrise la accst nivcl, ci doar In clascle derivate.

De cxcmplu, fie urmatoarea ierarhie de clase:

VIETlHTOARE: - LALELE

-" NUFERI

- CAl

- OAMENI

Sa prcsupunem ca in cadrul clasci VIETUITOARE cxista 0 metoda numita NUTRITIE. La acest nivel, functia NUTRITIE nu poate fi definita decit abstract. Desi putem spune cii NUTR!TIA cste "totalitatea proccselor de ingerarc §i asimilare a hranei" ([MDLRJ, p. 488), aceasta dcfinitie nu ne da nici 0 informatie despre modalitatilc practice de rcalizare a procesului rcspectiv, NUTRITIA va Ii insa foartc U§Of de definit concret in cadrul claselor derivate. Spre exemplu, procesele de tipul masticatiei sau digcstici

178

omencsti pot fi descrise p,ina in cdc mai mid amanunte. Cind spunem "0 VIETUITOARE SE HRANE~TE", ne vom inchipui un nufar, un cal sau un om. Deci, cind spunem "0 VIETUITOARE", ne referim la 0 entitate abstracta (deoarece oamenii, caii §i nuferii nu slnt instantieri ale clasei VIETUITOARE). Altfel spus, nu exista instantieri ale clasei VIETUITOARE, ci doar obiecte apartinind unor clase derivate din aceasta.

Revenind la Iimbajul C+ +, vom spune ca, prin definitie, functiile de tipul NUTRIT~E sc vor nurnj FUNCTII PURE. Dedt 0 FUNCfIE PURA este ° METOD_t\ VIRTUALAce nu poatefl DEFINITA (darva n obligatoriu DECLARATA) la un anumit nivel dcabstractlzarc.

_ Odata definite FUNCfIILE PURE, vom spune ca 0 CLASA ABSTRACTA este 0 clasa care arceel putin 0 metoda pura. 0 clasa de tipul VIETUHOARE va fi 0 CLASA ABSTRACr A tocmai fiindca NU POT EXIST A INST ANTIERI ALE El. Accst lucru se datoreaza imposibilitati! de a <lenni Iuncjia de NUTRITIE.

Claseleabstractese pot utilizaabsolut identiccu cdc ne-abstracte, cu unica diferenta ea NU POT EXISTA INSTANTIERI ALE LOR.

Urmarind excmplul de mai jos, totul se va clarifica pina in cdc rnai mid amanunte.

Exemplul l

# include <process.h>

# include <dos , h> II coniine prototipul functiilor "sound", II "nosound" sl "delay"

class jOC{ public :

II POST FIXAREA unci declaratii de metoda cu "::;:0" va determina II ca respect iva metoda sa devina PURA

virtual void demo(void) = 0 ; virtual void melodie(void) = 0 } ;

II FUNCTIE FURA II FUNCTIEPURA

class samurai : public jOC{ public :

void demo(void) {

system("samurai Id");

}

179

void melodie(void);

} ;

void samurai::melodie(void) {

for(int i=O;i<lOii++) {

sound( lOOO+100*i); I I provoaca emiterea unui sunet de I I trecventa egala cu valoarea paraI I'Jnetrului actual

de lay (500) ; I I opreste ,executia programului tim~ de 500 I I milisecunde (in acest mod se stabileste

I I durata sunetului emis)

no sound ( ) ; I I opreste emiterea sunetului

}

void main(void)

{

samurai Si

s .melodie (); }

De notat cii daca in declararea clasei "samurai" am fi avut secventa class samurai : public joc

{

public :

void demo(void) = 0; II ...

}

atunci "samurai" ar fi ramas 0 clasa abstracta, avlnd lnsa 0 singura metoda pura ("demo"). Atenjie deci: in cadrul ierarhiei de c1ase, orice functie pura trebuie ori sa fie redeclarata pura, ori sa fie deflnita!'!

5.2. OVERLOADING OPERATORS

Termcnul englezesc de "OVERLOADING OPERATORS" se refera la 0 facilitate specifica limbajului C++: dreptul programatorului de a adauga operatorilor limbajului §i aile sensuri dedi cele predefinite. Cu alte cuvinte, este yorba de 0 redefinire a operatorilor C+ +, fara ca vechile sensuri sa se piarda.

180

Se §tie ca operatorul "+" poate fi utilizat pentru a realiza adunari de nurnere intregi §i reale. Prin "OVERLOADING" (0 traducere destul de plastica ar fi SUPRAINCARCARE), operatorul " +" ar mal putea fj Ioloslt la adunarea de matrici, numere complexe sau rationale. Cum s-ar putca realiza aces! lueru? Foarte simplu: clasclor MATRICE, COMPLEX sau RATIONAL li se va adauga 0 metoda-operator nurnita "+". Evident, intrcaga grijii pentru 0 utilizare corccta a operatorului cade in grija compilatorului. Astlcl, presupunind ca am definit clasele COMPLEX §i RATIONAL ca avind un operator "+", in secvenja de mai jos totul va decurge dupa cum dorim:

int double COMPLEX RATIONAL

a=O,b=6; c=4.5,d=6.7; e(2,3),f(3,4); g(4,5) ,h(5,6) i

I I ...•••...

a=a+b; // adunarc de intregi c=c+d; 1/ adunare de reali

e=e+f; I I adunare de numere complexe g=g+h; I I adunare de numere rationale

Din punct dcvedere al limbajului.opcratorii rcdefiniti nu sint altceva decit niste metode oarecare, purtind tnsa nume de opera tori (de exemplu metodele cu numele +, -, etc). Pentru a nu provoca erori de sintaxa, nurnele metodelor in cauza vor f prefixate cu cuvintul chcie "operator". Sa eonsideram urmatoarea secvenja:

class matrice {

J I

f I .... -to

public

matrice& AdunaMatrici(matrice&); matrice& operator + (matriCe&)i

/1 } ;

Unica difercnja intrc ccle doua metode consta In comoditatea sporita a apelului celci de a doua:

matrice a,b/c; I I '.'

c=a.AdunaMatrice(b); IIINCOMOD

c=a v ope r a t o r+j b j ; 1/ CORECT, DAR LA FELDE INCOMOD

c=a+b ; I I MULT MAl COMOD SI MAl NATURAL

181

Inainte de a inchcia aceasta scctiune, vom mal face citcva observatii deoscbit de importantc. Desi aproape oricarui operator i sc poate da un nou scns, NICI UNUI OPERATOR NU I SE POATE SCHIMBA PRECEDENT A SAU SINT AXA (un operator binar nu poate fi facut unar §i nici invcrs)!!! !

Dc asemenea, nu este permis sa se dcfineasca (introduca) noi operatori, E o rcstrictte absolut norrnala, lipsa ei putlnd da nastere unor complicatii atit de mari lncit compilatorul 5:1 nu le mai poata rezolva,

Singurii opcratori care nu pot fi redefini]i slut urrnatorii:

* :: ?:

Rcaminum cit opcraiorul ".*" estc utilizat la manipularca pointerilor la metode (vczi "3.4. ").

ATENTlE: dircctivclc yreprocesor # §i ## nu pot Ii rcdcfinitc dcoarece ELE NU SINT OPERATORI!

o alta chestiunc imponanta [inc de libertatea de redcfinire a operatorilor.

Pentru ca ca sa fie totala, compilatorul NU VA FACE NICI 0 PRESUPUNERE LEGAT A DE UN SENS PREDEFINIT AL OPERATORILOR. Dupa cum sublinia §i Stroustrup ([STRO], p. 172), in incercarea de a oferi programatorului 0 maxima deschidere, compilatorul NU V AVERIFICA nici macar in cazul redcfinirii lui ":" DACA PRIMUL OPERAND ESTE SAU NU 0 "lvalue" (adica daca i se pot atribuivalori).

De ascmcnca, trebuie avut grija la urmatorul aspect: in limbajul C ex-

presiile

index++; index+=l; Lndex=Lnder .;- .1. ~

crau echivalcnte. In C++, rcdelinirea lui "+" §i "=" nu e suficienta pentru ca acest lucru sa fie adevarat, Trebuie ca programatorul sa defineasca corcspunzator TOTI opcratorii in cauza.

5.3. Operatorl unari §i blnarl

Un lucru pe care nu l-am spus plna acum este faptul ca un operator poatc fi nu doar metoda, ci §i Iunctie "friend", Poate mai putln a§teptata va fi IMPOSIBILITATEA de a dcfini un operator ca fiind METODA STATICAl!!

Daca opcratorul va fi definit ca metoda, exista doua posibilita]!:

estc operator UNAR - in aces! caz el nu va mai avea niei un argument

182

r

I

(operatorul unar are nevoie de un singur operand: obiectul care va apela metoda);

este operator BINAR - situatie in care metoda va avea un singur parametru (care reprezinta eel de al doilea operand).

I?aca operatorul este 0 funcjie "friend", vom avea alte doua posibilitati, dupa cum acesta va fi:

UNAR

- situatie in care va avea un singur paramctru;

BINAR

- §i atunci va avea doi pararnctrii.

Tot le~at de operatorii unari §i binari: fie "@" §i "#" cite un sirnbol r~p~ezcntu~d ~n operator unar, respectiv binar, Atunci, atit expresiilc "a@" §l @a", Cit §I expresia "x#y" pot avea cite doua interpretari, respectiv

a.operator@(); operator@(a);

x.operator#(Y)i operator# (X,y);

dupa cum.opcrat~rii "@" §i "#" vor fi metode sau functii "friend". Daca in vrcunul din ~zun a~ Iost de~larate ambele forme, compilatorul va decide apelul corect m functie de ordinea parametrilor in cele doua functii operator.

" . Tre~uic s~ face~ ~:rmat~arcva ~u~liniere: orice operator care e functie

~nend ~ uner clase B tre}Jule sa alba CEL PUTIN un argument de tipul "B" (I.nst~~l~~re a clasei "B"). In caz contrar, ar exista riscul de a introduce amblg?ltall ill ~drul expresiilor formate doar din termeni de tipuri predefinite. Unica exccppe de la aceasta regula 0 constituie operatorii "new" §i "delete".

Cu tj~lul de rc~~~~~, a~i~t,!~, faptul ca formele pre §i post fixate ale 0peratonlor redeftniti + + §I -- nu VOl' putea fi dcosebite de catre compilator,

o alt~. observatic, oarecum neasteptata, este cea legata de Iaptul ca dc§i op~ratoru m~§~e.ncs.c SINTAXA (unar/binar) §i PRECEDENTA operatonlor predcfinlti, CI nu vor mosteni §i COMUTATIVITATEA lor. Exemplul de mai jos va sublinia foarte bine acest lucru:

Exemplul I

class exemplu 1{ int a; - public :

exemplu_l(int al)

a t a L) {}

183

friend int Qperator+(int i,exemplu_1 e) {

return i+e.ai

}

friend int operator+(exemplu_1 e,int i) {

return i+e.a;

}

} ;

class exemplu 2{ int a; - public :

exemplu_2(int a1) ~ a(a1) {}

int operator+(int i) {

return i+aj

}

}i

void main(void)

{

exemplu 1 e1(10); exemplu-2 e2(100); int a; -

a=e1+1 ~ a=1+e1,

// CORECT // CORECT

a:::e2+1; a=1+e2j

}

// CORECT

// ERONAT: ordinca pararnetrilor nu sc potriveste

. <?e se remar~a, in acest scurt program'? In primul find frapcaza faptul ca, tn e~u~a asteptarilor, compilatorul nu va line cont de cornutattvitatea adunaru, §I va semnaliza in consecinja 0 eroare in linia

a=1+e2j

" !~ ~ee~a§i Ordi,?e de id~i, da~a a.m ~lirnina unul dintre cei doi operator! ai clasei exemplu_l , am rna! obtine mea 0 eroare de compilare,

Cea de a doua observatie va evidenua un avantaj al opcratorilor "friend"

184

(alii de cei de tip metoda: nu se poatc defini un operator comutativ care sa fie metoda ~i sa opereze cu operanzi de tipuri difcrite (dcoarcccimplicit primul operand va fii intotdcauna obicctul care apcleaza metoda). In schimb, aCC\'l

lucru va fi usor realizabil prin mecanismul de functii "friend", f

Yom prezenta acum inca doua exemple de redefinire a unor operator]. In citeva cuvinte, dcspre ce este yorba: vom defini o clasa "rational", care va simula 0 parte dintre operatiilc cu nurnere rationale, In excmplcle de rnai jos s-au implementat operatiile de inmuljire (operatic binara cu simbolul"*") §i inversa (operatic unarii cu simbolul "- ": se va inversa numitorul §i numaratorul, semnalizind evcntualele impartir! cu 0). ATENTIE: dcsi 0- peratia de lnmultirc cste corect cfectuJita, nu se opereaza evcntualele rcduceri posibile Intrc numitor §i numarator. In Excmplul2 operatorii vor fi mctode, jar in Exemplul3 functii "friend".

Exemplul z

# include <iostream.h> # include <conio.h>

class rational{

int numitor,numarator; public: rational(int,int);

void tip(char *);

rational& operator*(rational&); rational& operator-(void);

} ~

rational::rational(int nl,int n2) {

numarator=nl;

// NU SE ACCEPTA NUMITORUL 0 numitor = n21=0 ? n2 : 1;

}

void rational::tip(char * nume) {

int a;

cout«" Nr. rational "«nume«" «"/"«numitor«"\n"i

}

"«numarator

185

rational& r t'

{ a lonal::operator*(rational& n)

rational * r;

int nl,n2;

nl=numarator*n.numarator· n2=numitor*n.numitor· '

,

r=new rational (nl,n2);

return *r;

}

rational& r t' 1

{ a lona ::operator-(void)

int nl,n2i

rational* r;

if(numaratorl=O) {

/ / daca numaratorul este nenul sc va efectua operan

nl=numarator' a

n2=numitoro '

,

}

else {

/ / in caz contrar numarul este lasat neschimbat n2=numarator·

nl=numitor' '

,

}

r=new rational(n2,nl);

return *r;

}

void main(void)

{

rational a(1,2),b(3,4)i / / stergere ccran

c Lr s c r j ] ;

a.tip("a");

186

a=-a; a.tip("a")i

a=-a; a.tip("a");

b.tip("b");

b=-----b; b.tip("b");

b=-b;

b. tip ( "b" ) i

a=a*b; a.tip("a")i

b=a*b; b.tip("b");

a=-a*-b; /1 ATENTlE: preccdenta lui" -" este mat mare decit II cea a lui "*", deci "- " se vor executa inaintea lui "*"

a.tip("a")i

}

In urma executiei programului se vor afisa urmatoarele rezultate:

Nr. rational a = 1/2

Nr. rational a == 2/1

Nr. rational a == 1/2

Nr. rational b == 3/4 Nr. rational b = 4/3 Nr. rational b == 3/4 Nr. rational a = 3/8 Nr. rational b:::: 9/32 Nr. rational a == 256/27

Ceea ce ar trebui totusi evidentiat este modul in care se efectueaza operatiile. Deoarece se doreste 0 implementare cit mai rca Iii, rezultatul nu va Ii memorat in primul operand, ci va fi returnat ca entitatedesinesHitiitoarc. Avind in vederc aceasta necesitate, rezultatul va fi pastrat tntr-o zona de memorie alocata dinamic. Acest subterfugiu are doua avantaje:

a. daca rezultatul ar fi fost 0 variabila Iocala metodci "operator *", valoarea

187

s-ar fi pierdut in urma parasiri: Iuncjiei in cauza;

b. daca rezultatul ar fi fost pastrat intr-o variabila statica a mctodei "0- perator=", am fi putut avea nepliiccri in cazul unor inliinluiri de operatori de genul "m::=a*b*c*d";

Dupa cum am mai spus, reluam exemplul precedent, cu unica modificare ca operatorii vor fi de asra data funqn "friend",

Exemplul3

# include <iostream.h> # include <conio.h>

class rational{

int numitor,numarator; pUblic:

rational(int nl,int n2);

void tip(char *);

} ;

friend rational& operator*(rational&, rational&); friend rational& operator-(rational&);

rational::rational(int nl,int n2) {

numarator=nl; numitor = n21=0 ? n2

I-

I

}

void rational::tip(char * nume) {

int ai

cout«" Nr. rational "«nume«" = "«numarator «"/"«numitor«"'n";

}

rational& operator*(rational& n,rational& m)

{

rational * rj int nl,n2;

nl=m.numarator*n.numaratori n2=m.numitor*n.numitor;

188

r~new rational (nl,n2);

return *r;

}

rational& operator-(rational& m) {

int nl,n2; rational* r;

if(m.numarator!=O) { nl=m.numarator; n2=m.numitor;

}

else { n2=m.numarator; nl=m.numitor;

}

r=new rational(n2,nl);

return *ri

}

void main(void) {

rational a(l,2),b(3,4);

clrscr ( ) ;

a.tip("a");

a=-a; a.tip("a")i

a::::-a: a.tip("a");

b.tip("b");

b=-----bi b.tip("b");

189

b=-b; b.tip("b");

a=a*bi

a. tip ( .. a" ) ;

b=a*b; b.tip("b");

a=- a * -b; / / ATENTIE: precedents lui" -" cste mai marc / / dccit cca a lui "*"

a.tip("a"); }

In secjiunile ce urmeaza vom prezenta, pe rind, citeva cazuri concrete de opera tori a caror redcfinirc necesita 0 atcntie dcoscbita, datorata unui comportament mai pujin obisnuit, Cu cxccptia opcratorului "=" (vezi "5.6."), ABSOLUT TOTI CEILAL TI se mostcnesc conform regulilor obisnulte.

5.4. Operatorii "new" ~i IIdeletell .

Redefinirca acestor doi opera tori implies mai multe rcstrictii:

a. "new" trebuie sa rcturncze "void *" §i sa aiba ca prim parametru un "size_t" (tip de data definit in "stdlib.h" ~i utilizat pcntru manipularea Iungimii zonelor de memorie destinate pastraril obiectelor);

b. "delete" trebuie sa returneze "void", sa alba un prim parametru "void *" ~i eventual un al doilea de tipul "size_t";

e. ambii operatori vor fi IMPLICIT §i OBLIGATORIU metode statice;

d. din precedents conditio rciese ca "new" §i "delete" NU POT FI VIRTUALJ.

lntre atitca restrictii, iata §i un fapt imbucurator: operatorii standard "new" §i "delete" pot fi apelati alit in dorncniul de vizibilitate al clasci ce-i rcdefincste, clt §i in eel <II altor clase derivate din aceasta. Aces! lucru sc poate realiza in doua mod uri: fie implicit (in caz ca se crcaza/distrug obiecte de alt tip decit eel in cauza), fie explicit (prin Iormule de genul "::operator new" sau "::operator delete").

Vom da acum un excmplu foartc simplu, in care atit "new" cit §i "delete" vor Ii rcdcfiniti.

Exemplu

# include <iostream.h>

190

class exemplu{ int a,b,c;

/ / membrul "Iungime" se va declara "static" pentru a putea Ii accesat in / I cdc doua metode statice "new" si "delete"

static short lungime;

public : exemplu(void) {

cout«"Apel constructor EXEMPLU\n"; a=b=c=O;

}

-exemplu(void) {

cout«"Apel destructor EXEMPLu\n\n";

}

/ / se utilizcaza paramctrul "lng" deoarece eventualel~ clase derivate din / / "excmplu" pot ave a lungimi diferite de cea a clasei de baza

void * operator new(size_t lng)

{

cout«"Operatorul \"new\" redefinit II l\n"~ lungime=lng;

/ / tipul argumentului (sir de caractere) este suficicnt pentru a spccifica / / faptul ca se apcleaza operatorul standard "new"

return (void *) new char[lng];

}

void operator delete(void * p)

{ I . (. .)

/ / se tiparcstc lungimea obicctu UI 111 octcn

cout«exemplu::lungirne«"\n\noperatorul \"delete\" redefinit 1!I\n"~

1/ tipul argumentului (pointer la "char") este suficient pcntru a specifica 1/ faptul ea se apeleaza operatorul standard "delete"

delete [lungirne] (char*) p;

}

} ;

191

II variabilele membrustatice trebuie initializate short exernplu::lungime;O;

class derivat : public exemplu{

double dbl; .

public : derivat(void) {

cout«"Apel constructor DERIVAT\n\n"; dbl::::12.34i

}

-derivat(void) {

}

cout«"Apel destructor DERIVAT\n";

} ;

void main (void)

{

exemplu * ptr; derivat * ptd;

ptr=new exemplui delete ptri

ptd=new derivate delete ptd; I }

Execuua programuluiva dererrmna lipiirirea pc ecran a urmatoarclordate:

Operatorul"new" redefillit!!! Apel constructor EXEMPLU Apel destructor EXEMPLU

6

Operatorul "delete" redefinit!!! Operatorul"new" redcfinit!!! Apel constructor EXEMPLU Apcl constructor DERIVAT

192

Apel destructor D ERIV AT Apel destructor EXEMPLU

14

Opera torul "delete" redcfinit !!!

o observatie importanta este legata de setarea valorii paraf!)ctrului "Ing": el nu va fi setal de catre prograrnator, ci va fi dedus DE CATRE COMPILATORin mornentul apclului lui "new". Valoarea sa va Ii egala cu numarul de oct eli nccesar pcntru mcmorarcu unui obicct_ de tipul dat, Prezenta unui astfel de parametru este ABSOLUT NECESARA, dcoarece metoda "new" va fi mostenita §i de clasa "derivai", ale caret instanjicri au 0 alta lungime. Distrugerea obiectclor create diuarnic se va face corect tot prin grija compilatorului.

Redefinirea operatorului "apcl de functie" ne aduce In situatia de a crea un operator binar ne-statlc dc forma

exprcsierlista _ de _ expresii)

Utilizarea functiei-operator "0" are doua mad avantaje:

a. EVALUAREA §i VERIFICAREA listci de argumcnte se face intocmai ca la 0 functie obisnuita,

b. Modul de functionare al mecanismului de apel. Desi OPERATORUL este binar, FUNCfIA-OPERATOR poate avea oriciti parametrii (eel de al doilea operand fiind 0 lista de argumente). Dcoarece lista de pararnetrii poate fi vida, inscamna dl al do ilea operand poalc li psi cu totul,

In exernplul de mai jos, am folosit operatorul "0" PCIlIIll implemcntarea

unui iterator (functie de parcurgere a unci lisle inrr-o ordine prestabilita),

Exemplu

# include <iostream.h> # include <process.h>

struct elem {

int nri

struct elem * urmi

elem(int n,struct elem * p)

nr ~ n ) I urm ( p ) {}

193

} ;

/ / aceas ra clasa cste dest inata pastraril si manipularii de liste 1/ simplu inlantuitc de clemente de tipul "elem"

class list{

struct elem * cap,* curent;

/ / "cap" pastrcaza un pointer la prtmul clement al Iistei, jar

II "curent" pozitia la care ;Ii-a ajuns in cadrul operatlci de iterare

public :

list(void) { cap=NULL; }

void init(int);

/ I prototipul celor doi itcratori struct elem * operator() struct elem * operator;)

(void); (int);

} ;

/ I creaza 0 lista ell "n" clemente void list::init(int n) {

if (n<l) {

cout«"Initializare eronata 111\n\n"; exit(l);

}

for(int i=O;i<n;i++)

{

struct elem * Pi p=newelem(i,cap); cap=p;

}

1/ la inceput, pozitia curcnta reprczinta prirnul element curent=capi

}

/ I asigura 0 pareurgcre ciclica a listei (in urma terminarii II unei parcurgcrt, se va inccpe din nou cu primul element) struct elem * list::operator() (void)

{

194

struct elem * p=curent;

curent == curent->urml "'NULL. ? curent->urm return p;

cap;

}

II forteaza incepcrea itcrarii cu elernentul avind numarul de

II ordine "pozitie" .' .

struct elem * list::operator() (~nt poz~t~e)

{

curent=cap;

II parcurge lista (eventual de mai mu!te ~)ri, e~ci "pozi_ti.c· po~.te fi mai II marc decit numarul de clemente din lista) SI se poziuoncaza pc

II elcmentul dorit

for(int i=O;i<pozitie-l;i++) (*this) ( ) ;

return (*this)();

}

void main(void) {

list 11i

·11.inlt(4) ;

for(int i=0;i<13;i++) cout« ll()->nr «

n ". ,

cout« "\0" « 11(12)->or «"\n";

for(int j=O;j<13;j++) cout« ll()->nr «n Hi

}

In urma exccutiei, pc ecran se vor afisa rezultatele:

3210321032103

o

3210321032103

195

5.6. Operatorul 11:::11

Redefinirea operatorului ne-static":::::" pune probleme eu totul deosebite.

Acest Iapt se daroresie mai multor proprieta]: nelntilnite la ceilalp opera tori.

Cu riscul de a nc repcta, vom Incepe prin a reaminti efi tuturor claselor ce nu-l rcdefinesc pc "==" compilatorul lc ataseaza IMPLICIT 0 astfel de metoda. Ea va reprezenta un operator binar avind amnii opera tori de acelasi tip. Acest opcrator atribuie tuturorvariabilclor membru ale primului operand valorile mernbrilor corespunzatori din eel de al doilca, Practic, va f vorba de o copierc bit-cu-bit a tut uror variabilclor mcmbru ale respcctivei clase.

o a doua remarca se refera la fal)IUI ca operatorul "=" NU ESTE NICIODAT A MO~TENIT DE CLASELE DERIVATE.

In fine, ori de cite ori 0 clasa X are ca membru un pointer "p" pentru care se aloea dinamic memorie, trebuie avut grija sa nu se ajunga lntr-una din situatiile de eroare de mai jos.

Exemplul l

class ex

{

int * pointer;

pub Li.c i

ex(int i) { pointer~new inti (*pointer)=i; } -ex() { delete pointer; }

} ;

void functie(void) {

ex e1(10),e2(20);

ex e3=e2; II EROARE: constructorul va fi apelat doar pentru II "c2u, iar destructorul si pcntru "e2" si pentru "e3"

e1=e2; I I EROARE: zona de mernorie deja eliberata de doua

I I ori (de catre destructorii apclati pentru "e2" si "e3") va I I mai fi elibcrata inca 0 data de catre destructorul apclat II pentru tel"

}

Acest exemplufvidenliaza subtila deoscbire care exista lntre operatiile de "asignare" (se rcfera la un obiect CREAT DEJA) ~i "initializare" (se refera la actiunile ce au loc in cadrul constructorului),

196

Exista doua situatii speciale, in care nu va fi utilizat operarorul "=", ci

constructorul de copicrc ("X::X(X& )"):

_ transmiterea unor obiectc ca pararnetrii actuali ai unci functii; _ utilizarea obiecrelor ca valori returnate de catre 0 (!;!.!!.Ciic.

In cele ce urmcaza, vorn relua excmplul din sectiunea precedenta, av.ind grija ca, de asia data, sa IIU mai Iasarn la voia intimpliirii asignarca §i

inilializarca.

Exemplul Z

# include <iostream.h> # include <process.h> # include <conio.h>

struct elem

{

int nr;

struct elem * urm;

elem(int n,struct elem * p) } ;

nr (n) r urm (p) {}

class list{

struct e1em * cap,* curentj

public :

list(void) { cap=NULLi }

list (listE.) i II INITIALlZARE: constructor de copiere

-list(void) ;

void init(int);

void destruct(void); void construct(list&);

struct elem * operator() (void); struct e1em * operator() (int);

void operator= (list& 1) II ASIGNARE {

if (this::::=&l) return; II ATENTIElaformcde I I genu! "a =:: ali!

197

/ / In situatii de genul "a = a", in urma distrugerii vechiului obiect, / / reconstructia lui nu mai are scns !

destruct(), construct(l);

}

} ;

list::list(list& 1) {

construct(l);

}

void list::construct(list& 1) {

struct elem * tmp;l.capi

,cap=NULL;

/ / copiere de lista simplu inlantuita while(tmp!=NuLL)

{

struct elem * Pi

p=newelem(tmp->nr,cap); cap=p;

tmp=tmp->urm;

}

1/ pozitionare pe inceputul listei curent=capi

}

void list::destruct(void) {

struct elem * p=cap,* t;

/ / distrugere lista simplu inlantuita while (pl:::NULL)

{

t=Pi p=p->urmj delete t;

198

} }

list::-list(void) {

destruct();

}

/ / metoda "init" e identica cu cea din sectiunea precedenta void list::init(int n)

{

if(n<l) {

cout«"Initializare defectoasa 111\n\n"; exit( 1) ;

}

/ / crcarc lista sirnplu inlant uita for(int i=O;i<n;i++)

{

struct elem * Pi p=newelem(i,cap); cap=p;

}

cur en't=cap ;

}

II arnbii operatori sint idcntici cu cei din sectiunea precedenta struct elem * list::operator() (void)

{

struct elem * p=curent;

curent == curent->urml=NULL ? curent->urm return Pi

cap;

}

struct elem * list::operator() (int pozitie) {

curent=capi

for(int i=0;i<pozitie-1;i++) (*this) () i

199

return (*this)();

2 3

}

void main(void) {

5.7. Operatorulu"

c l.r sor j j ;

list Ll; L1.init(4);

Termenul englezesc "subscript" dcsemncaza operatorul "[J", care este un operator binar ne-static. Al do ilea operand £11 sau ("indexul") poate fi, practic, de orice tip. 0 linie de program de genu! "a[b];" va fi interpretata ca

"a.opera tor[] (b);".

Yom da acum un exernplu intcresant de utilizare a opcratorului "U". ldeea programului este foarte simpla, Se va crea un sir, ale carui clemente conun informatii legate de cite 0 persoana (in cazul nostru datcle personalc vor fi numele §i grcuratea), Se doreste identificarea persoanelor in cauza dupa trei criterii bine precizate (nume, greutate §i numar de ordine in cadrul §irului). In acest seop, au fost creati trei operatori de tipul "subscript", avind indexul de tipul pointer la caracter, respectiv numar real sau intreg. Toti acesti opcratori vor cauta in §ir elemcntul descris de valoarea in cauza. Daca este gas it, se va returna un pointer la cl, In caz contrar se va returns valoarea NULL.

Exemplu

* include <iostream.h> # include <string.h>

list L2, L3=Ll; L2.init(6);

L3=L2; L2=Ll;

cout«"Lucram cu \"L2\" :\n";

for(int i=Oii<5ii++)

cout« L2()->nr «"\n";

cout«"Lueram eu \"L3\" :\n";

eout« L3(3,->nr «W\n";

for(int j=Oij<7ij++)

cout« L3()->nr «"\n";

class exemplu;

}

class persoana{

char nume [ 2 0 ] i double grautate;

Rezultatele afisate de program vor fi cdc de mai ;~~.

li. 0.1 JlJ,:).

Lucrarn cu IOL2" : o

1

2

3

o

Lucram cu "L3" : 2

3

4

5

o

1

friend exemplu;

public :

II initializeaza un obiect de tipul "pcrsoana" void init(char * p,double g) {

greutate=g; strnepy(nume,p/19)i nume[191='\O'i

}

200

2f)1

I

I

I