Poo 04
Poo 04
Cuprins
Introducere
1 O trecere n revist a tehnicilor de programare
1.1 Programare nestructurat
1.2 Programare procedural
1.3 Programare modular
1.4 Un exemplu cu structuri de date
1.4.1 Lucrul cu liste simplu nlnuite
1.4.2 Lucrul cu mai multe liste
1.5 Probleme specifice programrii modulare
1.5.1 Creare i distrugere explicite
1.5.2 Date i operaii decuplate
1.5.3 Lipsa precizrii tipurilor
1.5.4 Strategii i reprezentare
1.6 Programare orientat pe obiecte
1.7 Exerciii
2 Tipuri abstracte de date
2.1 Tratarea problemelor
2.2 Proprieti ale tipurilor abstracte de date
2.3 Tipuri abstracte de date generice
2.4 Notaii
2.5 Tipuri abstracte de date i orientarea pe obiecte
2.6 Exerciii
3 Concepte ale orientrii pe obiecte
3.1 Implementarea tipurilor abstracte de date
3.2 Clase
3.3 Obiecte
3.4 Mesaje
3.5 Rezumat
3.6 Exerciii
4 Alte concepte ale orientrii pe obiecte
4.1 Relaii
4.1.1 Relaia "un fel de"
4.1.2 Relatia "este un/o"
4.1.3 Relaia "parte din"
4.1.4 Relaia "are un/o"
4.2 Motenire
4.3 Motenire multipl
4.4 Clase abstracte
4.5 Exerciii
5 Din nou concepte ale orientrii pe obiecte
5.1 Tipuri generice
5.2 Legare static i dinamic
5.3 Polimorfism
6 Implementarea conceptelor orientate pe obiecte n limbaje de programare
6.1 Programare orientat pe obiecte n TurboPascal
6.2 C++, limbaj creat pentru programarea orientat pe obiecte
6.2.1 Limbajul de programare C
6.2.1.1 Tipuri de date
6.2.1.2 Instruciuni
6.2.1.3 Operatori i expresii
6.2.1.4 Funcii
6.2.1.5 Pointeri i tablouri
6.2.1.6 Pointeri la funcii
6.2.1.7 Un prim program
6.2.2 De la C la C++
6.2.2.1 Incompatibiliti ntre limbajele C i C++
6.2.2.2 Extensii de baz
6.2.2.2.1 Tipuri de date
6.2.2.2.2 Funcii
6.2.2.2.3 Alte extensii de baz
6.2.2.3 Extensii orientate pe obiecte
6.2.2.3.1 Clase i obiecte
6.2.2.3.2 Constructori
6.2.2.3.3 Destructori
6.2.2.3.4 Motenire
6.2.2.3.4.1 Tipuri de motenire
6.2.2.3.4.2 Construcie
6.2.2.3.4.3 Distrugere
6.2.2.3.4.4 Motenire multipl
6.2.2.3. 5 Polimorfism
6.2.2.3. 6 Clase abstracte
6.2.2.3. 7 Suprancrcarea operatorilor
6.2.2.3. 8 Prieteni
6.2. 3 Cum se scrie un program
6.2.4 Exerciii
7 Rezolvrile exerciiilor
Introducere
4
Orientarea pe obiecte (OO) este o tehnologie de proiectare i programare bazat pe un sistem de concepte
informatice apropiat felului n care este perceput i neleas n mod obinuit lumea real. Aceast abordare
s-a dovedit a fi deosebit de fructuoas pentru elaborarea unor produse informatice performante i uor de
nvat i utilizat. Aceast tehnologie este de asemenea foarte adecvat pentru elaborarea mai uoar a
versiunilor succesive i ale diferitelor variante ale unui produs informatic, utilizarea ei avnd ca rezultat o
cretere semnificativ a eficienei activitii de dezvoltare de software.
Caracteristic acestei metodologii de dezvoltare este analiza prealabil a problemelor puse de o anumit
aplicaie, activitate urmrind elaborarea, printr-un proces de abstractizare, a unor tipuri abstracte de date,
nglobnd date i operaii asupra datelor. Ulterior aceste tipuri abstracte de date se implementeaz n limbaje
de programare adecvate.
Capitolul 1 prezint pe scurt evoluia tehnicilor de programare, scopul fiind acela de a se reliefa diferenele
eseniale dintre programarea OO i celelalte tehnici de programare. n capitolul 2 sunt definite tipurile
abstracte de date i sunt expuse ideile de baz legate de aceast noiune. Capitolele 3-5 prezint in mod
sistematic conceptele orientrii pe obiecte i problemele ridicate de implementarea acestor concepte.
Capitolul 6, cel mai amplu i cu structura cea mai complex, este dedicat prezentrii a dou limbaje n care
sunt implementare conceptele OO: TurboPascal i C++. TurboPascal este tratat pe scurt doar n seciunea
6.1, restul capitolului fiind ocupat cu prezentarea limbajului C++, creat special pentru OO.
Toate capitolele de mai sus conin exerciii. Rezolvrile exerciiilor sunt date n capitolul 7, ultimul al
prezentului curs. Cursul conine i o bibliografie cu 5 titluri.
Expunerea este bazat n mare msur pe prezentarea i tratarea amnunit a unor exemple. Seciunea 6.2.5
prezint n ntregime un proiect privind lucrul cu liste simplu nlanuite.
Cursul urmeaz cu unele modificri i adaptri linia lucrrii [web2], publicat pe Internet, care este un curs
pe care l gsim foarte adecvat scopului nostru. De asemenea, prezentul curs este apropiat spiritului lucrrii
[6]. Dei prezint multe elemente ale limbajelor de programare discutate, mai ales C i C++, cursul de fa
nu poate suplini manualele pentru aceste limbaje. In consecin recomandm consultarea de astfel de
manuale, dintre care n bibliografie sunt trecute [1], [2], [3], [4], [5] i [web4].
Acest capitol este o scurt trecere n revist a tehnicilor de programare. Utilizm un exemplu simplu pentru
a ilustra proprietile lor particulare i a indica ideile i problemele cele mai importante care apar n legtur
cu ele.
n linii mari, cineva care nva s programeze parcurge urmtoarele etape:
Programare nestructurat,
Programare procedural,
Programare modular i
Programare orientat pe obiecte.
Prezentul capitol este organizat dup cum urmeaz. Seciunile 1.1, 1.2, 1.3 descriu pe scurt primele trei
tehnici de programare. Prezentm apoi un exemplu simplu de utilizare a programrii modulare pentru a
implementa un modul pentru liste simplu nlnuite (seciunea 1.4). Legat de acesta, enunm cteva
probleme privind acest tip de tehnic n seciunea 1.5. n final, seciunea 1.6 descrie cea de-a patra tehnic
de programare.
program
program
programprincipal
principal
date
date
Figura 1.1
Dup cum desigur tii, aceast tehnic de programare are dezavantaje considerabile dac programele devin
destul de mari. De exemplu, dac aceeai secven de instruciuni este necesar n diferite locuri ale
programului, secvena trebuie copiat. Aceasta a dus la ideea de a se extrage aceste secvene s li se dea
nume i s se ofere o tehnic pentru a le apela i a se reveni din aceste proceduri.
Program principal
Procedur
Figura 1.2
Prin introducerea argumentelor i a procedurilor de proceduri ( subproceduri ) programele pot fi scrise mai
structurat i cu risc mai mic de eroare, deoarece, dac o procedur este corect, de fiecare dat cnd este
utilizat produce rezultate corecte. Prin urmare, n caz de eroare, cutarea acesteia se poate reduce la acele
locuri despre care nu se tie dac sunt corecte.
Un program poate s fie acum considerat ca o secven de apelri de procedur. Programul principal trebuie
s transfere datele la fiecare apelare, datele sunt prelucrate de proceduri i, cnd programul se termin,
datele
care
program
program
programprincipal
principal
date
date
procedura
procedura11
procedura
procedura22
procedura
procedura33
Figura 1.3
n rezumat: Avem acum un singur program care este mprit n pri mai mici numite proceduri. Pentru a
face posibili utilizarea procedurilor generale sau a grupurilor de proceduri i n alte programe, ele trebuie
s fie disponibile separat. Din acest motiv s-a conceput programarea modular care permite gruparea
procedurilor n module.
program
program
programprincipal
principal
date
date
modul
modul1 1
modul
modul2 2
date+date
date+date1 1
date
date++date
date2 2
procedura
procedura11
procedura
procedura22
8
procedura
procedura
33
Figura 1.4
Fiecare modul poate s aib propriile sale date i apare cel mult o dat n ntregul program. Aceasta permite
fiecrui modul s gestioneze o stare intern care este modificat prin apelri ale procedurilor acestui modul.
Pe parcursul executrii programului n fiecare moment fiecare modul are numai o singur stare.
Figura 1.5
Listele simplu nlnuite furnizeaz metode de acces pentru a aduga un nou element i pentru a erge
elementul din fa. Structurile de date mai complicate pot s utilizeze unele structuri deja existente. De
exemplu, o coad poate s fie structurat ca o list simplu nlnuit. Cozile furnizeaz metode de acces
pentru a aduga un element la sfrit i de a obine primul element (comportare numit n englez "first-in
first-aut", prescurtat FIFO).
Vom prezenta acum un exemplu pe care l utilizm pentru expunerea unor concepte de proiectare. Deoarece
acest exemplu este utilizat numai pentrua ilustra aceste concepte i probleme, el nu este nici complet, nici
optim. n capitolul 6, seciunea 6.2.5 se va face o discuie orientat pe obiecte complet avnd ca scop
proiectarea structurilor de date.
S presupunem c dorim s programm o list ntr-un limbaj de programare ca C sau Modula-2. Deoarece
considerm c lista este o structur de date frecvent utilizat, decidem s o implementm ntr-un modul
separat. n mod normal, aceasta ne oblig s scriem dou fiiere: definiia interfeei i fiierul de
implementare. n acest capitol vom utiliza un pseudocod simplu care poate fi neles fr dificultate. Vom
utiliza comentarii cuprinse n "/*...*/". Definiia interfeei ar putea arta ca mai jos.
/*
* Definitia interfetei pentru un modul care implementeaza
* o lista simplu inlantuita pentru a memora date de orice tip
*/
MODULE Lista-Simplu_Inlantuita-1
BOOL list_initialize();
BOOL list_append(ANY data);
BOOL list_delete();
list_end();
ANY list_getFirst();
10
ANY list_getNext();
BOOL list_isEmpty();
END Lista-Simplu_Inlantuita-1
Declarrile din interfa descriu numai ce parte a informaiei este disponibil i nu cum aceast parte
devine disponibil. Se ascunde informaia privind implementarea n fiierul de implementare. Aplicm
astfel un principiu fundamental n ingineria programrii care const din ascunderea informaiei despre
implementarea efectiv. Aceasta ne d posibilitatea s schimbm implementarea, de exemplu s utilizm un
algoritm mai rapid, dar care consum mai mult memorie, fr s apar necesitatea de a schimba alte
module ale programului ntruct apelrile de proceduri rmn aceleai. Este de asemenea posibil ca
declarrile din interfa s fie fcute ntr-un limbaj de programare, iar implementarea s fie fcut ntr-un alt
limbaj de programare, cu condiia s existe o anumit compatibilitate ntre cele dou limbaje. De exemplu,
n mediul vizual de programare Delphi interfaa i implementarea s-au fcut ntr-o extensie a limbajului
Pascal, iar n mediul de programare C++Builder s-a pstrat implementarea din Delphi, dar s-a creat o
interfa scris ntr-o extensie a limbajului C++.
Revenind la interfaa propus pentru liste, observm c ideea acestei interfee este urmtoarea: nainte de
utilizarea listei trebuie s se apeleze list_initialize() pentru a se iniializa variabilele locale din modul.
Urmtoarele dou proceduri implementeaz operaiile de accesare i modificare a structurii artate mai sus:
append (pentru adugare) i delete (pentru tergere). Se observ c toate procedurile din interfa ntorc
valori al cror tip este specificat naintea antetului declarrii de procedur, deci aceste proceduri pot fi
utilizate ca funcii. Tipul BOOL ntors de unele dintre dintre ele are dou valori i este introdus pentru a
modela valorile de adevr TRUE i FALSE, iar ntoarcerea unei valori de acest tip de o funcie are
semnificaia terminrii cu succes sau cu eec a execuiei funciei. Procedura append necesit o discuie mai
detaliat. Funcia list_append() are un argument date de un tip arbitrar. Acest lucru este necesar deoarece
dorim s utilizm lista n mai multe contexte diferite, deci tipul elementelor care se introduc n list nu este
cunoscut dinainte. Prin urmare, trebuie s utilizm un tip special ANY care ne permite s i atam orice tip
de date (nu orice limbaj de programare are un astfel de tip; n C acesta poate fi introdus cu pointeri). A treia
procedur list_end() trebuie s fie apelat cnd programul nceteaz, pentru a permite modulului s tearg
coninutul variabilelor sale interne. De exemplu putem dori s eliberm memoria alocat.
Cu urmtoarele proceduri list_getFfirst() i list_getNext() este oferit un mecanism simplu de traversare prin
list. Traversarea poate fi fcut utilizndu-se urmtoarea ciclare:
11
ANY data;
data <- list_getFirst();
WHILE data IS VALID DO
faceCeva(data);
data <- list_getNext();
END
Avem acum un modul pentru liste care ne permite s utilizm o list cu orice tip al elementelor. Ce s-ar
ntmpla ns dac am avea nevoie de mai mult de o list ntr-unul din programe?
Utilizm DECLARE TYPE pentru a introduce noul tip pe care l numim reprez_lista_t. Nu specificm cum
este reprezentat i implementat n mod efectiv acest tip. i de aceast dat ascundem detaliile de
implementare n fiierul de implementare. Se observ diferena fa de versiunea precedent, n care
ascundeam numai funcii, respectiv proceduri. Acum ascundem i informaiile pentru un tip de date definit
de utilizator, numit reprez_lista_t. Utilizm list_create() pentru a obine reprezentanta unei noi liste (vid).
Fiecare procedur conine acum argumentul special this care identific chiar lista respectiv. Toate
procedurile opereaz acum asupra acestei reprezentante n loc s opereze asupra unei liste globale a
modulului. Am putea spune acum c putem crea obiecte list. Fiecare astfel de obiect poate fi identificat n
mod unic prin reprezentanta sa i sunt aplicabile numai acele metode care sunt definite pentru a opera
asupra acestei reprezentante.
END
S comparm lista cu alte tipuri de date, de exemplu cu un ntreg. ntregii sunt declarai n interiorul unui
domeniu particular (de exemplu, n interiorul unei proceduri). Odat ce au fost definii, ei pot fi utilizai.
Dac domeniul este prsit (de exemplu procedura unde ntregul a fost definit), ntregul este pierdut. El este
creat i distrus n mod automat. Unele compilatoare chiar iniializeaz ntregii nou creai cu o anumit
valoare, de obicei 0 (zero). Care este diferena fa de "obiectele" list? Timpul de via al unei liste este i
el definit de domeniul su, deci ea poate fi creat de ndat ce se intr n domeniu i poate fi distrus de
ndat ce el este prsit. La momentul crerii o list trebuie iniializat ca vid. Prin urmare, am dori s
putem defini o list ca i pe un ntreg. Un cadru pentru aceasta ar putea arta aa:
PROCEDURE foo() BEGIN
reprez_lista_t lista_mea; /* Lista este creata si
initializata */
/* Se face ceva cu lista_mea */
...
END /* lista_mea este distrusa */
Avantajul este c acum compilatorul se ocup de apelrile procedurilor de iniializare i terminare, dup
cum este necesar. De exemplu, aceasta asigur c lista este tears corect, resursele fiind redate
programului.
14
15
Rutinele corespunztoare pentru liste ar ntoarce automat tipurile corecte de date. Compilatorul ar putea s
verifice automat corectitudinea.
program
obiect
obiect11
date
date
obiect
obiect44
date
date
obiect
obiect33
date
date
obiect
obiect22
date
date
16
Figura 1.6
S considerm din nou exemplul privitor la liste. Problema pus de programarea modular este c trebuie s
se creeze i s se distrug n mod explicit reprezentantele de liste. Apoi se utilizeaz procedurile modulului
pentru modificarea fiecreia dintre reprezentante.
Spre deosebire de aceasta, n programarea orientat pe obiecte putem avea smultan orict de multe obiecte
list este necesar. n loc s se apeleze o procedur creia s-i fie furnizat reprezentanta corect a unei liste,
se va trimite un mesaj respectivului obiect list. n linii mari, se poate spune c fiecare obiect
implementeaz propriul su modul care s permit, de exemplu, ca mai multe liste s coexiste.
Fiecare obiect trebuie s se creeze i s se distrug n mod automat. n consecin, nu mai este nevoie s se
apeleze n mod explicit o procedur de creare i terminare.
S-ar putea pune ntrebarea: "i ce-i cu asta? Nu este aceasta doar o tehnic modular de programare mai
sofisticat?". Aa pare s fie, dac aceasta ar fi totul n legtur cu orientarea pe obiecte. Din fericire, nu
este. ncepnd cu capitolele urmtoare vor fi introduse noi caracteristici i concepte ale orientrii pe obiecte,
care fac ca programarea orientat pe obiecte s fie cu adevrat o nou tehnic de programare, care s
permit creterea eficienei activitilor de proiectare i programare a aplicaiilor.
1.7 Exerciii
17
1. Exemplul privitor la liste include tipul special ANY pentru a putea permite unei liste s conin date de
orice tip. Presupunei c dorii s scriei un modul pentru o list specializat de ntregi care ofer verificarea
tipului. Tot ceea ce avei este definiia interfeei modulului Lista-Simplu-Inlantuita-2.
(a) Cum arat definiia interfeei pentru un modul Intreg-Lista ?
(b) Discutai problemele care apar prin utilizarea tipului ANY pentru elementele listei n modulul ListaSimplu-Inlantuita-2.
(c) Care sunt soluiile posibile ale acestor probleme?
2. Care sunt principalele diferene conceptuale ntre programarea orientat pe obiecte i alte tehnici de
programare?
Problem
real
Abstractizare
18
Model
Model
Figura 2.1
Faptul c modelul reprezint o prezentare abstract a problemei implic definirea unor proprieti ale
problemei. Aceste proprieti includ date i operaii.
S considerm, de exemplu, c administraia unei instituii dorete un program pentru gestiunea angajailor
instituiei. Apar o serie de ntrebri: ce informaii sunt necesare administraiei? Ce operaii trebuie efectuate?
Angajaii sunt persoane reale care pot fi caracterizate de multe proprieti, ca de exemplu:
nume,
nlime,
data naterii,
sex,
numr de identificare,
numrul camerei unde lucreaz,
culoarea prului,
preocupri extraprofesionale, etc.
Cu certitudine, nu toate aceste proprieti sunt necesare pentru rezolvarea problemei administraiei. n
consecin, trebuie creat un model de angajat pentru aceast problem. Acest model conine numai
proprietile care sunt necesare pentru a satisface cerinele administraiei, de exemplu: nume, data naterii
sex, numr de identificare. Aceste proprieti sunt datele modelului de angajat. Astfel, o persoan real va fi
descris cu ajutorul unui "angajat abstract". Desigur, nu este suficient numai descrierea. Trebuie definite
anumite operaii cu ajutorul crora administraia s poat lucra cu "angajaii abstraci". De exemplu, trebuie
s existe o operaie care s permit crearea unui nou "angajat abstract" atunci cnd o nou persoan este
angajat de instituie. n consecin, trebuie identificate operaiile care s poat fi efectuate cu un angajat
abstract. De asemenea, putem decide ca accesul la datele angajailor s fie fcut numai prin anumite operaii
19
asociate. Aceasta ne permite s ne asigurm c datele sunt n permanen ntr-o stare bun. De exemplu,
putem s verificm dac o anumit dat este corect.
n consecin, abstractizarea este procesul de structurare a unei probleme nebuloase n entiti bine
precizate prin definirea datelor i operaiilor. Prin urmare, aceste entiti combin datele i operaiile, care
nu sunt separate unele de celelalte.
structur
abstract
de date
tip abstract
de date
interfa
Figura 2.2
Dup ce se definete un TAD, se pot crea instanieri ale sale prin acordarea de valori datelor din structur.
De exemplu, atunci cnd un nou angajat este "creat", structura de date este ncrcat cu valorile efective: se
obine astfel o instaniere a unui angajat abstract. Se pot crea attea instanieri ale unui angajat abstract cte
sunt necesare pentru a descrie fiecare persoan real angajat. Caracteristicile unei persoane reale vor fi
tratate astfel ntr-un mod formalizat.
20
Definiie (Tip abstract de date). Un tip abstract de date (TAD) este caracterizat de urmtoarele
proprieti:
1. Export un tip.
2. Export un set de operaii. Acest set este numit interfa.
3. Operaiile din interfa reprezint unicul mecanism de acces la stuctura de date a tipului.
4. Domeniul de aplicabilitate (termen prin care se desemneaz att domeniul valorilor tipului, ct i
domeniile de definiie ale operaiilor i rezultatele operaiilor) este definit prin axiome i precondiii.
Cu prima proprietate este posibil s se creeze mai mult de o instaniere a unui TAD, aa cum s-a vzut n
exemplul de mai sus, prin declararea unei instanieri a tipului. Reamintim i exemplul anterior referitor la
liste. n prima versiune am implementat o list ca un modul i eram capabili s utilizm numai o singur
list la un moment dat. A doua versiune introduce "reprezentanta" ca o referin la un "obiect list".
Conform celor artate acum, reprezentanta, mpreun cu operaiile definite n modulul listei definete un
TAD Lista.
1. Cnd utilizm reprezentanta definim variabila corespunztoare ca fiind de tipul Lista.
2. Interfaa ctre instanierile tipului Lista este asigurat de ctre fiierul de definiie a interfeei.
3. Deoarece definiia interfeei nu include reprezentarea efectiv a reprezentantei, ea nu poate fi modificat
direct.
4. Domeniul de aplicabilitate este definit de semnificaia (semantica) operaiilor introduse. Axiomele i
precondiiile includ enunuri ca:
"O list vid este o list"
"Fie l=(d1, d2, d3,..., dN) o list. Atunci l.append(dM) are ca rezultat l=(d1, d2, d3,..., dN, dM)."
"Primul element al unei liste poate fi ters numai dac lista nu este vid."
Proprietile tipului Lista sunt rezultatul felului n care noi nelegem dorim i putem s utilizm listele.
Este responsabilitatea noastr de a utiliza instanierile tipului n conformitate cu regulile enunate.
definite mai multe operatii: adunarea, scderea, mulirea, mprirea sunt cteva dintre ele. Axiomele i
precondiiile sunt definite prin definiia numrului complex. De exemplu, exist element neutru pentru
adunare.
Pentru a reprenzenta un numr complex este necesar s se defineasc structura de date care s fie utilizat
de TAD-ul su. Se pot concepe cel puin dou posibiliti de a face acest lucru:
Ambele pri sunt memorate ntr-un tablou cu dou elemente unde primul element indic partea reala si al
doilea element indic partea imaginar a numrului complex c.Dac x reprezint partea real i y partea
imaginar, putem s le accesm prin indicarea de indici ai tabloului: x=c[0] i y=c[1].
Ambele pri sunt memorate ntr-o structur cu dou componente, reprezentate prin nume. Dac numele
componentei reprezentnd partea real este r i cel al prii imaginare este i atunci x i y se obin ca x=c.r i
y=c.i.
Punctul 3 al definiiei TAD spune c pentru orice acces la structura de date trebuie s fie definit un operator.
Exemplul de mai sus pare s contazic aceast cerin. Este acest lucru adevrat?
S considerm din nou cele dou posibiliti de a reprezenta numere complexe i s ne ocupm de partea
real.
n prima versiune x primete valoarea c[0]. n cea de a doua versiune x primete valoarea c.r. n ambele
cazuri x primete ca valoare "ceva". Acest "ceva" difer de structura efectiv de date utilizat. n ambele
cazuri operaia efectiv "primete valoarea" are acelai neles, i anume de a declara c x este egal cu partea
real a numrului complex c: cele dou cazuri nglobeaz aceeai semantic.
Dac ne gndim la operaii mai complexe impactul decuplrii structurii de date de operaii devine i mai
clar. De exemplu, adunarea a dou numere complexe necesit s se efectueze o adunare pentru fiecare parte.
Prin urmare este necesar s se acceseze valoarea fiecrei pri, care este diferit pentru fiecare versiune.
Dac se realizeaz operaia "adunare" se pot incapsula aceste detalii care rmn separate de utilizarea
operaiei i invizibile pentru utilizator. n contextul unei aplicaii, pur i simplu "se adun dou numere
complexe", fr a se ine seama de felul n care aceast operaie este definit efectiv.
O dat creat un TAD pentru numere complexe, fie acesta Complex, el poate fi utilizat n acelai mod ca i
alte tipuri de date, bine cunoscute cum ar fi intregii. n rezumat: separarea dintre structurile de date si
operaii i constrngerea ca structura de date s fie accesat numai printr-o interfa bine definit ne permite
s alegem structurile de date cele mai potrivite n contextul unei aplicaii.
TAD-urile sunt utilizate pentru a se defini un nou tip, din care pot fi create instanieri. Aa cum s-a artat n
exemplul cu listele, uneori aceste instanieri pot opera i ele cu diferite tipuri de date. De exemplu, putem s
ne gndim la o list de persoane sau la o list de maini sau chiar la o list de liste. Definiia semantic a
listei este mereu aceeai. Numai tipul datelor elementare se schimb dup tipul datelor cu care lista
opereaz.
Aceast informaie adiional ar putea fi specificat ca un parametru generic care este specificat n
momentul crerii instanierii. Astfel, o instaniere a unui TAD generic este de fapt o instaniere a unei
variante particulare a acestui TAD. O list de persoane poate fi deci declarat, presupunndu-se c s-a
definit n prealabil tipul de date Persoana reprezentnd persoanele:
Lista<Persoana> listaDePersoane;
Parantezele unghiulare cuprind aici tipul de date pentru care trebuie creat o variant a TAD-ului generic
Lista. listaDePersoane ofer interfaa ca orice alt list, dar opereaz asupra instanierilor tipului
Persoana.
2.4 Notaii
Deoarece TAD furnizeaz un mod abstract de descriere a proprietilor unei mulimi de entiti, utilizarea
lor este independent de orice limbaj de programare particular. Introducem deci, urmtoarea notaie. Fiecare
descriere a unui TAD const din dou pri:
Date : Aceast parte descrie structura de date utilizat n TAD ntr-un mod neformalizat.
Operaii
Aceast parte descrie operaiile valide pentru acest TAD, deci descrie interfaa sa. Utilizm operaiile
speciale constructor pentru a descrie aciunile care urmeaz s fie executate cnd o entitate a acestui TAD
este creat i destructor pentru a descrie aciunile care urmeaz s fie executate cnd o entitate este
distrus. Pentru fiecare operaie se dau argumente, precondiii i postcondiii.
Prezentm ca exemplu descrierea unui TAD numit Intreg. Fie k o expresie ntreag:
TAD Intreg este
Date
O secven de cifre precedat opional de un semn plus sau minus.
Notm cu N acest numr ntreg cu semn.
Operaii
constructor
23
24
2.6 Exerciii
1. TAD Intreg
(a) De ce nu sunt precondiii pentru operaiile sum i dif ? Evident, descrierea TAD-ului Intreg este
incomplet. Adugai metodele inm (nmulire), imp (mprire) i nc una. Descriei impactul lor
specificnd pre- i postcondiii.
2. Proiectai un TAD Fractie care descrie proprietile fraciilor.
(a) Ce structur de date poate fi utilizat?
(b) Cum se prezint interfaa?
(c) Prezentai cteva axiome i precondiii.
3. Descriei cu cuvintele proprii proprietile tipurilor abstracte de date.
4. De ce este necesar s se includ axiome i precondiii n descrierea unui TAD?
5. Descriei cu cuvintele proprii relaiile ntre:
instaniere i tip abstract de date,
tip abstract de date i tipul de date corespunztor,
instanieri ale unui tip abstract de date.
Programele orientate pe obiecte permit implemetarea TAD-urilor. n consecin, atunci cnd un TAD este
implementat, avem o reprezentare a sa pe care o putem utiliza.
S considerm din nou TAD Intreg. Limbajele de programare ca Pascal, C, Modula-2 i altele ofer o
implementare pentru el, numit adesea int sau integer. Atunci cnd este creat o variabil de acest tip, se pot
utiliza operaiile care sunt date pentru ea. De exemplu, se pot aduna doi ntregi:
int i, j, k; /* Se definesc trei intregi */
i = 1; /* Atribuie 1 intregului i */
j = 2; /* Atribuie 2 intregului j */
k = i + j; /* Atribuie suma lui i si j lui k */
n fragmentul de cod surs de mai sus vom evidenia relaia cu TAD-ul Intreg. Prima linie definete trei
instanieri i, j i k ale tipului Intreg. n consecin, pentru fiecare instaniere, operaia special constructor
trebuie apelat. n exemplul nostru, acest lucru este fcut implicit de ctre compilator. Compilatorul rezerv
memorie pentru a pstra valoarea unui ntreg i "leag" numele corespunztor cu aceasta.Dac ne referim la
i, ne referim de fapt la acea zon de memorie care a fost "construit" de definiia lui i opional,
compilatoarele pot alege s iniializeze memoria, de exemplu cu 0 (zero). Urmtoarea linie
i=1;
atribuie lui i valoarea 1. Deci putem s descriem aceast linie cu ajutorul notaiei TAD astfel:
Execut operaia atr cu argumentul 1 asupra instanierii lui Intreg i. Aceasta se scrie aa: i.atr(1)
Avem acum o reprezentare pe dou nivele. Primul nivel este nivelul definirii unui TAD, n care se specific
tot ceea se face cu o instaniere a acestuia prin invocarea operaiilor definite. La acest nivel, pre- i
postcondiiile sunt utilizate pentru a descrie ceea ce se efectueaz.
n exemplul urmtor includem aceste condiii n acolade.
{Precondiie: i=n unde n este orice Intreg }
i.atr(1)
{Postcondiie: i=1}
Reamintim c acum discutm despre nivelul TAD, n consecin, condiiile sunt formulate cu mijloace
matematice.
26
Nivelul al doilea este nivelul de implementare, n care este aleas pentru operaie o reprezentare efectiv
ntr-un limbaj de programare.
n limbajul C semnul "=" (egal) implementeaz operaia atr(). n limbajul Pascal a fost urmtoarea
reprezentare: i:=1; n ambele cazuri este implementat operaia atr. S considerm linia
k=i+j;
Evident "+" a fost ales pentru a implemeta operaia sum. Putem citi partea "i+j" ca "adun valoarea lui j la
valoarea lui i", deci, la nivelul TAD se obine:
{Precondiie: Fie i=n1 i j=n2 cu n1, n2 ntregi particulari }
i.sum(j)
{Postcondiie:i=n1 i j=n2 }
Postcondiia asigur c i i j nu i schimb valorile. Reamintim specificaia lui sum. Ea arat c un nou
Intreg este creat, a crui valoare este suma. Prin urmare, trebuie s dm un mecanism pentru a accesa
aceast nou instaniere. Facem aceasta cu operaia atr aplicat instanierii k.
{Precondiie:Fie k=n unde n este un Intreg oarecare}
k.atr(i.sum(j))
{Postcondiie:k=i+j }
Dup cum se vede, unele limbaje de programare aleg o reprezentare care este aproape identic cu
formularea matematic utilizat n pre- i postcondiii. Acest fapt face dificil departajarea ntre cele dou
nivele.
3.2 Clase
O clas este o reprezentare efectiv a unui TAD. Ea conine detalii de implementare pentru structurile de
date i operaiile utilizate. Lucrm cu TAD-ul Intreg i proiectm propria noastr clas pentru el:
class Intreg {
attributes:
int i
methods:
setValue(int n)
27
Intreg addValue(Intreg j)
}
n exemplul de mai sus, ca i n urmtoarele utilizm o notaie care nu aparine unui anumit limbaj de
programare (este un pseudolimbaj). n aceast notaie class{...} reprezint definiia unei clase. ntre acolade
se afl dou seciuni, una fiind attributes i alta methods care definesc implementarea structurilor de date
i ale operaiilor respectivului TAD. Distingem din nou ntre cele dou nivele utiliznd termeni diferii: la
nivelul de implementare vorbim despre "atribute" care sunt elementele structurii de date de la nivelul TAD.
Aceasta se aplic i la "metode" care sunt implementri ale operaiilor TAD. n exemplul nostru, structura
de date const dintr-un singur element: o secven de cifre cu semn. Atributul corespunztor este un ntreg
obinuit al unui limbaj de programare. Definim numai dou metode setValue() i addValue()
reprezentnd cele dou operaii atr i sum respectiv.
Definiie (Clas). O clas este implementarea unui tip abstract de date (TAD). Ea definete atributele i
metodele care implementeaz structurile de date i operaiile din TAD, respectiv.
Instanierile claselor se numesc obiecte. n consecin, clasele definesc proprietile i comportarea unei
mulimi de obiecte.
Conceperea de TAD-uri i declararea, ntr-un limbaj de programare, a claselor ce le sunt asociate formeaz
obiectul proiectrii, iar conceperea algoritmilor pentru realizarea operaiilor i implementarea acestora prin
definirea metodelor n limbajul de programare formeaz obiectul programrii orientate pe obiecte.
O trstur caracteristic a orientrii pe obiecte este ponderea mare pe care o are faza de proiectare n
realizarea unei aplicaii.
3.3 Obiecte
Reamintim exemplul cu angajaii de la seciunea 2.1. Am vorbit acolo despre instanieri ale unor angajai
abstraci. Aceste instanieri sunt exemple efective de angajat abstract deci ele conin valori efective care
reprezint un anume angajat. Numim aceste instanieri obiecte.
Obiectele sunt identificabile n mod unic printr-un nume. Prin urmare, putem avea dou obiecte distincte cu
acelai set de valori. Acest fapt este ntlnit n limbajele de programare "tradiionale" unde putem avea, de
exemplu, doi ntregi i i j cu aceeai valoare "2".
28
Observm utilizarea lui "i" i "j" n ultima propoziie ca nume pentru doi ntregi. Vom numi totalitatea
valorilor la un moment dat ale atributelor ataate unui obiect starea obiectului.
Definiie (Obiect). Un obiect este o instaniere a unei clase. El poate fi identificat n mod unic prin numele
su i n fiecare moment are o stare care reprezint valorile atributelor sale n acel moment.
Starea unui obiect se schimb n funcie de metodele care sunt aplicate asupra sa. Numim aceast posibil
secven de schimbari ale strii comportarea obiectului.
Definiie (Comportare). Comportarea unui obiect este definit de mulimea metodelor care pot fi aplicate
asupra sa.
Am introdus pn acum dou concepte principale ale orientrii pe obiecte: clas si obiect. Programarea
orientat pe obiecte (P00) este deci implementarea tipurilor abstracte de date sau, mai simplu, scrierea de
clase. n momentul execuiei instanierile acestor clase, obiectele, realizeaz scopul programului
schimbndu-i strile. n consecin, se poate gndi la un program n curs de execuie ca la o colecie de
obiecte. Apare ntrebarea: cum reacioneaz aceste obiecte? Apare necesitatea conceptului de mesaj care
este prezentat n continuare.
3.4 Mesaje
Un program aflat n execuie este o colecie de obiecte n care obiectele sunt create, distruse i
interacioneaz ntre ele. Aceast interaciune este bazat pe mesaje care sunt trimise de la un obiect la altul,
prin care obiectul emitor cere obiectului receptor s aplice o metod asupra sa. Pentru a nelege aceast
comunicare, vom considera din nou clasa Intreg prezentat mai sus. n pseudo-limbajul de programare pe
care l utilizm putem s crem noi obiecte i s aplicm metode asupra lor. De exemplu, putem folosi
Intreg i; /* Defineste un nou obiect intreg */
i.setValue(1); /* Ii atribuie valoarea 1 */
29
pentru a stabili faptul c obiectul i i atribuie valoarea 1. Aceasta este mesajul "Aplic metoda setValue cu
argumentul 1 asupra ta " trimis obiectului i. Am notat trimiterea unui mesaj cu ".". Aceast notaie este
utilizat n C++; alte limbaje orientate pe obiecte ar putea utiliza alte notaii, de exemplu ">".
Trimiterea unui mesaj care cere unui obiect s aplice o metod este similar cu apelul unei proceduri n
limbajele"tradiionale" de programare. Totui, n orientarea pe obiecte se au n vedere obiecte autonome
care comunic unele cu altele schimbnd mesaje. Obiectele reacioneaz atunci cnd primesc mesaje
aplicnd metode asupra lor nile. Ele pot de asemenea s refuze executarea unei metode, de exemplu dac
obiectul apelant nu este autorizat s execute metoda cerut.
n exemplul nostru, mesajul i metoda care trebuie aplicat atunci cnd mesajul este primit au acelai nume.
Am trimis "setValue cu argumentul 1" obiectului i care aplic "setValue(1)". n orientarea pe obiecte
aplicarea sau executarea unei metode se numete invocare.
Definiie (Mesaj). Un mesaj este o cerere ctre un obiect de a invoca una din metodele sale.
Un mesaj conine
numele metodei i
argumentele metodei.
n consecin, invocarea unei metode este o reacie cauzat de recepionarea unui mesaj.
Definiie (Metod). O metod este asociat cu o clas. Un obiect invoc o metod ca o reacie la
recepionarea unui mesaj. Program
3.5 Rezumat
obiect2 care interacioneaz este un principiu fundamental n
obiect3 ca o colecie de obiecte
Considerarea unui program
programarea orientat pe obiecte. Obiectele din aceast colecie reacioneaz la primirea unor mesaje,
schimbndu-i starea n funcie de invocarea unor metode care poate, la rndul ei, s cauzeze trimiterea altor
mesaje ctre alte obiecte. Acest fapt este ilustrat n Figura 3.1.
obiect4
obiect2
30
Figura 3.1
n aceast figur programul const din numai patru obiecte. Aceste obiecte i trimit mesaje unul altuia,
dup cum indic sgeile. Se observ c obiectul al treilea i trimite lui nsui un mesaj.
Cum ne poate ajuta aceast concepie s dezvoltm software? Pentru a rspunde la aceast ntrebare vom
revedea felul cum s-a dezvoltat software cu limbajele procedurale. Primul pas const n a diviza problema n
pri mai mici care s poat fi tratate cu uurin separat. Aceste pri erau concepute ca orientate spre
proceduri, care, n rezolvarea problemei, aveau un rol esenial, n timp ce datele ocupau o poziie secundar.
S considerm, de exemplu, felul n care un caracter apare pe ecranul unui calculator atunci cnd este
apsat o tast. ntr-un mediu procedural urmeaz s fie scrii urmtorii pai necesari pentru a trimite un
caracter pe ecran:
1. ateapt pn la apsarea unei taste
2. citete valoarea tastei
3. scrie valoarea tastei la poziia curent a cursorului.
Nu se pot distinge obiectele ca entiti cu proprieti i comportare bine definite. ntru-un mediu orientat pe
obiecte se pot distinge obiectele tast i ecran aflate n interaciune. Atunci cnd o tast primete mesajul c
trebuie s i schimbe starea i s fie apsat, obiectul su corespunztor trimite un mesaj ctre obiectul
ecran s afieze valoarea asociat tastei.
31
3.6 Exerciii
1. Clas.
(a) Prin ce se deosebee o clas de un TAD?
(b) Proiectai o clas pentru TAD-ul Complex. Ce reprezentani alegei pentru operaiile din TAD?
Justificai alegerea.
2. Obiecte in interaciune.
Alegei din viaa dumneavoastr cotidian o activitate care nu are prea muli pai (de exemplu: privitul la
TV, gtirea unei mncri, etc). Descriei aceast activitate n form procedural i n form orientat pe
obiecte. ncercai s vedei lumea ca fiind alctuit din obiecte. Ce dificulti ntmpinai?
3. Mesaje.
(a) De ce vorbim despre "mesaje" n loc de "apelri de proceduri" ?
(b) Artai cteva mesaje care au sens n mediul Internet (Trebuie deci s identificai obiectele).
(c) De ce termenul "mesaje" este mai potrivit n contextul ultimului exerciiu dect termenul "apelare de
procedur"?
4.1 Relaii
n exerciiile 2.6.5 ai investigat deja relaiile dintre tipurile abstracte de date i instanieri i le-ai descris cu
propriile cuvinte. Aici vor fi tratate mai detaliat.
methods:
setX(int nouX)
getX()
setY(int nouY)
getY()
}
Continum s definim clasele programului nostru de desen cu o clas care descrie cercuri. Un cerc este
definit prin centru i raz:
class Cerc {
attributes:
int x, y,
raza
methods:
setX(int nouX)
getX()
setY(int nouY)
getY()
setRaza(int nouaRaza)
getRaza()
}
Comparnd cele dou definiii de clase observm urmtoarele:
Ambele clase au dou date membre x i y. n clasa Punct aceste elemente descriu poziia punctului, iar n
clasa Cerc ele descriu centrul cercului. Astfel, x i y au acelai nteles: n ambele clase descriu poziia
obiectelor asociate prin definirea unui punct.
Ambele clase ofer acelai set de metode pentru a obine i a atribui valoarea celor dou date membre x i
y.
Clasa Cerc "adaug" o nou dat membr raza i metode corespunztoare de acces.
33
Cunoscnd proprietile clasei Punct putem descrie cercul ca un punct plus o raz i metodele de a o
accesa. Deci, un cerc este "un fel de" punct. Totui, un cerc este ceva mai "specializat". Ilustrm acest fapt
n Figura 4.1.
Cerc
un fel de
Punct
Figura 4.1
n aceasta i n urmtoarele figuri clasele sunt reprezentate prin dreptunghiuri. Numele lor ncepe totdeauna
cu majuscule. Sgeata indic direcia unei relaii, deci se va citi "Cerc este un fel de Punct".
cerc
este un
punct
Figura 4.2
34
class Logo {
attributes:
Cerc cerc
Triunghi triunghi
methods:
set(Punct loc)
}
Ilustrm acest fapt n Figura 4.3.
parte din
Cerc
parte din
Logo
Triunghi
Figura 4.3
parte din
parte din
Cerc
are un
Logo
are un
Triunghi
Figura 4.4
4.2 Motenire
Motenirea ne permite s utilizm relaiile "un fel de" i "este un/o". Aa cum se arat aici, clasele care sunt
"un fel de" alt clas au proprietile comune cu acesta. Exemplul nostru privitor la punct i cerc poate fi
rescris specificnd c un cerc motenete de la (n englez inherits from ) punct:
35
37
Punct
motenete de la
Cerc
Figura 4.5
n literatur se ntlnesc i reprezentri grafice n care sgeile au cellalt sens, dup cum diveri autori
nteleg respectiva relaie. n continuarea prezentei lucrri sgeile reprezentnd motenirea nu vor mai fi
etichetate.
38
Text
Punct
TextDesenabil
Figura 4.6
n pseudo-limbajul nostru scriem aceasta separnd supraclasele aceleiai subclase prin virgule:
class TextDesenabil inherits from Punct, Text {
attributes:
/* Toate mostenite de la supraclase */
methods:
/* Toate mostenite de la supraclase */
}
Putem utiliza obiectele clasei TextDesenabil att ca puncte, ct i ca texte. Deoarece un TextDesenabil "este
un" Punct, putem s l deplasm
TextDesenabil textd
...
textd.move(10)
...
Deoarece "este un" Text, putem s-i adugm alt text:
39
40
Figura 4.7
Se pune ntrebarea: ce proprieti motenete efectiv clasa D de la supraclasele sale B i C ? Unele din
limbajele de programare existente rezolv acest graf de motenire derivnd D cu:
proprietile lui A plus
proprietile lui B i C fr proprietile pe care aceste clase le-au motenit de la A.
n consecin, D nu poate s introduc conflicte de nume cu nume din clasa A. Totui, dac B i C au
proprieti cu acelai nume, D ajunge ntr-un conflict de nume.
O alt posibil soluie este ca D s moteneasc din ambele ramuri de motenire. n aceast soluie, D
posed dou copii ale proprietilor din A : una este motenit de B i cealalt de C.
Dei motenirea multipl este un mecanism puternic orientat pe obiecte, problemele introduse de
conflictele de nume au determinat pe unii autori s l "condamne". Deoarece rezultatele motenirii multiple
pot fi ntotdeauna obinute utiliznd motenirea (simpl), unele limbaje de programare orientate pe obiecte
41
nu permit utilizarea sa. Totui, utilizat cu atenie, n anumite condiii, motenirea multipl reprezint un
instrument elegant i eficient de proiectare orientat pe obiecte.
42
methods:
setX(int nouX)
getX()
setY(int nouY)
getY()
print() /* Redefinire pentru Punct */
}
Putem acum s form fiecare obiect desenabil s aib o metod print care s dea funcionaliatea desenrii
obiectului n interiorul zonei de desenare. Supraclasa tuturor obiectelor desenabile, clasa ObiectDesenabil,
nu ofer funcionaliatea pentru desenare. Aceast clas nu este destinat pentru a se crea obiecte din ea. Ea
specific mai degrab proprieti care trebuie definite de ctre fiecare clas derivat. Numim acest tip
special de clas, clas abstract.
Definiie (Clas abstract). O clas A se numete clas abstract dac ea este utilizat numai ca
supraclas pentru alte clase. Clasa A doar specific proprieti. Ea nu este utilizat pentru a se crea
obiecte. Clasele derivate trebuie s defineasc proprietile clasei A.
Clasele abstracte ne permit s structurm graful de motenire. Totui nu dorim s crem obiecte din ele;
dorim numai s exprimm caracteristicile comune ale unei mulimi de clase.
4.5 Exerciii
1. Motenire.
S considerm programul de desenare.
(a) Definii clasa Dreptunghi prin motenire de la clasa Punct. Punctul va indica colul din stnga sus al
dreptunghiului. Care sunt atributele acestei clase? Ce metode adiionale introducei?
(b) Toate exemplele de mai sus sunt bazate pe viziunea bidimensional. Dorim acum s introducem obiecte
tridimensionale cum ar fi: sfere, cuburi, paralelipipede dreptunghice, etc. Proiectai clasa Sfera utiliznd o
clas Punct-3D ale crei obiecte sun punctele n spaiul tridimensional. Specificai rolul punctului n sfer.
Ce relaie utilizai ntre clasele Sfera i Punct-3D?
(c) Ce funcionalitate d move() pentru obiectele tridimensionale? Dai o exprimare ct mai precis.
43
(d) Desenai graful de motenire care s includa urmatoarele clase: ObiectDesenabil, Punct, Cerc,
Dreptunghi, Punct-3D i Sfera.
(e) Privii graful de motenire din Figura 4.8.
Punct
Cerc
Sfer
Figura 4.8
O definiie corespunztoare ar putea fi:
class Sfera inherits from Cerc {
attributes:
int z /* Adauga a treia dimensiune */
methods:
setZ(int nouZ)
getZ()
}
Motivai avantajele/dezavantajele acestei alternative.
2. Motenire multipl. Comparai graful de motenire din Figura 4.9. Aici noi artm c B i C au fiecare o
copie a clasei A.
44
Figura 4.9
Ce conflicte de nume pot s apar? Prezentai cazurile prin exemple simple de clase.
45
opera cu alte tipuri. De exemplu, pot fi liste de persoane, liste de maini, liste de numere complexe, sau
chiar liste de liste.
Atunci cnd scriem definiia unei clase, trebuie s putem preciza dac aceast clas definete un tip generic.
Totui, nu tim cu care tipuri va fi utilizat clasa. n consecin, trebuie s putem defini clasa cu ajutorul
unui "lociitor" la care ne referim ca i cnd ar fi tipul asupra cruia opereaz clasa. Astfel, definiia clasei
ne este dat ca un model sau ablon (traducerea termenului englez template) al unei clase efective. Definiia
clasei este creat de fapt atunci cnd declarm un obiect particular. Vom ilustra aceasta cu urmtorul
exemplu. S presupunem c dorim s definim o clas de liste de persoane, maini, sau de alt tip.
template class Lista for T {
attributes:
... /* Structura de date necesara pentru */
/* implementarea listei */
methods:
append(T element)
T getFirst()
T getNext()
bool more()
}
Modelul de clas Lista de mai sus arat ca orice alt definiie de clas. Totui, prima linie declar Lista ca
fiind un model pentru diferite tipuri. Identificatorul T este utilizat ca lociitor pentru un tip real. De exemplu,
append() are un element ca argument. Tipul acestui element trebuie s fie tipul de date cu care o list
efectiv de obiecte este creat. De exemplu, putem s declarm un obiect list de persoane dac exist o
definiie a tipului Persoana:
Lista for Persoana persoanaLista
Persoana oPersoana,
altaPersoana
persoanaLista.append(altaPersoana)
persoanaLista.append(oPersoana)
46
Prima linie declar persoanaLista ca fiind o list de persoane. n acest moment, compilatorul utilizeaz
definiia modelului, nlocuiete fiecare apariie a lui T cu Persoana i creaz o definiie efectiv de clas
pentru ea. Aceasta conduce la o definiie de clas efectiv similar cu urmtoarea:
class Lista {
attributes:
... /* Structura de date necesara pentru */
/* implementarea listei */
methods:
append(Persoana element)
Persoana getFirst()
Persoana getNext()
bool more()
}
Aceasta nu este chiar ceea ce generaz compilatorul. Compilatorul trebuie s asigure c putem crea mai
multe liste pentru diferite tipuri n orice moment. De exemplu, dac avem nevoie de o alt list, de exemplu
pentru maini, putem scrie:
Lista for Persoana persoanaLista
Lista for Masina masinaLista
...
n ambele cazuri compilatorul genereaz o definiie efectiv de clas. Cele dou definiii nu se afl n
conflict deoarece compilatorul genereaz nume diferite. Totui, deoarece acest lucru nu este vizibil pentru
noi, nu vom prezenta aici mai multe detalii. n orice caz, dac declarm o alt list de persoane,
compilatorul poate s stabileasc dac exist deja o definiie efectiv de clas i s o utilizeze sau, dac este
necesar, s o creeze. Astfel,
Lista for Persoana oLista
47
Numim acest tip particular de legtur "static" deoarece este fixat n momentul compilrii.
Definiie (Legare static). Dac tipul T al unei variabile este asociat n mod explicit cu numele su N prin
declaraie, spunem c N este legat static de T. Procesul de asociere se numeste legare static.
Exist limbaje de programare care nu utilizeaz variabile cu un tip asociat n mod explicit. De exemplu,
anumite limbaje permit s fie introduse variabile atunci cnd sunt utilizate:
...
5.3 Polimorfism
49
Polimorfismul (termen de origine greac poli=mai multe morphe=form) permite unei entiti (de exemplu:
variabil, funcie sau obiect) s aib diferite reprezentri (adic sub acelai nume se prezint diferite
coninuturi). Aici vom pune n eviden mai multe tipuri de polimorfism.
Primul tip este similar cu conceptul de legare dinamic. n acest caz, tipul unei variable depinde de
coninutul su ntr-un anumit moment:
v := 123 /* v este intreg */
... /* utilizeaza v ca intreg */
v := 'abc' /* v "comuta" la un sir de caractere */
... /* utilizeaza v ca sir de caractere */
Definiie (Polimorfism (1)). Conceptul de legare dinamic permite unei variabile s primeasc diferite
tipuri n funcie de coninutul su ntr-un anume moment. Aceast particularitate a unei variabile este
numit polimorfism.
Un alt tip de polimorfism poate s fie definit pentru funcii. De exemplu s presupunem c dorim s definim
o funcie isNull() care ntoarce rezultatul TRUE dac argumentul su este 0(zero) i FALSE altfel. Pentru
numere ntregi este uor:
boolean isNull(int i) {
if (i == 0) then
return TRUE
else
return FALSE
endif
}
Totui, dac dorim s facem verificarea pentru numere reale, va trebui s utilizm alt comparaie datorit
problemelor de precizie:
boolean isNull(real r) {
if (r < 0.01 and r > -0.99) then
return TRUE
50
else
return FALSE
endif
}
n ambele cazuri dorim ca funcia s aib numele isNull. n limbajele de programare fr polimorfism
pentru funcii nu putem s declarm aceste dou funcii deoarece numele isNull ar fi definit de dou ori.
Fr polimorfism pentru funcii, dubla definire a numelor ar duce la ambiguitate. Totui, dac limbajul ar
ine cont de argumentele funciilor, ar fi posibil. Astfel funciile (sau metodele) sunt unic definite prin:
numele funciei (sau metodei) i
tipurile din lista de argumente.
Deoarece listele de argumente ale celor doua funcii isNull difer, compilatorul poate s construiasc corect
apelul de funcie utiliznd tipurile efective ale argumentelor:
var i : integer
var r : real
i = 0
r = 0.0
...
if (isNull(i)) then... /* Utilizeaza isNull(int) */
...
if (isNull(r)) then... /* Utilizeaza isNull(real) */
Definiie (Polimorfism (2)). Dac o funcie (sau metod) este definit prin combinarea dintre
numele su i
lista tipurilor argumentelor sale
spunem c avem polimorfism.
51
Acest tip de polimorfism ne permite s reutilizm acelai nume pentru funcii (sau metode) dac listele de
argumente sunt diferite. Uneori acest tip de polimorfism este numit suprancrcare, supraacoperire sau
supradefinire.
Ultimul tip de polimorfism permite unui obiect s-i aleag metodele corecte. S considerm din nou
funcia move(), care are un obiect al clasei Punct ca argument. Am utilizat aceast funcie ca orice obiect
al claselor derivate, deoarece are loc relaia "este un/o".
S considerm o funcie display() care s fie utilizat pentru a afia obiecte desenabile. Declaraia acestei
funcii ar putea fi:
display(ObiectDesenabil o) {
...
o.print()
...
}
Dorim s utilizm aceast funcie cu obiecte ale claselor derivate din ObiectDesenabil:
Cerc uncerc
Punct unpunct
Dreptunghi undreptunghi
display(unpunct) /* Trebuie invocata unpunct.print() */
display(uncerc) /* Trebuie invocata uncerc.print() */
display(undreptunghi)/* Trebuie invocata undreptunghi.print() */
Metoda care va fi utilizat efectiv va fi definit de obiectul o al funciei display(). Deoarece acest lucru
este cam complicat, dm un exemplu mai abstract, dar mai simplu:
class Baza {
attributes:
methods:
52
virtual foo()
bar()
}
class Derivata inherits from Baza {
attributes:
methods:
virtual foo()
bar()
}
demo(Baza o) {
o.foo()
o.bar()
}
Baza obaza
Derivata oderivata
demo(obaza)
demo(oderivata)
n acest exemplu definim dou clase: Baza i Derivata. Fiecare clas definete dou metode: foo() i
bar(). Prima metod este definit ca virtual (prin cuvntul cheie virtual). Aceasta nseamn c dac
aceast metod este invocat, definiia sa va fi evaluat prin coninutul obiectului.
Definim apoi o funcie demo() care are un obiect din Baza ca argument. Prin urmare, putem s utilizm
aceast funcie cu obiecte ale clasei Derivata ca i cum ar fi valabil relaia "este un/o". Apelm aceast
funcie cu un obiect din Baza i cu un obiect din clasa Derivata, respectiv s presupunem c funciile foo()
i bar() sunt definite astfel nct singurul lucru pe care l fac este s-i scrie numele i clasa n care sunt
definite. Atunci ieirea este:
53
54
procedure complex.atr(v:complex);
begin
comp(v.re, v.im)
end;
function complex.sum_re(v:complex):real;
begin
sum_re:=re+v.re
end;
function complex.sum_im(v:complex):real;
begin
sum_im:=im+v.im
end;
procedure complex.sum(v:complex; var s:complex);
begin
s.re:=re+v.re;
s.im:=im+v.im
end;
procedure complex.citeste;
begin
read(re, im); readln
end;
procedure complex.scrie;
begin
write('re=', re, ' im=', im); writeln
end;
56
{program principal}
var c1, c2:complex;
begin
c1.citeste;
c2.atr(c1);
c2.sum(c1, c1);
c1.scrie;
c2.scrie;
readln
end.
Dm mai jos o scurt prezentare a celorlalte elemente de programare orientat pe obiecte pe care
TurboPascal le ofer.
Posibilitatea ca un tip object s moteneasc un alt tip object, declaraia ncepnd cu o specificare a
tipurilor cu sintaxa:
tip_obiect object tip_obiect_mostenit
urmat de declararea datelor i metodelor.
Posibilitatea de a se suprancrca metodele motenite prin definirea n obiectul motenitor unor metode
cu acelai nume, dar cu un corp diferit i, eventual, cu alt list de argumente.
Posibilitatea ca unele din metodele unui tip obiect, s fie declarate virtuale prin adugarea cuvntului
cheie virtual la declaraia metodei, ca de exemplu:
procedure nume(argumente); virtual;
ceea ce oblig la declararea ca virtuale a metodelor cu acelai nume ale tipurilor object motenitoare.
Efectul acestor declararaii este rezolvarea tuturor apelurilor procedurilor virtuale n momentul execuiei i
nu al compilrii, adic rezolvarea apelurilor este dinamic. Acest mecanism de apel trebuie s fie pregtit n
timpul execuiei prin apelarea nainte de apelarea oricrei metode virtuale a unei metode declarat cu
cuvntul cheie constructor (n loc de procedure sau function).
57
Metodele care nu sunt virtuale se numesc statice. Este interzis suprancrcarea metodelor virtuale cu
metode statice.
Pentru mai multe informaii asupra limbajului TurboPascal, ca i asupra programrii orientate pe obiecte n
acest limbaj, recomandm s se consulte [1].
Din scurta prezentare de mai sus se observ absena implementrii unor concepte importante ale orientrii
pe obiecte cum sunt: clase abstracte, modele de clase, motenire multipl. Principiul incapsulrii datelor i
metodelor este de asemenea incomplet implementat, lipsind elmentele care s permit limitarea i
diferenierea accesului la componentele unui tip object.
Aceste lipsuri se explic prin faptul c limbajul TurboPascal a fost realizat de firma Borland prin extensia
limbajului standard Pascal, care este un limbaj procedural tipic, astfel nct s fie nglobate i celelalte
tehnici de programare: programarea modular i programarea orientat pe obiecte. Conceptele orientrii pe
obiecte s-au implementat ns incomplet, probabil pentru c limbajul de baz standard Pascal are
caracteristici care fceau foarte dificil o abordare mai ampl: ar fi trebuit creat un limbaj cu totul nou, mult
ndeprtat de limbajul de baz.
Situaia este substanial diferit n cazul limbajului C++, cu toate c i acest limbaj a fost realizat prin
extensia "limbajului de baz" C. Flexibilitatea deosebit a limbajului C a permis ca adugarea facilitilor de
programare orientat pe obiecte, care este principala raiune a crerii limbajului C++, s se fac ntr-un mod
mult mai complet i eficient, fr s se piard posibilitatea ca limbajul s fie utilizat doar ca un limbaj
procedural obinuit.
58
cum ar fi Pascal, sunt interzise din cauza influenei lor nefaste asupra stilului de programare. Oricum, dac
este utlizat cu o anumit rigoare, C este un limbaj la fel de bun ca i multe alte limbaje de nivel nalt.
Comentariile n C sunt cuprinse ntre /*...*/.
Tip
<signed> char
double
Descriere
Lungime
Domeniu/Precizie
Octet cu semn
128..127
Numr n dubl
precizie
<signed> <short>int |
<signed> short
float
ntreg cu semn
Numr n virgul
mobil
215.. 215 1
ca (3, 4X1038..3, 4X1038)
precizie 7 cifre
ntreg lung cu
semn
231.. 231 1
long double
10
unsigned char
Octet fr semn
0..255
unsigned <short>
<int>
unsigned long <int>
ntreg fr semn
0.. 216 1
ntreg lung fr
semn
0.. 232 1
tipul short <int> are lungimea 2, domeniul 215.. 215 1 i coincide cu <signed> short <int>;
59
tipul <signed> int are lungimea 4, domeniul 231.. 231 1 i coincide cu <signed> long <int>;
tipul unsigned <int> are lungimea 4, domeniul 0.. 232 1 i coincide cu tipul unsigned long <int>;
exist modificatorul long long i tipul <signed> long long <int> cu lungimea 8 i domeniul 263.. 263
1;
exist tipul unsigned long long <int> cu lungimea 8 i domeniul 0.. 264 1;
Variabilele acestor tipuri sunt definite simplu prin specificarea tipului naintea numelui:
int un_int;
float un_float;
long long un_intreg_foarte_lung;
Cu struct se pot combina mai multe tipuri definite. n alte limbaje acest tip structurat este numit record:
struct data_s {
int ziua, luna, an;
} oData;
Definiia de mai sus pentru oData este de asemenea declaraia unei structuri numit data_s. Putem defini
alte variabile de acest tip referindu-ne la structur prin nume:
struct data_s altaData;
Nu este necesar s denumim structurile. Dac le omitem numele, nu putem s le reutilizam. Totui, dac
denumim o structur, putem doar s o declarm, fr a defini o variabil:
struct timp_s {
int ora, minut, secunda;
};
60
Putem s utilizm aceast structur aa cum s-a artat pentru altaData. Acest lucru este foarte asemntor
cu definiiile de tipuri din alte limbaje, unde un tip este declarat naintea definirii unei variabile de acest
tip. Variabilele trebuie definite naintea utilizrii lor. Aceste definiii trebuie s apar naintea oricrei
instruciuni, deci ele formeaz partea superioar a unui bloc de instruciuni.
6.2.1.2 Instruciuni
C conine toate tipurile uzuale de instruciuni. Instruciunile sunt terminate prin ";". Putem s grupm mai
multe instruciuni n blocuri prin plasarea lor ntre acolade. n interiorul fiecrui bloc putem s definim noi
variabile:
{ /* Incepe blocul exterior */
int i; /* Defineste un i */
i = 1; /* Atribuie lui i valoarea 1 */
{ /* Incepe un nou bloc */
int i; /* Defineste un nou i */
i = 2; /* care primeste valoarea 2 */
} /* Inchide blocul */
/* Aici i este 1 din nou din blocul exterior */
}
Tabelul 6.2 listeaz toate instruciunile
Tabelul 6.2
Instruciune
Descriere
expr;
break;
continue;
expr.
61
do
instr
while(expr)
for ([expr1]; [expr2]; [expr3];)
instr
goto eticheta;
return [expr];
switch (expr) {
Instruciunea for este singura care difer esenial de instruciunile for cunoscute n alte limbaje. O form
special are i instruciunea switch, care execut lista de instruciuni ncepnd cu "eticheta" case
corespunztoare valorii expresiei testate pn la execuia unei instruciuni break. De asemenea, posibilitatea
de a se termina execuia instruciunilor repetitive cu instruciunea break i posibilitatea de a se ntrerupe
ciclul curent cu instruciunea continue sunt elemente care pot contribui la construirea unor programe
62
eficiente. Toate celelalte instruciuni difer de cele din alte limbaje mai mult sau mai puin prin sintax. Mai
jos artm dou blocuri care au absolut aceeai funcionalitate. Unul utilizeaz bucla while, iar cellalt
instruciunea for :
{
int ix, suma;
suma = 0;
ix = 0; /* initializare */
while (ix < 10) { /* conditie */
suma = suma + 1;
ix = ix + 1; /* pas */
}
}
{
int ix, suma;
suma = 0;
for (ix = 0; ix < 10; ix = ix + 1)
suma = suma + 1;
}
Cele de mai sus pot fi bine nelese dac se ine seama de nc o proprietate caracteristic a limbajului C:
instruciunea de atribuire este n acelai timp o expresie.
63
kx = jx = ix = 12;
S-a efectuat ceea ce, n stilul comun celor mai multe limbaje de programare s-ar fi efectuat prin
ix = 12;
jx = 12;
kx = 12;
n C valoarea 0 (zero) reprezint valoarea de adevr FALSE. Orice alt valoare reprezint TRUE. De
exemplu, funcia standard strcmp(), cu dou iruri ca argumente, ntoarce -1 dac primul este mai mic
dect al doilea, 0 dac sunt egale i 1 dac primul este mai mare dect al doilea. Pentru a testa dac dou
iruri str1 i str2 sunt egale, se utilizeaz adesea instruciunea condiional:
if (!strcmp(str1, str2)) {
/* str1 este egal cu str2 */
}
else {
/* str1 nu este egal cu str2 */
}
Semnul de exclamare reprezint operatorul boolean NOT (negatie). Astfel, expresia este evaluat TRUE
numai dac strcmp() ntoarce 0. Expresiile se construiesc prin combinarea termenilor (sau operanzilor) i a
operatorilor. Termenii pot fi constante, variabile, sau expresii. Operatorii limbajului C sunt cei cunoscui din
alte limbaje, la care se adaug civa care reprezint prescurtri ale unor combinaii ale altor operatori.
Tabelul 6.3 listeaz toi operatorii disponibili. Coloana a doua arat prioritatea lor, numerele mai mici
indicnd o prioritate mai nalt i numerele egale, aceeai prioritate. Coloana a treia conine aritatea (adic
numrul de operanzi). Aritatea primului operator (apelul de funcie) este varabil, depinznd de felul n care
este declarat primul operand (funcie sau pointer la funcie) printr-un prototip. Ultima coloan listeaz
ordinea de evaluare.
Tabelul 6.3
64
Preceden
1
1
1
1
2
2
2
2
2
2
2
2
2
3
3
3
4
4
5
5
6
6
6
6
7
7
8
9
Operator
Descriere
()
[]
Aritate
Ordine
variabil
2
din stnga
din stnga
2
2
1
1
1
1
1
1
1
1
1
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
din stnga
din stnga
din dreapta
din dreapta
din dreapta
din dreapta
din dreapta
din dreapta
din dreapta
din dreapta
din dreapta
din stnga
din stnga
din stnga
din stnga
din stnga
din stnga
din stnga
din stnga
din stnga
din stnga
din stnga
din stnga
din stnga
din stnga
din stnga
10
11
12
13
14
14
=
op=
&&
||
?:
2
2
2
3
din stnga
din stnga
din stnga
din dreapta
Atribuire simpl
2
Atribuire combinat cu un operator binar 2
op, unde op poate fi unul din operatorii
din dreapta
din dreapta
15
din stnga
Probabil c cele mai multe din aceste operaii v sunt deja cunoscute. S observm mai nti c operatorii
booleeni &, |, ^ au prioritate inferioar operatorilor == i !=$. n consecin, dac dorim s scriem expresii
care conin aceti operatori, trebuie, aa ca n exemplul de mai jos, n care se face verificarea unei
succesiuni de bii cu ajutorul unei "mti":
if ((sir_de_biti & MASCA) == MASCA) {
...
}
s se nchid operaiile booleene ntre paranteze. Operatorii de incrementare/ decrementare ++ i -- pot fi
explicai prin urmtorul exemplu. Dac avem urmtoarea secven de instruciuni
a = a + 1;
b = a;
putem utiliza operatorul de preincrementare b = ++a;
De asemenea, dac avem instruciunile n ordinea invers
66
b = a;
a = a + 1;
putem utiliza operatorul de postincrementare
b = a++;
Astfel operatorul de preincrementare mai nti incrementeaz variabila asociat, apoi ntoarce noua valoare,
n timp ce operatorul de postincrementare ntoarce mai nti valoarea, apoi incrementeraz variabila.
Aceleai reguli se aplic operatorilor de pre- i postdecrementare --.
Apelrile de funcii, atribuirile imbricate i operatorii de incrementare/ decrementare produc efecte
colaterale n momentul aplicrii. Aceasta poate introduce dependene de compilator deoarece ordinea de
evaluare n anumite situaii este dependent de compilator. S considerm urmtorul exemplu care
demonstreaz acest lucru:
a[i] = i++;
Depinde de ordinea n care compilatorul este prevzut s evalueze expresiile din stnga i din dreapta
operatorului de atribuire dac vechea sau noua valoare a lui i este utilizat ca indice n tabloul a.
Operatorul condiional ?: este o prescurtare pentru o instruciune if utilizat n mod obinuit. De exemplu,
pentru a atribui variabilei max cea mai mare dintre valorile variabilelor a, b, se poate utiliza:
if (a > b)
max = a;
else
max = b;
Aceasta se poate scrie mai scurt:
max = (a > b) ? a : b;
67
Urmtorul operator neobinuit este cel de atribuire combinat cu alt operator. Utilizm adesea atribuiri de
urmtoarea form:
expr1 = (expr1) op (expr2)
de exemplu
i = i * (j + 1);
n aceste atribuiri valoarea din stnga apare i n partea dreapt, imediat lng operatorul de atribuire. C
permite ca aceste tipuri de atribuire s fie prescurtate ca:
i*= j + 1;
Putem face asta cu aproape toi operatorii binari. De reinut c operatorul de mai sus implementeaz forma
lung, dei "j+1" nu este n paranteze. Urmtorul operator neobinuit este operatorul ", ". El poate fi
explicat cel mai bine printr-un exemplu:
i = 0;
j = (i += 1, i += 2, i + 3);
Acest operator i evalueaz argumentele de la stnga la dreapta i ntoarce valoare expresiei din dreapta.
Astfel, n exemplul de mai sus operatorul evaluez mai nti "i+=1" care, ca un efct colateral,
incrementeaz valoarea lui i; apoi este evaluat expresia "i+=2" care adaug 2 la 3, deci i primete valoarea
3. A treia expresie este evaluat i valoarea sa este ntoars ca rezultat al operatorului. Astfel, j primete
valoarea b.
Operatorul virgul introduce o anumit "capcan" atunci cnd se utilizeaz tablouri n-dimensionale cu n>1.
O eroare frecvent este utilizarea unei liste de indici separai prin virgule:
int matrice[10][5]; // matrice bidimensionala
int i;
68
...
i = matrice[1, 2]; // NU FUNCTIONEAZA!!
i = matrice[1][2]; // BINE
n realitate, la prima atribuire, lista de indici separai prin virgule este interpretat ca operator virgul, ceea
ce conduce la o atribuire a adresei celei de a treia secvene de cinci elemente a matricii! Limbajul C ignor
valorile neutilizate ale expresiilor. De exemplu, se poate scrie secvena:
ix = 1;
4711;
jx = 2;
dar linia a doua, dei ntoarece o valoare (4711), nu are nici un efect. Trecnd peste aceste lucruri oarecum
stranii, vom lua n considerare lucrurile cu adevrat utile. n continuare vom discuta despre funcii.
6.2.1.4 Funcii
Deoarece C este un limbaj procedural, el permite definirea funciilor. Procedurile sunt simulate prin funcii
care nu ntorc valori, mai exact, ntorc un tip special numit void. Funciile sunt declarate ca i variabile, dar
trebuie s aib argumente cuprinse ntre paranteze (chiar dac nu exist argumente, parantezele trebuie
specificate):
int sum(int to);/* Declaratia functiei sum cu un argument */
int bar(); /* Declaratia functiei bar fara argumente */
void foo(int ix, int jx)/* Declaratia functiei foo cu doua
argumente */
Pentru a defini efectiv o funcie, se adaug corpul su: C permite numai transmiterea argumentelor prin
valori. n consecin nu se poate schimba valoarea unui argument al unei funcii. Dac dorii s transmitei
un argument prin referin, trebuie s transmitei ca valoare a argumentului corespunztor al funciei, adresa
variabilei a crei valoare dorii s fie schimbat, deci trebuie s utilizai pointeri.
69
Una din cele mai obinuite probleme ale programrii in C (i uneori n C++) este nelegerea pointerilor i
tablourilor. n C (C++) aceste concepte sunt foarte nrudite, cu unele mici (dar eseniale) diferene.
Se declar un pointer punnd un asterisc ntre tipul de date i numele variabilei sau a funciei:
char *strp; /* strp este 'pointer la char' */
Accesm coninutul unui pointer utiliznd din nou un asterisc:
*strp = 'a'; /* Un singur caracter */
Ca i n alte limbaje, trebuie s furnizm un anumit spaiu pentru valoarea la care trimite un pointer. Un
pointer la caractere poate fi utilizat pentru a indica un ir de caractere. irurile de caractere n C se termin
prin caracterul special NULL (0 sau constanta de tip char '0'). Astfel, putem avea iruri de orice lungime.
irurile sunt nchise ntre ghilimele:
strp = "salut";
n acest caz compilatorul adaug automat caracterul NULL la sfrit. Astfel, strp trimite la un ir de 6
caractere: primul este 's', al doilea 'a', etc. Putem accesa caracterele cu un indice n strp:
strp[0] /* s */
strp[1] /* a */
strp[2] /* l */
strp[3] /* u */
strp[4] /* t */
strp[5] /* \0 */
Primul caracter este egal i cu *strp, ceea ce poate fi scris i ca *(strp+0). Aceasta reprezint ceea ce se
numeste aritmetica pointerilor i este una din cele mai puternice caracteristici ale limbajului C. Astfel, avem
urmatoarele egaliti:
*strp == *(strp + 0) == strp[0]
70
*(strp + 1) == strp[1]
*(strp + 2) == strp[2]
...
De reinut c aceste egaliti sunt adevarate pentru orice tip de date. Adunarea nu este orientat spre octei,
ci este orientat spre lungimea respectivului tip de pointer!
Pointerul strp poate s trimit i la alte locaii de memorie. Destinaia sa este schimbtoare. Spre deosebire
de aceasta, tablourile sunt pointeri fici. Ei trimit spre o zona predefinit de memorie care este specificat
ntre paranteze drepte:
char str[6];
Putem s considerm str ca o constant pointer care trimite la o zon cu 6 caractere. Nu este permis s fie
utilizat astfel:
str = "salut"; /* EROARE */
deoarece aceasta ar nsemna schimbarea pointerului pentru a trimite la 's'. Trebuie s copiem irul n zona
de memorie pentru date. Putem utiliza o funcie din biblioteca C standard, numit strcpy().
strcpy(str, "salut"); /* E bine */
De reinut c, exceptnd atribuirea, se poate folosi str oriunde poate fi folosit un pointer de caracter,
deoarece el este un pointer (fix).
(*nume) (lista_de_tipuri)
71
unde tip este un tip oarecare sau void, reprezentnd tipul de rezultat ntors funciile a cror adres poate fi
valoare a pointerului declarat, nume identificatorul care desemneaz pointerul, iar lista_de_tipuri
este vid sau conine un singur tip sau este o succesiune de tipuri separate de virgule, aa cum se utilizeaz
ntr-o declaraie (prototip) de funcie.
Iat cteva exemple de astfel de declaraii:
int (*pf-int1)(float, int); // este intors tipul int
void (*pointf)(int, int, *char); // nu exista rezultat
double (*pf-d)(int); // este intors tipul double
char *(*pf-int1)(); // este intors pointer la char
Daca se declar o funcie, de exemplu
int
f-int(float, int);
un pointer declarat conform cu prototipul acestei funcii poate primi ca valoare, cu ajutorul operatorului de
atribuire, adresa din memorie a funciei, astfel:
pf-int1=f-int;
Se observ c numele unei funcii este un pointer constant care are ca valoare adresa funciei.
Cu un pointer la funcie se poate construi, utilizndu-se operatorul de apel de funcie, o expresie al crei
efect este apelarea funciei a crei adres a fost n prealabil atribuit ca valoare pointerului. Astfel, expresia
pf-int1(1.5, -2)
poate fi utilizat, dup executarea atribuirii de mai sus, n locul expresiei
f-int(float, int).
Pointerii la funcie permit elaborarea unor programe deosebit de flexibile, ei permind ca un program s
stabileasc n mod dinamic (adic n timpul execuei) dac va fi apelat o funcie sau alta ntr-un anumit
72
punct al su. De asemenea, ei lrgesc considerabil posibilitile de programare deoarece pot fi utilizai ca
argumente ale unor funcii sau ca elemente ale unor structuri.
Se pot defini tablouri de pointeri la funcii i poiteri la pointeri la funcii, aa cum ilustrm prin exemplele
de mai jos:
int (*tab-pf[5]) (char *, int); // tablou cu 5 elemente pointer la
// functii
float (**ppf) (double, int, int); // pointer la pointer la functie
ceea ce poate aduga elemente importante de flexibilitate programelor.
73
n cel de al doilea pas, codul C generat n primul pas este compilat i se creaz cod executabil. Fiecare cod
executabil trebuie s conin o funcie numit main(). Aceast funcie este apelat atunci cnd programul
este pornit (adic lansat n execuie). Acest funcie ntoarce un ntreg care reprezint starea de ieire a
programului.
Funcia main() poate s aib argumente care reprezint argumentele din linia de comand. i artm aici,
dar nu vom explica felul cum pot fi utilizai. Pentru astfel de explicaii se pot consulta [2], [3]:
#include <stdio.h>
int main(int argc, char *argv[]) {
int ix;
for (ix = 0; ix < argc; ix++)
printf("Argumentul meu nr. %d este %s\n", ix, argv[ix]);
return 0;
} /* main */
Primul argument, argc ntoarce numrul de argumente din linia de comand. Argumentul al doilea, argv
este un tablou de iruri de caractere (reamintim c irurile sunt reprezentate prin pointeri la caractere. Astfel,
argv este un tablou de pointeri la caractere).
6.2.2 De la C la C++
Aceast seciune prezint mai nti unele incompatibiliti ntre limbajele C i C++, apoi extensiile
limbajului C care au fost introduse de C++. De asemenea sunt artate conceptele orientate pe obiecte i
implementarea lor.
are n C valoarea 2, n implementarea Borland C++, respectiv valoarea 4 n implementarea Unix, iar n C ++
are valoarea 1 n ambele implementri.
2. n C++ un salt cu instruciunea goto nu poate trece peste o declaraie cu iniializare dect dac trece
peste ntregul bloc al declaraiei. De exemplu, programul
/* In C este permis, dar in C++ nu este permis saltul peste o
declaratie cu initializare daca nu se face peste intregul bloc
care o contine.*/
#include <stdio.h>
void main(void)
{ int a;
printf("a="); scanf("%d", &a);
if (a>10) goto et; // eticheta din blocul interior
{ int b=10;
printf("b="); scanf("%d", &b); a+= b;
et: printf("a=%d b=%d\n", a, b);
}
}
este corect n C, dar nu este corect n C ++, deoarece instruciunea goto trece peste declaraia cu
iniializare din blocul interior, fr a trece peste tot acest bloc.
3. C++ admite numai conversia de tip de la un pointer de un tip oarecare la tipul void, nu i conversia n
sens invers, spre deosebire de C. n C++ conversia n sens invers se poate face numai cu operatorul de
conversie explicit (notat (tip) ). Dm cteva exemple:
void* pv;
int* pi;
pv=pi; /*corect in C si in C++*/
pi=pv; /*corect in C, incorect in C++ */
75
#include<process.h>
#include<stdlib.h>
int a=10;
void main(void)
{
if (a) {printf("\na=%d ", a); a--; main();} else exit(getch());
}
Dac sunt compilate cu un compilator de C, compilarea se ncheie cu succes i, la execuie fiecare din
programe va afia
10 9 8 7 6 5 4 3 2 1
i se va opri dup apsarea unei taste. Un compilator de C++ nu va accepta nici unul din aceste programe.
6. Dup cum se va vedea la 6.2.3 (Paii compilrii), programele pot fi alctuite din mai multe fiiere surs.
Un identificator declarat (i, eventual iniializat) ntr-un fiier poate avea aceeai semnificaie n ntregul
program (ceea ce nseamn c va ocupa o anumit zon de memorie n programul executabil) sau va fi
recunoscut doar la nivelul fiierului n care este declarat (deci, dac va aprea o nou declarare a sa n alt
fiier, acestei noi declarri i va corespunde alt zon de memorie n programul executabil). Se spune c
identificatorii din prima categorie sunt cu legtur extern, iar cei din a doua categorie sunt cu
legtur intern. Atribuirea de memorie n funcie de tipul de legtur se face prin aciunea
compilatorului i a editorului de legturi. Pentru variabilele globale care sunt declarate cu modificatorul
const (adic nu i pot schimba valoarea) legtura implicit este n C extern, iar n C++ intern.
Legtura intern pentru o variabil global se declar cu modificatorul static, iar cea extern cu
modificatorul extern. n concluzie, exist anumite echivalene ntre declaraiile de variabile globale cu
modificatorul const, pe care le artm mai jos:
/* C */
/* C++ */
77
78
Tabelul 6.4
Declaraie
tip nume;
tip nume[];
tip nume[n];
tip *nume;
tip *nume[];
tip (*nume[]);
tip (*nume)[];
tip &nume;
tip nume( );
tip *nume( );
tip *(nume( ));
tip (*nume)( );
tip &nume( );
nume este
de tipul tip
tablou (deschis) de tip
tablou cu n elemente de tipul tip
(nume[0], nume[1], , nume[n-1])
pointer la tipul tip
tablou (deschis) de pointeri la tipul tip
tablou (deschis) de pointeri la tipul tip
pointer la tablou (deschis) de tipul tip
referin la tipul tip
funcie care ntoarce tipul tip
funcie care ntoarce pointer la tipul tip
funcie care ntoarce pointer la tipul tip
pointer la funcie care ntoarce tipul tip
funcie care ntoarce referin la tipul tip
Exemplu
int num;
int num[];
int num[5];
int
int
int
int
int
int
int
int
int
int
*num;
*num[];
*(num[]);
(*num)[];
#
num();
*num();
*(num());
(*num)();
&num();
n C i C++ se poate utiliza modificatorul const pentru a se declara anumite aspecte ale unei variabile (sau
obiect) ca fiind constante. Urmtorul tabel 6.5 listeaz posibilele combinaii i descrie semnificaia lor.
Tabelul 6.5
Declaraie
const tip nume = valoare;
tip *const nume = valoare;
const tip *nume = valoare;
const tip *const nume = valoare;
nume este
constant de tipul tip
pointer constant la tipul tip
pointer (variabil) la constant de
tipul tip
pointer constant la constant de
tipul tip
Vom investiga unele exemple de variabile declarate cu modificatorul const mpreun cu utilizarea lor. S
considerm urmtoarele declaraii:
int i; // un intreg obisnuit
79
80
#include <stdio.h>
int main() {
const int ci = 1;
const int &cr = ci;
int &r = ci; // creaza un intreg temporar pentru referinta
// cr = 7; // nu se poate atribui valoare unei referinte
// constante
r = 3; // schimba valoarea intregului temporar
printf("ci == %d, r == %d, cr==%d\n", ci, r, cr);
return 0;
}
Dac se compileaz cu GNU C++, compilatorul afieaz urmtorul avertisment:
conversion from 'const int' to 'int &' discards const
Ceea ce se ntmpl de fapt este crearea de ctre compilator a unei variabile ntregi temporare cu valoarea
lui ci cu care este iniializat referina lui r. Prin urmare, cnd se schimb r, valoarea variabilei temporare
este cea care se schimb, i nu ci. Aceast variabil temporar exist att timp ct exist referina r.
Alte compilatoare (de exemplu TURBO C++, BORLAND C++) fac acelai lucru, dar nu afieaz nici un
avertisment.
Referina cr este definit ca read-only (referin constant). Aceasta interzice utilizarea ei n partea stng a
atribuirilor. Dac am terge marcajul comentariu (//) din faa liniei care conine o astfel de atribuire
interzis, compilatorul ar da un mesaj de eroare.
6.2.2.2.2 Funcii
C++ permite suprancrcarea aa cum s-a definit la sectiunea 5.3. De exemplu, putem s definim dou
funcii max(), din care una ntoarce cel mai mare dintre doi ntregi, iar cealalt ntoarce cel mai mare dintre
dou iruri de caractere.
#include <stdio.h>
81
82
ix = jx = 1;
foo(ix, jx);
/* ix == 1, jx == 42 */
}
Utilizarea
plasarea
referinelor
simbolului
&
la
dup
ntoarcerea
tipul
valorilor
valorii
funciilor
ntoarse.
acest
se
specific
caz
orice
prin
operaie
return care a produs oprirea execuiei funciei i ntoarcerea valorii. Aceasta permite ca astfel de apleuri de
funcii s fie utilizate i n membrul stng al unei atribuiri.
Exemplu:
#include <iostream.h>
int a;
int & schimba(int b)
{
a+=b;
cout<<"in schimba a="<<a<<endl;
return a; // se intoarce referinta la variabila globala a schimbata de
// functie
}
int main()
{
schimba(2)+=3; // se schimba valoarea lui a de catre functie si de
// catre atribuire
cout<<"in main a="<<a<<endl;
}
ntoarcerea referinelor la variabile locale (sau temporare, de exemplu rezultatul unor expresii) din blocul
funciei este incorect (semnalat de obicei de compilator cu un avertisment) deoarece n acest caz variabila
local este eliminat imediat dup execuia funciei, deci este eliminat i referina, iar valoarea nu mai este
ntoars n funcia apelant. De notat c, dac nu s-ar fi folosit referin ca valoare de ntoarcere, varabila
local sau temporar respectiv ar fi fost eliminat dup transmiterea rezultatului n funcia apelant.
83
#include<iostream.h>
void func (int, int=10);
void main()
{ cout<<"func(5, 99)"
func(5, 99); //apel normal
cout<<"func(5), ";
func(5); //apel cu un singur argument
}
void func(inti, intj)
{ cout <<"argumente:i="<<i;
cout<<"j="<<j<<"\n";
}
84
Programul afieaz:
Un apel fr argumente (func(); ) este eronat deoarece argumentul i nu are asociat o valoare implicit.
Alocarea/eliberarea dinamic a memoriei folosind operatorii new i delete
Alocarea i eliberarea dinamic a memoriei se face n C cu anumite funcii de bibliotec (malloc(),
free() i altele). Pe lng acestea, n C++ se introduc operatorii unari new (pentru alocare) i delete (pentru
eliberare). Operatorul new poate fi utilizat n urmtoarele forme:
pt_la_tip = new tip;
pt_la_tip = new tip(val_init);
pt_la_tip = new tip[n];
unde:
- tip = tipul variabilei dinamice, care poate fi un tip de date oarecare;
-
Varianta a treia se utilizeaz pentru alocarea memoriei pentru n elemente de tipul tip (un tablou cu n
elemente) i n este o expresie ntreag. Se pot aloca i tablouri multidimensionale, specificnd toate
dimensiunile. Iniializarea tablourilor nu este posibil. Operatorul new aloc spaiul necesar, corespunztor
tipului, i ofer ca rezultat:
-dac alocarea a reuit un pointer de tipul (tip *) coninnd adresa zonei de memorie alocate; -n caz contrar
(memorie insuficient sau fragmentat), un pointer cu valoarea NULL (=0).
Exemplul urmtor ilustreaz sintaxa pentru toate variantele:
int n=15, *pi1, *pi2, *pit1, *pit2;
85
pi1=new int;
//variabila intreaga initializata
pi2=new int(23)
//variabila intraga initializata cu 23
pit1=new int[330];
//tablou unidimensional int
pit2=new int[n][3*n];
//tablou bidimensional int
Operatorul delete are sintaxa
delete pt_la_tip;
unde pt_la_tip este adresa obinut n urma unei alocri cu operatorul new, utilizarea altei valori fiind
ilegal, comportarea programului ntr-o astfel de situaie fiind nedefinit.
Ca i n cazul utilizrii funciilor de alocare i eliberare dinamic a memoriei, este
necesar ca, dup ce unei variabile pointer i s-a atribuit o adres cu operatorul new,
s nu i se atribuie alt adres i s nu se ias din blocul n care pointerul este
declarat dect dup eliberarea vechii zone de memorie cu operatorul delete. n caz
contrar, vechea zon de memorie va rmne ocupat, fra a mai putea fi utilizat, ceea
ce poate duce, mai ales prin repetarea n cursul execuiei a operaiilor de mai sus la
mari consumuri inutile de memorie. De reinut c grelile de mai sus nu sunt semnalate
n cursul comiplrii sau al execuiei.
Funcii "inline"
Directiva preprocesor #define ofer posibilitatea utilizrii macrodefiniiilor cu argumente. Pentru fiecare
apelare preprocesorul insereaz n textul programului, n locul apelului, textul macrodefiniiei n care se
substituie numele argumentelor formale cu textele care reprezint argumentele efective (de reinut c este o
simpl nlocuire de texte, fr nicio verificare de corectitudine sintactic). Textul rezultat este preluat apoi
de compilatorul propiu_zis. Princialul avantaj al macrodefiniiei faa de funcie este obinerea unui timp de
execuie mai scurt, deoarece codul obiect este inserat efectiv n program i se elimin operaiile asociate
apelului unei funcii (salvri i restabiliri de registre ale unei microprocesorului, transfer de argumente prin
stiv, etc), iar principalul dezavantaj este creterea dimensiunii codului obiect generat de compilator,
deoarece pentru fiecare apel se insereaz codul surs corespunztor instruciunilor din macrodefiniie.
86
Procedeul este avantajos atunci cnd codul obiect rezultat din compilarea macrodefiniiei nu este prea lung
i se fac puine inserri de macrodefiniii, dar, n momentul execuiei, se fac multe execuii ale operaiilor
aprute ca urmare a utilizrii macrodefiniiilor, ceea ce se poate ntmpla dac substituirile de macrodefiniii
se fac n interiorul blocurilor unor instruciuni repetitive.
Un dezavantaj special este constituit de faptul c "expresiile" din macrodefiniii sunt de fapt simple texte
care nu sunt supuse nici verificrii corectitudinii, nici nu sunt evaluate nainte de substituire, ceea ce poate
duce la efecte nedorite.
Un exemplu simplu l reprezint macrodefiniia:
#define patrat(x) x*x
...
int i, j, rez;
rez=patrat(i); //cirect:rez=i*i;
rez=patrat(i+1); //eroare:rez=i+1*i+1, deci
//rez=2*i+1
care, dup cum se vede, nu reuete s defineasc o funcie care s calculeze ptratul unei expresii. Eroarea
de mai sus poate fi nlturat definind patrat(x) astfel:
#define patrat(x) (x)*(x)
...
rez=patrat(i+1); //corect:rez=(i+1)*(i+1);
Nici acum ns nu se elimin toate posibilitile de eroare, ceea ce se vede mai jos:
rez=patrat(++i); //eroare :rez=(++i)*(++i),
//dubla incrementare
C++ ofer posibilitatea declarrii funciilor "inline" care combin avantajele funciilor propriu-zise cu cele
ale macrodefiniilor. Definiiile acestora sunt compilate ca i cele ale funciilor obinuite, dar, dar, spre
deosebire de funciile obinuite, la fiecare apelare codul obiect al funciei inline este inserat n codul
programului de ctre compilator. n acest fel, ca i n cazul macrodefiniiilor se elimin operaiile aferente
87
apelului. Funciile inline pstreaz ns toate proprietiile funciilor n privina verificrii validitii
apelurilor, modului de calcul i transfer al argumentelor, etc.
Se pstreaz avantajul creterii vitezei, da i dezavantajul creterii dimensiunii codului. Nu mai este
posibil compilarea separat a unei funcii inline sau utilizarea unui pointer ctre o asemenea funcie.
Funciile inline se definesc i se urilizeaz similar cu cele obinuite, cu excepia adugrii specificatorului
inline. Problema din exemplul de mai sus se poate rezolva complet cu o funcie inline, dup cum urmeaz:
inline int patrat(int x){ return x*x; }
...
rez=patrat(++i);
Operatorul de rezoluie
n C++ este definit operatorul de rezoluie (: : ) care permite accesul la un identificator global, dintr-un bloc
n care acesta nu este vizibil datorit unei redeclarri. Se mai numete operator de acces. De exemplu:
char sir[20]="Sir global";
void func()
{
char *sir; //variabila locala
sir="Sir local";
puts(::sir); //afiseaza sirul global
puts(sir); //afiseaza sirul local
}
88
class Punct {
int _x, _y; // coordonatele punctului
public: // inceputul sectiunii interfata
void setX(const int val);
void setY(const int val);
int getX() { return _x; }
int getY() { return _y; }
};
Punct unpunct;
Se declar o clas Punct i se declar un obiect unpunct. Putem s gndim o definiie de clas ca pe o
definiie de structur cu funcii (sau "metode"). n plus, se pot specifica mai detaliat "drepturi de acces". De
exemplu _x i _y sunt private, deoarece datele membre ale claselor sunt private. Prin urmare, trebuie s se
"comute" explicit drepturile de acces pentru a se declara urmatoarele ca publice. Aceasta se face prin
utilizarea cuvntului cheie public urmat de ":". Orice membru care urmeaz acest cuvnt cheie este accesibil
din afara clasei.
Putem s comutm iari la drepturi de acces private ncepnd o seciune privat cu cuvntul cheie private.
Acest lucru este posibil de cte ori este necesar:
class Foo {
// private implicit...
public:
// ceea ce urmeaza este public pana...
private:
//...aici, unde se revine la private...
public:
//... si iarasi la public.
89
};
Amintim c o structur struct este o combinaie de diferite date membre care sunt accesibile din afar.
Putem acum s exprimm o structur cu ajutorul unei clase, n care toi membrii sunt declarate ca publice:
class Struct {
public: // Membrii structurii sunt implicit publici
// date, metode
};
C++ face exact acelai lucru cu struct. Structurile sunt tratate ca i clasele. n timp ce membrii claselor
(definite cu class) sunt implicit privai, membrii structurilor (definite cu struct) sunt publici. Totui, putem
s utilizm de asemenea private : pentru a comuta la o seciune privat n structuri.
S revenim la clasa noastr Punct. Interfaa ei ncepe cu seciunea public n care definim patru metode,
cte dou pentru fiecare coordonat pentru a atribui i, respectiv, a furniza valoarea sa. Metodele pentru a
atribui valori sunt numai declarate. Funcionalitatea lor efectiv urmeaz s fie definit. Metodele pentru
furnizarea valorii au un corp de funcie. Ele sunt definite n interiorul clasei, sau cu un termen englez uzual,
sunt metode inline.
Acest tip de definire a metodelor este util pentru corpuri de funcie mici i simple. El de asemenea
mbunatete performana, deoarece corpurile metodelor inline sunt "copiate" n cod oriunde apare o
apelare a unei astfel de metode.
Dimpotriv, apelrile metodelor de obinere a valorii vor avea ca rezultat o apelare de funcie "adevarat".
Definim aceste metode n afara declaraiei de clas. Aceasta face necesar s se declare crei clase aparine o
anumit definiie de metod. De exemplu, o alt clas ar putea s defineasc o metod setX() care este
diferit de aceea din Punct. Trebuie s putem defini domeniul de definiie; utilizm deci operatorul de
rezoluie "::":
void Punct::setX(const int val) {
_x = val;
}
90
_y = val;
}
Aici definim metoda setX() (setY()) n interiorul domeniului clasei Punct. Obiectul unpunct poate s
utilizeze aceste metode pentru a a-i atribui i a furniza informaii despre el nsui:
Punct unpunct;
unpunct.setX(1); // Initializare
unpunct.setY(1);
//
// x este necesar incepand de aici, deci il definim aici si
// il initializam cu coordonata x a lui unpunct
//
int x = unpunct.getX();
Apare ntrebarea: cum "tiu" metodele din ce obiect sunt invocate? Aceasta se face prin transferul implicit
al unui pointer la obiectul care invoc metoda. Putem accesa acest pointer n nteriorul metodei prin
cuvntul cheie this. Definiiile metodelor setX() i setY() utilizeaz membrii clasei _x i _y respectiv.
Dac sunt invocai de un obiect, aceti membri sunt "automat" atribuii obiectului corect. Putem s utilizm
this pentru a ilustra ce se ntampl de fapt:
void Punct::setX(const int val) {
this->_x = val; // Aceasta se utilizeaza pentru a referi
// obiectul invocant
}
void Punct::setY(const int val) {
this->_y = val;
}
91
Aici utilizm explicit pointerul this pentru a dereferi explicit obiectul invocant. Din fericire, compilatorul
insereaz automat aceste dereferiri ale membrilor clasei, deci putem s utilizm efectiv primele definiii ale
metodelor setX() i setY(). Totui, uneori are sens s se tie c exist un pointer this disponibil care indic
obiectul invocant.
n mod curent avem nevoie s apelm metodele de atribuire pentru a iniializa un obiect punct. Totui, am
putea dori s iniializm punctul atunci cnd l definim. Utilizm, prin urmare, metode speciale numite
constructori.
6.2.2.3.2 Constructori
Constructorii sunt metode pentru a iniializa un obiect n momentul definirii sale. Lrgim clasa noastr
Punct astfel nct s iniializeze un punct cu coordonatele (0, 0):
class Punct {
int _x, _y;
public:
Punct() {
_x = _y = 0;
}
void setX(const int val);
void setY(const int val);
int getX() { return _x; }
int getY() { return _y; }
};
Constructorii au acelai nume cu clasa (astfel sunt recunoscui ca fiind constructori). Ei nu au valoare de
ntoarcere. Ca i alte metode, ei pot avea argumente. De exemplu, putem dori s iniializm un punct cu alte
coordonate dect (0, 0). Definim deci un al doilea constructor avnd dou argumente ntregi n interiorul
clasei:
class Punct {
92
93
94
Cu ajutorul constructorilor am satisfcut una din cerinele noastre privind implementarea tipurilor de date
abstracte: iniializarea n momentul definirii. Avem nevoie nc de un mecanism care s "distrug" automat
un obiect atunci cnd devine non-valid (de exemplu, din cauza prsirii domeniului su. Prin urmare, clasele
pot s defineasc destructori.
6.2.2.3.3 Destructori
S considerm o clas Lista. Elementele listei sunt adugate i terse n mod dinamic. Constructorul ne
ajut s crem o list iniial vid. Totui, atunci cnd ieim din domeniul definiiei unui obiect list, trebuie
s ne asigurm c memoria alocat este eliberat. Definim prin urmare o metod special numit destructor
care este apelat o dat pentru fiecare obiect n momentul distrugerii sale:
void foo() {
Lista olista; // Lista::Lista() este initializata
// cu lista vida.
... // adauga/sterge elemente
} // Apelarea destructorului!
Distrugerea unui obiect are loc automat atunci cnd obiectul i prsete domeniul de definiie sau este
distrus n mod explicit. Ultima aciune are loc atunci cnd se aloc dinamic un obiect i l eliberm dac nu
mai este necesar.
Destructorii sunt declarai ca i constructorii. Astfel, i ei utilizeaz numele clasei, dar prefixat cu simbolul
~ (tilda):
class Punct {
int _x, _y;
public:
Punct() {
_x = _y = 0;
}
Punct(const int x, const int y) {
_x = xval;
95
_y = yval;
}
Punct(const Punct &din) {
_x = din._x;
_y = din._y;
}
~Punct() { /* Nu se face nimic! */ }
void setX(const int val);
void setY(const int val);
int getX() { return _x; }
int getY() { return _y; }
};
Destructorii nu au argumente. Este chiar eronat s se defineasc argumente, deoarece destructorii sunt
apelai implicit n momentul distrugerii: nu exist deci ansa s se specifice argumente efective.
6.2.2.3.4 Motenire
n pseudo-limbajul nostru, am formulat motenirea prin "inherits from". n C++ aceste cuvinte sunt
nlocuite prin caracterul ":". De exemplu, s proiectm o clas pentru puncte tridimensionale. Dorim,
desigur, s reutilizm clasa noastr deja existent Punct. ncepem proiectarea clasei noastre dup cum
urmeaz:
class Punct3D : public Punct {
int _z;
public:
Punct3D() {
setX(0);
setY(0);
_z = 0;
96
}
Punct3D(const int x, const int y, const int z) {
setX(x);
setY(y);
_z = z;
}
~Punct3D() { /* Nimic de facut */ }
int getZ() { return _z; }
void setZ(const int val) { _z = val; }
};
Tip de motenire
private protected public
inaccesibil inaccesibil
private
protected
protected private
private
protected
public
inaccesibil
protected
public
Tabelul 6.6
97
Coloana din stnga listeaz drepturi posibile de acces pentru membrii supraclaselor. Un rol special are un al
treilea mod de acces: protected. Acesta este utilizat pentru membri care s fie direct utilizabili n subclase
dar care s nu fie accesibili din afar. Astfel, s-ar putea spune c membrii de acest tip sunt ntre private i
public prin aceea ca ei pot fi utilizai n ierarhia de clase avnd drept rdcin clasa corespunztoare,
utilizndu-se i tipul de motenire protected.
Urmtoarele coloane arat drepturile de acces care rezult pentru membrii unei supraclase n funcie de
tipul de motenire.
6.2.2.3.4.2 Construcie
Atunci cnd crem o instaniere a clasei Punct3D constructorul su este apelat. Deoarece Punct3D este
derivat din Punct, constructorul clasei Punct este apelat i el. Totui, acest constructor este apelat naintea
executrii corpului constructorului clasei Punct3D. n general, naintea execuiei corpului unui anumit
constructor, constructorii fiecrei supraclase sunt apelai pentru a iniializa partea din obiectul creat. Cnd
crem un obiect cu
Punct3D punct(1, 2, 3);
al doilea constructor al clasei Punct3D este invocat, dar, naintea executrii corpului constructorului,
constructorul Punct() este invocat pentru a iniializa partea Punct a obiectului punct. Este bine c am
definit un constructor fr argumente. Acest constructor iniializeaz coordonatele bidimensionale _x i _y
cu 0 (zero). Deoarece Punct3D este derivat numai din Punct nu exist alt apelare de constructor i corpul
lui Punct3D(const int, const int, const int) este executat. Aici se invoc metodele setX() i setY() pentru a
se iniializa explicit coordonatele bidimensionale. n plus, primete valoare i a treia coordonat _z. Acest
fapt este foarte nesatisfctor, deoarece am definit un constructor punct() care are dou argumente pentru
iniializa cu ele coordonatele. Astfel, trebuie numai s putem declara c, n loc de utilizarea constructorului
implicit Punct(), se va utiliza constructorul parametrizat Punct (const int, const int). Putem face aceasta
specificnd constructorul dorit dup un singur caracter ":" chiar naintea corpului constructorului Punct3D:
class Punct3D : public Punct {
...
public:
98
Punct3D() {... }
Punct3D(
const int x,
const int y,
const int z) : Punct(x, y) {
_z = z;
}
...
};
Dac am avea mai multe supraclase, am da apelrile lor de constructori ca pe nite liste cu elementele
separate prin virgule. De exemplu, s presupunem c o clas Parte definete numai un constructor cu un
argument. Atunci, pentru a crea un obiect al clasei Compusa trebuie s invocm Parte() cu argumentul
su:
class Compusa {
Parte parte;
...
public:
Compusa(const int parteParametru) : parte(parteParametru) {
...
}
...
};
Aceast iniializare dinamic poate s fie de asemenea utilizat cu tipuri de clase predefinite. De exemplu,
constructorii clasei Punct ar putea fi definii ca:
Punct() : _x(0), _y(0) {}
Punct(const int x, const int y) : _x(x), _y(y) {}
99
Este bine s utilizm aceast metod de iniializare ct mai des posibil, deoarece ea permite compilatorului
s creeze variabile i obiecte iniializate corect n loc s le creeze cu o valoare implicit i s utilizeze o
atribuire suplimentar, (sau alt mecanism) pentru a da valori.
6.2.2.3.4.3 Distrugere
Dac un obiect este distrus, de exemplu prin prsirea domeniului su de definiie, destructorul clasei
corespunztoare este invocat. Dac aceast clas este derivat din alte clase, destructorii lor sunt i ei
apelai, ajungndu-se la un lan recursiv de apelri.
6.2.2.3. 5 Polimorfism
n pseudo-limbajul nostru puteam s decalarm metode ale unor clase ca fiind virtuale, fornd ca valoarea
lor s fie bazat pe coninut n loc de tipul obiectelor. Putem s utilizm aceasta i n C++:
100
class ObiectDesenabil {
public:
virtual void print();
};
Clasa ObiectDesenabil definete o metod print() care este virtual. Putem s derivm din aceast clas
alte clase:
class Punct : public ObiectDesenabil {
...
public:
...
void print() {... }
};
Din nou print() este o metod virtual, deoarece motenete aceast proprietate de la ObiectDesenabil.
Funcia display() care este capabil s afieze orice fel de obiect desenabil, poate fi definit ca:
void display(const ObiectDesenabil &ob) {
// pregateste tot ce este necesar
ob.print();
}
Atunci cnd se utilizeaz metode virtuale unele compilatoare avertizeaz dac destructorul clasei
corespunztoare nu este declarat i el virtual. Acest lucru este necesar dac se utilizeaz pointeri la subclase
(virtuale) atunci cnd acetia sunt distrui. Deoarece pointerul este declarat ca supraclas, n mod normal
destructorul su ar fi apelat. Dac destructorul este virtual, destructorul obiectului efectiv referit este apelat
(i atunci, recursiv, toi destructorii supraclaselor sale). Iat un exemplu:
class Culoare {
public:
101
virtual ~Culoare();
};
class Rosu : public Culoare {
public:
~Rosu(); // Mostenit virtual de la Culoare
};
class RosuDeschis : public Rosu {
public:
~RosuDeschis();
};
Utiliznd aceste clase definim o palet dup cum urmeaz:
Culoare *paleta[3];
102
// Apeleaza ~Culoare()
Diferitele apelri de destructori apar numai din cauz c sunt utilizai destructori virtuali. Dac ei nu ar fi
fost declarai virtuali, fiecare delete ar fi apelat numai ~Culoare() (deoarece paleta[i] este de tipul pointer
la Culoare).
103
public:
Complex() : _real(0.0), _imag(0.0) {}
Complex(const double real, const double imag) :
_real(real), _imag(imag) {}
Complex sum(const Complex op);
Complex inm(const Complex op);
...
};
Vom putea atunci s utilizm numere complexe i s "calculm" cu ele:
Complex a(1.0, 2.0), b(3.5, 1.2), c;
c = a.sum(b);
Aici atribuim lui c suma numerelor a i b. Dei absolut corect, acesta nu este un mod foarte convenabil de
exprimare. Este mai natural s se utilizeze bine-cunoscutul "+" pentru a se exprima adunarea a dou numere
complexe. Din fericire, C++ ne permite s suprancrcm aproape toi operatorii si cu tipuri nou create. De
exemplu, am putea defini un operator "+" pentru clasa noastr Complex:
class Complex {
...
public:
...
Complex operator +(const Complex &op) {
double real = _real + op._real,
imag = _imag + op._imag;
return(Complex(real, imag));
}
104
...
};
n acest caz, am fcut operatorul + membru al clasei Complex. O expresie de forma:
c = a + b;
este tradus ntr-un apel de metod:
c = a.operator +(b);
Astfel, operatorul + necesit numai un argument. Primul argument este implicit furnizat de obiectul invocat
(n acest caz a).
Totui, o apelare a operatorului poate fi interpretat i ca o apelare obinuit de funcie, ca n:
c = operator +(a, b);
n acest caz operatorul suprancrcat nu este membru al unei clase. El este definit n exterior ca o funcie
suprancrcat normal. De exemplu, am putea s definim operatorul + astfel:
class Complex {
...
public:
...
double real() { return _real; }
double imag() { return _imag; }
// Nu este nevoie sa se defineasca operatorul aici!
};
105
6.2.2.3. 8 Prieteni
Putem defini funcii sau clase ca fiind prieteni ai unei clase pentru a le permite accesul la datele lor membre
private. De exemplu, n seciunea precedent am putea s dorim ca funcia pentru operatorul + s aib acces
la datele membre private _real i _imag ale clasei Complex. Prin urmare, declarm operatorul + ca prieten
al clasei Complex:
class Complex {
...
public:
...
friend Complex operator +(
const Complex &,
const Complex &
);
};
Complex operator +(const Complex &op1, const Complex &op2) {
double real = op1._real + op2._real,
106
Tipuri de fiier
Descriere de iterfa (fiiere
"antet" sau "de inclus")
Fiier de implementare (surs)
n C
Fiier de implementare (surs)
n C++
Descriere de interfa (fiier
"model" sau "ablon")
Implementare
Extensii
Borland
.
h.H.hpp.HPP.hxx.HX
.h.hpp.hxx
.c.C
.c
.cpp.CPP
.cpp.c++.C.cc.cxx
.tpl.TPL
.tpl
UNIX
Borland
UNIX
Borland
UNIX
Borland
UNIX
107
n acest suport de curs utilizm .h pentru fiiere C++ i .tpl pentru fiiere care conin definiii de modele (n
englez template). Chiar dac scriem numai cod C, se poate utiliza.cc pentru a determina compilatorul s l
trateze ca C++. Acesta face mai simple combinaiile celor dou limbaje, dei mecanismele interne de
aranjare a numelor n program ale compilatorului difer de la un limbaj la altul.
Paii compilrii
Procesul de compilare preia fiierele.cc, le preproceseaz (nlturnd comentariile i incluznd fiierele
antet) i le translateaz n fiiere obiect. Sufixele tipice pentru aceste fiiere sunt.o.obj.
Dup terminarea cu succes a compilrii, mulimea de fiiere obiect rezultat este prelucrat de un editor de
legturi (n englez linker). Acest program combin fiierele i bibliotecile necesare i creaz un program
executabil. Sub UNIX acest fiier este denumit a.out dac nu se specific altfel. Aceti pai sunt ilustrai n
Figura 6.1.
.cc
compilator
compilator
.h,.tpl
.cc
editor
editor de
de
legturi
legturi
biblioteci
.out
Figura 6.1
Compilatoarele moderne permit combinarea celor doi pai. De exemplu programele mici date ca
exemplemai sus pot fi compilate i legate cu compilatorul GNU C++ dup cum urmeaz ("exemplu.cc"este
numai numele unui exemplu, desigur ):
108
gcc exemplu.cc
Despre stil
Fiierele antet sunt utilizate pentru a descrie interfaa fiierelor de implementare. n consecin, sunt incluse
n fiecare fiier de implementare care utilizeaz interfaa unui fiier de implementare particular. Aa cum s-a
menionat mai sus aceast includereeste efectuat prin copierea coninutului fiierelui antet la fiecare
apariie a directivei de preprocesor # include ajungndu-se la un fiier C++ pur uria. Pentru a evita
includerea unor copii multiple cauzat de dependenele multiple utilizm cod condiional. Preprocesorul
definete directive condiionale pentru verificarea diferitelor aspecte ale prelucrrii sale. De exemplu putem
verifica dac un macro este deja definit:
#ifndef
MACRO
109
#endif /* __ANTETULMEU_H */
__ANTETULMEU_H este un nume unic asociat fiecrui fiier antet. Prima dat cnd fiierul este inclus,
__ANTETULMEU_H nu este definit, astfel nct fiecare linie este inclus i prelucrat.Prima linie definete
un macro numit __ANTETULMEU_H. Dac accidental fiierul ar urma s fie inclus a doua oar (n timpul
prelucrrii aceluiai fiier de intrare), __ANTETULMEU_H este definit, deci tot ceea ce urmeaz pn la
#endif este srit.
6.2.4 Exerciii
1. Polimorfism.
Explicai de ce
void display(const ObiectDesenabil ob);
nu produce ieirea dorit.
7 Rezolvrile exerciiilor
Aceast seciune prezint exemple de rezolvri pentru exerciiile de mai sus.
110
int date);
INTEGER int_lista_getFirst(int_reprez_lista_t this);
INTEGER int_lista_getNext(int_reprez_lista_t this);
BOOL int_lista_isEmpty(int_reprez_lista_t this);
END Intreg-Lista;
Aceast reprezentare introduce probleme suplimentare care sunt cauzate de nesepararea traversrii de
structura de date. Dup cum v amintii, pentru a itera asupra elementelor listei am utilizat o instruciune de
ciclare cu urmtoarea condiie:
WHILE date IS VALID DO
date a fost iniializat printr-o apelare a list_getFirst(). Procedura de tip list de ntregi int_list_getFirst()
ntoarce un ntreg, prin urmare nu exist ceva cum ar fi "ntreg invalid" care s poat fi utilizat pentru
verificarea terminrii ciclului.
2. Diferenele ntre programarea orientat pe obiecte i alte tehnici. n programarea orientat pe obiecte
obiectele schimb mesaje unul cu altul. n celelalte tehnici de programare, datele sunt schimbate ntre
proceduri sub controlul unui program principal. Pot coexista obiecte de acelai fel, dar fiecare cu starea sa
proprie. Acest fapt este n contrast cu abordarea modular, n care fiecare modul are numai o stare global.
Operaia inm nu cere nici o precondiie, la fel ca sum i dif. Postcondiia este, desigur rez=n*k. Urmtoarea
operaie imp necesit s avem k diferit de 0 (zero). Prin urmare, definim precondiia: k diferit de 0. Ultima
operaie abs ntoarce valoarea lui N dac N este pozitiv sau 0 sau -N dac N este negativ. Din nou nu
conteaz ce valoare are N cnd aceast operaie este aplicat. Postcondiia este:
dac N>=0 atunci
abs=N
altfel
abs=-N.
2. TAD Fracie.
(a) O fracie simpl const din numrtor i numitor. Ambele sunt numere ntregi. Acest fapt este similar cu
exemplul referitor la numrul complex prezentat n seciune. Am putea alege celpuin dou stucturi de date
pentru a pstra valorile: un tablou sau un record.
(b) Prezentarea interfeei. Amintim c interfaa este chiar setul de operaii vizibile din exterior. Putem
descrie o interfaa a unei fracii ntr-o manier verbal, n consecin, avem nevoie de operaii:
pentru a obine valoarea numrtorului/ numitorului;
pentru a atribui o valoare numrtorului/ numitorului;
pentru a aduna o fracie, ntorcnd suma;
pentru a scdea o fracie, ntorcnd diferena;
...
(c) Dm mai jos unele axiome i precondiii pentru fiecare fracie care sunt de asemenea satisfcute pentru
TAD:
Numitorul trebuie s fie diferit de 0 (zero), altfel valoarea fraciei nu este definit.
Dac numrtorul este 0 (zero), valoarea fraciei este 0 pentru orice valoare a numitorului.
Orice numr ntreg poate fi reprezentat printr-o fracie al crei numrtor este numrul, iar numitorul este
1.
3. TAD-urile definesc proprietile unui set de instanieri. Acestea furnizeaz o viziune abstract asupra
acestor proprieti furniznd un set de operaii care pot fi aplicate asupra instanierilor. Acest set de operaii,
adic interfaa, este cel care definete proprietile instanierilor.
Utilizarea unui TAD este restricionat de axiome i precondiii. Ele definesc condiii i proprieti ale
mediului n care pot fi utilizate instanieri ale TAD-ului.
112
4. Avem nevoie s enunm axiome i s definim precondiii care s asigure utilizarea corect a
instanierilorTAD-urilor. De exemplu, dac nu declarm 0 (zero) ca fiind elementul neutru al adunrii
ntregilor, ar putea exista un TAD Intreg care s fac ceva prestabilit adunnd 0 la N. Aceasta nu este
ceeeace se ateapt de la un ntreg. Astfel, axiomele i precondiiilene dau un mijloc de a ne asigura c
TAD-urile "funcioneaz"aa cum dorim s o fac.
5. Descrierea relaiilor.
(a) O instaniere este un reprezentant efectiv TAD. (Este astfel un"exemplu" al su. Acolo unde TAD-ul
declar c utilizeaz un"numr ntreg cu semn" ca strctur de date, o instaniere pastreaz efectiv o valoare,
s spunem, "-5".
(b) TAD-urile generice definesc aceleai proprieti ale TAD-urilorlor corespunztoare. Totui, ele sunt
destinate altui tip particular.De exemplu, TAD Lista definete proprieti ale listelor. Astfel, am putea avea o
operaie adauga(elem) care adaugun nou element elem la list. Nu spunem de ce tip este elem, ci numai c
el va fiultimul element al listei dup aceast operaie. Dac utilizm un TAD generic Lista tipul acestui
element este cunoscut: el este dat de parametrul generic.
(c) instanierile aceluiai TAD generic ar putea fi considerate ca "rude". Ele ar putea fi "veri" a instanierilor
unui alt TAD generic dac cele dou TAD-uri generice au n comun acelai TAD.
114
(obiect). Acest fapt poate fi privit astfel: clientul "trimite un mesaj" server-ului prin care cere s i se
furnizeze datele memorate acolo.
(a) n mediul client/server avem n realitate dou entiti care actioneazla distan: procesele client i
server. n mod normal, aceste dou entiti schimb date ntre ele sub forma unor mesaje Internet.
int _raza;
methods:
setRaza(int nouaRaza)
getRaza()
}
Aceasta este similar clasei Cerc n spaiul bidimensional (plan). Aici Punct-3D este un Punct cu o
dimensiune suplimentar.
class Punct-3D inherits from Punct {
attributes:
int _z;
methods:
setZ(int nouZ);
getZ();
}
n consecin, Punct-3D i Punct sunt legate printr-o relaie de tipul "este-un".
(c)Funcionalitatea lui move(). move(), aa cum este definit n seciunea 4.2, permite obiectelor
tridimensionale s se deplaseze dup axa X, deci numai ntr-o singur dimensiune. Ea face aceasta doar prin
modificarea prii bidimensionale a obiectelor tridimensionale. Aceast parte bidimensional este definit
de clasa Punct i motenit direct sau indirect de obiectele tridimensionale.
(a) Graful de motenire (vezi Figura 7.1)
116
ObiectDesenabil
Punct
Dreptunghi
Punct-3D
Cerc
Sfera
Figura 7.1
(e) Alt graf de motenire. n acest exemplu, clasa Sfera motenete de la Cerc i adaug simplu a treia
coordonat. Acesta are avantajul c o sfer poate fi tratat ca un cerc (de exemplu raza sa poate s fie uor
modificat cu metodele/funciile care traseaz cercul). El are dezavantajul c"distribuie" tratarea obiectului
(centrul n spaiul tridimensional) prin ierarhia de motenire: de la Punct prin Cerc la Sfera. Astfel, aceast
tratare nu este accesibil ca un tot.
2. Motenire multipl. Graful de motenire din Figura 4.9 introduce evident conflicte de nume cu
proprietile clasei A.
Totui, aceste proprieti sunt determinate unic prin urmrirea cii de la D la A. Astfel, D poate schimba
proprietile lui A motenite de la B urmnd calea de motenire prin B. La fel, D poate schimba proprietile
lui A motenite de C urmnd calea de motenire prin C. Prin urmare, acest conflict de nume nu duce n mod
necesar la eroare, ct timp cile sunt indicate.
117
S observm nti c n C++ argumentele funciilor i metodelor sunt transmise prin valoare. Prin urmare,
obj va fi o copie a argumentului efectiv dat la apelarea funciei. Aceasta nseamn c ObiectDesenabil
trebuie s fie o clas din care s se poat crea obiecte. Nu ar fi acesta cazul, dac ObiectDesenabil Ar fi o
clas abstract (aa cum ar fi dac print() ar fi definit ca o metod pur).
Dac exist o metod virtual print() care este definit de clasa ObiectDesenabil, atunci (deoarece obj este
numai o copie a argumentului efectiv) aceast metod este invocat. Ea nu este metoda definit de clasa
argumentului efectiv (din cauz c acesta nu mai joac un rol semnificativ!).
2. Adugarea metodei de tergere. Nu dm un program, dar dm algoritmul. Metoda sterge() tebuie s
itereze aupra listei pn cnd ntlnete un nod cu elementul de date cerut, s tearg acel nod i s ntoarc
1. Dac lista este vid sau elementul de date nu poate fi gsit, ntoarce 0(zero).
n timpul iterrii, sterge() trebuie s compare succesiv elementul de date dat cu cele din list. Prin urmare,
poate exista o comparaie ca:
if (date == curent->date()) {
// element gasit
}
Aici utilizm operatorul de egalitate "==" pentru a compara cele dou elemente. Cum aceste elemente pot fi
de orice tip, ele pot fi mai ales obiecte ale claselor definite de utilizator. A pune ntrebarea: cum este
"egalitatea" definit pentru aceste noi tipuri? n consecin, pentru a se permite metodei sterge() s lucreze
corect, lista va fi utilizat numai pentru tipuri pentru care se definesc operatorii "==" i "!=" n mod
corespunztor. Altfel sunt utilizate comparri implicite, ceea ce ar putea duce la rezultate ciudate.
3. Clasa ListaNumrat. O list numrat este o list care are asociat i numrul elementelor sale. Astfel,
cnd un element de date este adugat, numrul este mrit cu unu, iar cnd un element este ters, este
micorat cu unu. Din nou, nu dm implementarea complet, artm doar o metod ( adauga() ) i felul
cum implementarea este modificat:
class ListaNumarata : public Lista {
int _numar; // Numarul de elemente
...
public:
118
...
virtual void adauga(const T date) {
_numar++; // Incrementeaza si...
Lista::adauga(date); //... adauga la lista
}
...
}
Nu orice metod poate fi implementat astfel. La unele metode trebuie s se verifice dac _numar necesit
s fie modificat sau nu. Totui, ideea principal este c fiecare metod de liste este expandat (sau
specializat ) pentru lista numrat.
4. Problema iteratorului. Pentru a rezolva problema iteratorului, ne-am putea gndi la o soluie n care
iteratorul conine o referin la lista lui corespunztoare.
n momentul crerii iteratorului aceast referin este iniializat pentru a indica lista dat. Metodele
iteratorului trebuie modificate pentru a utiliza aceast referin n locul pointer-ului _pornire.
Bibliografie
[1] T. Blanescu, S. Gavril, H. Georgescu, M. Gheorghe, L. Sofonea, I. Vduva
"Pascal i Turbo Pascal", vol 2, Ed. Tehnic, Bucureti, 1992
[2] K. Jamsa, L. Klander "Totul despre C i C++", Editura Teora, Bucureti, 2002
(traducere din limba englez)
[3] O. Catrina, I. Cojocaru "Turbo C++", Editura Teora, Bucureti, 1993
[4] H. Schildt: "C++ manual complet", Editura Teora, Bucureti, 2000
[5] Bjarne Stroustrup: "The C++ Programming Language", Adisson-Wesley, 3nd edition, 1997
[6] C. Spircu, I. Loptaru "POO: analiza, proiectarea i programarea orientat pe obiecte ", Editura Teora,
Bucureti, 1995
119
Resurse on-line
[web1] Object-Oriented System Development by Dennis de Champeaux, Douglas Lea, and Penelope Faure
(http://g.oswego.edu/dl/oosdw3/)
[web2]
Peter
Muller:
Introduction
to
Object-Oriented
Programming
(http://www.gnacademy.org/uu-gna/text/cc/material.html)
[web3] Bruce Eckel: Thinking in C++, 2nd Edition (http://www.bruceeckel.com/)
[web4] Online C++ tutorial (http://www.intap.net/~drw/cpp/index.htm)
120
Using
C++