Sunteți pe pagina 1din 141

Algoritmica si programare/Semestrul I:

Programare Œn limbajul Turbo (Borland) Pascal

1. Generalitati
2. Elemente de sintaxa
3. Structura unui program
4. Tipuri de date predefinite
5. Expresii
6. Declaratii si definitii
7. Instructiuni
8. Subprograme Pascal
9. Tipuri de date structurate
10. Structuri dinamice de date
11. Elemente de modularizare
12. Abstractizarea datelor
13. Programarea orientata pe obiecte

Capitolul 8. Subprograme Pascal

8.1. Proiectarea top-down


8.2. Abstractizarea subprogramelor
8.3. Rolul subprogramelor Œntr-un program
8.4. Declararea si apelarea procedurilor
8.4.1. Proceduri fara parametri
8.4.2. Proceduri cu parametri de intrare
8.4.3. Proceduri cu parametri de intrare si de iesire
8.5. Declararea si apelarea functiilor
8.6. Apelarea procedurilor si transmiterea parametrilor. Semantica apelului
8.6.1. Parametri transmisi prin valoare
8.6.2. Parametri transmisi prin adresa (referinta)
8.6.3.Verificarea corespondentei parametrilor formali cu cei actuali
8.6.4. Variabile globale si locale Œntr-un subprogram
8.6.5. Reguli de proiectare a subprogramelor
8.7. Subprograme apelate din subprograme
8.7.1. Apelul subprogramelor din subprograme. Subprograme locale Œn
subprograme
8.7.2. Apelul recursiv
8.7.3. Subprograme mutual recursive
8.8. Tipuri procedurale
8.8.1. Tipuri Procedure
8.8.2. Tipuri Function
8.1. Proiectarea top-down
Proiectarea top-down (de sus Œn jos) este o metoda de dezvoltare a programelor prin care problema de
rezolvat este descompusa Œn subprobleme mai simple (care se pot rezolva mai usor). La rƒndul sau,
fiecare subproblema poate fi din nou descompusa Œn alte subprobleme daca este necesar.
Formal, proiectarea top-down sau descompunerea functionala se poate exprima astfel:
Se da o problema P care trebuie rezolvata.
Problema P se descompune Œn subproblemele P1, P2, ..., Pn astfel Œncƒt:
1) Subproblemele Pi (1 = i = n) sunt deduse din specificarea problemei P
2) Fiecare subproblema Pi (1 = i = n) se poate rezolva independent
3) Fiecare subproblema Pi (1 = i = n) este mai simpla decƒt problema initiala P
4) Solutia problemei P se obtine prin compunerea solutiilor subproblemelor P1, P2, ..., Pn
1
5) Pentru o problema data, descompunerea se opreste cƒnd problema se poate rezolva direct
Limbajul Pascal pune la dispozitia programatorului urmatoarele instrumente pentru proiectarea top-
down:
- subprogramele (procedure si function) care corespund subproblemelor
- structurile de calcul secventiala, alternativa si repetitiva care se folosesc la compunerea solutiilor
subproblemelor
- instructiunile de apel de subprograme care se folosesc la determinarea solutiilor subproblemelor si la
compunerea acestora (Œn cazul apelului recursiv)
O metoda complementara proiectarii top-down este proiectarea bottom-up (de jos Œn sus). Aceasta se
bazeaza pe existenta solutiilor pentru unele probleme simple sub forma unor subprograme. Prin
compunerea solutiilor acestor subprobleme se obtine solutia problemei initiale fara a fi nevoie de
rescrierea tuturor subprogramelor necesare.
Exemplul 1): Determinarea solutiei ecuatiei de gradul II.
Definirea problemei este urmatoarea:
Se considera ecuatia de gradul II de forma a*X^2 + b*X + c = 0, unde a, b
si c sunt numere reale. Sa se determine radacinile ale acesteia.

Analiza problemei
Problema initiala se poate reformula astfel: se dau coeficientii ecuatiei de gradul II a*X^2 + b*X + c =
0 si se cere sa se determine radacinile x1, x2 ale acesteia, reale sau complexe.
Descompunerea (nivelul 1)
Din acest enunt identificam deja trei subprobleme
P1: Citirea datelor de intrare (CitesteDate)
P2: Determinarea solutiei ecuatiei de gradul II
P3: Afisarea solutiei determinate (AfiseazaRezultate)

Solutia problemei initiale P se obtine prin compunerea solutiilor subproblemelor P1, P2, P3 (aici
compunerea Œnseamna rezolvarea lor Œn ordinea enumerarii date).
Arborele de structura al programului este:

ÚÄÄÄ¿
³P³
ÀÄÂÄÙ
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
ÚÄÄÁÄ¿ ÚÄÁÄÄ¿ ÚÄÁÄÄ¿
³ P1 ³ ³ P2 ³ ³ P3 ³
ÀÄÄÄÄÙ ÀÄÄÄÄÙ ÀÄÄÄÄÙ

Continuarea descompunerii
Subproblema P1: Citirea coeficientilor ecuatiei de gradul II
Specificare: Sa se citeasca trei numere reale a, b si c
Descompunere (nivelul 2)
Se identifica Œn acest caz o singura subproblema, numita P4:
CitesteReal care va trebui aplicata (rezolvata) de trei ori:
1) CitesteReal pentru a
2) CitesteReal pentru b
3) CitesteReal pentru c
Subproblema P4 (CitesteReal) este suficient de simpla si nu mai trebuieste descompusa (se poate
implementa direct)
Subproblema P2: Determinarea solutiei ecuatiei de gradul II
Specificare: Sa se rezolve ecuatia de gradul II cu coeficientii a, b si c
Descompunere (nivelul 2)
2
Se identifica Œn acest caz doua subprobleme:
P5: verifica daca ecuatia este de gradul II (DateCorecte)
P6: determina solutia ec. de gradul II (Prelucreaza)
Solutia subproblemei P2 este
Daca DateCorecte atunci Prelucreaza
Subproblema P3: Afisarea rezultatelor
Specificare: Sa se afiseze rezultatele rezolvarii ec. de gradul II
Nu mai este nevoie de descompunere
Arborele de structura al programului este:
ÚÄÄÄ¿
³P³
ÀÄÂÄÙ
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
ÚÄÄÁÄ¿ ÚÄÁÄÄ¿ ÚÄÁÄÄ¿
³ P1 ³ ³ P2 ³ ³ P3 ³
ÀÄÄÂÄÙ ÀÄÂÄÄÙ ÀÄÄÄÄÙ
³ ÚÄÄÄÄÄÁÄÄÄÄÄÄ¿
ÚÄÄÁÄ¿ ÚÄÄÁÄ¿ ÚÄÄÁÄ¿
³ P4 ³ ³ P5 ³ ³ P6 ³
ÀÄÄÄÄÙ ÀÄÄÄÄÙ ÀÄÄÄÄÙ

8.2. Abstractizarea subprogramelor


Un subprogram poate fi considerat ca o 'cutie neagra' care primeste date de intrare si furnizeaza
rezultate. Proiectarea unui subprogram se realizeaza combinƒnd doua metode:
- 1) abstractizarea prin parametrizare
- 2) abstractizarea prin specificare
1) Abstractizarea prin parametrizare Œnseamna identificarea, pentru o problema data P, a intrarilor
(datelor de intrare) si iesirilor (rezultatelor) acesteia.
Intrarile si iesirile problemei se vor numi Œn continuare 'parametri'.
Parametrii asigura generalitatea problemei si independenta de mediul de apel (programul care va apela
problema data va trebui sa furnizeze doar valori concrete pentru parametrii de intrare si sa foloseasca
rezultatele furnizate de parametrii de iesire). Independenta de mediul de apel asigura reutilizarea
(solutiei) problemei Œn alte locuri decƒt cel pentru care a fost rezolvata initial. Mediul de apel este
format din toate variabilele care au in domeniul lor de vizibilitate punctul apelului (atƒt cele declarate
Œn programul apelant, deci locale Œn acesta, cƒt si cele declarate Œn blocuri exterioare programului
apelant).
Din punctul de vedere al comunicarii cu mediul extern (care Œl poate apela),un subprogram P are trei
tipuri de parametri
- parametri de intrare: introduc informatie Œn P fara a fi modificati Œn timpul executiei acestuia (IN)
- parametri de iesire: scot informatie (rezultate) din P; nu intereseaza valorile cu care acesti parametri
intra Œn P (OUT)
- parametri de intrare-iesire: sunt concomitent parametri de intrare si de iesire pentru P (IN-OUT)
2) Abstractizarea prin specificare ataseaza fiecarei probleme o specificare.
Specificarea unei probleme asigura independenta folosirii acesteia de modul de rezolvare a ei (altfel spus
de algoritmul folosit pentru rezolvare). Unei aceleiasi specificatii Œi pot corespunde mai multe
implementari; toate aceste implementari vor avea Œn comun aceeasi maniera de apel a procedurii. Prin
specificare se precizeaza ce trebuie sa faca problema si nu cum face.
Specificarea contine precizarea numelui problemei, a parametrilor acesteia si a unor asertiuni (predicate)
numite pre-conditii si post-conditii:
- pre-conditiile precizeaza cerintele impuse asupra datelor de intrare pentru
ca subprogramul sa se execute corect.
- post-conditiile precizeaza efectele executiei subprogramului asupra datelor
3
de intrare/iesire (care formeaza Œmpreuna starea programului).
De obicei specificarea unei probleme se face dupa ce s-au stabilit parametrii acesteia sau simultan cu
parametrizarea. Dupa ce specificarea s-a Œncheiat, se poate trece la implementarea acesteia. Unei
specificari pot sa-i corespunda mai multe implementari diferite (folosind, de exemplu, algoritmi diferiti
pentru transformarea datelor de intrare Œn date de iesire).
Specificarea serveste Œn primul rƒnd celui care foloseste rezolvarea problemei. Din acest punct de
vedere, ea trebuie privita ca un contract Œntre cel care a rezolvat problema si cel care vrea sa foloseasca
rezolvarea respectiva Œntr-un program. Cƒt timp specificarea nu se schimba, toti clientii acesteia (toti
programatorii care apeleaza o implementare sau alta a ei) vor obtine aceleasi rezultate pe aceleasi date
de intrare, folosind acelasi apel (indiferent de implementarea folosita).
Parametrizarea si specificarea sunt legate Œntre ele, ambele concurƒnd la realizarea a ceea ce am numit
'abstractizarea procedurala'. In paragraful urmator vom discuta acest lucru din punctul de vedere al
folosirii procedurilor (subprogramelor) Œntr-un program.

Pentru exemplul nostru:


Parametrizarea
P1 - CitesteDate are trei parametri de iesire, care vor fi notati cu a, b si c
P4 - CitesteReal are un singur parametru de iesire- numarul real citit x
P5 - DateCorecte are doi parametri:
a - coeficientul lui x2 (parametru de intrare)
corect - parametru de iesire
corect = 0 - ecuatia este de gradul II (a ? 0)
corect = 1 - ecuatia nu este de gradul II (a = 0)
P6 - Prelucreaza are urmatorii parametri
a, b, c - coeficientii ecuatiei de gradul II (parametri de intrare)
x1, x2 - solutia ecuatiei (parametri de iesire)
cod - informatie privind solutia (parametru de iesire)
cod = 2 - radacini reale diferite
cod = 3 - radacini reale egale
cod = 4 - radacini complexe
P3 - AfiseazaRezultate are urmatorii parametri
x1, x2 - solutia ecuatiei (parametri de intrare)
cod - informatie privind solutia (parametru de intrare)
cod = 1 - ecuatia nu este de gradul II
cod = 2 - radacini reale diferite
cod = 3 - radacini reale egale
cod = 4 - radacini complexe
Specificarea
P1 - CitesteDate(a, b, c)
Pre: True
Post: a, b, c î R
P4 - CitesteReal(x)
Pre: True
Post: x î R
P5 - DateCorecte(a, corect)
Pre: a î R
Post: corect î {0, 1}
corect = 1 daca ecuatia este de gradul II
corect = 0 Œn caz contrar

P6 - Prelucreaza(a, b, c, x1, x2, cod)


Pre: a, b, c î R si a <> 0
4
Post:
x1, x2 solutii ale ecuatiei a*X^2 + b*X + c = 0
cod î {2, 3, 4}
cod = 2 - radacini reale diferite
cod = 3 - radacini reale egale
cod = 4 - radacini complexe

P3 - AfiseazaRezultate(x1, x2, cod)


Pre:
x1, x2 î R
cod î {1, 2, 3, 4}
Post: True

8.3. Rolul subprogramelor Œntr-un program


Prima ratiune de folosire a subprogramelor Œntr-un program este mai buna structurare a acestuia.
Folosind subprograme, corpul programului (numit si program principal) se exprima printr-un numar mai
mic de instructiuni si este mai usor de Œnteles. De asemenea, programul este mai usor de modificat.
Exemplu: vezi structurile de programe discutate anterior.
A doua ratiune de folosire a subprogramelor Œntr-un program este evitarea repetarii unor grupuri de
instructiuni identice sau aproape identice.
Un exemplu foarte simplu este chiar citirea coeficientilor ecuatiei de gradul II, care Œnseamna repetarea
de trei ori a aceluiasi grup de instructiuni (afiseaza mesaj, citeste coeficient). Prin parametrizare,
grupurile de instructiuni aproape identice se pot transforma Œn subprograme. Folosind apoi valori
corespunzatoare pentru parametri, fiecare apel realizeaza functia sa dorita (grupul de instructiuni
specific).
A treia ratiune de folosire a subprogramelor Œntr-un program este refolosirea lor (apelarea lor Œn
programe diferite). Daca subprogramele sunt suficient de generale si daca ele necesita un volum mare de
munca (de programare si de testare), atunci este rational ca, odata ce au fost realizate, sa se poata apela
oriunde este nevoie de ele, fara a le rescrie. Pentru aceasta, Turbo Pascal pune la dispozitia
programatorului (Œncepƒnd din versiunea 4.0) modulele (numite unit-uri). Un astfel de modul contine
(simplist vorbind) subprograme care se pot folosi Œn alte programe. Pentru a le putea folosi, este
suficient sa includem numele unit-ului Œntr-o fraza Uses pusa la Œnceputul zonei de declaratii a
programului Pascal. Mediul Turbo Pascal are o serie Œntreaga de unit-uri care contin subprograme ce se
pot apela Œn programele pe care le realizam. Versiunile anterioare (sub 4.0) ofera o directiva de
compilare ce permite includerea unui fisier sursa (care contine numai subprogramul refolosit) Œntr-un
alt fisier sursa ($I).
Prin urmare, Œntr-un program Pascal putem folosi urmatoarele categorii de subprograme:
- subprograme standard: se gasesc Œn unit-ul System (care este folosit implicit de catre orice program
Turbo Pascal); exemple: Read, ReadLn, Write, WriteLn, functiile matematice, subprogramele de lucru
cu stringuri, etc.
- subprograme locale programului: sunt declarate Œn zona de declaratii a acestuia si se pot folosi (apela)
numai Œn programul respectiv
- subprograme de biblioteca: sunt declarate Œn module (ale mediului sau realizate de programator); se
pot apela daca programul contine Œn clause Uses numele unit-ului (modulului) Œn care sunt declarate.
In toate cazurile, subprogramele se pot apela (folosi) numai dupa ce au fost declarate. Apelul de
subprogram este considerat Œn Pascal ca instructiune simpla si Œnseamna urmatoarele
Program apelant P Subprogram SP
[declaratii] [declaratii]

begin ( corpul P } ÚÄ> begin { corpul SP }


... ³ instr_1;
apel SP ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ...
5
instr_dupa_apel <ÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ instr_n;
... ÀÄÄ end; { SP }
end. { P }

La Œntƒlnirea instructiunii de apel, controlul executiei este predate subprogramului apelat


(subprogramul SP). Executia instructiunilor din programul apelant se opreste si Œncepe executia
instructiunilor din corpul subprogramului SP. La terminarea executiei corpului SP (atingerea
instructiunii end; sau Œntƒlnirea unei instructiuni Exit - vezi cap 7), se reia executia instructiunilor din
programul apelant cu instr_dupa_apel.
Ce variabile se pot folosi Œn corpul SP?
- variabile locale (cele declarate Œn SP)
- parametrii SP (daca are)
- variabilele globale din programul apelant (numai daca SP este subprogram local al lui P)
Din punctul de vedere al comunicarii cu programul apelant, subprogramele se pot Œmparti Œn:
- subprograme care nu folosesc mediul programului apelant (deci nu au nevoie de parametri si nu recurg
la folosirea unor variabile globale)
- subprograme ce comunica cu mediul programului apelant prin variabile globale si/sau parametri
- subprograme ce comunica cu mediul programului apelant numai prin parametri
Dintre categoriile expuse mai sus, prima si a treia contin subprograme ce se pot include Œn module si
deci refolosi Œn mai multe programe. In a doua categorie (care foloseste variabile locale din programul
apelant care sunt globale Œn el) se includ subprogramele locale ale acestuia, iar declaratia lor trebuie sa
apara dupa declaratia variabilelor folosite (atƒt declaratia variabilelor globale, cƒt si declaratia de
subprogram vor face parte din zona de declaratii a programului apelant, Œnsa acele variabile care se
folosesc Œn corpul subprogramului trebuie declarate Œnainte de declararea subprogramului).
Exemplu:
Program P1a;
{ foloseste un subprogram local, P2 }
Var
Contor: Integer;
Procedure P2;
Begin
WriteLn('Valoarea lui Contor este', Contor:4)
End; {P2}
Begin {programul principal}
For Contor := 1 To 3 Do P2
End; { P1a }

Programul P1a apeleaza subprogramul local P2, care nu are parametri si care
foloseste variabila globala Contor, declarata Œnainte de declaratia lui P2.
El va afisa ca rezultat
Valoarea lui Contor este 1
Valoarea lui Contor este 2
Valoarea lui Contor este 3
Programul functioneaza corect deoarece P2 este Œn domeniul de vizibilitate al variabilei Contor.
Daca Œnsa declaratia variabilei Contor apare dupa declaratia subprogramului P2, ca Œn exemplul
urmator,
Program P1b;
{ foloseste un subprogram local, P2 }
Procedure P2;
Begin
WriteLn('Valoarea lui Contor este', Contor:4)
{ aici se va produce eroare de compilare: Contor este
6
identificator necunoscut }
End; {P2}
Var
Contor: Integer;
Begin {programul principal}
For Contor := 1 To 3 Do P2
End; { P1b }
atunci se va produce o eroare de compilare la compilarea lui P1b, deoarece identificatorul Contor (referit
acolo) nu este Œnca declarat (P2 nu este Œn DoV al variabilei Contor si refera aceasta variabila).
Cum se corecteaza eroarea de mai sus?
- varianta 1 (nerecomandata): Programul P1a
- varianta 2 (recomandata): Programul P1c - Contor devine parametru al lui P2
Program P1c;
{ foloseste un subprogram local, P2 }
Procedure P2(C: Integer);
Begin
WriteLn('Valoarea lui Contor este', C:4)
End; {P2}
Var
Contor: Integer;
Begin {programul principal}
For Contor := 1 To 3 Do P2(Contor)
End; { P1c }
Sfaturi:
- Concepeti orice subprogram astfel Œncƒt el sa poata fi inclus Œntr-un modul.
- Nu folositi Œntr-un subprogram variabile globale.
- In locul variabilelor globale folositi parametri.
- Pentru a fi siguri ca subprogramele locale nu folosesc variabile globale, puneti declaratiile de variabile
(Var) dupa declaratiile de subprograme (ca Œn programul P1c). Aceasta va garanta faptul ca variabilele
globale se vor folosi numai Œn corpul programului, nu si Œn subprograme.

In Pascal exista doua feluri de subprograme: procedura si functie.


Subprogramele de tip procedura realizeaza o operatie bine precizata si apelul lor este considerat
instructiune simpla. Subprogramele de tip functie calculeaza o valoare si apelul lor este considerat
operand Œntr-o expresie.
8.4. Declararea si apelarea procedurilor
8.4.1. Proceduri fara parametri
Procedurile fara parametri executa anumite operatii independente de mediul programului. Un astfel de
exemplu este procedura standard ClrScr aflata Œn unit-ul Crt, care realizeaza stergerea ecranului. Alte
exemple de proceduri standard fara parametri sunt Break, Continue si Exit din unit-ul System (discutate
Œn cap. 7).
Sintaxa de declarare a unei proceduri fara parametri este:
Procedure Nume_Proc;
[declaratii]
Begin { corpul procedurii }
instr_1;
...
instr_n
End; { Nume_Proc }
unde
- Procedure, Begin, End sunt cuvinte rezervate
- Nume_Proc este numele procedurii (identificator)
7
- declaratia de procedura introduce un bloc
- prima linie a procedurii se numeste antet (la fel ca la programul principal)
- zona declaratii contine declaratiile locale Œn procedura, care vor avea ca
DoV blocul procedurii; se pot include aici toate tipurile de declarati
discutate [DoV=domeniu de vizibilitate;DuV=durata de viata]
- instr_1, ..., instr_n sunt instructiuni Pascal (simple sau compuse)
Apelul unei asemenea proceduri se face printr-o instructiune care contine doar numele procedurii.
Exemplu:
[declarare]
Procedure Afiseaza_Titlu;
Begin
ClrScr;
WriteLn('Ce face programul')
End; { Afiseaza_Titlu }
[apelare]
begin
...
Afiseaza_Titlu;
....
End.
Conceperea unei proceduri fara parametri Œnseamna de fapt (pentru limbajul Pascal, si nu numai)
realizarea unei noi instructiuni simple a limbajului.
Odata procedura declarata, aceasta Œnseamna pentru Pascal o noua instructiune simpla, care grupeaza
(se traduce Œn) toate instructiunile continute Œn corpul ei. Aceasta este cea mai simpla modalitate de
extindere a limbajului cu noi instructiuni, din ce Œn ce mai complexe. Unit-urile mediului Turbo
(Borland) Pascal contin o serie de astfel de proceduri.
8.4.2. Proceduri cu parametri de intrare
Aceste proceduri nu modifica mediul programului care le apeleaza. Rolul lor este de a efectua prelucrari,
de a verifica anumite conditii sau de a afisa anumite rezultate.
Sintaxa de declarare a unei proceduri cu parametri de intrare este:
Procedure Nume_Proc(<lista_parametri_formali>);
[declaratii]
Begin { corpul procedurii }
instr_1;
...
instr_n
End; { Nume_Proc }
unde toate precizarile de la procedurile fara parametri ramƒn valabile si
- lista_parametri_formali este o lista de declaratii de parametri formali de
forma:
<lista_parametri_formali> ::= {<declaratie parametri formali>}
<declaratie parametri formali> ::= <lista parametri>:<tip_de_date>;

- ultima <declaratie parametri formali> nu se Œncheie cu ;


Lista de parametri formali se poate transcrie Œn forma:
<lista_parametri_formali> ::= pf1: t1; pf2: t2; ..., pf_n: t_n
unde pf1, pf2, ..., pf_n sunt parametrii formali, iar t1, t2, ..., t_n sunt tipurile de date asociate acestora.
Apelul unei asemenea proceduri se face printr-o instructiune care contine numele procedurii, urmata de
lista parametrilor actuali:
Nume_Proc(<lista_parametri_actuali>);
unde <lista_parametri_actuali> este o lista de expresii, care trebuie sa corespunda cu
<lista_parametri_formali>
8
<lista_parametri_actuali> ::= pa1, pa2, ..., pa_n
Corespondenta dintre <lista_parametri_actuali> si <lista_parametri_formali>
Œnseamna urmatoarele (vezi si 8.6.3):
- cele doua liste trebuie sa aiba acelasi numar de elemente
- elementul de pe pozitia i din <lista_parametri_actuali> trebuie sa fie de
tipul de date ti (cum apare el precizat Œn <lista_parametri_formali>)
- altfel spus
- pa1 trebuie sa fie de tipul t1
- pa2 trebuie sa fie de tipul t2
- ...
- pan trebuie sa fie de tipul t_n

Exemplu:
Program P2a;
{ foloseste o procedura locala, ScrieSuma }
Procedure ScrieSuma(A, B, C: Integer);
Var
S: Integer;
Begin
S := A + B + C;
WriteLn('Suma numerelor este', S:4);
A := 0;
B := 0;
C := 0
End; {ScrieSuma}
Var
N1, N2, N3: Integer;
Begin {programul principal}
N1 := 12;
N2 := 23;
N3 := 34;
WriteLn('Se aduna numerele: ', N1:3, N2:3, N3:3);
ScrieSuma(N1, N2, N3);
WriteLn('S-au adunat numerele: ', N1:3, N2:3, N3:3)
End; { P2a }

In exemplul de mai sus, ScrieSuma contine o parte de declaratii locale, Œn care este declarata variabila
Œntreaga S. Aceasta variabila are ca domeniu de vizibilitate corpul procedurii ScrieSuma si poate fi
referita numai Œn acesta.
Daca se Œncearca referirea ei Œn programul principal, se va produce o eroare de compilare.
Parametrii formali ai procedurii ScrieSuma sunt declarati astfel:
A, B, C: Integer,
declaratie echivalenta cu
A: Integer; B: Integer, C: Integer (cƒnd mai multi parametri formali consecutivi au acelasi tip de date,
ei pot fi pusi Œntr-o lista (numele lor se separa prin virgule) si se scrie o singura data tipul (la fel ca la
declaratia de variabile). Declararea parametrilor formali ai procedurii se poate considera tot o declaratie
de variabile locale: acestia au semnificatie doar atƒta timp cƒt procedura se executa.
Antetul procedurii ScrieSuma stabileste modalitatea Œn care aceasta poate fi apelata. Prin urmare am
dedus o regula importanta: declaratia de procedura stabileste modul de apel al acesteia. Orice apel care
nu se conformeaza declaratiei va fi respins (Œn cazul limbajului Pascal de catre compilator).

9
Apelul procedurii ScrieSuma din exemplul de mai sus se conformeaza regulii enuntate. Toti parametrii
actuali de intrare pot fi orice expresii care se pot evalua Œn momentul apelului (Œn cazul nostru ei sunt
expresii simple, formate din variabile declarate Œn zona de declaratii a programului apelant).
Executia acestui program va produce afisarea urmatoarelor mesaje:
Se aduna numerele: 12 23 34
Suma numerelor este 69
S-au adunat numerele: 12 23 34
8.4.3. Proceduri cu parametri de intrare si de iesire
De cele mai multe ori, o procedura trebuie sa efectueze o operatie care modifica mediul programului
apelant. Am vazut anterior ca acest lucru se poate face Œn doua moduri:
- prin modificarea unei variabile globale (declarata Œn programul appellant Œnainte de declaratia
procedurii apelate, care este vizibila Œn corpul procedurii)
- prin transmiterea modificarii spre programul apelant printr-un parametru de iesire
Apare aici un element nou: cum se poate extrage (scoate) din procedura o valoare calculata Œn ea si
apoi aceasta sa se faca disponibila Œn programul apelant, care sa o poata folosi Œn continuare? Cu alte
cuvinte, cum se specifica faptul ca un parametru formal de procedura este parametru de iesire?

Limbajul Pascal pune la dispozitia programatorului un mecanism simplu de specificare a parametrilor de


iesire, prefixƒnd declaratia acestora cu cuvƒntul rezervat 'Var'. Pƒna acum am vazut ca 'Var' este folostit
pentru a preciza declaratii de variabile. Acum apare a doua folosire a sa, specificarea parametrilor de
iesire.

Sintaxa declaratiei de procedura cu parametri de intrare si de iesire este identica cu cea pentru
procedurile cu parametri de intrare, cu singura deosebire ca declaratiile parametrilor formali de iesire
trebuie prefixate cu Var. Din nou, daca o procedura are mai multi parametri de iesire de acelasi tip, si
care sunt consecutivi, declararea acestora se poate condensa.

Apelarea unei proceduri cu parametri de intrare si de iesire se face la fel ca Œn cazul procedurilor cu
parametri de intrare. Exista Œnca o restrictie privitoare la parametrii actuali de iesire. Daca parametrii
actuali de intrare pot sa fie orice expresii care se pot evalua Œn momentul apelului, parametrii actuali de
iesire trebuie sa fie obligatoriu variabile care au punctual apelului Œn domeniul lor de vizibilitate.

Exemple:
Declaratia de procedura

Procedure Imparte(a, b: Integer; Var c, r: Integer); {Œmpartirea numerelor a si b produce cƒtul c si


restul r}

este echivalenta cu
Procedure Imparte(a: Integer; b: Integer; Var c: Integer; Var r: Integer);
In declaratiile de mai sus, a si b sunt parametri de intrare, iar c si r sunt parametri de iesire.

Exemplul prezentat Œn paragraful precedent se poate rescrie aici astfel:


Program P2b;
{ foloseste o procedura locala, CalculSuma }
Procedure CalculSuma(A, B, C: Integer; Var S: Integer);
Begin
S := A + B + C;
A := 0;
B := 0;
C := 0
End; {CalculSuma}
10
Var
N1, N2, N3, Suma: Integer;
Begin {programul principal}
N1 := 12;
N2 := 23;
N3 := 34;
WriteLn('Se aduna numerele: ', N1:3, N2:3, N3:3);
CalculSuma(N1, N2, N3, Suma);
WriteLn('Suma numerelor este', Suma:4);
WriteLn('S-au adunat numerele: ', N1:3, N2:3, N3:3)
End; { P2b }

In exemplul de mai sus, CalculSuma nu mai contine declaratia locala a variabilei Œntregi S, aceasta
transformƒndu-se Œn parametru de iesire, fiind declarat ca atare Œn antetul procedurii.
Parametrii formali ai procedurii CalculSuma sunt declarati astfel
A, B, C: Integer; var S: Integer.
Aceasta declaratie specifica faptul ca primii trei sunt parametri de intrare, iar S este parametru de iesire.
Daca analizam programul principal, constatam ca:
- s-a declarat o noua variabila Œntreaga, Suma
- apelul procedurii CalculSuma contine patru parametri actuali, primii trei cu semnificatia din exemplul
anterior, iar al patrulea pe post de parametru de iesire (Œn cazul nostru variabila Suma joaca acest rol,
valoarea ei fiind folosita Œn continuare pentru afisare
- afisarea sumei numerelor s-a transferat din subprogram Œn programul appellant (motiv pentru care
numele subprogramului s-a schimbat)
Executia acestui program va produce afisarea acelorasi mesaje ca si cele de la programul precedent:
Se aduna numerele: 12 23 34
Suma numerelor este 69
S-au adunat numerele: 12 23 34

8.5. Declararea si apelarea functiilor

O categorie speciala de subprograme este formata din functii. Scopul unei functii este calculul unei
valori, care este Œntoarsa (folosita) Œn programul apelant. Asemanarile dintre proceduri si functii sunt
urmatoarele
- ambele sunt subprograme
- declaratiile lor formeaza un bloc
- ambele au parametri.
Deosebirile dintre proceduri si functii sunt cel putin urmatoarele:
- functiile Œntorc un singur rezultat, de un tip simplu (Œn Pascal)
- apelul de procedura este considerat Œn Pascal instructiune, pe cƒnd apelul de functie este considerat
operand Œntr-o expresie
- functiile nu trebuie sa aiba parametri de iesire
- la functii trebuie precizat tipul rezultatului intors (calculat), care este folosit in programul apelant
Din punct de vedere sintactic, declaratia de functie are forma:
Function Nume_Functie[(<lista_parametri_formali>)]: tip_rezultat;
[declaratii]
Begin { corpul functiei }
instr_1;
...
instr_n
End; { Nume_Functie }

11
unde
- Function, Begin, End sunt cuvinte rezervate
- Nume_Functie este numele functiei (identificator)
- lista_parametri_formali este o lista de declaratii de parametri formali (poate sa lipseasca)
- tip_rezultat este tipul de data al rezultatului Œntors de functie (este obligatoriu)
- declaratia de functie introduce un bloc
- prima linie a declaratiei de functie se numeste antet (la fel ca la programul principal)
- zona declaratii contine declaratiile locale Œn functie, care vor avea ca
DoV blocul functiei; se pot include aici toate tipurile de declarati discutate
- instr_1, ..., instr_n sunt instructiuni Pascal (simple sau compuse); Œntre aceste instructiuni trebuie sa
apara cel putin odata o instructiune de atribuire de forma
Nume_Functie := expresie;
Apelul unui subprogram de tip functie se face identic cu cel de procedura, adica se scrie numele
subprogramului (urmat Œn paranteza de parametrii actuali, daca functia are parametri). Ceea ce difera
este semnificatia apelului: deoarece functia Œntoarce o valoare, apelul de functie are semantica unui
operator Œntr-o expresie si nu semantica unei instructiuni simple, ca apelul de procedura. Prin urmare,
apelul de functie poate sa apara oriunde poate apare o expresie de tipul sau.
Exemplul urmator este echivalent cu cele prezentate anterior, folosind de aceasta data un subprogram de
tip functie:

Program P2c;
{ foloseste o functie locala, CalculSuma }
Function CalculSuma(A, B, C: Integer): Integer;
Begin
CalculSuma := A + B + C;
A := 0;
B := 0;
C := 0
End; {CalculSuma}
Var
N1, N2, N3: Integer;
Begin {programul principal}
N1 := 12;
N2 := 23;
N3 := 34;
WriteLn('Se aduna numerele: ', N1:3, N2:3, N3:3);
WriteLn('Suma numerelor este', CalculSuma(N1, N2, N3):4);
WriteLn('S-au adunat numerele: ', N1:3, N2:3, N3:3)
End; { P2c }

In exemplul de mai sus, CalculSuma nu mai contine declaratia locala a variabilei Œntregi S, si nici
declaratia parametrului de iesire S; rezultatul Œntors de functie va avea semnificatia lui S din exemplele
precedente. Acest lucru se specifica folosind Œn corpul functiei numele acesteia Œn membrul stƒng al
operatiei de atribuire: CalculSuma := A + B + C;.
Parametrii formali ai functiei CalculSuma sunt declarati la fel ca Œn
exemplul P2a.
Daca analizam programul principal, constatam ca:
- nu mai este nevoie de variabila Œntreaga Suma
- apelul functiei CalculSuma apare ca parametru al instructiunii de afisare a rezultatului:

WriteLn('Suma numerelor este', CalculSuma(N1, N2, N3):4);

12
Executia acestui program va produce afisarea acelorasi mesaje ca si cele dela programele precedente:
Se aduna numerele: 12 23 34
Suma numerelor este 69
S-au adunat numerele: 12 23 34

8.6. Apelarea procedurilor si transmiterea parametrilor. Semantica apelului

Apelul unui subprogram Œnseamna


- determinarea (localizarea) subprogramului apelat (din numele acesteia); reaminitim ca subprogramul
apelat poate fi:
- subprogram local (ca Œn exemplele P2a, P2b, P2c de mai sus)
- subprogram standard (aflat Œn unit-ul System)
- subprogram declarat Œntr-un alt unit (al carui nume apare Œntr-o clauza Uses si care a fost Œn
prealabil creat - fie ca este un unit al mediului, fie ca este un unit realizat de un programator)
- verificarea corectitudinii apelului (dupa ce s-a localizat subprogramul, antetul lui este disponibil si deci
si lista de parametri formali)
- evaluarea parametrilor actuali si stabilirea corespondentei dintre acestia si parametrii formali
- executia subprogramului, Œn care parametrii formali primesc la Œnceput valorile parametrilor actuali
- la terminarea executiei se preda controlul programului apelant, care continua executia cu instructiunea
de dupa apel
In ce cazuri apelul unui subprogram nu este corect (si compilatorul da un mesaj de eroare)?
- subprogramul apelat nu este declarat (sau s-a scris gresit numele Œn apel)
- lista parametrilor actuali nu corespunde cu lista de parametri formali
- DoV al subprogramului apelat nu contine punctul (locul) de unde s-a facut apelul
Vom discuta la Œnceput modalitatile de transmitere a parametrilor de intrare si de iesire si apoi vom
analiza modul cum se face verificarea corespondentei parametrilor actuali cu cei formali.

8.6.1. Parametri transmisi prin valoare

Vom comenta Œn cele ce urmeaza programul P2a. Presupunem ca s-a facut verificarea corespondentei
dintre parametrii actuali din apelul procedurii ScrieSuma si ca acestia corespund cu parametrii formali
precizati Œn declaratia procedurii.
Constatam ca numarul parametrilor actuali si tipul acestora coincide (parametru cu parametru), deci din
punct de vedere formal conditiile de apelare sunt Œndeplinite. Ce se Œntƒmpla mai departe pentru
executia apelului?
Urmatorul pas este evaluarea parametrilor actuali si punerea lor Œn corespondenta cu cei formali.
Evaluarea este simpla: parametrii actuali au fost initializati anterior, deci au valori bine determinate, iar
corespondenta se stabileste astfel:
- N1 este pus Œn corespondenta cu A
- N2 este pus Œn corespondenta cu B
- N3 este pus Œn corespondenta cu C
Punerea Œn corespondenta Œnseamna (Œn cazul nostru) copierea valorilor parametrilor actuali Œn
parametrii formali corespunzatori, deci se poate scrie printr-o secventa de atribuiri:
A := N1; { atribuie lui A valoarea expresiei N1 }
B := N2; { atribuie lui B valoarea expresiei N2 }
C := N3; { atribuie lui C valoarea expresiei N3 }
Explicatia de mai sus justifica de ce se pot folosi expresii pe post de parametri formali: acestea trebuie
doar sa se poata evalua Œn momentulapelului.
Acum procedura ScrieSuma se poate executa. Prima instructiune din corpul ei realizeaza calcului sumei
S := A + B + C;
care va folosi valorile parametrilor initializate anterior. Valoarea lui S este afisata apoi pe ecran Œn a
doua instructiune, iar urmatoarele trei instructiuni initializeaza pe A, B si C cu 0. Dupa aceasta se
13
Œntƒlneste cuvƒntul rezervat 'end', adica s-a terminat corpul procedurii. Prin urmare, s-a terminat si
executia procedurii ScrieSuma si se revine Œn programul apelant, la instructiunea urmatoare apelului.
Aceasta va afisa valorile dinainte de apel ale variabilelor N1, N2 si N3, cu toate ca parametrii
corespunzatori lor au fost modificati Œn timpul executiei corpului procedurii ScrieSuma.
Motivul pentru care se Œntƒmpla asa este acela ca modificarea valorilor parametrilor formali Œn
ScrieSuma nu influenteaza mediul de apel (adica valorile parametrilor actuali din programul apelant)
deoarece parametrii formali sunt tratati drept variabile locale ale subprogramului si ca la terminarea
executiei acestuia ei se distrug - nu mai are loc o atribuire inversa de valori de forma:
N1 := A:;
N2 := B;
N3 := C;
Cu alte cuvinte, A, B si C sunt doar copii ale parametrilor actuali N1, N2 si N3 si modificarea valorii lor
nu influenteaza valorile originalelor.
Aceasta modalitate de transmitere a parametrilor catre un subprogram (procedura sau functie) se
numeste 'transmitere prin valoare'. Fiecarui parametru actual transmis prin valoare Œi corespunde Œn
subprogramul apelat o pseudo-variabila locala, care are chiar numele parametrului formal corespunzator.
Concluzia acestei analize este urmatoarea:
Parametrii actuali transmisi prin valoare dintr-un program apelant P spre un subprogram apelat SP Œsi
pastreaza valorile dinainte de apelul SP, indiferent daca parametrii formali corespunzatori lor se
modifica sau nu Œn SP.

8.6.2. Parametri transmisi prin adresa (referinta)


Programul P2b contine o procedura ce are si parametri de iesire. Presupunem ca s-a facut verificarea
corespondentei dintre parametrii actuali din apelul procedurii CalculSuma si ca acestia corespund cu
parametrii formali precizati Œn declaratia procedurii.
Urmatorul pas este evaluarea parametrilor actuali si punerea lor Œn corespondenta cu cei formali. Primii
trei parametri actuali au fost initializati anterior (si sunt transmisi prin valoare), deci au valori bine
determinate, iar corespondenta se stabileste astfel:
- N1 este pus Œn corespondenta cu A
- N2 este pus Œn corespondenta cu B
- N3 este pus Œn corespondenta cu C
conform atribuirilor:
A := N1; { atribuie lui A valoarea expresiei N1 }
B := N2; { atribuie lui B valoarea expresiei N2 }
C := N3; { atribuie lui C valoarea expresiei N3 }
Al patrulea parametru actual, Suma, este variabila globala Œn P2b care Œnca n-a fost initializata. De
fapt, pƒna la apelul
CalculSuma(N1, N2, N3, Suma);
n-am avut nevoie de ea. Mai mult, vrem sa-i determinam valoarea prin acest apel si s-o folosim mai
departe (afisƒnd-o).
Parametru actual Suma din apel Œi corespunde parametrul formal S din antetul procedurii, care este
declarata asfel:
Procedure CalculSuma(A, B, C: Integer; Var S: Integer);
Acum apare deosebirea fata de transmiterea prin valoare. Am spus anterior ca S este Œn procedura un
parametru formal de iesire si ca am marcat acest lucru prefixƒnd Œn antet declaratia lui cu 'Var'. Ce
Œnseamna de fapt acest lucru?
Un parametru formal declarat cu Var se transmite prin referinta (adresa), adica corespondenta dintre
parametrul actual si cel formal Œn acest caz Œnseamna egalitate de adresa (si nu de valoare, ca Œn
cazul transmiterii prin valoare).
Prin urmare, corespondenta dintre parametrul actual Suma din apelul procedurii CalculSuma si
parametrul formal S din declaratia acesteia se stabileste prin atribuirea adresei variabilei Suma
(parametrul actual) ca adresa a parametrului formal S:
14
adresa(S) := adresa(Suma);
Explicatia de mai sus justifica de ce nu se pot folosi expresii pe post de parametri formali la parametrii
de iesire: o expresie nu are asociata o adresa fixa Œn memorie (pe timpul evaluarii sale poate fi de
exemplu memorata Œntr-un registru al UC). Mai mult, nu se justifica ca un parametru de iesire sa fie o
expresie.
Acum procedura CalculSuma se poate executa. Prima instructiune din corpul ei realizeaza calcului
sumei
S := A + B + C;
care va folosi valorile parametrilor initializate anterior si va modifica valoarea variabilei locale S. Dar
cum S si Suma au aceeasi adresa (si sunt de acelasi tip), se va modifica concomitent si valoarea
variabilei Suma (asa cum este ea vazuta Œn programul apelant). Urmatoarele trei instructiuni
initializeaza pe A, B si C cu 0 si executia corpului procedurii se termina, revenindu-se Œn programul
principal.
Urmatoarea instructiune de dupa apel realizeaza chiar afisarea valorii variabilei Suma (care am vazut
cum s-a modificat Œn programul apelat).
Modalitate de transmitere a parametrilor catre un subprogram (procedura sau functie) explicata Œn
aceasta sectiune se numeste 'transmitere prin adresa' sau 'transmitere prin referinta'. Pentru fiecare
parametru actual 'pa' transmis prin referinta, parametrul formal corespunzator 'pf' din subprogramul
apelatva avea aceeasi adresa cu el:
adresa(pf) := adresa(pa);
pe toata durata de executie a subprogramului si orice modificare a valorii parametrului formal 'pf' Œn
subprogram va avea ca efect imediat si modificarea valorii parametrului actual 'pa'.
Procedura CalculSuma are atƒt parametri transmisi prin valoare, cƒt si parametri transmisi prin referinta.
Modul Œn care acestia se comporta este ilustrat de rezultatele executiei programului P2b.
Concluzia acestei analize este urmatoarea:
Parametrii actuali transmisi prin referinta dintr-un program apelant P spre un subprogram apelat SP
Œsi modifica valorile simultan cu parametrii formali corespunzatori lor din SP.

8.6.3.Verificarea corespondentei parametrilor formali cu cei actuali


Am Œnceput sa discutam aceasta problema Œn 8.4.2, dar acolo Œnca nu aveam toate elementele
necesare pentru o prezentare completa a problemei. Acum putem sa afirmam ca verificarea se refera la
cele doua liste (lista parametrilor formali, precizata Œn declararea subprogramului si lista parametrilor
actuali, precizata Œn apel) si ca se verifica:
- numarul parametrilor transmisi (acelasi Œn ambele liste)
- tipul acestora (parametrii de pe pozitia 'i 'din ambele liste trebuie sa fie de acelasi tip)
- modalitatea de transmitere (parametrii de pe pozitia 'i' din lista de parametri actuali trebuie sa poata fi
transmis prin modalitatea de transmitere specificata Œn lista de parametri formali pentru al 'i'-lea
parametru formal).
Ne vom referi Œn continuare la programul P2b prezentat anterior. Apelul procedurii ScrieSuma
stabileste o corespondenta Œntre parametrii formali si parametrii actuali. Apelul este realizat prin
instructiunea
ScrieSuma(N1, N2, N3);
si primul pas care-l face compilatorul este sa stabileasca daca apelul este corect.
Cum stabileste compilatorul ca apelul s-a facut corect? Ne aducem aminte ca Pascal este un limbaj
puternic tipizat, ceea ce Œnseamna Œntre altele ca tipul oricarei expresii poate fi determinat la
compilare. In cazul nostru, expresiile care corespund parametrilor actuali sunt chiar identificatori de
variabile, deci expresii simple. Tipul acestor expresii este dedus Œn acest caz chiar din declaratia
acestora
N1, N2, N3: Integer;
ce apare Œn partea de declaratii a programului principal.

15
8.6.4. Variabile globale si locale Œntr-un subprogram
Am discutat Œn capitolul 6 (Declaratii) despre domeniul de vizibilitate al unui identificator Œn general
si despre domeniul de vizibilitate si durata de viata a variabilelor. Exemplul urmator, pe care-l vom
comenta, contine atƒt variabile globale cƒt si variabile locale si exista o pereche de declaratii pentru
aceeasi variabila.
Program P3;
{ Domeniul de vizibilitate al variabilelor -
variabile locale si globale }
Var
Contor : Integer; { globala }
Indice : Integer; { globala }
Procedure Afiseaza_Date;
Var
Contor, { locala }
Alta : Integer; { locala }
Begin
Contor := 7;
Writeln('In Afiseaza_Date Contor =', Contor:5,
' Indice =',Indice:5);
End; { Afiseaza_Date }
Procedure Scrie_Sfarsit;
Begin
Writeln('Programul s-a terminat');
End; { Scrie_Sfarsit }
Begin { Programul principal }
For Indice := 1 to 3 do Begin
Contor := Indice;
Writeln('In programul principal Contor =',Contor:5,
' Indice =',Indice:5);
Afiseaza_Date;
Writeln('In programul principal Contor =',Contor:5,
' Indice =',Indice:5);
Writeln;
End; { For }
Scrie_Sfarsit
End. { Programul principal }

{ Rezultatul executiei
In programul principal Contor = 1 Indice = 1
In Afiseaza_Date Contor = 7 Index = 1
In programul principal Contor = 1 Indice = 1

In programul principal Contor = 2 Indice = 2


In Afiseaza_Date Contor = 7 Index = 2
In programul principal Contor = 2 Indice = 2

In programul principal Contor = 3 Indice = 3


In Afiseaza_Date Contor = 7 Index = 3
In programul principal Contor = 3 Indice = 3

Programul s-a terminat


}
16
Variabila Contor este declarata de doua ori: odata Œn zona de declaratii a programului principal (de
unde va rezulta ca este globala) si Œnca odata Œn zona de declaratii a procedurii Afiseaza_Date, ceea ce
Œnseamna ca este locala Œn respectiva procedura. Daca analizam DoV al variabilei Contor globala
acesta tine de la declaratia sa si pƒna la sfƒrsitul programului principal, mai putin procedura
Afiseaza_Date, care o redeclara. Aceasta Œnseamna ca
- Œn tot corpul programului principal si al subprogramelor declarate dupa declaratia variabilei globale
Contor (cu exceptia blocului procedurii Afiseaza_Date) numele Contor se refera la variabila globala;
- Œn blocul procedurii Afiseaza_Date, numele Contor se refera la variabila locala, declarata acolo;
Executia programului justifica afirmatiile de mai sus. Tehnic vorbind, redeclararea unui identificator Œn
DoV al sau are ca efect introducerea unei 'gauri' Œn acesta; blocul de redeclarare va folosi numele
redeclarat Œn acceptiunea cea mai recent Œntƒlnita. Acesta este motivul pentru care se obtin valori
diferite pentru variabila Contor Œn programul principal si Œn procedura Afiseaza_Date: este vorba de
fapt de doua variabile diferite, cu domenii de vizibilitate disjuncte (si cu durate de viata diferite) si care
au prin urmare adrese diferite Œn memorie, deci valori diferite.
Situatia opusa este atunci cƒnd doua variabile diferite au acceasi adresa de memorie si (daca sunt de
acelasi tip) aceeasi valoare, care se modifica simultan pentru ambele. Este situatia descrisa mai Œnainte
a parametrilor actuali transmisi prin referinta: atƒt parametrul formal, cƒt si parametrul actual (care sunt
doua variabile diferite, cu toate atributele ce le definesc) au aceeasi adresa din memorie si trebuie sa aiba
acelasi tip de date (altfel apelul n-ar fi corect).

8.6.5. Reguli de proiectare a subprogramelor


Vom da Œn continuare cƒteva reguli privind proiectarea subprogramelor.
Aceste reguli privesc declararea acestora (am vazut anterior ca declararea este cea care dicteaza modul
de apelare). Ne vom referi la:
- sarcinile realizate de subprogram
- stabilirea parametrilor subprogramului si a modului de transmitere a acestora
- alegerea tipului de subprogram (procedura sau functie)
- stabilirea numelui subprogramului si a parametrilor acestuia
Orice subprogram trebuie sa realizeze o actiune bine determinata (calculul unui determinant,
determinarea minimului unui sir de numere, rezolvarea unui sistem de ecuatii, afisarea unui meniu, etc).
Se spune ca subprogramul este bine gƒndit daca actiunea realizata de el se poate concentra Œn numele
acestuia.
Urmatoarea actiune care trebuie Œntreprinsa este stabilirea parametrilor de intrare si de iesire. Daca ne
gƒndim ca un subprogram este implementarea unui subalgoritm, atunci datele de intrare ale
subalgoritmului vor fi parametrii de intrare, iar rezultatele algoritmului vor fi parametrii de iesire.
Parametrii de intrare se transmit de obicei prin valoare (cu unele exceptii care le vom discuta la vremea
lor), iar parametrii de iesire se transmit Œntotdeauna prin referinta.
Alegerea tipului subprogramului (procedura sau functie) este Œn strƒnsa legatura cu actiunea efectuata
si cu numarul de parametri de iesire ai subprogramului. Daca subprogramul are un singur parametru de
iesire si acesta este de un tip simplu de data, atunci subprogramul va fi de tip functie. De exemplu:
calculul minimului dintr-un sir sau calculul de determinant se pot implementa ca subprograme de tip
functie. Chiar si afisarea unui meniu se poate implementa ca functie, daca Œnsotim afisarea de
selectarea unei optiuni din meniu (caz Œn care functia va Œntoarce codul optiunii selectate). Daca Œnsa
subprogramul are mai multi parametri de iesire sau daca tipul parametrului de iesire este unul structurat,
atunci el va fi proiectat ca procedura.
Atƒt numele subprogramului, cƒt si numele parametrilor sai trebuie sa fie sugestive. Numele
subprogramului procedura va desemna o actiune, pe cƒnd numele subprogramului functie poate desemna
obiectul (valoarea) Œntoarsa.
Una din deosebirile dintre proceduri si functii, enumerate Œn 8.5 s-a referit la restrictia aplicata
functiilor de a nu avea parametri de iesire. Daca o instructiune din corpul unui subprogram de tip functie
modifica valoarea unei variabile globale (accesata direct sau printr-un parametru transmis prin referinta),
17
se spune ca functia are un 'efect secundar' sau 'efect colateral'. Acest efect se numeste 'secundar'
deoarece efectul 'primar' al functiei este calculul unei valori, care este Œntoarsa de aceasta Œn
programul apelant. De aceea, unele limbaje (de exemplu Ada; Pascal nu) impun ca toti parametrii unei
functii sa se transmita prin valoare (sau prin constanta, o varianta a apelului prin valoare care interzice
modificarea valorilor parametrilor formali Œn corpul subprogramului). Folosirea efectelor secundare
este proprie programatorilor experimentati, Œnsa Œn general nu se recomanda.
Regulile prezentate aici vin sa completeze sfaturile pe care le-am enuntat la sfƒrsitul sectiunii 8.3
privitoare la rolul subprogramelor Œntr-un program.

8.7. Subprograme apelate din subprograme

Din punctul de vedere al drepturilor sale de apel, un subprogram SP se


comporta exact ca un program principal P; la rƒndul sau, SP poate avea Œn
corpul sau instructiuni de apel ale altor subprograme, inclusiv apelul
propriu. Singura conditie impusa este aceea ca subprogramele apelate de SP sa
includa Œn domeniul lor de vizibilitate corpul subprogramului SP.

8.7.1. Apelul subprogramelor din subprograme. Subprograme locale Œn


subprograme

Sa consideram urmatorul exemplu:

Program P4a;
{ Apelul unei proceduri din alta procedura }

Procedure Unu;
Begin
Writeln('Aceasta este procedura Unu');
End; { Unu }

Procedure Doi;
Begin
Unu;
Writeln('Aceasta este procedura Doi');
End; { Doi }

Procedure Trei;
Begin
Doi;
Writeln('Aceasta este procedura Trei');
End; { Trei }

Procedure Scrie_Sfarsit;
Begin
Writeln('Programul s-a terminat');
End; { Scrie_Sfarsit }

begin { Programul principal }


Unu;
Writeln;
Doi;
Writeln;
18
Trei;
Scrie_Sfarsit
end. { P4a }

{ Rezultatul executiei

Aceasta este procedura Unu

Aceasta este procedura Unu


Aceasta este procedura Doi

Aceasta este procedura Unu


Aceasta este procedura Doi
Aceasta este procedura Trei
Programul s-a terminat
}
Programul P4a este un prim exemplu de apel de procedura Œn procedura.
Procedura Doi contine Œn corpul ei apelul procedurii Unu, iar procedura Trei
contine Œn corpul ei apelul procedurii Doi. Rezultatul apelului acestor
proceduri Œn programul principal este ilustrat Œn exemplul de mai sus.
Analizƒnd structura acestui program:
Program P4a;
[declaratii]
Procedure Unu;
Procedure Doi;
Procedure Trei;
Procedure Scrie_Sfarsit;
begin { Programul principal }
[corp]
end. { P4a }

se observa ca declaratiile procedurilor Unu, Doi, Trei, Scrie_Sfarsit sunt continute (Œn aceasta ordine)
Œn partea de declaratii a sa. De aici rezulta ca:
- DoV al numelui Unu contine declaratiile procedurilor Unu, Doi, Trei, Scrie_Sfarsit, precum si
programul principal, deci procedura Unu va putea fi apelata Œn oricare dintre acestea (inclusiv Œn Unu
- vezi paragraful urmator);
- DoV al numelui Doi contine declaratiile procedurilor Doi, Trei, Scrie_Sfarsit, precum si programul
principal, deci procedura Doi va putea fi apelata Œn oricare dintre acestea, mai putin Œn Unu - care nu
face parte din DoV al lui Doi
- DoV al numelui Trei contine declaratiile procedurilor Trei, Scrie_Sfarsit, precum si programul
principal, deci procedura Trei va putea fi apelata Œn oricare dintre acestea, mai putin Œn Unu si Doi -
care nu fac parte din DoV al lui Trei
- s.a.m.d.

Urmatorul exemplu vine sa ilustreze generalitatea conceptului de bloc din


Pascal. Cum zona de declaratii a unui subprogram poate contine orice
declaratie valida, ea va putea contine si declaratii de subprograme. In
programul P4b am inclus declaratia procedurii Doi Œn zona de declaratii a
procedurii Unu si am inversat apelurile: Unu apeleaza pe Doi. Ce constatam
daca dorim sa lansam Œn executie acest program este ca el nu functioneaza:
se produce o eroare de compilare:

19
Unknown identifier - Identificator necunoscut

la apelul procedurii Doi Œn corpul lui Trei si (daca comentam acest apel) la
apelul lui Doi din programul principal.

Program P4b;
{ Apelul unei proceduri din alta procedura }

Procedure Unu;
Procedure Doi;
Begin
Writeln('Aceasta este procedura Doi din Unu');
End; { Doi }

Begin { Unu }
Writeln('Aceasta este procedura Unu');
Doi; { corect! Doi este locala in Unu }
End; { Unu }

Procedure Trei;
Begin
Doi; { eroare de compilare: Identificator necunoscut }
Writeln('Aceasta este procedura Trei');
End; { Trei }

Procedure Scrie_Sfarsit;
Begin
Writeln('Programul s-a terminat');
End; { Scrie_Sfarsit }

begin { Programul principal }


Unu;
Writeln;
Doi; { eroare de compilare: Identificator necunoscut }
Writeln;
Trei; { nu se poate compila Trei }
Scrie_Sfarsit
end. { P4b }

{ Rezultatul executiei: programul are erori de compilare }

Structura programului P4b se Œnscrie Œn urmatoarea schema:

Program P4b;
[declaratii]
Procedure Unu;
Procedure Doi; { locala Œn Unu si invizibila Œn afara }
Procedure Trei;
Procedure Scrie_Sfarsit;
begin { Programul principal }
[corp]
end. { P4b }
20
din care se observa ca procedurile locale Œn program sunt procedurilor Unu,
Trei, Scrie_Sfarsit care sunt continute (Œn aceasta ordine) Œn partea de
declaratii a sa, iar procedura Doi este locala Œn Unu. De aici rezulta ca:
- DoV al numelui Unu contine declaratiile procedurilor Unu, Doi, Trei,
Scrie_Sfarsit, precum si programul principal, deci procedura Unu va putea
fi apelata Œn oricare dintre acestea (inclusiv Œn Unu - vezi paragraful
urmator);
- DoV al numelui Doi contine doar declaratiile procedurilor Doi si Unu, fara
programul principal, deci procedura Doi nu va putea fi apelata decƒt Œn
corpul lui Unu - unde este locala si Œn propriul sau corp (apel recursiv -
vezi sectiunea urmatoare)
- DoV al numelui Trei contine declaratiile procedurilor Trei, Scrie_Sfarsit,
precum si programul principal, deci procedura Doi va putea fi apelata Œn
oricare dintre acestea, mai putin Œn Unu si Doi - care nu fac parte din DoV
al sau
- s.a.m.d.

Ultimul exemplu care-l dam aici reproduce functionalitatea programului P4a


folosind proceduri declarate local Œn alte proceduri:

program P4c;
{ Apelul unei proceduri din alta procedura }

Procedure Unu;
Begin
Writeln('Aceasta este procedura Unu');
End; { Unu }

Procedure Doi;
Procedure Unu;
Begin
Writeln('Aceasta este procedura Unu din Doi');
End; { Unu }
Begin { Doi }
Unu; { apelul procedurii locale Unu }
Writeln('Aceasta este procedura Doi');
End; { Doi }

Procedure Trei;
Procedure Doi;
Procedure Unu;
Begin
Writeln('Aceasta este procedura Unu din Doi din Trei');
End; { Unu }
Begin { Doi }
Unu; { apelul procedurii locale Unu }
Writeln('Aceasta este procedura Doi din Trei');
End; { Doi }
Begin { Trei }
Doi; { apelul procedurii locale Doi }
Writeln('Aceasta este procedura Trei');
21
End; { Trei }

Procedure Scrie_Sfarsit;
Begin
Writeln('Programul s-a terminat');
End; { Scrie_Sfarsit }

begin { Programul principal }


Unu; { apelul procedurii Unu din programul principal }
Writeln;
Doi;
Writeln;
Trei;
Scrie_Sfarsit
end. { P4c }

{ Rezultatul executiei

Aceasta este procedura Unu

Aceasta este procedura Unu din Doi


Aceasta este procedura Doi

Aceasta este procedura Unu din Doi din Trei


Aceasta este procedura Doi din Trei
Aceasta este procedura Trei
Programul s-a terminat
}

Structura acestui program este urmatoarea

Program P4c;
[declaratii]
Procedure Unu;
Procedure Doi;
Procedure Unu; { locala Œn Doi si invizibila Œn afara }
Procedure Trei;
Procedure Doi; { locala Œn Trei si invizibila Œn afara }
Procedure Unu; { locala Œn Doi si invizibila Œn afara }
Procedure Scrie_Sfarsit;
begin { Programul principal }
[corp]
end. { P4c }

iar functionarea programului (ilustrata de rezultatul tiparit) este


justificata doar de regulile de vizibilitate a identificatorilor Œntr-un
program cu structura de bloc. Sa remarcam aici ca declaratiile procedurilor
locale introduc 'gauri' Œn DoV al identificatorilor cu acelasi nume
declarati Œn programul principal si ca Œn corpul procedurilor Doi si Trei vor
fi apelate, din acest motiv, subprogramele locale acestora si nu cele globale.
Mai mult, acest program va functiona corect indiferent de ordinea Œn care
apar declaratiile procedurilor Unu, Doi si Trei Œn zona de declaratii a
22
programului principal (ceea ce nu se va Œntƒmpla Œn P4a - acolo s-ar produce
o eroare de compilare). Sa remarcam de asemenea ca Œn P4c se folosesc
identificatori redefiniti fara a se provoca o eroare de compilare. Fiecare
identificator are propriul sau DoV si nu exista ambiguitati.

8.7.2. Apelul recursiv

In paragraful precedent am amintit ca numele unei proceduri se poate referi


Œn corpul acesteia. Am discutat de asemenea Œn 8.5 faptul ca numele unei
functii este obligatoriu sa apara Œn membrul stƒng al unei operatii de
atribuire. Ce se Œntƒmpla Œnsa daca Œn corpul procedurii P apare o
instructiune de apel a sa, sau daca Œn corpul functiei F numele acesteia
apare Œn membrul drept al unei operatii de atribuire? Ambele situatii sunt
permise (Œn anumite conditii care vor fi discutate Œn continuare) si sunt
tratate ca apeluri (de procedura, respectiv de functie), fiind numite
'apeluri recursive'.

Subprogramele recursive permit o descriere eleganta a proceselor de calcul


(algoritmilor) care se preteaza gƒndirii recursive. De fapt recursivitatea
este o alta metoda de reducere a unei probleme date P la o problema mai
simpla. Spre deosebire de descompunerea functionala, Œn care subproblemele
sunt diferite de problema initiala, Œn cazul recursivitatii simple rezolvarea
problemei P (de dimensiune n) se reduce la rezolvarea aceleiasi probleme P
(de dimensiune n-1 sau mai mica).

Un exemplu simplu de aplicare a recursivitatii este determinarea factorialului


din numarul natural n:

Fact(n) ::= n * Fact(n-1);

care nu inseamna decat aplicarea formulei de recurenta

n! = n * (n-1)!

Din punctul de vedere al analizei noastre, am redus problema determinarii lui


n! (care spunem ca are dimensiunea n) la problema determinarii lui (n-1)!
(deci aceeasi problema, dar de dimensiune n-1).

Din schema de mai sus se observa ca problema nu este Œn Œntregime rezolvata.


Pe lƒnga stabilirea unei legaturi Œntre P(n) si P(n-1), trebuie obligatoriu
gasit un criteriu de stop (similar criteriului de oprire a descompunerii):
cea mai mare valoare a lui n pentru care P(n) are solutie cunoscuta si deci
nu mai trebuie aplicata recursivitatea.

Prin urmare, rezolvarea recursiva a unei probleme P(n) Œnseamna stabilirea a


doua elemente, la fel de importante:
- o relatie Œntre P(n) si P(n-1)
- un criteriu de oprire (o valoare a lui n pentru care P(n) are solutie
cunoscuta)
- Œn cazul factorialului, criteriul de stop se poate considera n = 1,
avƒnd Œn vedere ca 1! = 1 este cunoscut (din definitia factorialului)

23
Un alt exemplu simplu de aplicare a recursivitatii este determinarea
minimului unui sir de numere a1, a2, ..., an. Plecam de la urmatoarea
constatare:

* n = 2: P(2) Œnseamna determinarea minimului a doua numere, a1, a2 si se


considera ca are solutie (se poate implementa direct)

Minim(a1, a2) ::= a1 daca a1 < a2 , respectiv a2 daca a2 = a1

* n > 2: P(n) se rezolva astfel:

Minim(a1, a2, ..., an) ::= Minim(a1, Minim(a2, ..., an))

Criteriul de stop este aici atingerea valorii n = 2.

Atƒt procedurile, cƒt si functiile se pot apela recursiv. Exemplele urmatoare


ilustreaza acest lucru.

program P5;
{ Proceduri recursive }
Uses Crt;

Procedure Afiseaza_Titlu;
Begin
ClrScr;
WriteLn('Program care foloseste o procedura recursiva');
End; { AfiseazaTitlu }

Procedure Afiseaza_Si_Micsoreaza(Indice : integer);


Begin
Writeln('Valoarea indicelui este ',Indice:3);
Indice := Indice - 1;
If Indice > 0 Then
Afiseaza_Si_Micsoreaza(Indice);
End; { Afiseaza_Si_Micsoreaza }

Procedure Scrie_Sfarsit;
Begin
Writeln('Programul s-a terminat');
End; { Scrie_Sfarsit }

Var
Contor: Integer;

Begin { programul principal }


Afiseaza_Titlu;
Contor := 7;
Afiseaza_Si_Micsoreaza(Contor);
Scrie_Sfarsit
End. { P5 }

{ Rezultatul executiei
24
Program care foloseste o procedura recursiva
Valoarea indicelui este 7
Valoarea indicelui este 6
Valoarea indicelui este 5
Valoarea indicelui este 4
Valoarea indicelui este 3
Valoarea indicelui este 2
Valoarea indicelui este 1
Programul s-a terminat

Procedura recursiva din acest program este Afiseaza_Si_Micsoreaza. Ea primeste


ca parametru (transmis prin valoare) un Œntreg Indice care este afisat
(valoarea cu care s-a facut apelul), apoi este micsorat si (daca valoarea sa
este strict pozitiva) se apeleaza din nou procedura (cu noua valoare a lui
Indice). Se observa si din modul de executie a programului ca la urmatoarea
executie se repeta aceleasi operatii. Criteriul de stop al apelului recursiv
este atingerea valorii 0 pentru Indice. Se observa ca apelul recursiv trebuie
sa respecte Œntr-u totul sintaxa uzuala de apel a procedurii.

Al doilea exemplu de program din acest paragraf ilustreaza folosirea


functiilor recursive. Reamintim ca apelul functiei este diferit sintactic si
semantic de apelul de procedura.

program P6;
{ Functii recursive }
Uses Crt;

Procedure Afiseaza_Titlu;
Begin
ClrScr;
WriteLn('Program care determina CMMDC a doua numere');
End; { AfiseazaTitlu }

Function CMMDC_Rec(a, b: LongInt): LongInt;


{ determina cmmdc al numerelor a si b folosind formula:
cmmdc(a, b) = cmmdc(b, a mod b) daca b <> 0
=a daca b = 0
Preconditie: a > b > 0
}
Begin
WriteLn('CMMDC(',a:11, ',', b:11, ')=');
If b = 0 then CMMDC_Rec := a
Else CMMDC_Rec := CMMDC_Rec(b, a mod b);
End; { CMMDC_Rec }

Procedure Scrie_Sfarsit;
Begin
Writeln('Programul s-a terminat');
End; { Scrie_Sfarsit }
25
Var
Numar1: LongInt; { primul numar citit }
Numar2: LongInt; { al doilea numar citit }

Begin { Programul principal }


Afiseaza_Titlu;
Write('Introduceti primul numar: ');
ReadLn(Numar1);
Write('Introduceti al doilea numar: ');
ReadLn(Numar2);
If Numar1 >= Numar2 Then WriteLn(CMMDC_Rec(Numar1, Numar2))
Else WriteLn(CMMDC_Rec(Numar2, Numar1));
Scrie_Sfarsit
End. { P6 }

{ Rezultatul executiei

Program care determina CMMDC a doua numere


Introduceti primul numar: 1235
Introduceti al doilea numar: 65700
CMMDC( 65700, 1235) =
CMMDC( 1235, 245) =
CMMDC( 245, 10) =
CMMDC( 10, 5) =
CMMDC( 5, 0) =
5
Programul s-a terminat

Acest exemplu implementeaza o varianta recursiva a algoritmului de determinare


a celui mai mare divizor comun a doua numere naturale, folosind relatia de
recurenta enuntata Œn comentariile aferente functiei CMMDC_Rec. Conformƒndu-ne
regulilor enuntate anterior, deoarece subprogramul de determinare a cmmdc
primeste ca parametri doua numere Œntregi si Œntoarce un alt numar, este
natural sa fie conceput ca functie. Criteriul de stop al recursivitatii este
dat de relatia

cmmdc(a, 0) ::= a

iar reducerea problemei la una de dimensiune mai mica (dimensiune mai mica
Œnseamna aici parametri cu valori mai mici) se bazeaza pe relatia amintita:

cmmdc(a, b) ::= cmmdc(b, a mod b) (Preconditie: a >= b)

Verificarea preconditiei se face Œn programul apelant (programul principal),


dupa citirea valorilor numerelor pentru care se calculeaza cmmdc, rezultƒnd
doua apeluri Œnglobate Œntr-o structura alternativa. Corpul subprogramului
afiseaza prima data parametrii de apel (pentru a se ilustra succesiunea
apelurilor si deci maniera de rezolvare a problemei). Sa mai observam ca
relatia de recurenta enuntata conserva preconditia (Œntotdeauna b > a mod b).
26
Un algoritm mai lent este cel Œn care relatia de recurenta este

cmmdc(a, b) ::= cmmdc(b, a - b)

care Œnsa nu mai pastreaza preconditia. Sugeram cititorului implementarea lui.

8.7.3. Subprograme mutual recursive

Un caz interesant de recursivitate este acela Œn care sunt implicate doua


subprograme, A si B si Œn care
- A apeleaza Œn corpul sau pe B
- B apeleaza Œn corpul sau pe A

si oricare dintre ele se pot apela din programul principal. Subprogramele A


si B care Œndeplinesc aceste conditii se numesc 'mutual recursive'.

Cu ceea ce stim pƒna acum despre Pascal, acest lucru este imposibil. Sa vedem
de ce.

Consideram ca A si B sunt subprograme declarate local Œn programul P.


Declararea lor ar putea fi Œn ordinea A, B sau B, A. In ambele situatii,
apelul reciproc nu se poate implementa. De exemplu, ordinea

Program P;
Procedure A;
[corp A] {contine apelul lui B - eroare! B nedeclarat Œnca}
Procedure B;
[corp B] {contine apelul lui A}
Begin
A;
B
End.

ar provoca o eroare de compilare (Œn corpul lui A se apeleaza B, care nu a


fost Œnca declarat, deci se obtine eroarea de compilare

Unknown identifier = Identificator necunoscut

Analog, daca se inverseaza ordinea de declarare, eroarea de compilare s-ar


produce Œn corpul lui B.

Ar fi o solutie care ar rezolva partial problema (A si B s-ar putea apela


reciproc), Œnsa numai una dintre ele ar putea fi apelata din programul
principal:

Program P;
Procedure A;
Procedure B; { locala Œn A }
[corp B] {contine apelul lui A - corect! A este declarat}
[corp A] {contine apelul lui B - corect! B este declarat}
Begin
27
A;
B {eroare de compilare! B nu este vizibil aici}
End.

Pentru a rezolva complet problema, este nevoie de Œnca un mecanism. Acesta


este concretizat Œn Turbo Pascal de directiva 'Forward', care are ca scop sa
anunte compilatorului sintaxa de apel a unui subprogram care va fi declarat
mai tƒrziu.

Sintaxa acestei directive este

Antet subprogram; Forward;

si se aplica ca Œn exemplul urmator

Program P;
Procedure B(parametri); Forward;
Procedure A(parametri);
[corp A] {contine apelul lui B - corect!
apelul lui B este precizat Œn Forward}
Procedure B;{nu mai este obligatorie precizarea parametrilor}
[corp B] {contine apelul lui A}
Begin
A;
B
End.

Este valabila si versiunea:

Program P;
Procedure A(parametri); Forward;
Procedure B(parametri);
[corp B] {contine apelul lui A - corect!
apelul lui A este precizat Œn Forward}
Procedure A;{nu mai este obligatorie precizarea parametrilor}
[corp A] {contine apelul lui B}
Begin
A;
B
End.

Exemplul prezentat Œn continuare exemplifica declararea si folosirea


procedurilor mutual recursive. Sa remarcam ca cel putin una dintre proceduri
trebuie sa aiba Œn corpul ei o instructiune de stop - altfel s-ar ajunge la
un ciclu infinit.

program P7;
{ Proceduri mutual recursive }
Uses Crt;

Procedure Afiseaza_Titlu;
Begin
28
ClrScr;
WriteLn('Apel recursiv - proceduri mutual recursive');
End; { AfiseazaTitlu }

Procedure Scrie_O_Linie(var Contor : Integer); Forward;

Procedure Micsoreaza(var Indice : Integer);


Begin
Indice := Indice - 1;
If Indice > 0 Then
Scrie_O_Linie(Indice);
End; { Micsoreaza }

Procedure Scrie_O_Linie;
begin
Writeln('Valoarea contorului este acum ',Contor:4);
Micsoreaza(Contor);
end; { Scrie_O_Linie }

Procedure Scrie_Sfarsit;
Begin
Writeln('Programul s-a terminat');
End; { Scrie_Sfarsit }

Var
De_Cate_Ori : integer;

Begin { programul principal }


Afiseaza_Titlu;
De_Cate_Ori := 7;
WriteLn('Apelez Micsoreaza');
Micsoreaza(De_Cate_Ori);
Writeln;
De_Cate_Ori := 7;
WriteLn('Apelez Scrie_O_Linie');
Scrie_O_Linie(De_Cate_Ori);
Scrie_Sfarsit
End. { P7 }

{ Rezultatul executiei

Apel recursiv - proceduri mutual recursive


Apelez Micsoreaza
Valoarea contorului este acum 6
Valoarea contorului este acum 5
Valoarea contorului este acum 4
Valoarea contorului este acum 3
Valoarea contorului este acum 2
Valoarea contorului este acum 1

Apelez Scrie_O_Linie
Valoarea contorului este acum 7
29
Valoarea contorului este acum 6
Valoarea contorului este acum 5
Valoarea contorului este acum 4
Valoarea contorului este acum 3
Valoarea contorului este acum 2
Valoarea contorului este acum 1
Programul s-a terminat

8.8. Tipuri procedurale

Incepƒnd cu versiunea 5.0, mediul Turbo Pascal poseda doua tipuri de date noi,
numite generic 'tipuri procedurale': tipul Procedure si tipul Function.
Aceste tipuri se pot declara de catre utilizator si se includ Œn sistemul de
tipuri al limbajului. Prin urmare, se pot declara si folosi variabile de
tipurile respective.

Sintaxa declaratiei tipurilor procedurale este:

Type
Nume_Tip_Proc = Procedure(lista_parametri_formali);
Nume_Tip_Func = Function(lista_parametri_formali): tip_rezultat;

unde lista_parametri_formali si tip_rezultat au semnificatia de la declararea


procedurilor si functiilor.

Rostul declaratiei de tip procedural este precizarea signaturii tipului de


subprogram, anume:
- precizarea tipurilor parametrilor formali (si a modului de transmitere)
- nu este obligatoriu ca numele parametrilor sa coincida
- perechile (tip_parametru, mod de transmitere) pentru fiecare parametru
determina ceea ce numim 'signatura' tipului procedural
- perechile (tip_parametru, mod de transmitere) pentru fiecare parametru,
impreuna cu tipul rezultatului intors determina ceea ce numim 'signatura'
tipului functional
- precizarea tipului rezultatului Œntors (numai pentru tipurile functionale)

Pentru ca sa se poata folosi tipuri procedurale Œntr-un program, acesta


trebuie sa contina o directiva de compilare {$F+} (Force Far Calls).

8.8.1. Tipuri Procedure

Numele oricarui subprogram de tip Procedure care are signatura identica cu


signatura tipului Nume_Tip_Proc va putea fi atribuit unei variabile de tipul
Nume_Tip_Proc si orice variabila de acest tip va putea Œnlocui Œntr-o
instructiune de apel numele concret al subprogramului continut de ea.

Exemplul urmator este sugestiv:

program P8a;
{ Tipuri procedurale }
30
Uses Crt;

{$F+} { Aceasta directiva forteaza apelurile far si este necesara


cand dorim sa lucram cu tipuri procedurale in Turbo Pascal }

Type
Op_Aritm = Procedure(a, b, c : Integer;
var r : Integer); { tip procedural }

Procedure Afiseaza_Titlu;
Begin
ClrScr;
WriteLn('Program care foloseste tipuri procedurale si');
WriteLn('efectueaza operatii aritmetice cu trei numere')
End; { AfiseazaTitlu }

Procedure Adunare(In1, In2, In3: Integer;


var Rezultat : Integer);
Begin
Rezultat := In1 + In2 + In3;
Writeln('Suma numerelor este ', Rezultat:6);
End; { Adunare }

Procedure Inmultire(In1, In2, In3: Integer;


var Rezultat : Integer);
Begin
Rezultat := In1 * In2 * In3;
Writeln('Produsul numerelor este ', Rezultat:6);
End; { Inmultire }

Procedure Medie(In1, In2, In3: Integer;


var Rezultat : Integer);
Begin
Rezultat := (In1 + In2 + In3) div 3;
Writeln('Media numerelor este ', Rezultat:6);
End; { Medie }

Procedure Scrie_Sfarsit;
Begin
Writeln('Programul s-a terminat');
End; { Scrie_Sfarsit }

Var
Numar1, Numar2, Numar3: Integer;
Rezultat_Final: Integer;
Operatie: Op_Aritm;

Begin { Programul principal }

Afiseaza_Titlu;
Write('Introduceti primul numar: ');
ReadLn(Numar1);
31
Write('Introduceti al doilea numar: ');
ReadLn(Numar2);
Write('Introduceti al treilea numar: ');
ReadLn(Numar3);

Operatie := Adunare;
Operatie(Numar1, Numar2, Numar3, Rezultat_Final);

Operatie := Inmultire;
Operatie(Numar1, Numar2, Numar3, Rezultat_Final);

Operatie := Medie;
Operatie(Numar1, Numar2, Numar3, Rezultat_Final);

Scrie_Sfarsit
end. { P8a }

{ Rezultatul executiei

Program care foloseste tipuri procedurale si


efectueaza operatii aritmetice cu trei numere
Introduceti primul numar: 12
Introduceti al doilea numar: 23
Introduceti al treilea numar: 34
Suma numerelor este 69
Produsul numerelor este 9384
Media numerelor este 23
Programul s-a terminat

Programul de mai sus defineste tipul procedural Op_Aritm, care are trei parametri Œntregi de intrare
(transmisi prin valoare) si unul Œntreg de iesire (transmis prin adresa). In partea sa de declaratii sunt
definite si trei proceduri care efectueaza operatii aritmetice (numite Adunare, Inmultire si Medie) care au
aceeasi signatura ca si Op_Aritm (chiar daca numele parametrilor difera, numarul lor este acelasi, si ei
corespund ca tip si ca modalitate de transmitere). Zona de declaratii de variabile a programului contine
declararea unei variabile procedurale (de tipul Op_Aritm) numita Operatie, care va fi folosita Œn corpul
programului pentru apelul indirect al procedurilor aritmetice precizate anterior.

De exemplu, maniera de apelare a procedurii Adunare este urmatoarea:

Operatie := Adunare;
Operatie(Numar1, Numar2, Numar3, Rezultat_Final);

Prima instructiune initializeaza variabila Operatie cu adresa procedurii


Adunare. Initializarea este posibila deoarece Adunare si Operatie au aceeasi
signatura (si deci acelasi tip de date).

A doua instructiune se traduce printr-un apel indirect de procedura. Variabila


Operatie contine adresa procedurii Adunare si prin urmare acest apel se
traduce de fapt Œn apelul

32
Adunare(Numar1, Numar2, Numar3, Rezultat_Final);

8.8.2. Tipuri Function


Numele oricarui subprogram de tip Function care are signatura identica cu signatura tipului
Nume_Tip_Func va putea fi atribuit unei variabile de tipul
Nume_Tip_Func si orice variabila de acest tip va putea Œnlocui Œntr-o instructiune de apel numele
concret al subprogramului continut de ea.
Exemplul urmator este echivalent cu cel de la tipuri procedurale, folosind de data aceasta tipuri
functionale (care sunt mai naturale pentru cazul ilustrat, deoarece operatiile aritmetice prezentate se
implementeaza natural ca functii):

program P8b;
{ Tipuri functionale }
Uses Crt;

{$F+} { Aceasta directiva forteaza apelurile far si este necesara cand dorim sa lucram cu tipuri
procedurale sau functional in Turbo Pascal }

Type
Op_Aritm = Function(a, b, c : integer): Integer;

Procedure Afiseaza_Titlu;
Begin
ClrScr;
WriteLn('Program care foloseste tipuri functionale si');
WriteLn('efectueaza operatii aritmetice cu trei numere')
End; { AfiseazaTitlu }

Function Adunare(In1, In2, In3: Integer): Integer;


Var
R: Integer;
Begin
R := In1 + In2 + In3;
Writeln('Suma numerelor este ', R:6);
Adunare := R
End; { Adunare }

Function Inmultire(In1, In2, In3: Integer): Integer;


Var
R: Integer;
Begin
R := In1 * In2 * In3;
Writeln('Produsul numerelor este ', R:6);
Inmultire := R
End; { Inmultire }

Function Medie(In1, In2, In3: Integer): Integer;


Var
R: Integer;
Begin
R := (In1 + In2 + In3) div 3;
Writeln('Media numerelor este ', R:6);
33
Medie := R
End; { Medie }

Procedure Scrie_Sfarsit;
Begin
Writeln('Programul s-a terminat');
End; { Scrie_Sfarsit }

Var
Numar1, Numar2, Numar3: Integer;
Rezultat_Final: Integer;
Operatie: Op_Aritm;

Begin { Programul principal }

Afiseaza_Titlu;
Write('Introduceti primul numar: ');
ReadLn(Numar1);
Write('Introduceti al doilea numar: ');
ReadLn(Numar2);
Write('Introduceti al treilea numar: ');
ReadLn(Numar3);

Operatie := Adunare;
Rezultat_Final := Operatie(Numar1, Numar2, Numar3);

Operatie := Inmultire;
Rezultat_Final := Operatie(Numar1, Numar2, Numar3);

Operatie := Medie;
Rezultat_Final := Operatie(Numar1, Numar2, Numar3);

Scrie_Sfarsit
end. { P8b }
{ Rezultatul executiei
Program care foloseste tipuri procedurale si
efectueaza operatii aritmetice cu trei numere
Introduceti primul numar: 12
Introduceti al doilea numar: 23
Introduceti al treilea numar: 34
Suma numerelor este 69
Produsul numerelor este 9384
Media numerelor este 23
Programul s-a terminat
}
Sa remarcam ca s-au modificat corespunzator declaratiile tipului procedural (functional) Op_Aritm, ale
subprogramelor aritmetice (care sunt acum functii) si apelul acestora din programul principal.

1. Generalitati
2. Elemente de sintaxa
3. Structura unui program
4. Tipuri de date predefinite
34
5. Expresii
6. Declaratii si definitii
7. Instructiuni
8. Subprograme Pascal
9. Tipuri de date structurate
10. Structuri dinamice de date
11. Elemente de modularizare
12. Abstractizarea datelor
13. Programarea orientata pe obiecte

Capitolul 9. Tipuri de date structurate

9.1. Tipul de date tablou


9.1.1. Conceptul de tablou
9.1.2. Declararea tablourilor
9.1.3. Accesarea tablourilor
9.1.4. Tablouri bidimensionale
9.1.5. Tablouri multidimensionale
9.1.6. Operatii globale pe tablori
9.1.7. Stringuri
9.1.8. Tablouri deschise

9.2. Tipul de date multime


9.2.1. Definitia tipurilor multime
9.2.2. Construirea multimilor si atribuirea
9.2.3. Compararea multimilor si testul de apartenenta
9.2.4. Operatii cu multimi

9.3. Tipul de date Œnregistrare


9.3.1. Conceptul de Œnregistrare
9.3.2. Declararea tipului Œnregistrare
9.3.3. Accesarea componentelor. Instructiunea With
9.3.4. Structuri complexe
9.3.5. Inregistrari cu variante
9.3.6. Initializarea Œnregistrarilor

9.4. Tipul de date fisier


9.4.1. Notiunea de fisier
9.4.2. Operatii asupra fisierelor
9.4.3. Fisiere text
9.4.4. Fisiere cu tip
9.4.5. Fisiere fara tip (stream)
9.4.6. Fisierele sistem

Tipuri de date structurate

Limbajele de programare dispun de modalitati de agregare a datelor care


permit apoi tratarea globala a acestora. Este vorba Œn general de date care
corespund nivelului de abstractizare al limbajului, deci care nu au
corespondent direct Œn tipurile masina. Pentru ca aceste date definite de
utilizator conform nevoilor sale concrete sa poata fi integrate Œn mecanismul
de tipuri al limbajului, acesta din urma pune la dispozitia programatorului
35
constructorii de tipuri. In acest capitol se vor discuta tipurile de date
structurate.

Spre deosebire de datele simple, care sunt atomice, indivizibile, datele


structurate (compuse, agregate) se descompun Œn componente sau elemente,
fiecare de un tip precizat (simplu sau structurat). O data structurata poate
fi accesata fie ca Œntreg (global), fie pe componente. Structura unei date
stabileste relatiile care exista Œntre componentele acesteia.

Exista patru tipuri de legaturi structurale fundamentale:


- multime (nici o legatura Œntre componente),
- liniara (legatura 1:1),
- arbore (legatura 1:n) si
- graf (legatura m:n).

Din punctul de vedere al uniformitatii structurale, datele structurate se


Œmpart Œn:
- omogene (toate componentele au acelasi tip); tipurile de date aferente sunt
numite tablou (engl. array), multime (engl. set) si fisier (engl. file);
- heterogene (elementele unei date au de obicei componente diferite ca tip);
ele apartin tipului de date Œnregistrare (engl. record).

Tablourile, fisierele si Œnregistrarile au structura liniara: exista o prima


si o ultima componenta, iar toate celelalte au fiecare atƒt predecesor, cƒt si
succesor. Prin urmare, un element (al tabloului), o Œnregistrare (din fisier)
sau un cƒmp (al Œnregistrarii) se pot localiza. Un tablou este un agregat de
elemente de acelasi tip, un element fiind localizat prin pozitia pe care o
ocupa Œn cadrul acestuia (indicele elementului de tablou). Un fisier este
constituit si el din elemente (Œnregistrari) de acelasi tip, localizate tot
dupa pozitia ocupata Œn fisier. Deosebirea dintre un tablou si un fisier
consta Œn aceea ca tabloul este memorat Œn memoria interna a calculatorului,
iar fisierul este memorat Œn memoria externa (pe un suport magnetic sau
magneto-optic). O Œnregistrare este un agregat care grupeaza de obicei
elemente de tipuri diferite numite cƒmpuri si localizate prin numele lor.
Multimea are o structura amorfa: ea contine elemente de acelasi tip, care
Œnsa nu pot fi localizate explicit, neexistƒnd o ordine in care sa fie
considerate elementele din ea.

Pentru tipurile structurate, exista doua operatii de baza: construirea si


selectarea componentelor. Operatia de construire a unei variabile de tip
structurat se face dupa regulile proprii ale fiecarui limbaj. Pentru tablouri,
fisiere si Œnregistrari exista si operatia de selectare a unei componente,
care este realizata Œn maniere diferite.

In cazul unui tablou, selectarea unui element se face pe baza unei expresii
de indice, atasata numelui variabilei tablou. Pe baza expresiei de indice si
a informatiilor despre tablou se efectueaza calculul adresei elementului Œn
cauza. Expresia de indice nu se poate evalua la compilare (ea contine de
regula identificatori), valoarea ei fiind obtinuta la executie.

Pentru fisiere, selectarea unei Œnregistrari se face dupa pozitia pe care


Œnregistrarea respectiva o ocupa Œn fisier. Din nou, pozitia se poate da
36
printr-o expresie care nu se poate evalua la compilare.

Domeniul de vizibilitate al numelor cƒmpurilor unei Œnregistrari Œncepe cu


punctul lor de declarare si se termina la sfƒrsitul declaratiei tipului
Œnregistrare. Selectarea unei componente (cƒmp) se face pe baza unei expresii
de selectare, care contine numele cƒmpului calificat cu numele variabilei
Œnregistrare. In acest caz, adresa relativa a cƒmpului Œn cauza se poate
determina la compilare.

Pentru fiecare tip de date T introdus printr-o declaratie de tip, Turbo Pascal
pune la dispozitia programatorului o functie numita SizeOf, care Œntoarce
numarul de octeti pe care se reprezinta o variabila de tipul respectiv.
SizeOf poate se poate folosi
- cu parametrul T: SizeOf(T); T este un nume de tip de date
- cu parametrul V: SizeOf(V); V este o variabila de tipul T

Exemplu de folosire SizeOf: daca avem constructiile

[declararea lui T]
Var
V: T;

begin
If SizeOf(V) = SizeOf(T) Then ... {expresia este True}
End.

9.1. Tipul de date tablou

9.1.1. Conceptul de tablou


9.1.2. Declararea tablourilor
9.1.3. Accesarea tablourilor
9.1.4. Tablouri bidimensionale
9.1.5. Tablouri multidimensionale
9.1.6. Operatii globale pe tablori
9.1.7. Stringuri
9.1.8. Tablouri deschise

Elementele definitorii ale unui tip de date tablou sunt:

- numele (optional),
- lista dimensiunilor,
- tipul elementului de tablou,
- domeniul pentru multimea indicilor.

Numele unui tip tablou este un identificator. Exista si tipuri tablou anonime,
care nu necesita declarare de tip, ele putƒndu-se folosi la declararea de
variabile. Lista dimensiunilor precizeaza numarul de dimensiuni al tabloului
respectiv (monodimensional, bidimensional, tridimensional s.a.m.d), existƒnd
restrictii de la limbaj la limbaj privind numarul maxim de dimensiuni permis.
Tipul elementului de tablou defineste natura acestui element, precizƒnd
reprezentarea lui, iar domeniul pentru multimea indicilor este de obicei
(dar nu Œntotdeauna) de tip subdomeniu, oferind informatie despre indicii
37
valizi: pentru fiecare dimensiune se specifica limita inferioara 'li' si
limita superioara 'ls', cu Ord(li) <= Ord(ls); numarul de elemente al
dimensiunii respective este Ord(ls) - Ord(li) + 1. Numarul de elemente al
tabloului este produsul numerelor de elemente din fiecare dimensiune.
Lungimea de reprezentare (dimensiunea alocata) al unei variabile de tip
tablou este egal cu produsul dintre numarul de elemente al tabloului si
lungimea de reprezentare a elementului de tablou.

Criteriile de clasificare a tablourilor sunt cel putin urmatoarele:


- numarul de dimensiuni;
- tipul elementului (care poate induce operatii specifice);
- momentul alocarii;
- posibilitatea de redimensionare.

Din punctul de vedere al numarului de dimensiuni, Œntƒlnim tablouri


monodimensionale (numite si vectori), tablouri bidimensionale (numite si
matrici), tablouri tridimensionale, Œn general tablouri d-dimensionale.

La Œnceputurile folosirii calculatoarelor, cƒnd calculele stiintifice erau


principalul domeniu de folosire a acestora, tablourile utilizate erau
numerice, Œn special vectori si matrici. In limbajele de programare actuale,
tablourile pot avea o mare diversitate de tipuri de elemente: numerice,
caractere, siruri de caractere, Œnregistrari, pointeri.

Din punctul de vedere al momentului alocarii, exista doua categorii de


tablouri: statice si dinamice. Tablourile statice se aloca la compilare (sau
cel putin trebuie cunoscute la compilare dimensiunile). Pentru tablourile
dinamice, dimensiunile acestora se determina abia la executie. O categorie
speciala de tablouri dinamice o reprezinta tablourile flexibile, care se pot
redimensiona Œn timpul executiei.

Incepƒnd cu versiunea 7.0, Turbo Pascal permite si declararea tablourilor


deschise, pentru care nu se precizeaza domeniul indicilor. De regula, aceste
tablouri apar ca parametri formali ai subprogramelor.

9.1.1. Conceptul de tablou

Tablourile se folosesc pentru a grupa variabile de tipuri identice si a le


manipula prin operatii. Daca fiecare variabila ar fi declarata individual,
atunci fiecare operatie ar trebui specificata separat, fapt care ar duce la
programe lungi.

Un tablou ne permite sa grupam variabilele de acelasi tip sub un singur nume


si sa putem referi fiecare variabila (numita element al tabloului) asociind
numelui un indice (care o va identifica unic). Prin aceasta se reduce
considerabil dimensiunea unui program care efectueaza operatii similare
asupra mai multor elemente din tablou, folosind indicii si instructiunile de
ciclare.

9.1.2. Declararea tablourilor

Incepƒnd cu Algol68 si Pascal, tipul tablou este integrat Œn sistemul de


38
tipuri al limbajului. Intre altele, sistemul de tipuri verifica concordanta
tipurilor pentru parametrii formali si actuali de tip tablou la apelarea de
subprograme, motiv pentru care este aproape impusa folosirea de tipuri
tablou cu nume. De altfel, limbajul Pascal nu permite specificarea unui
parametru formal de tip tablou ca tip anonim.

In Pascal, declaratia de tip tablou are sintaxa:

Type tip_tablou = Array[tip_index] Of tip_element;

unde:
- Array si Of sunt cuvinte rezervate
- tip_element, numit tipul componentei, poate fi orice tip recunoscut de
sistemul de tipuri
- tip_index este o lista de tipuri de indici; numarul elementelor din aceasta
lista denota numarul de dimensiuni al tabloului, care nu este limitat.
Tipurile indicilor trebuie sa fie ordinale (cu exceptia lui Longint si a
subdomeniilor de Longint). Daca tipul componentei este tot un tip tablou,
rezultatul acestei declaratii de tip poate fi tratat fie ca un tablou de
tablouri, fie ca un tablou monodimensional. De exemplu, a doua declaratie:

type D = 20..30;
type T = array[boolean] of array[1..10] of array[D] of real;

este identica (structural) cu declaratia:

type T1 = array[boolean, 1..10, D] of real;

iar referiri corecte de elemente sunt:

var
A: T;
A1: T1;

A[false] si A1[false] sunt de tip array[1..10] of array[D]of real;


A[false,5] si A1[false,5] sunt de tip array[D] of real;
A[false][5] si A1[false][5] sunt de tip array[D] of real;
A[false,5,30] si A1[false,5,30] sunt de tip real;
A[false][5][30] si A1[false][5]30] sunt de tip real;

Declararea de variabile de tip tablou nu trebuie neaparat sa contina numele


unui tip de date tablou. Constructorul de tip se poate include Œn declaratia
de variabila. Astfel,

Var
V: Array[1..10] Of Integer;

este o declaratie valida de variabila. In aceasta declaratie de variabila,


numele tipului tablou este referit chiar prin constructorul de tip, adica

Array[1..10] Of Integer;

39
In acest caz se spune ca folosim un tip de data anonim, adica fara nume
(el n-are numele declarat Œntr-o declaratie de tip de date). Nu recomandam o
asemenea practica, deoarece sistemul de tipuri al limbajului nu lucreaza asa
cum ne-am astepta noi cu tipurile anonime. Deci sa nu uitam o noua regula:

Nu folositi tipuri de date anonime.

Numarul de elemente al unui tablou este dat de produsul numarului de elemente


Œn fiecare dimensiune.

9.1.3. Accesarea tablourilor

Accesarea (referirea) unui element de tablou se face prin precizarea


numelui tabloului urmata de o expresie de indice. Expresia de indice contine,
Œn paranteze drepte valorile efective ale indicilor tabloului (ce trebuie,
de obicei, sa concorde ca numar si tip cu declararea acestuia). Exista Œn
general doua modalitati de referire: punctuala si de subtablouri. Ambele
modalitati se bazeaza pe calculul de adresa.

Calculul de adresa realizeaza, pentru o expresie de indice data, determinarea


locatiei de memorie (a adresei) ce contine elementul de tabloul referit.
Deoarece memoria calculatorului poate fi considerata ca tablou monodimensional
de locatii adresabile, problema calculului de adresa se reduce la determinarea,
pe baza informatiei asupra indicilor si asupra tabloului Œn general, a unui
numar ce reprezinta adresa cautata. Pentru fiecare tablou declarat, se
memoreaza Œn descriptorul de tablou urmatoarele informatii:

- nume = numele tabloului;


- tip = tipul elementului de tablou;
- lung = lungimea reprezentarii unui element de tablou (Œn unitati de alocare);
- adrs = adresa de unde Œncepe memorarea tabloului;
- nrd = numarul de dimensiuni al tabloului;
- pentru fiecare dimensiune i, limitele lii si lsi (1(i(nrd)

Se presupune ca tabloului i se aloca locatii consecutive de memorie, deci el


ocupa o zona compacta. Fiecare element de tablou va ocupa, Œn zona respectiva,
o locatie unic determinata. Exista doua modalitati de memorare a unui tablou:
pe linii (cƒnd ultimul indice variaza cel mai repede, engl. row major order),
si pe coloane (cƒnd primul indice variaza cel mai repede, engl. column major
order). Notƒnd cu loc o functie ce Œntoarce adresa locatiei de memorie a unei
referinte de tablou, pentru doua tablouri A(li:ls) si B(li1:ls1,li2:ls2),
calculele de adresa se descriu astfel:

loc(A(i)) = adrs + (i - li) * lung SAU


loc(A(i)) = adrs - li * lung + i * lung

Memorare pe linii:
toate elementele liniilor anterioare liniei i plus primele j-1 elemente ale
liniei i;

loc(B(i,j)) = adrs + (i - li1) * (ls2 - li2 +1) * lung +


(j - li2) * lung sau
40
loc(B(i,j)) = adrs - li1 * (ls2 - li2 + 1) * lung -
li2 * lung + j * lung + i * (ls2 - li2 + 1) * lung

Memorare pe coloane:
toate elementele coloanelor anterioare coloanei j plus primele i-1
elemente ale coloanei j;

loc(B(i,j)) = adrs + (j - li2) * (ls1 - li1 +1) * lung +


(i - li1) * lung sau

loc(B(i,j)) = adrs - li2 * (ls1 - li1 +1) * lung - li1 * lung +


i * lung + j * (ls1 - li1 + 1) * lung

In expresiile de mai sus, ultimele relatii corespund unor calcule optime de


adresa, prin gruparea la Œnceput a termenilor constanti (termenii ce nu
contin indicii i si j ca factori).

Sintaxa expresiei de indice Œn Pascal permite folosirea indicilor de tip


enumerare. Folosind tipul enumerare Luni, pe baza declaratiilor:

Type
Luni = (Ian, Feb, Mar, Apr, Mai, Iun, Iul, Aug, Sep, Oct, Nov, Dec);
Var
temp : array[Luni] of real; {contine temperaturile lunare }
i : luni;
Const
x: real = 0.0; { suma temperaturilor lunare }

instructiunea de ciclare:

For i := Ian To Dec Do x := x + temp[i];

care calculeaza Œn x suma temperaturilor lunare este valida. Sa remarcam aici


ca declaratia de variabila pentru temp foloseste un tip anonim.

9.1.4. Tablouri bidimensionale

Tablourile bidimensionale (numite si matrici Œn limbajul curent) au linii si


coloane. Am vazut Œn paragraful precedent cum se memoreaza aceste tablouri.
Dam aici un exemplu care foloseste doua modalitati diferite (si structural
echivalente) de declarare a tablourilor bidimensionale.

Type
DomeniuLinii = 1..25;
DomeniuColoane = 1..80;
Element = Record
Car: Char;
Atr: Byte
End;
TabEcran1 = Array[DomeniuLinii, DomeniuColoane] Of Element;
TabEcran2 = Array[DomeniuLinii] Of Array[DomeniuColoane] Of
41
Element;

Var
EcranColor1: TabEcran1 Absolute $B800:0000;
EcranMono1: TabEcran1 Absolute $B000:0000;
EcranColor2: TabEcran2 Absolute $B800:0000;
EcranMono2: TabEcran2 Absolute $B000:0000;

Begin
EcranColor1[12, 33].Car := 'A';
Write(EcranColor2[12][33].Car);
EcranMono2[11][25].Car := 'B';
Write(EcranMono1[11,25].Car);
End.

Prima modalitate de declarare permite accesarea


- punctuala: EcranColor1[12, 33]
- globala: EcranColor1

A doua modalitate de declarare a tabloului permite accesarea


- punctuala: EcranColor2[12, 33] sau EcranColor2[12][33]
- a unei linii: EcranColor2[12] (care va fi de tipul Array[DomeniuColoane]
Of Element)
- globala: EcranColor2

9.1.5. Tablouri multidimensionale

Au mai mult de doua dimensiuni. Limbajul Pascal nu impune o regula privind


numarul maxim de dimensiuni al unui tablou. Trebuie avuta Œn vedere doar
restrictia ca spatiul alocat unei variabile sa nu depaseasca dimensiunea
segmentului de date sau de stiva.

9.1.6. Operatii globale pe tablori

Operatiile definite pe tipul tablou sunt atribuirea si testul de egalitate.


De asemenea, variabilele de tip tablou se pot transmite ca parametri Œn
subprograme.

9.1.7. Stringuri

Amintim ca Œn Standard Pascal apare (Œntre primele) tipul sir de caractere


Œmpachetat care este declarat prin:

packed array[li..lf] of Char; li ó lf

Pentru compatibilitate cu Standard Pascal, Turbo Pascal accepta si astfel de


declaratii, Œnsa packed nu are nici un efect. La vremea respectiva (cƒnd a
aparut standardul Pascal), setul de caractere folosit era setul ASCII (un
caracter reprezentat pe 7 biti).

Un String de lungime N caractere (N > 1) este considerat Œn Pascal standard


ca fiind constanta de tipul:
42
packed array[1..N] of Char;

Variabilele de tipul packed array[1..N] of Char se numesc variabile string


de lungime N.

In Turbo Pascal este definit tipul String, cu lungime dinamica, Œn doua forme:

Type
String = Array[0..255] Of Char;
String[N] = Array[0..N] Of Char;

Operatiile si subprogramele de lucru cu variabile String au fost discutate


Œn cap. 5 (Expresii).

9.1.8. Tablouri deschise

In Turbo (Borland) Pascal 7.0 se pot folosi si tablouri deschise (engl. open
arrays) pe post de parametri formali Œn declaratiile de subprograme.
Declararea unui tablou deschis nu se poate face cu sintaxa:

Type tip_tablou = Array Of tip-element; { declaratie incorecta }

ci doar ca tip anonim, Œntre parametrii unei proceduri sau functii. Pentru
tablourile deschise, Turbo Pascal are doua functii standard, numite Low si
High (parametrul lor este numele tabloului deschis) care Œntorc limita
inferioara, respectiv limita superioara a tabloului deschis.

Functia Low(A) Œntoarce cea mai mica valoare a domeniului argumentului A.


In cazul general, A poate sa fie identificator de tip sau nume de variabila
de tip ordinal, tablou, sir de caractere si tablou deschis. Valorile Œntoarse
sunt precizate Œn tabelul urmator:

-----------------------------------------------------------------------
Tipul lui A Ce Œntoarce Low(A)
-----------------------------------------------------------------------
Ordinal cea mai mica valoare a domeniului tipului
Array cea mai mica valoare a domeniului indicilor
tabloului
String 0
Open array 0
parametru String 0
-----------------------------------------------------------------------

Functia High(A) Œntoarce cea mai mare valoare a domeniului argumentului A,


care are semantica de la Low. Valorile Œntoarse sunt precizate Œn tabelul
urmator:

------------------------------------------------------------------------
Tipul lui A Ce Œntoarce High(A)
------------------------------------------------------------------------
Ordinal cea mai mare valoare a domeniului tipului
43
Array cea mai mare valoare a domeniului indicilor
tabloului
String dimensiunea declarata a sirului de caractere
Open array numarul de elemente din tablou - 1
parametru String numarul de caractere din sir - 1
------------------------------------------------------------------------

Exemplul urmator prezinta folosirea tabloului deschis la determinarea


elementului minim dintr-un tablou de numere. Functia Min este generala, ea
acceptƒnd orice tablou de numere reale ca parametru, tocmai prin folosirea
tabloului deschis. In corpul ei, determinarea primului si ultimului indice
se face cu ajutorul functiilor Low si High descrise anterior.

Program TabDesc;

Function Min(var A: array of Real): Real;


{A este parametru tablou deschis }
Var
I: integer;
M: real;
begin
M := 10e20;
for I := Low(A) to High(A) do
if A[I] < M then M := A[I];
Min := M
end;

const { constante tablou cu tip }

CA1: array[5..20] of real =(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);


CA2: array[-5..7] of real =(-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7);

Begin
WriteLn(Min(CA1):10:2);
WriteLn(Min(CA2):10:2);
WriteLn
End. { TabDesc }

Pentru un parametru tablou cu declaratia

T : Array[li..lf] of Real,

care este argument de apel al functiei Min din exemplul anterior, la executia
acesteia parametrul formal A va fi considerat de tipul:

A : array[0..nr-1] of Real;

unde nr este numarul de elemente al tabloului parametru actual, adica


lf - li + 1. Prin urmare, are loc o translatie a domeniului indicilor, de la
li..lf la 0..nr-1, limitele efective (de lucru) ale indicelui fiind Œntoarse
de functiile Low si High: A[0] (adica A[Low(A)]) corespunde lui T[li], A[1]
lui T[li+1], ... A[nr-1] (adica A[High(A)]) corespunde lui T[lf].
44
9.2. Tipul de date multime

9.2.1. Definitia tipurilor multime


9.2.2. Construirea multimilor si atribuirea
9.2.3. Compararea multimilor si testul de apartenenta
9.2.4. Operatii cu multimi

9.2.1. Definitia tipurilor multime

Limbajul Pascal este unul din primele limbaje care a introdus tipul multime.
Fiind dat un tip ordinal B, numit tip de baza, tipul multime T se declara
folosind sintaxa:

Type tip_multime = Set Of tip_de_baza;

Domeniul tipului tip_multime T este multimea partilor (submultimilor)


domeniului tipului tip_de_baza. Astfel, daca tipul B are domeniul {a, b, c},
atunci domeniul lui T va fi

{{}, {a}, {b}, {c}, {a, b}, {a, c}, {b, c}, {a, b, c}}.

Tipul T se considera structurat deoarece fiecare element al sau contine


elemente (posibil nici unul) din domeniul tipului de baza B.

Implementarea tipului multime se face pe siruri de biti. Daca domeniul tipului


de baza B are n elemente, atunci domeniul tipului multime T va avea 2^n
(2 la puterea n) elemente, deci o variabila de tip T se poate reprezinta pe
un sir de n biti. Bitul de pe pozitia i din sirul respectiv (1óión) va fi
setat pe 1 daca al i-lea element din domeniul lui B (ordinal) apartine
multimii si 0 Œn caz contrar. Operatiile tipului multime se implementeaza
eficient prin operatii pe siruri de biti. In cazul limbajului Pascal, n = 256.

Multimile reprezinta un instrument matematic elegant. Prezenta lor Œn


limbajele de programare Œmbogateste expresivitatea acestuia. Din pacate,
limitarile impuse asupra domeniului tipului de baza (numar de elemente si tip)
restrƒng utilizarea multimilor. In astfel de situatii, utilizatorul poate
sa-si defineasca propriile sale tipuri multime.

9.2.2. Construirea multimilor si atribuirea

Inainte de a opera cu variabile de tip multime, ele trebuie construite.


Regulile de construire a variabilelor de tip multime Œn Pascal sunt
urmatoarele:
- o multime Pascal este formata dintr-o lista de elemente separate prin
virgula si incluse Œn paranteze drepte;
- multimea vida se marcheaza prin [];
- toate elementele din lista trebuie sa fie de acelasi tip, tipul de baza al
multimii
- toate elementele sunt diferite
- Œn loc sa se enumere toate elementele dintr-un subdomeniu, se poate preciza
subdomeniul pe post de element Œn lista (primul si ultimul element cu ..
45
Œntre ele)

Ordinea Œn care se executa operatiile:


- declararea tipului multime T
- declararea variabilei V de tipul T
- construirea (initializarea) variabilei V

Exemple:

Type
Luni = (Ian, Feb, Mar, Apr, Mai, Iun, Iul, Aug, Sep, Oct, Nov, Dec);
Anotimp = Set Of Luni;
Caractere = Set Of Char;
Var
Iarna, Primavara, Vara, Toamna: Anotimp;
Litere, Cifre: Caractere;

Begin
Iarna := [Dec, Ian, Feb, Mar];
Vara := [];
Toamna := [Aug, Sep, Oct, Nov];
Primavara := [Apr, Mai, Iun, Iul];
If Vara = [] Then WriteLn('Anul asta n-a fost vara!');
Litere := ['A'..'Z', 'a'..'z'];
Cifre := ['0'..'9']
End.

9.2.3. Compararea multimilor si testul de apartenenta

Asa cum am discutat Œn cap. 5, Pascal are cinci operatori relationali care
opereaza cu expresii de tipuri multime ca operanzi si care sunt prezentati Œn
tabelul urmator. Primii patru dintre ei cer ca ambii operanzi sa fie tipuri
multime compatibile, iar ultimul are un operand de tipul B si altul de tipul
T unde T este Set Of B.

----------------------------------------------------------------------------
Operator Descriere Exemplu de folosire Rezultat
----------------------------------------------------------------------------
= egalitate de multimi [Ian, Feb] = [ ] False
<> diferit [Ian, Mar] <> [Apr] True
<= incluziunea A <= B [Ian, Mar] <= [Ian, Aug, Mar] True
inseamna ca orice element [Ian, Mar] <= [Feb] False
din A apartine si lui B
>= include: A >= B Œnseamna [Ian, Aug, Mar] >= [Ian, Apr] False
ca orice element din B [Ian, Mar] >= [Mar] True
apartine si lui A
in x in A Œnseamna x Ian in [Aug, Feb] False
apartine lui A Mar in [Ian, Aug, Mar] True
x trebuie sa fie din 'P' in ['A'..'Z'] True
tipul de baza
----------------------------------------------------------------------------

46
9.2.4. Operatii cu multimi

Operatiile proprii tipului multime sunt cele cunoscute: reuniunea,


intersectia, diferenta (operatii binare cu rezultat multimi), incluziunea,
testul de egalitate (operatii binare cu rezultat boolean), apartenenta,
atribuirea. Ele au fost discutate Œn capitolul 5 (Expresii).

Incepƒnd cu versiunea 7.0, Turbo Pascal poseda doua noi proceduri, Include
si Exclude. Procedura Include, declarata prin

procedure Include(var S: set of T; I:T);

are ca efect adaugarea elementului I la multimea S, iar procedura Exclude, cu


aceeasi signatura, are ca efect eliminarea elementului I din multimea S.

9.3. Tipul de date Œnregistrare


9.3.1. Conceptul de Œnregistrare
9.3.2. Declararea tipului Œnregistrare
9.3.3. Accesarea componentelor. Instructiunea With
9.3.4. Structuri complexe
9.3.5. Inregistrari cu variante
9.3.6. Initializarea Œnregistrarilor

9.3.1. Conceptul de Œnregistrare

Elementele definitorii ale unei Œnregistrari sunt:

- numele tipului Œnregistrare (optional),


- numarul de cƒmpuri,
- numele si tipul fiecarui cƒmp.

Inregistrarea este o modalitate de agregare (punere Œmpreuna a unor date de


tipuri (Œn general) diferite. Numele tipului de data Œnregistrare este un
identificator. Numarul de cƒmpuri este dedus din lista de declarare a
cƒmpurilor. Cƒmpurile unei Œnregistrari se memoreaza Œn zone adiacente de
memorie. Informatia de tip a fiecarui cƒmp serveste la stabilirea lungimii de
reprezentare a acestuia, iar numele cƒmpului se foloseste pentru a accesa
valoarea lui (prin operatia de accesare). De obicei, sunt accesibile atƒt
data compusa (Œnregistrarea), cƒt si componentele (cƒmpurile) acesteia.

Exista doua clase de Œnregistrari: cu structura fixa si cu variante.


Inregistrarile cu structura fixa au o singura definitie, iar Œnregistrarile
cu variante au o parte fixa, un discriminant si mai multe definitii de
variante.

Lungimea unei Œnregistrari este suma lungimilor cƒmpurilor componente. Se


poate folosi functia SizeOf cu parametru tipul de data Œnregistrare sau
variabila de tip Œnregistrare.

Elementele de discutie privitoare la tipul de date Œnregistrare sunt


urmatoarele:
- maniera de declarare a tipurilor Œnregistrare;
47
- maniera de accesare a cƒmpurilor unei Œnregistrari;
- definirea Œnregistrarilor cu structura variabila;
- initializarea unei variabile Œnregistrare;
- operatiile permise pe variabile de tip Œnregistrare.

9.3.2. Declararea tipului Œnregistrare

Odata cu Pascal si Algol68, prin mecanismele de tipizare se pot declara


tipuri de date Œnregistrare. In Pascal, de exemplu, tipurile de date
DataC (data calendaristica), Timp si ElementEcran se declara astfel:

Type
DataC = Record
zi : 1..31;
lu : 1..12;
an : 1800..2000;
End;
Timp = Record
ora: 0..23;
min: 0..59;
sec: 0..59
End;
Element = Record { definitia unui element pe ecranul text }
Car: Char;
Atr: Byte
End;

Se observa ca aceste tipuri de date au componente (cƒmpuri) diferite ca tip


de date. Odata declarate aceste tipuri, se pot declara variabile de tipurile
respective:

Var
DataNasterii, DataCasatoriei: DataC;
OraDeIncepere: Timp;
Ecran: Array[1..25] Of Array[1..80] Of ElementEcran;

Doar Œn momentul declaratiei de variabila se poate stabili dimensiunea de


alocare pentru variabilele de tipurile respective.

9.3.3. Accesarea componentelor. Instructiunea With

Referirea componentelor sau selectarea (accesarea) cƒmpurilor unei


Œnregistrari se face Œn Pascal Œn doua moduri
- folosind calificarea cu punct
- folosind instructiunea With

Calificarea cu punct permite accesarea sau modificarea valorii unui cƒmp


dintr-o variabila Œnregistrare. Ea se face Œn forma:

nume_variabila.nume_cƒmp

In Pascal, daca se acceseaza succesiv mai multe cƒmpuri ale aceleiasi


48
variabile de tip Œnregistrare, se poate folosi instructiunea With (vezi 9.3.3
si 7.7).

Variabilele de tip Œnregistrare se pot accesa si global, prin numele lor.


Este permisa de asemenea operatia de atribuire, cƒnd variabilele implicate
sunt de acelasi tip:

Var
Data1, Data2: DataC;

----------------------------------------------
Atribuire globala Echivalenta cu
----------------------------------------------
Data1 := Data2 Data1.zi := Data2.zi
Data1.lu := Data2.lu
Data1.an := Data2.an
----------------------------------------------

9.3.4. Structuri complexe

Cƒmpurile unei Œnregistrari pot fi de orice tip recunoscut de sistemul de


tipuri, Œn particular si tip Œnregistrare.

Type
DataC = Record
zi : 1..31;
lu : 1..12;
an : 1800..2000;
End;
A40 = Array[1..40] Of Char;
TipSex = (masc,fem);
StareCiv = (necas,casat,vaduv,divortat);
Persoana = Record
Nume : String[30];
NrId : Longint;
Adr : A40;
Sex : TipSex;
StareC : StareCiv;
DataN : Datac;
inalt : Real
End;

Tipurile cƒmpurilor sunt predefinite (String, LongInt, Real Œn Turbo Pascal),


simple utilizator (TipSex si StareCiv), respectiv structurate (A40 si DataC).
Prin urmare, cu astfel de notatii de tip se pot declara tipuri oricƒt de
complexe. Daca se declara o variabila de tip Persoana:

Var p : Persoana;

ea va putea fi tratata fie global (atribuire, test de egalitate), fie selectiv


(p.Inalt va semnifica cƒmpul inalt din Œnregistrarea p). Tabelul urmator
49
prezinta exemple de accesare (cu punct si cu With).

With P Do Begin
P.Nume := 'IONESCU'; Nume := 'IONESCU';
P.Sex := masc; Sex := masc;
P.StareC := casat; StareC := casat;
P.Inalt := 1.80; Inalt := 1.80;
P.DataN.zi := 23; DataN.zi := 23
End

Alte exemple de structuri complexe


- Œnregistrari cu cƒmpuri tablou
- tablouri de Œnregistrari

Const
Max_Persoane = 100;
Max_Discipline = 20;
Type
Personal: Array[1..Max_Persoane] Of Persoana;
Medie = Record {facem economie: Medie: Real ar ocupa 6 byte }
Parte_Int: Byte; {SizeOf(Medie) = 2}
Parte_Zec: Byte
End; { Medie }
Medii = Array[1..Max_Discipline] Of Medie;
Trimestru = 1..3;
Elev = Record
Nume: String[40];
DataN: DataC; {declarat Œn exemplul de mai sus}
SitScolara: Array[Trimestru] Of Medii;
End; { Elev }

9.3.5. Inregistrari cu variante

Inregistrarile date ca exemplu pƒna acum au o structura fixa. O Œnregistrare


cu structura variabila contine diferite definitii alternative ale unora
dintre componentele sale. De obicei, o Œnregistrare cu structura variabila
are o parte fixa, comuna tuturor alternativelor sale, si o parte variabila,
a carei structura trebuie definita explicit Œn fiecare alternativa. Partea
variabila este identificata prin valoarea pe care o poate lua un cƒmp special
al Œnregistrarii numit 'discriminant'. Valori distincte ale discriminantului
vor produce alternative distincte. Modul Œn care se precizeaza alternativele
partii variabile este de obicei de tip case. In Pascal, sintaxa partii
variabile este:

<parte variabila record> ::=


case <camp discriminant> <identificator de tip> of <varianta>
<camp discriminant> ::= [<identificator>:]
<varianta> ::= [<lista etichete case>:(<lista campuri>)]

cu precizarea general valabila ca partea fixa a Œnregistrarii trebuie


declarata Œnaintea partii variabile.

50
Inregistrarile cu variante respecta principiul de reprezentare a celor cu
structura fixa. Lungimea de reprezentare a unei variabile de tip Œnregistrare
cu variante este de obicei lungimea partii fixe plus maximul lungimii
variantelor. Partea fixa contine inclusiv cƒmpul discriminant. Prin urmare,
Œntr-o Œnregistrare cu variante, primele cƒmpuri ale variantelor au aceeasi
adresa relativa la Œnceputul Œnregistrarii.

De exemplu, Œn Pascal se poate defini o Œnregistrare cu variante astfel:

Type
CuloareOchi = (caprui,albastru,negru,verde);
Sex = (Masc,Fem);
Persoana = record
Nume : string[30];
NrId : longint;
Adr : array[1..40] of char;
Case s:Sex of { discriminant }
Masc : (Inalt: Real; DataN: DataC);
Fem : (CO: CuloareOchi; Bust:Real)
End;

Partea fixa a Œnregistrarii Persoana contine cƒmpurile Nume, NrId, Adr si S,


iar pentru partea variabila exista doua alternative. Daca valoarea
discriminantului S este Masc, atunci vor putea fi accesate cƒmpurile Inalt si
DataN, iar daca S are valoarea Fem cƒmpurile valide sunt CO si Bust.

Un alt exemplu:

Type
ambele = (by,ch);
bc = record
case t:ambele of
by : (b:byte);
ch : (c:char);
end;
var a1, a2, a3 : bc;

Prin urmare variabilele a1, a2, a3 vor contine fie cƒmpuri de tip byte, fie
cƒmpuri de tip caracter. Sunt permise urmatoarele instructiuni:

a1.t := ch; {discriminantul ch: accesibil a1.c }


a1.c := 'a';
a1.t := by; {discriminantul by: accesibil a1.b }
writeln(a1.c); { care nu este initializat }

Accesarea variantei a1.c cu valoarea discriminantului a1.t=by reprezinta o


utilizare inconsistenta a variabilei a1. Acest lucru nu ar trebui sa fie
permis, deoarece cƒmpul discriminant Œsi pierde astfel "puterea de decizie"
Œn ceea ce priveste varianta aleasa, rolul sau devenind practic Œn acest caz
unul pur decorativ.

Folosirea sistemului de tipuri pentru a realiza verificarile de consistenta


51
necesare nu este posibila Œn cazul tipizarii statice, specifica limbajului
Pascal.

9.3.6. Initializarea Œnregistrarilor

Inregistrarile se pot initializa folosind constante cu tip. Declararea unei


constante de tip Œnregistrare specifica numele (identificatorul) si valoarea
asociata pentru fiecare cƒmp. Cƒmpurile trebuie specificate Œn ordinea
declararii lor Œn tipul Œnregistrare..

Nu se pot initializa Œnregistrari ale caror tipuri contin cƒmpuri de tip file.

type
Punct = record
X, Y: Real;
end;
Vector = array[0..1] of Punct;
Luna = (Ian,Feb,Mar,Apr,Mai,Iun,Iul,Aug,Sep,Oct,Nov,Dec);
DataC = Record
zi : 1..31;
lu : Luna;
an : 1800..2000;
End;
const
Origine: Punct = (X: 0.0; Y: 0.0);
Linie: Vector = ((X: -3.1; Y: 1.5), (X: 5.8; Y: 3.0));
ZiuaMea: DataC = (zi: 2; lu: Dec; an: 1960);

9.4. Tipul de date fisier

9.4.1. Notiunea de fisier


9.4.2. Operatii asupra fisierelor
9.4.3. Fisiere text
9.4.4. Fisiere record
9.4.5. Fisiere fara tip (stream)
9.4.6. Fisierele sistem

9.4.1. Notiunea de fisier

Programele realizate pƒna acum au folosit date de intrare furnizate de la


tastatura si au prezentat rezultatele pe ecran. In cazul mai general, un
program P va prelua date dintr-un fisier de intrare si va furniza rezultate
Œntr-un fisier de iesire. Spunem Œn acest caz ca P efectueaza operatii de
intrare/iesire (I/E).

Implicit, un program Pascal considera ca preia datele de intrare de la


fisierul standard de intrare al sistemului de operare si ca scrie rezultatele
Œn fisierul standard de iesire al sistemului. Uzual, fisierul standard de
intrare este tastatura sau un dispozitiv special de introducere de date, pe
cƒnd fisierul standard de iesire este ecranul terminalului sau imprimanta. In
9.4.6. vom discuta despre aceste fisiere sistem.

52
Fisierul este o colectie de informatii. Aceste informatii se pot structura Œn
Pascal sub forma de text, sub forma de date atomice (de tipuri simple,
predefinite) sau sub forma de Œnregistrari (record). Turbo Pascal poseda si
fisiere cu structura nedefinita, numite fisiere fara tip. Fisierul se poate
considera din doua puncte de vedere
- fizic sau extern programului (respectƒnd conventiile sistemului de operare):
specificator de fisier
- logic sau intern programului care-l foloseste: identificator logic de fisier.

Din punct de vedere fizic, un fisier este identificat de


- perifericul pe care este memorat
- directorul (calea) Œn care se afla
- numele sau (nume.tip)
iar din punct de vedere logic, un fisier este manipulat Œntr-un program
printr-o variabila de tip fisier. In Pascal tipul fisierului se construieste
specific.

9.4.2. Operatii asupra fisierelor

Fisierele sunt folosite pentru a pastra (memora) informatie pe suport extern.


Aceste informatii au un caracter persistent, adica ele exista si dupa ce
programul care le-a creat Œsi termina executia. Spre exemplu, programele
sursa Borland (Turbo) Pascal se gasesc Œn fisiere text, care sunt create cu
ajutorul unui editor de texte. Odata introdus textul sursa, acesta poate fi
modificat dupa dorinta programatorului, folosind un editor similar.

Operatiile efectuate asupra fisierelor Œntr-un program Pascal sunt:


- declararea
- deschiderea
- Œnchiderea
- pozitionarea
- citirea
- scrierea

Ordinea Œn care aceste operatii se efectueaza este de obicei urmatoarea:


- declararea fisierului
- deschiderea fisierului, care Œnseamna si o pozitionare implicita
- citirea sau scrierea de informatie din/Œn fisier, precedate sau nu de
operatii de pozitionare
- Œnchiderea fisierului

Fisierele servesc la transferul de informatie dintre memoria interna si


suportul extern (periferice). Operatiile de transfer sunt citirea (transferul
de informatie din fisier Œn memoria interna) si scrierea (transferul de
informatie din memoria interna Œn fisier).

Operatiile de pozitionare sunt necesare pentru a preciza locul de unde se


citeste informatia (Œn cazul citirii), respectiv locul de unde Œncepe
scrierea de informatie pe fisier (in cazul scrierii). Fiecare fisier non-text
deschis are asociate o zona de memorie, numita buffer, care serveste ca
intermediar Œntre memoria interna si fisier, continƒnd informatia supusa
transferului. Toate fisierele au asociat un Œntreg numit contor de pozitie,
53
care specifica pozitia curenta din fisier de la care Œncepe transferul.

Exceptƒnd declararea, necesara datorita caracterului puternic tipizat al


limbajului, toate celelalte operatii enumerate se traduc Œn operatii fizice
de accesare a suportului pe care se gaseste fisierul, fiind numite generic
operatii de intrare-iesire. Aceste operatii pot sa provoace mari batai de cap
programatorilor, motiv pentru care mediile Turbo si Borland Pascal le pun la
dispozitie o modalitate de verificare a modului de terminare a lor, cu
ajutorul functiei standard IOResult, care Œntoarce starea ultimei operatii
de intrare-iesire efectuate.

Declaratia acestei functii este urmatoarea:

function IOResult: Integer;

si ea Œntoarce:
- 0 daca ultima operatie de intrare/iesire s-a terminat cu succes
- o valoare diferita de 0 (de obicei un cod al erorii produse) daca ultima
operatie de intrare-iesire a esuat.

Pentru ca programatorul sa poata folosi aceasta functie, trebuie sa se comute


indicatorul $I de verificare a operatiilor de intrare-iesire pe off. Acest
lucru se poate face fie incluzƒnd Œn cod directiva de compilare {$I-}, fie
folosind meniul Options|Compiler|I/O Checking. Uzual, comutatorul $I este
setat pe on, adica {$I-}.

9.4.2.1. Declararea fisierelor

La nivelul programului sursa Pascal, un fisier este referit printr-o variabila


fisier, ceea ce am numit anterior identificator logic de fisier. Deoarece
Pascal este un limbaj puternic tipizat, identificatorul logic de fisier
trebuie sa fie de una dintre urmatoarele tipuri:
- text (pentru fisiere text)
- file of tip_componenta (fisierul este un fisier record Œn care fiecare
Œnregistrare este de tipul tip_componenta)
* file (fisierul este nedefinit, fiind considerat ca o succesiune de octeti -
stream)

Declararea variabilelor fisier se face uzual, folosind cuvƒntul rezervat var.

Variabilele fisier nu se pot folosi decƒt Œn operatiile specifice lucrului cu


fisiere. Mai exact, o variabila fisier se initializeaza prin operatia de
deschidere a fisierului si Œsi pierde valoarea la Œnchiderea acestuia.

var
fis_text: Text; { fisier text }
fis_intregi = File of Integer; { fisier de intregi }
fis_nedefinit: File; { fisier nedefinit }

9.4.2.2. Deschiderea unui fisier

Operatia de deschidere a unui fisier este prima operatie care se efectueaza


54
asupra acestuia, Œnaintea prelucrarilor propriu-zise la care acesta este
supus. Menirea acestei operatii este de a stabili
- modul Œn care este folosit fisierul (citire sau scriere)
- alocarea buffer-ului si initializarea contorului de pozitie.

In Turbo si Borland Pascal, operatia de deschidere a unui fisier se


realizeaza Œn doi pasi:
- initializarea variabilei fisier;
- deschiderea propriu-zisa a fisierului.

Initializarea variabilei fisier se face cu procedura standard Assign, care


are declaratia:

procedure Assign(var var_fis; nume_fis:String);

unde:
- var_fis este o variabila fisier de oricare tip (identificatorul logic de
fisier), iar
- nume_fis este un sir de caractere ce desemneaza numele extern al fisierului
(specificatorul de fisier)

Dupa executia procedurii, var_fis va fi initializata, fiind asociata


fisierului extern nume_fis; cu alte cuvinte, orice operatie pe var_fis va
Œnsemna de fapt lucrul cu fisierul nume_fis. Asocierea ramƒne valabila pƒna
cƒnd se Œnchide fisierul referit de var_fis sau pƒna cƒnd var_fis apare Œntr-o
alta procedura Assign.

Daca nume_fis este sirul de caractere vid, var_fis va fi asociata unuia dintre
fisierele sistem. Daca nume_fis este numele unui fisier deja deschis, se
produce o eroare de executie.

Deschiderea propriu-zisa a unui fisier se face Œn mod obisnuit prin apelul


uneia dintre procedurile standard Reset sau Rewrite.

Procedura standard Reset realizeaza deschiderea unui fisier (de obicei) Œn


citire. Declaratia sa este:

procedure Reset(var var_fis);

unde var_fis este o variabila fisier de oricare tip (identificatorul logic de


fisier), asociat Œn prealabil unui fisier extern prin apelul procedurii
Assign.

Reset deschide fisierul extern asociat variabilei var_fis. Daca fisierul


respectiv este deja deschis, el se Œnchide Œn prealabil si apoi are loc
deschiderea. Contorul de pozitie al fisierului se seteaza pe Œnceputul
fisierului. Reset produce o eroare de intrare-iesire daca fisierul extern nu
exista. In cazul fisierelor text, Reset produce deschiderea acestora numai Œn
citire.

Pentru fisierele non-text, Unit-ul System contine variabila FileMode, care


contine informatia privitoare la modul de deschidere a acestora prin Reset:
55
- 0 - Read only (numai citire)
- 1 - Write only (numai scriere)
- 2 - Read/Write (valoare implicita).

Aceasta variabila se poate seta de catre programator prin operatia de


atribuire.

Functia Exista din programul CitireT (9.4.2.3) verifica daca fisierul


specificat prin parametrul NumeFisier (care reprezinta un specificator
complet de fisier) exista pe perifericul specificat Œn el. Daca perifericul
si calea lipsesc, atunci se considera ca fisierul este Œn directorul curent.

Procedura standard Rewrite realizeaza deschiderea unui fisier Œn scriere.


Declaratia sa este:

procedure Rewrite(var var_fis);

unde var_fis este o variabila fisier de oricare tip (identificatorul logic de


fisier), asociat Œn prealabil unui fisier extern prin apelul procedurii
Assign.

Rewrite creeaza fisierul extern asociat variabilei var_fis. Daca fisierul


respectiv exista, atunci el se sterge si se creeaza un fisier vid. Daca el
este deja deschis, atunci se Œnchide Œn prealabil si apoi este re-creat.
Contorul de pozitie al fisierului se seteaza pe Œnceputul fisierului.
In cazul fisierelor text, Rewrite produce deschiderea acestora numai Œn
scriere.

9.4.2.3. Inchiderea unui fisier

Operatia de Œnchidere a unui fisier semnifica terminarea lucrului cu acesta.


Prin Œnchidere se realizeaza urmatoarele:
- transferarea informatiei din buffer pe suport (Œn cazul fisierelor deschise
Œn scriere)
- distrugerea asocierii Œntre variabila fisier si numele extern al fisierului.

Dupa Œnchidere, fisierul se poate folosi din nou, respectƒnd etapele descrise
anterior. Variabila fisier devine disponibila, ea putƒnd fi folosita Œntr-un
alt apel al procedurii Assign.

Pentru un fisier deschis Œn citire, prelucrarea sa se termina de regula


atunci cƒnd s-a ajuns la sfƒrsitul sau. Detectarea sfƒrsitului de fisier se
face cu functia standard EOF. Declaratia acesteia este:

function Eof(var var_fis): Boolean; { fisiere non-text }


function Eof [ (var var_fis: Text) ]: Boolean; { fisiere text }

In cazul fisierelor text, daca var_fis lipseste se considera ca EOF se refera


la fisierul standard de intrare. Altfel, var_fis va referi un fisier deschis
Œn prealabil prin Assign si Reset/Rewrite. EOF Œntoarce:
- True daca contorul de pozitie este dupa ultimul octet din fisier sau daca
fisierul este vid
56
- False Œn toate celelalte cazuri.

Exemplu: citirea unui fisier text si afisarea acestuia pe ecran;

Program CitireT;
var f: text;
linie: string;
numeFis: String;
este: Boolean;

function Exista(NumeFisier: String): Boolean;


{ intoarce
True daca fisierul extern reprezentat de NumeFisier exista
False altfel
}
var
f: File;
begin
{$I-} { comuta pe off indicatorul $I }
Assign(f, NumeFisier);
FileMode := 0; { deschide in citire }
Reset(f);
Close(f);
{$I+}
Exista := (IOResult = 0) and (NumeFisier <> '')
end; { Exista }

begin
WriteLn('CitireT - afisarea unui fisier text pe ecran');
Repeat
Write('Dati numele fisierului: ');
ReadLn(numeFis);
este := Exista(numeFis);
if not este then WriteLn('Fisier inexistent!');
Until este;
Assign(f, numeFis); { asociaza f la numeFis }
Reset(f); { deschide f in citire }
While not Eof(f) do begin { cat timp nu s-a ajuns la sfarsit }
Readln(f, linie); { citeste o linie din fisier }
Writeln(linie) { scrie linia la iesirea standard }
end;
Close(f); { inchide fisierul }
end. { CitireT }

9.4.2.4. Pozitionarea

Operatia de pozitionare se refera la fisierele non-text si are ca efect


modificarea contorului de pozitie. Modificarea se face Œn doua moduri:
- implicit, prin operatiile de citire si de scriere; orice operatie de
transfer modifica contorul de pozitie, acesta avansƒnd spre capatul
fisierului
- explicit, prin procedura standard Seek.
57
Mediile Borland pun la dispozitia programatorului o functie si o procedura
care Œi permit acestuia sa acceseze si sa modifice contorul de pozitie:
FilePos si Seek. De asemenea, functia FileSize determina dimensiunea unui
fisier.

Functia FileSize Œntoarce numarul de componente dintr-un fisier. Declaratia


sa este urmatoarea:

function FileSize(var var_fis): Longint;

Œn care var_fis este o variabila fisier, asociata unui fisier deschis Œn


prealabil. FileSize Œntoarce numarul de componente al fisierului. Daca
fisierul este vid, FileSize Œntoarce 0. Vom discuta Œn paragrafele urmatoare
semantica exacta a lui FileSize pentru fiecare clasa de fisiere.

Functia FilePos Œntoarce valoarea contorului de pozitie al unui fisier


non-text. Declaratia sa este:

function FilePos(var var_fis): Longint;

Œn care var_fis este o variabila fisier, asociata unui fisier deschis Œn


prealabil. Daca contorul de pozitie este pe Œnceputul fisierului, FilePos va
Œntoarce 0, iar daca contorul de pozitie este la sfƒrsitul fisierului, atunci
FilePos(var_fis) va fi egal cu FileSize(var_fis).

Contorul de pozitie al unui fisier non-text se exprima Œn unitati de masura


proprii tipului de fisier. El semnifica Œnregistrarea curenta din fisier,
luƒnd valori de la 0 (Œnceputul fisierului, prima Œnregistrare din el) la
FileSize(var_fis) - 1 (ultima Œnregistrare din fisier). Procedura standard
Seek realizeaza modificarea contorului de pozitie la o noua valoare,
specificata ca parametru al acesteia. Declaratia sa este:

procedure Seek(var var_fis; pozitie: Longint);

Œn care:
- var_fis este o variabila fisier, asociata unui fisier deschis Œn prealabil
- pozitie este un Œntreg, cu urmatoarele valori valide:
- Œntre 0 si FileSize(var_fis) - 1: contorul de pozitie al fisierului
var_fis se va seta la valoarea pozitie
- FileSize(var_fis): Œn fisierul var_fis se va adauga (la sfƒrsit) o noua
Œnregistrare

Exista, de asemenea, procedura Truncate, care permite trunchierea unui fisier


non-text. Declaratia sa este:

procedure Truncate(var var_fis);

Œn care var_fis este o variabila fisier, asociata unui fisier deschis Œn


prealabil. Truncate pastreaza Œn fisier numai Œnregistrarile de la 0 si pƒna
la FilePos(var_fis) - 1, eliminƒnd celelalte Œnregistrari din el (daca exista)
si setƒnd EOF(var_fis) pe True.
58
9.4.2.5. Citirea

Operatia de citire Œnseamna transferarea de informatie din fisierul extern Œn


memoria interna a calculatorului. Mai exact, citirea realizeaza initializarea
unor variabile din programul Pascal cu valori preluate din fisierul extern.

Citirea se face diferit pentru fiecare clasa de fisiere Borland (Turbo) Pascal.
Ea poate sau nu sa fie Œnsotita de conversii.

9.4.2.6. Scrierea

Operatia de scriere Œnseamna transferarea de informatie din memoria interna a


calculatorului Œn fisierul extern. Mai exact, scrierea realizeaza memorarea
valorilor unor variabile din programul Pascal Œn fisierul extern.

Scrierea se face diferit pentru fiecare clasa de fisiere Borland (Turbo)


Pascal. Ea poate sau nu sa fie Œnsotita de conversii.

9.4.3. Fisiere text

Fisierele text sunt fisiere speciale, care se pot citi sau edita de orice
editor de texte standard. Un fisier text este o succesiune de caractere ASCII
organizate Œn linii. Numarul de linii este variabil, iar fiecare linie contine
un numar variabil de caractere. O linie se termina cu o combinatie speciala de
caractere (de regula CR+LF, adica ASCII 10 + ASCII 13), iar sfƒrsitul de
fisier poate fi determinat cu ajutorul functiei EOF. Aceasta poate folosi:
- functia FileSize (care determina numarul de caractere din fisier)
- un caracter special de sfƒrsit de fisier (cu codul ASCII 26, recunoscut
prin combinatia CTRL+Z de la tastatura).

Nu este obligatoriu, cel putin Œn Turbo Pascal, ca un fisier text sa contina


terminatorul de sfƒrsit de fisier.

Mediile Borland si Turbo Pascal permit specificarea unui fisier text prin
cuvƒntul cheie text, care are declaratia:

type text = file of char;

In Borland si Turbo Pascal sunt disponibile urmatoarele functii si proceduri


specifice lucrului cu fisierele text:
- Append (procedura)
- EOLN (functie)
- Flush (procedura)
- Read (procedura)
- ReadLn (procedura)
- SeekEOF (functie)
- SeekEOLN (functie)
- SetTExtBuf (procedura)
- Write (procedura)
- WriteLn (procedura)

59
In cele ce urmeaza, cu exceptia locurilor unde se face o referire explicita,
prin var_fis vom desemna o variabila fisier de tip text, deschis Œn prealabil.

Procedura Append este specifica fisierelor text. Ea permite deschiderea unui


fisier Œn adaugare, adica:
- deschide fisierul Œn scriere
- seteaza contorul de pozitie la sfƒrsitul fisierului.

Declaratia sa este:

procedure Append(var var_fis: Text);

unde var_fis este o variabila fisier de tip text, asociata Œn prealabil


printr-o procedura Assign unui fisier extern existent. Daca fisierul extern
nu exista, se produce o eroare de intrare-iesire. Daca var_fis desemneaza un
fisier deja deschis, acesta se Œnchide Œn prealabil si apoi se executa
operatiile specifice lui Append.

Daca terminatorul de sfƒrsit de fisier CTRL+Z este prezent, contorul de


pozitie se seteaza pe pozitia acestuia. Prin urmare, dupa fiecare scriere Œn
fisier acesta va contine la sfƒrsitul sau terminatorul de fisier.

Functia EOLN Œntoarce statutul de sfƒrsit de linie, adica EOLN


- Œntoarce True daca :
- caracterul curent din fisier (specificat prin contorul de pozitie) este un
terminator de linie sau de fisier
- Œntre caracterul curent si sfƒrsitul de linie nu exista decƒt spatii
- Œntoarce False altfel.

Declaratia aceste functii este:

function EOLN [(var var_fis: Text) ]: Boolean;

Daca var_fis lipseste, se considera ca EOLN se refera la fisierul standard de


intrare.

Procedura Flush realizeaza transferul fizic de informatie din bufferul unui


fisier deschis Œn scriere (cu Rewrite sau Append) pe suport. Declaratia sa
este:

procedure Flush(var var_fis: Text);

Scrierea pe un fisier text se realizeaza prin intermediul unui buffer. In mod


normal, scrierea fizica se efectueaza numai atunci cƒnd bufferul este plin.
Apelƒnd Flush, ne asiguram ca se efectueaza scrierea si cƒnd bufferul nu este
plin.

Procedura standard Read citeste valori dintr-un fisier text Œntr-una sau mai
multe variabile. Citirea se efectueaza Œncepƒnd de la contorul de pozitie,
avansƒndu-se spre sfƒrsitul fisierului. Se pot efectua conversii, Œn functie
de tipul variabilelor prezente ca parametri ai lui Read. Declaratia procedurii
este:
60
procedure Read( [ var var_fis: Text; ] V1 [, V2,...,Vn ] );

unde V1, V2, ..., Vn sunt variabile pentru care Read este Œn domeniul lor de
vizibilitate. Citirea se opreste la sfƒrsitul de linie sau de fisier, fara a
se citi si aceste caractere speciale.

Procedura standard ReadLn este similara procedurii Read, cu exceptia faptului


ca dupa terminarea citirii trece la linia urmatoare: la Œntƒlnirea
caracterelor de sfƒrsit de linie le sare, setƒnd contorul de pozitie dupa ele.
Declaratia procedurii este:

procedure ReadLn( [ var var_fis: Text; ] V1 [, V2,...,Vn ] );

Dupa executarea lui ReadLn, contorul de pozitie va fi setat pe Œnceputul unei


noi linii.

Functia SeekEOF Œntoarce True daca s-a ajuns la sfƒrsitul de fisier si False
altfel. Declaratia sa este:

function SeekEOF [ (var var_fis: Text) ]: Boolean;

Functia SeekEOLN Œntoarce True daca s-a ajuns la sfƒrsit de linie si False
altfel. Declaratia sa este:

function SeekEOLN [ (var var_fis: Text) ]: Boolean;

Procedura SetTextBuf atribuie unui fisier text un buffer de intrare-iesire.


Uzual, bufferul de I/E pentru fisierele text este de 128 de octeti. Cu cƒt
bufferul este mai mare, cu atƒt operatiile de citire si de scriere se executa
mai rapid si printr-un numar mai mic de accese la suportul fizic. Declaratia
este urmatoarea:

procedure SetTextBuf(var var_fis: Text; var Buf [ ; Lung: Word ] );

unde:
- Buf este numele unei variabile (de obicei de tipul unui tablou de caractere)
care va fi folosita pe post de buffer
- Lung este dimensiunea bufferului (Œn octeti)

SetTextBuf trebuie apelata imediat dupa deschiderea fisierului (urmatoarea


instructiune dupa Reset, Rewrite sau Append).

Procedura Write scrie valoarea uneia sau mai multor variabile Œntr-un fisier
text. Scrierea se efectueaza Œncepƒnd de la contorul de pozitie, avansƒndu-se
spre sfƒrsitul fisierului. Se pot efectua conversii, Œn functie de tipul
variabilelor prezente ca parametri ai lui Write. Declaratia procedurii este:

procedure Write( [ var var_fis: Text; ] P1 [, P2,...,Pn ] );

unde P1, P2, ..., Pn sunt expresii de formatare, formate din nume de
variabile sau expresii (de una din tipurile Char, Integer, Real, String si
61
Boolean) ce contin variabile Œmpreuna cu specificatori de lungime si de
numar de zecimale. Pentru tipurile numerice, se face conversia la string
Œnainte de scrierea Œn fisier. Fisierul trebuie sa fie deschis cu Rewrite
sau Append. Contorul de pozitie se va mari cu lungimea stringurilor scrise.

O expresie de formatare P are formatul: V:l[:z], unde


- v este o variabila de tip numeric
- l este lungimea stringului generat (daca partea Œntreaga a lui v are
lungimea mai mare ca l, atunci l se va seta la lungimea partii Œntregi a
lui v - luƒnd Œn considerare si semnul)
- z este numarul de cifre la partea zecimala (numai pentru variabile reale).

Procedura WriteLn este similara cu Write, scriind la sfƒrsit un terminator de


linie. Declaratia procedurii este:

procedure WriteLn( [ var var_fis: Text; ] P1 [, P2,...,Pn ] );

iar semantica ei este:

Write( var var_fis, P1 [, P2,...,Pn ] );


Write( var var_fis, Chr(13), Chr(10) ); { terminator de linie }

Procedurile ReadLn si WriteLn sunt specifice fisierelor text, pe cƒnd


procedurile Read si Write se folosesc si Œn cazul fisierelor cu tip.

9.4.4. Fisiere cu tip

Fisierele cu tip (pe care le numim si fisiere record) sunt accesate prin
intermediul unei variabile fisier declarata astfel:

var
var_fis: file of tip_componenta;

sau, mai elegant:

type
tip_componenta = ... { definitia tipului componentei }
tip_fisier = file of tip_componenta;
var
var_fis: tip_fisier;

Fisierele cu tip respecta definitia generala a fisierului, precizata la


Œnceputul acestei sectiuni: ele contin componente de acelasi tip, notat mai
sus prin tip_componenta. Tipul componentei poate fi orice tip recunoscut de
sistemul de tipuri al limbajului, cu exceptia tipului file.

Spre exemplu, urmatoarele tipuri de fisiere sunt tipuri valide:

type
fisier_integer = file of Integer;
fisier_boolean = file of Boolean;
fisier_persoane = file of Persoana; { tipul Persoana din 9.3.4. }
62
9.4.4.1. Bufferul fisierului

Operatiile efectuate asupra fisierelor cu tip sunt cele discutate general Œn


9.4.2. Transferul de date Œntre suportul extern (fisierul extern) si memorie
(variabilele din program) se realizeaza prin intermediul unei zone pe care
am numit-o buffer (denumirea romƒneasca de pe vremuri era zona tampon).

Pentru un fisier declarat Œn forma:

var
var_fis: tip_fisier;

bufferul este o zona de memorie speciala, fara nume, care o vom nota Œn cele
ce urmeaza cu var_fis^. Initializarea variabilei fisier var_fis se face prin
apelul procedurii standard Assign; deschiderea fisierului se face folosind
procedurile standard Reset (Œn citire sau Œn scriere si citire) si Rewrite
(Œn scriere). Odata cu deschiderea, devine accesibil si bufferul fisierului,
adica variabila var_fis^.

Bufferul trebuie considerat ca zona de memorie Œn care se citesc informatiile


din fisierul extern, Œnainte ca acestea sa fie atribuite variabilelor din
program; similar, Œn buffer sunt depozitate informatiile care se doreste a fi
scrise Œn fisierul extern, Œnainte ca scrierea sa aiba loc. Ratiunea de a fi
a bufferului este aceea de a optimiza (Œn general de a micsora) numarul de
accese la suportul extern, pe care se gaseste fisierul supus prelucrarii.
De obicei, operatiile fizice de citire si scriere pe suport extern realizeaza
transferul unei cantitati fixe de informatie, numita bloc. Dimensiunea
blocului depinde de caracteristicile fizice ale suportului si perifericului
pe care se memoreaza fisierul. Din punct de vedere logic, adica al fisierului
prelucrat Œn programul Pascal, o citire sau scriere logica realizeaza
transferarea unei cantitati de informatie egala cu dimensiunea unei componente a
fisierului, adica SizeOf(tip_componenta). De regula, dimensiunea bufferului
este un multiplu al dimensiunii bloc

9.4.4.2. Citirea prin intermediul bufferului

Pentru a simplifica lucrurile, consideram ca dimensiunile blocului,


bufferului si componentei sunt egale. Folosind declaratia:

type
tip_componenta = Integer;
tip_fisier = file of tip_componenta;
var
var_fis: tip_fisier;
componenta: tip_componenta;

vom deschide acum Œn citire fisierul TEST.DAT si vom putea accesa deja prima
componenta a lui:

Begin
Assign(var_fis, 'TEST.DAT');
63
Reset(var_fis);
componenta := var_fis^
End.

Deschiderea provoaca si setarea contorului de pozitie pe prima Œnregistrare.


Pentru a trece la urmatoarea componenta (adica pentru a aduce Œn buffer
urmatoarea componenta) se foloseste o procedura speciala, numita get.

Semantica acesteia este:


- aducerea Œn buffer a urmatoarei componente din fisier
- marirea cu 1 a contorului de pozitie.

Prin urmare, daca dorim o prelucrare completa a fisierului (de exemplu


afisarea fiecarei componente pe ecran), atunci programul de mai sus s-ar
scrie astfel:

Begin
Assign(var_fis, 'TEST.DAT');
Reset(var_fis);
While not Eof(var_fis) do begin
componenta := var_fis^;
get(var_fis);
WriteLn(componenta)
end;
Close(var_fis)
end.

Am vazut Œnsa ca Œn Pascal exista procedura standard Read pentru citirea din
fisier. De fapt, semantica exacta a procedurii Read este data Œn tabelul
urmator:

-------------------------------------------------------
Procedura standard Este echivalenta cu
-------------------------------------------------------
Read(var_fis, componenta) componenta := var_fis^;
get(var_fis);
-------------------------------------------------------

Procedura get detecteaza sfƒrsitul de fisier: cƒnd nu mai exista o urmatoare


Œnregistrare de citit, ea nu va Œntoarce nimic, iar functia Eof(var_fis) va
Œntoarce True, deci citirea fisierului se termina.

Folosind procedura standard Read, programul de mai sus se scrie astfel:

Begin
Assign(var_fis, 'TEST.DAT');
Reset(var_fis);
While not Eof(var_fis) do begin
Read(var_fis, componenta);
WriteLn(componenta)
end;
Close(var_fis)
64
end.

9.4.4.3. Scrierea Œn fisier

Folosind aceleasi declaratii:

type
tip_componenta = Integer;
tip_fisier = file of tip_componenta;
var
var_fis: tip_fisier;
componenta: tip_componenta;
i: Integer;

vom deschide acum Œn scriere fisierul TEST.DAT si vom pune prima componenta a
lui Œn buffer:

Begin
Assign(var_fis, 'TEST.DAT');
Rewrite(var_fis);
var_fis^ := componenta
End.

Deschiderea Œn scriere provoaca stergerea fisierului (daca acesta exista) si


setarea contorului de pozitie pe 0. Pentru a scrie o componenta Œn fisier
este nevoie ca aceasta sa fie trecuta prima data Œn buffer (lucru realizat de
ultima instructiune din program), dupa care se foloseste o procedura speciala,
numita put. Semantica acesteia este:
- scrierea continutului bufferului Œn fisier
- marirea cu 1 a contorului de pozitie.
Prin urmare, daca dorim o prelucrare completa a fisierului (de exemplu
scrierea fiecarei componente Œn el), atunci programul de mai sus s--ar scrie
astfel:

Begin
Assign(var_fis, 'TEST.DAT');
Rewrite(var_fis);
For i := 1 to 10 do begin
componenta := i;
var_fis^ := componenta;
put(var_fis)
end;
Close(var_fis)
end.

Am vazut Œnsa ca Œn Pascal exista procedura standard Write pentru scrierea Œn


fisier. De fapt, semantica exacta a procedurii Write este data Œn tabelul
urmator:

-------------------------------------------------------
Procedura standard Este echivalenta cu
-------------------------------------------------------
65
Write(var_fis, componenta) var_fis^ := componenta;
put(var_fis);
-------------------------------------------------------

In scriere, sfƒrsitul de fisier trebuie marcat. Acest lucru este efectuat de


procedura standard Close.

Folosind procedura standard Write, programul de mai sus se scrie astfel:

Begin
Assign(var_fis, 'TEST.DAT');
Rewrite(var_fis);
For i := 1 to 10 do begin
componenta := i;
Write(var_fis, componenta)
end;
Close(var_fis)
end.

9.4.5. Fisiere fara tip (stream)

Fisierele fara tip pot fi considerate ca fiind fisiere cu tip in care o


inregistrare are un octet, si tipul ei este Byte

type
file = file of byte;

Deoarece este greoaie manipularea inregistrarilor de 1 byte, la fisierele


fara tip se foloseste un parametru nou, dimensiunea inregistrarii (bufferului),
numit in engleza RecSize. Acest parametru se precizeaza la deschiderea
fisierului. Daca nu este precizat, se considera implicit valoarea 128.
Procedurile standard Reset si Rewrite au forma generala:

procedure Reset(var var_fis [: File; Recsize: Word ] );


procedure Rewrite(var F: File [; Recsize: Word ] );

Pentru citirea si scrierea din fisierele fara tip se folosesc proceduri


specifice, BlockRead si BlockWrite.

Procedura BlockRead are sintaxa:

procedure BlockRead(var F: File; var Buf; Count: Word [; var Result: Word]);

unde:
F Variabila fisier fara tip
Buf variabila de orice tip (de obicei un tablou de byte), de
lungime cel putin egala cu RecSize
Count expresie de tip Word
Result variabila de tip Word

Semantica acestei proceduri este urmatoarea: se citesc cel mult Count


inregistrari (de lungime RecSize fiecare) din fisierul F in variabila Buf
66
(care joaca rolul bufferului). Numarul efectiv de inregistrari citite este
intors in parametrul optional de iesire Result. Daca acesta lipseste, se
declanseaza o eroare de intrare/iesire (detectabila prin folosirea lui
IOResult) daca numarul de inregistrari citite este mai mic decat Count.
Pentru ca operatia sa aiba sens, dimensiunea bufferului Buf trebuie sa
fie cel putin egala cu Count * RecSize octeti, dar nu mai mare decat 64K:

65535 ò> SizeOf(Buf) ò> Count * RecSize

Daca Count * RecSize > 65535 se declanseaza o eroare de intrare/iesire.

Parametrul de iesire Result va avea valoarea (daca este prezent in apel)


- egala cu Count daca din fisier s-a putut transfera numarul de octeti
precizat
- mai mic decat Count daca in timpul transferului s-a detectat sfarsitul
de fisier; Result va contine numarul de inregistrari complete citit

Dupa terminarea transferului, contorul de pozitie al acestuia se mareste


cu Result inregistrari.

Procedura BlockWrite are sintaxa:

procedure BlockWrite(var F: File; var Buf; Count: Word [; var Result: Word]);

unde:
F Variabila fisier fara tip
Buf variabila de orice tip (de obicei un tablou de byte), de
lungime cel putin egala cu RecSize
Count expresie de tip Word
Result variabila de tip Word

Semantica acestei proceduri este urmatoarea: se scriu cel mult Count


inregistrari (de lungime RecSize fiecare) din variabila Buf in fisierul F.
Numarul efectiv de inregistrari scrise este intors in parametrul optional de
iesire Result. Daca acesta lipseste, se declanseaza o eroare de intrare/iesire
(detectabila prin folosirea lui IOResult) daca numarul de inregistrari scrise
este mai mic decat Count.

Pentru ca operatia sa aiba sens, dimensiunea bufferului Buf trebuie sa


fie cel putin egala cu Count * RecSize octeti, dar nu mai mare decat 64K:

65535 ò > SizeOf(Buf) ò >= Count * RecSize

Daca Count * RecSize > 65535 se declanseaza o eroare de intrare/iesire.

Parametrul de iesire Result va avea valoarea (daca este prezent in apel)


- egala cu Count daca s-a putut transfera in fisier numarul de octeti
precizat
- mai mic decat Count daca suportul pe care se face transferul se umple;
Result va contine numarul de inregistrari complete scris

Dupa terminarea transferului, contorul de pozitie al acestuia se mareste


67
cu Result inregistrari.

9.4.6. Fisierele sistem

Sistemul de operare MS-DOS are doua fisiere standard: de intrare (tastatura),


de unde se preia informatia de prelucrat, si de iesire (ecranul monitorului),
unde se afiseaza rezultatul prelucrarilor.

In versiunea standard a limbajului Pascal cele doua fisiere se specifica Œn


antetul programului:

program Nume(input, output);

In Turbo Pascal, aceasta specificare este implicita si poate sa lipseasca.


Unit-ul System contine doua variabile fisier de tip text, cu numele
respective:

Var
Input: Text { Input standard file }
Output: Text { Output standard file }

In Unit-ul Printer, exista o variabila fisier de tip text care desemneaza


imprimanta sistem:

var Lst : text; { System printer }

Algoritmica si programare - sem 2.


Capitolele 3-6 - Structuri dinamice de date
3.1. Tipul pointer in Pascal
3.2. Liste simplu inlantuite
3.3. Stive
3.4. Cozi
3.5. Structuri liniare
3.6. Arbori
3.1. Tipul pointer in Pascal
vezi POINTER.LEC - directorul POINTER

4 Liste simplu inlantuite


vezi LISTE.LEC - directorul LISTE

5.1. Stive
vezi STIVE.LEC - directorul STIVE

5.2. Cozi
vezi COZI.LEC - directorul COZI

5.3. Structuri liniare


vezi STRLIN.LEC - directorul STRLIN

6. Arbori
vezi ARBORI.LEC - directorul ARBORI

68
1. Programarea modulara.

1.1. Termenul de modul


1.2. Structura unui modul
1.3. Exemple
1.4. Cum s-a ajuns la programarea modulara

1.1. Termenul de modul

Termenul de modul a fost folosit de-a lungul timpului in mai


multe acceptiuni
- subprogram (sursa sau obiect)
- biblioteca de subprograme
- colectie de subprograme destinate unui anumit domeniu
sau de uz general
- exemple
- biblioteca matematica a firmei IBM -
IBM SSP (Scientific Subroutine Package)
- biblioteca matematica a calculatorului Felix
MATHLAB
- biblioteca matematica a firmei DEC
- modul obiect - rezultat din compilarea unui modul sursa
- modul executabil (DLL)
- fisier ce contine cod executabil (functii) si este incarcat
cand o aplicatie apeleaza o functie din el
DLL - Dynamic Link Library, biblioteca cu legare dinamica

Ce intelegem prin modul


- unitate de program
- contine declaratii de
- subprograme
- tipuri de date
- variabile
- constante
- etc
- declaratiile sunt
- publice (exportate) - se pot folosi de clientii modulului
- private (interne) - se pot folosi numai de modul
- se compileaza separat
- faciliteaza reutilizarea codului
- respecta principiul ascunderii informatiei
- datele private ale modulului sunt accesibile numai prin
intermediul subprogramelor publice ale acestuia.

Ideile programarii modulare


- sa se grupeze subprogramele in functie de datele pe care
le folosesc
- sa se protejeze datele de accesul neautorizat prin 'ascunderea'
lor in zona privata a modulului
- o mai buna diviziune a muncii de programare
- module server: mai complexe, mai dificil de implementat
- module client: folosesc declaratiile publice din modulele
69
server

Exemplu de modul: unit-ul Turbo (Borland) Pascal

1.2. Structura unui modul

Un modul are urmatoarele parti


- antet
- parte de interfata (publica)
- parte de implementare (privata)
- cod de initializare

1.2.1. Antetul

- precizeaza numele modulului


- numele modulului apare in frazele Uses din modulele client

Unit Nume_Modul;

- Unit este un cuvant rezervat


- restrictii:
- Nume_Modul sa fie identic numelui fisierului sursa (Nume_Modul.PAS)
- Uses provoaca cautarea unui modul Nume_Modul.TPU

1.2.2. Partea de interfata

Partea de interfata contine declaratiile numelor (simbolurilor) publice.


Declaratiile se refera la
- modulele server (o fraza Uses)
- constante
- tipuri de date
- variabile
- subprograme (proceduri si functii)

Toti clientii unit-ului pot folosi numele care sunt declarate in aceasta
parte. In situatia in care exista conflict de nume, numele in conflict se
califica cu numele modulului:

Nume_Modul.Nume

Sintaxa partii de interfata este:

interface { declaratii de nume publice }:


uses { ce module se folosesc in partea de interfata }
const { declaratii de constante }
type { declaratii de tipuri de date }
var { declaratii de variabile }
procedure { antete de proceduri }
function { antete de functii }

1.2.3. Partea de implementare

70
Partea de implementare este obligatoriu sa contina corpul subprogramelor
(proceduri si functii) declarate in partea de interfata. In plus, aceasta
parte poate contine si obiecte (etichete, constante, tipuri de date,
variabile, subprograme) locale. Termenul de "local" inseamna ca aceste
obiecte sunt folosibile numai in partea de implementare, nefiind vizibile
in afara acesteia.

Sintaxa partii de implementare este:

implementation { declaratii de nume locale }:


uses { ce module se folosesc in implementare }
label { declaratii de etichete locale }
const { declaratii de constante locale }
type { declaratii de tipuri de date locale }
var { declaratii de variabile locale }
procedure { corpul procedurilor publice }
function { corpul functiilor publice }
procedure { corpul procedurilor locale }
function { corpul functiilor locale }

Pentru subprogramele publice nu este necesara specificarea parametrilor.


Declaratia completa a lor este obligatoriu sa fie facuta in partea de
interfata. Daca se specifica parametrii si in partea de implementare, antetul
subprogramului din partea de implementare trebuie sa fie identic (textual) cu
cel dat in partea de interfata.

Subprogramele publice se pot apela in partea de implementare fara a respecta


regula: declarare, apoi apelare. Acest lucru este posibil deoarece declararea
lor in partea de interfata actioneaza ca o declaratie "forward".

Subprogramele locale (private) trebuie sa respecte regulile uzuale: declarare,


apoi apelare.

1.2.4. Codul de initializare

Codul de initializare a modulului are ca ca functia principala initializarea


variabilelor (locale in special) din modul. Daca nu este nevoie de un
asemenea cod, se pune pur si simplu cuvantul rezervat "end."
Daca trebuie cod de initializare, acesta se va pune intre begin si end.

Din acest punct de vedere, codul de initializare a unui modul poate fi


asimilat programului principal Pascal (care are instructiunile intre
begin si end.).

1.3. Exemple:

Unit-urile standard
Crt
Dos
Printer

Client Module folosite


71
Complexe UComplex
Test UDataC
Crt
Calendar UDataC
CitireT UFile

UPolinom - rez.de ecuatii de gr I si II

1.4. Cum s-a ajuns la programarea modulara


1.4.1. Reutilizarea codului
1.4.2. Biblioteci de subprograme
1.4.3. Deficientele bibliotecilor de subprograme
1.4.4. Principiul programarii modulare
1.4.5. Deficientele programarii modulare

1.4.1. Reutilizarea codului

La putin timp dupa aparitia limbajelor de nivel inalt a aparut si conceptul


de subprogram, si odata cu acesta, principiul abstractizarii procedurale.
Conform acestui principiu, o secventa de instructiuni care efectueaza o
aceeasi prelucrare se pot grupa intr-o unitate de program independenta,
numita subprogram, care se caracterizeaza prin:
- un nume (cu care se apeleaza)
- o lista de parametri formali (de intrare si de iesire), care atribuie
subprogramului un caracter general (rezolva o clasa de probleme
definita de parametrii sai)

In orice loc in care este nevoie de executia instructiunilor cuprinse in


corpul subprogramului, nu este nevoie sa se rescrie acele instructiuni.
Este nevoie doar de o instructiune de apel a subprogramului, care contine
- numele acestuia
- lista de parametri actuali (sau efectivi), care trebuie sa corespunda
listei parametrilor formali ai subprogramului

Pentru a putea fi refolosit in programe diferite, codul sursa al unui


subprogram nu trebuie sa refere nici un nume din mediul sau de apel (adica
de acolo unde a fost scris prima data). Rezulta de aici ca in corpul
unui subprogram:
- nu se folosesc variabile globale
- nu se folosesc nici un alt fel de declaratii exterioare subprogramului
- toate numele folosite sunt fie nume de parametri, fie nume declarate
local in subprogram.

Aceasta este prima forma de reutilizare a codului sursa: codul sursa al


unui subprogram este scris o singura data si folosit in orice loc este
nevoie de el.

Limbajul Pascal standard rezolva problema reutilizarii codului prin


includerea textului sursa al unui subprogram (existent intr-un fisier
sursa) in codul sursa al programului care-l apela (folosind o directiva
de includere). Mediile Turbo si Borland Pascal au pastrat si aceasta forma
72
de reutilizare a codului prin directiva de compilare $I.

1.4.2. Biblioteci de subprograme

O maniera mai eleganta de grupare a subprogramelor este cunoscuta sub


numele de "biblioteci de subprograme". Acestea sunt fisiere sursa care
contin codul sursa al mai multor subprograme. In unele limbaje precum
FORTRAN, aceste fisiere se puteau compila separat, rezultand module
obiect care se puteau pe urma lega cu programele ce le apelau, formand
prin link-editare programe executabile.

Astfel de biblioteci sunt:


- biblioteca matematica a calculatorului Felix C-256 (MathLib)
- biblioteca stiintifica a firmei IBM (IBM SSP - Scientific Subroutine
Package)
- biblioteca matematica a firmei DEC

Utilizatorul unei astfel de biblioteci trebuia doar sa cunoasca modul de


apelare a subprogramului dorit.

1.4.3. Deficientele bibliotecilor de subprograme

Intre deficientele folosirii bibliotecilor de subprograme mentionam:


- ele nu contin decat subprograme
- nu se face nici un fel de verificare a corectitudinii apelului
(la compilare nu este nevoie de existenta bibliotecii; este sarcina
link-editorului sa rezolve apelul de subprogram, avand codul compilat
din modulul obiect, pe care-l include in programul executabil)
- toata responsabilitatea folosirii corecte a apelului cade in
sarcina programatorului client (cel care apeleaza subprogramul).

1.4.4. Principiul programarii modulare

Odata cu cresterea complexitatii programelor, a devenit din ce in ce


mai imperioasa problema protejarii datelor dintr-un program. In speta
este vorba de variabilele globale ale unui program, care (cel putin
teoretic) se pot modifica accidental si in partile programului care nu ar
trebui sa le modifice.

Tinand cont de observatia (reala) de mai sus, s-a enuntat principiul


programarii modulare, care afirma urmatoarele:
- grupeaza subprogramele in module, dupa principiul datelor comune
- foloseste principiul ascunderii informatiei pentru aceste date.

Asa s-a ajuns de la biblioteci de subprograme (in care subprogramele


erau grupate de obicei pe criteriul functionalitatii) la module, in care
subprogramele se grupeaza pe criteriul datelor pe care trebuie sa le acceseze
sau sa le modifice. Aceste date vor fi considerate locale modulului (deci
invizibile in afara lui), iar subprogramele care le prelucreaza vor fi
considerate publice.

Gruparea procedurilor in module pe criteriul datelor comune rezolva deci


73
- protectia datelor (care sunt invizibile in afara modulului)
- reutilizarea modulului (impreuna cu datele din el) oriunde este nevoie
de functionalitatea lui
- o mai fina structurare a programelor client (o alta paradigma de
programare).

1.4.5. Deficientele programarii modulare

Pana la un punct, intr-un modul putem concentra definitia unui nou tip
de date, impreuna cu operatiile pe acest tip. Am spus pana la un punct,
deoarece daca declaram un tip de date nou, declaratia trebuie sa fie
publica - deci in partea de interfata. O declaratie publica de tip are ca
deficienta faptul ca orice variabila de tipul respectiv este modificabila
(toate componentele sale sunt accesibile) - deci se incalca pe jumatate
principiul ascunderii informatiei).

Exemplul unit-ului UDataC este edificator: el declara un tip de data


nou, DataC (public) si implementeaza o serie de operatii pe el (si nu numai):
- Compara
- ZiCorecta
- ZiuaDinAn
- NumarZile
- Aduna
- ZiLucratoare
- DataCToStr8
- DataCToStr10
- DataCToStr
- AdunaZi
- ScadeZi

Toate aceste operatii sunt utile, insa unit-ul UDataC nu ascunde "structura"
DataC, deci nu se elimina posibilitatea modificarii accidentale (sau
neautorizate) a unei variabile de tip DataC in programele client ale
unit-ului.

O solutie ar fi renuntarea la o declaratie de tip nou si ascunderea


structurii acestuia in partea de implementare a modulului. Pentru unit-ul
DataC acest lucru ar insemna sa renuntam la toate declaratiile de tipuri din
partea de interfata, sa le includem in partea de implementare si sa modificam
aproape toate antetele subprogramelor (pentru ca nu mai avem tipul DataC).
Nu am castiga mai nimic in ceea ce priveste protectia datelor, insa am
castiga sigur in neclaritate.

Exista si situatii cand putem ascunde structura tipului in partea de


implementare. Unit-ul UListCCD.PAS reprezinta, impreuna cu programul sau
client, LSTCapCd.PAS un exemplu de modul corect gandit, in spiritul
paradigmei programarii modulare.

In unit-ul UListCCD au fost incluse subprograme care opereaza cu o structura


de date dinamica numita ListaCapCoada. Declaratia tipului, ca si a
unicei variabile de tipul respectiv sunt incluse in partea de implementare a
modulului, deci nu sunt vizibile in afara acestuia:
74
type
ListaCapCoada = Record
Cap, { capul listei: folosit la traversare }
Coada: PNod { coada listei: acolo se fac inserari si stergeri }
End;
var
L: ListaCapCoada;

Deoarece lucreaza toate cu variabila (locala) L, subprogramele publice


- Creeaza: Crearea unei liste vide
- Insereaza: Adaugarea nodului N in coada listei
- Sterge: Stergerea nodului din coada listei
- Cauta(I): Cauta in lista nodul cu informatia utila I
- Traverseaza: Traverseaza toate nodurile listei si afiseaza
informatia utila din ele
- Elibereaza: Dealoca toate elementele listei, transformand-o
intr-o lista vida
nu au nevoie de L ca parametru. Ca fapt divers, aici am incalcat principiul
expus mai sus, al autonomiei subprogramelor. De data aceasta, deoarece ele
sunt grupate in acest modul pe principiul datelor comune care le folosesc
(in cazul nostru variabila L), nu este nevoie ca acest principiu sa fie
respectat: daca datele si implementarea subprogramelor ce actioneaza pe ele
sunt ascunse exteriorului, nu este nici un pericol in a altera aceste date
din afara.

In acest exemplu am ilustrat principiul programarii modulare. Exemplul


functioneaza corect atata timp cat in programele client avem nevoie de o
singura lista. Daca insa am avea nevoie de doua liste, modulul nostru nu mai
e bun. Aceasta este de fapt principala deficienta a programarii modulare:
- datele locale in modul permit lucrul cu o singura instanta a modulului
(considerat ca tip de date).

A fost nevoie de urmatoarea paradigma de programare


- abstractizarea datelor sau
- programarea bazata pe obiecte
pentru a rezolva aceasta problema.

2. Abstractizarea datelor
2.1. Istoric
2.2. Ce este abstractizarea datelor
2.2.1. Tipuri de date definite de utilizator
2.2.2. Nivele de abstractizare a tipurilor de date
2.3. Tipuri abstracte de date
2.3.1. Specificarea tipurilor abstracte de date
2.3.2. Proiectarea operatiilor unui TAD
2.3.3. Implementarea tipurilor abstracte de date
2.3.4. Avantajele TAD
2.4. Exemple

2.1. Istoric

75
Date Operatii Imbunatatire
--------------------------------------------------------------
adrese de memorie instructiuni masina performanta

celule de memorie instructiuni in lizibilitate


cu nume limbaj de asamblare mai usor de
(mnemonice) inteles

tipuri de date instructiuni standard independenta de


standard (secventa, decizie, masina
repetitie)

tipuri de date subprograme limbaje virtuale


utilizator
\ /
\ /
module ascunderea informatiei

tipuri abstracte de date instante multiple


(abstractizarea datelor)
(programare bazata pe obiecte)

clase polimorfism dinamic


(programare orientata pe mostenire
obiecte) legare dinamica

2.2. Ce este abstractizarea datelor


2.2.1. Tipuri de date definite de utilizator
2.2.2. Nivele de abstractizare a tipurilor de date

Abstractizarea inseamna
- neglijarea detaliilor nesemnificative
- concentrarea asupra esentei.

2.2.1. TDU - Tipuri de date definit de utilizator

Am vazut ca limbajul Pascal ne permite, prin intermediul declaratiei


'type', sa definim tipuri de date proprii, altele decat cele standard.
Stim ca un tip de date T este caracterizat de
- domeniul valorilor (multimea valorilor pe care le poate lua o
variabila de tipul T); domeniul este precizat prin componentele
noului tip definit
- enumerare
- subdomeniu
- multime
- tablou
- record
- multimea operatiilor care se pot efectua asupra variabilelor de
tipul T.

Din pacate, daca Pascal acorda atentie speciala numai definirii


domeniului unui nou tip T, lasand nerezolvata problema definirii
76
operatiilor pe noul tip T. Cu exceptia tipurilor multime, toate
celelalte tipuri definite de utilizator au ca operatii predefinite:
- atribuirea (componenta cu componenta)
- testul de egalitate (componenta cu componenta).

Exemple:
Type T = ... tip definit de utilizator
var a1,a2: T; a1 si a2 sunt variabile de tipul T

- atribuirea (componenta cu componenta)


- T de tip tablou: a1 := a2 inseamna
a1[i] := a2[i] pentru i luand valori de la cel mai mic indice
pana la cel mai mare indice
- T de tip record: a1 := a2 inseamna
a1.c1 := a2.c1; c1, c2, ..., cn sunt campurile din
a1.c2 := a2.c2; declaratia lui T
...
a1.cn := a2.cn;
- testul de egalitate (componenta cu componenta)
- T de tip tablou: a1 = a2 inseamna
a1[i] = a2[i] pentru i luand valori de la cel mai mic indice
pana la cel mai mare indice
- T de tip record: a1 = a2 inseamna
a1.c1 = a2.c1; c1, c2, ..., cn sunt campurile din
a1.c2 = a2.c2; declaratia lui T
...
a1.cn = a2.cn;

2.2.2. Nivele de abstractizare a tipurilor de date

Notiunea de tip de date este discutata aici din punctul de vedere


al programarii. In fond, exista doua entitati care coopereaza: utilizatorul
uman, ce poseda inteligenta si foloseste un limbaj specific speciei umane
si masina fizica (calculatorul), care executa un program format din
instructiuni pe care el le Œntelege. Intermediarul Œntre aceste entitati
este o masina virtuala, necesara unei comunicari cƒt mai lesnicioase Œntre
ele, cu alte cuvinte un limbaj de programare.

Se pot distinge trei nivele de abstractizare ale unui TD simplu sau


structurat: abstract, virtual si fizic. Ordinea de enumerare a acestor
nivele pleaca de la utilizatorul uman si are ca destinatie masina fizica
pe care se lucreaza, calculatorul.

Plecƒnd de la rationamentul uman, primul nivel este cel abstract,


concretizat Œn ceea ce numim tip abstract de date TAD. Abstractizarea are
rolul de a pune accent pe proprietatile esentiale ale TD (vizƒnd atƒt
domeniul, cƒt si operatiile), neglijƒnd detaliile de reprezentare si
algoritmii necesari pentru realizarea concreta a operatiilor. Altfel spus,
TAD pune accent pe semantica TD, Œn mod esential pe comportamentul
acestuia, precizƒnd CE trebuie sa faca fiecare operatie si NU CUM
actioneaza aceasta.

77
Abstractizarea poate fi vazuta si ca o modalitate de ordonare a
gƒndirii umane. Conceperea unui TAD Œnseamna:
- identificarea TAD (Œn termenii rationamentului uman),
- stabilirea unui nume (care sa sintetizeze proprietatile sale),
- precizarea domeniului si a operatiilor.

Definirea domeniului TAD Œnseamna enumerarea proprietatilor pe care


trebuie sa le Œndeplineasca valorile TAD, iar definirea fiecarei operatii
o : E -> F Œnseamna:
- stabilirea conditiilor de utilizare corecta a ei,
- stabilirea domeniului E (parametrii de intrare) si a codomeniului F
(parametrii de iesire), precum si
- precizarea rezultatului ei.

Nivelul abstract este cel mai apropiat de definitia datei, vazuta ca


element purtator de informatie. La acest nivel, detaliile de reprezentare a
valorilor posibile ale ei (domeniul TD) nu sunt precizate, esentiale fiind
operatiile, adica comportamentul, functionalitatea datei, posibilitatile de
valorificare a informatiei continuta Œn data.

Definirea TAD poarta numele de specificare.

Nivelul intermediar de abstractizare este cel virtual, Œn care


se executa urmatoarele actiuni:
- se alege un limbaj de programare LP (masina virtuala);
- se defineste reprezentarea TAD Œn termenii LP;
Niklaus Wirth: Algorithms + Data Structures = Programs
Prentice-Hall, 1976
- se proiecteaza algoritmii necesari pentru operatii cu ajutorul
reprezentarii alese;
- se scrie codul sursa pentru operatiile TAD sub forma de subprograme
Œn LP.

Masina virtuala exista numai ca o combinatie Œntre compilatorul


(sau interpretorul) LP, sistemul de operare si arhitectura hard a
calculatorului gazda. LP poseda TD proprii si/sau mecanisme de tipizare,
precum si un set de instructiuni, cu ajutorul carora se pot exprima
reprezentarea si operatiile TD concepute la nivelul abstract.
La nivelul virtual putem vorbi de o concretizare a TAD, obtinƒndu-se un
tip virtual de date TVD. Trecerea de la TAD la TVD este numita
implementare sau codificare.

Ultimul nivel de abstractizare este cel fizic, Œn care componentele


TVD sunt exprimate folosind resursele fizice ale calculatorului (memorie
si instructiuni cablate), ca rezultat al procesului de traducere a TVD.
Masina fizica poate fi considerata si ea ca un TD, Œn care domeniul ar
fi memoria, iar operatiile ar fi instructiunile cablate. Din punct de vedere
fizic, memoria poate fi vazuta ca un vector de octeti (octetul fiind
unitatea adresabila), iar instructiunile cablate sunt singurele operatii
pe care masina stie sa le execute, ele formƒnd limbajul masina. Acest model
de TD Œl vom numi tip fizic de date TFD. Trecerea de la TVD la TFD se
realizeaza prin traducerea programului, cu ajutorul unui translator
78
(compilator, asamblor sau interpretor) si eventual a unui editor de
legaturi.

Rezumƒnd, cele trei nivele de abstractizare ale unui TD si


transformarile de trecere sunt prezentate Œn figura 1.

OM MASINA
ÄÄÄÄÄÄÄÄÄÄÄ> TAD ÄÄÄÄÄÄÄÄÄÄÄÄ> TVD ÄÄÄÄÄÄÄÄÄÄÄÄ> TFD
specificare implementare traducere

Figura 1. Nivele de abstractizare a unui tip de date

Fiecarui nivel de abstractizare Œi corespunde un limbaj Œn care


trebuiesc precizate componentele TD, adica domeniul si operatiile,
conform tabelului 1.

ÚÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ Nivel ³ Limbaj ³
ÃÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´
³ abstract ³ de specificare ³
³ virtual ³ de programare ³
³ fizic ³ masina ³
ÀÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ

Tabelul 1. Nivele de abstractizare si limbaje

Limbajul de specificare a TAD poate fi formal sau informal. Limbajul


formal este riguros, exact, dar greu de Œnteles de nespecialisti (facƒnd
apel la notatii matematice), pe cƒnd limbajul informal este mult mai
apropiat de exprimarea umana, chiar daca el sufera Œn rigoare si exactitate.
Specificarea formala a TAD utilizeaza Œn esenta mecanisme algebrice
(algebre universale, algebre multisortate, ä-algebre), reprezentƒnd astazi
o directie importanta de cercetare Œn informatica teoretica.
Limbajul de programare, vazut ca mijloc de exprimare la nivelul
virtual de abstractizare, poate fi mai apropiat de masina fizica (limbaj de
asamblare) sau de utilizatorul uman (limbaj de nivel Œnalt). In ambele
ipostaze, folosirea lui este conditionata de existenta unui translator
capabil sa traduca TVD Œntr-un program executabil pe masina fizica.

Traducerea TVD Œnseamna exprimarea lui (ca reprezentare si operatii)


Œn limbaj masina. Ca programe, TVD si TFD sunt semantic echivalente (adica
produc acelasi rezultat), deosebirea dintre ele constƒnd doar Œn mijloacele
de exprimare, adica Œn limbajul folosit.

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
ÄÄÄ¿
³ Nivel ³ Nivel virtual ³ Nivel fizic ³
³ abstract ³ (Turbo Pascal) ³ (procesor 80x86) ³
ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
ÄÄÄ´
³ Adresa ³ Record ³ Implementarea ³
³ ³ Localitate:string[20]; ³ TD Record pe ³
79
³ ³ Strada:String[10]; ³ IBM-PC ³
³ ³ Numar:Integer ³ 34=21+11+2 oct. ³
³ ³ End; ³ ³
³ ³ ³ ³
³ Temperaturi ³ Array[1..12] of Real; ³ Implementarea ³
³ medii ³ ³ TD Array pe ³
³ lunare ³ ³ IBM-PC ³
³ ³ ³ 12*6=72 octeti ³
³ ³ ³ ³
³ Vƒrsta ³ Integer ³ Œntreg 80x86 ³
³ ³ ³ 2 octeti ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
ÄÄÄÙ

Figura 2. Tipuri de date si nivele de abstractizare

2.3. Tipuri abstracte de date (TAD)

2.3.1. Specificarea tipurilor abstracte de date

Asa cum am aratat mai inainte, TAD se precizeaza cu ajutorul unui


asa-numit limbaj de specificare, formal sau informal. In cele ce urmeaza
vom folosi un limbaj de specificare informal, cu ajutorul caruia vom defini
scheme de specificare pentru TD simple si pentru TD structurate. In esenta,
o schema de specificare a TAD contine doua parti: specificarea domeniului
TD si specificarea operatiilor. In cazul TD structurate, specificarea
domeniului TD Œnseamna:
- specificarea elementelor (descrierea elementelor componente),
- specificarea structurii (adica a relatiei sau legaturii dintre elemente)
- specificarea propriu-zisa a domeniului (descrierea multimii valorilor
posibile).

Specificarea unei operatii se realizeaza Œn maniera procedurala,


operatiei atasƒndu-i-se un nume, o lista de parametri, ea furnizƒnd un
rezultat. De asemenea, pentru fiecare operatie trebuiesc precizate
preconditia si postconditia. Preconditia este o asertiune (afirmatie)
care precizeaza Œn ce conditii operatia are sens si ea trebuie verificata
inainte, pentru ca operatia sa se execute corect, iar postconditia exprima
legaturile dintre rezultatele obtinute Œn urma executarii operatiei si
operanzi Œn ipoteza ca preconditia este verificata.
Este important de precizat ca postconditia specifica numai CE
rezultate se obtin, fara a intra Œn detalii privind CUM se obtin acestea.
In figura 3 prezentam schemele de specificare pentru TAD simplu, TAD
structurat si pentru o operatie.

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ a) Schema de specificare a unui TAD simplu ³
³ ³
³ Domeniu: descrierea multimii valorilor posibile ³
³ ³
³ Operatii: specificarea fiecarei operatii, vezi c) ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
80
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
ÄÄÄ¿
³ b) Schema de specificare a unui TAD structurat ³
³ ³
³ Elemente: descrierea elementelor componente ³
³ ³
³ Structura: descrierea relatiilor sau legaturilor Œntre ³
³ elemente ³
³ ³
³ Domeniu: descrierea multimii valorilor posibile ³
³ ³
³ Operatii: specificarea fiecarei operatii, vezi c) ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
ÄÄÄÙ

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
ÄÄÄÄÄ¿
³ c) Schema de specificare a unei operatii ³
³ ³
³ Nume: numele operatiei (identificator, cƒt mai sugestiv) ³
³ ³
³ Lista de parametri: specificarea parametrilor, ca ³
³ perechi (identificator, TD) ³
³ ³
³ Rezultat: (cƒnd operatia are sensul unei functii Pascal) ³
³ TD al rezultatului Œntors ³
³ ³
³ Pre: preconditia (eventuale conditii impuse parametrilor ³
³ de intrare) ³
³ ³
³ Post: postconditia (precizarea legaturilor dintre ³
³ rezultatele obtinute si parametrii ³
³ de intrare) ³
³ ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
ÄÄÄÄÄÙ
Figura 3. Scheme de specificare

Pentru exemplificare, vom specifica cƒteva TAD simple: Zile,


Natural si Sutime si TAD structurat Monede.

Specificarea 1. Tipul abstract simplu Zile

Domeniu: multimea valorilor posibile este:


SAP={luni,marti,miercuri,joi,vineri,sƒmbata,duminica}.
Operatii:
Ieri(z:Zile):Zile {Stabileste ziua precedenta a zilei z}
Pre: zîSAP.
Post: Ieri(z) este ziua precedenta a zilei z.
Mƒine(z:Zile):Zile {Stabileste ziua ce urmeaza zilei z}
Pre: zîSAP.
81
Post: Mƒine(z) este ziua urmatoare a zilei z.
Libera(z:Zile):Boolean {z este zi nelucratoare ?}
Pre: zîSAP.
Post: Daca z este zi nelucratoare.
atunci Libera(z) este True
altfel Libera(z) este False.
Scrie(z:Zile) {scrie valoarea lui z la dispozitivul standard
de iesire}
Pre: zSAP.
Post: Se va afisa ziua z ca sir de caractere.
Citeste(var z:Zile) {citeste valoarea lui z de la dispozitivul
standard de intrare}
Pre: TRUE.
Post: rezultatul z se va citi ca sir de caractere ce reprezinta
o valoare din domeniul SAP.

Specificarea 2. Tipul abstract simplu Natural

Domeniu: multimea valorilor posibile este multimea N a numerelor


naturale.
Operatii:
MaiMare(n1,n2:Natural):Boolean {n1 > n2 ?}
Pre: n1,n2îN.
Post: Daca n1 > n2
atunci MaiMare(n1,n2) va fi True
altfel MaiMare(n1,n2) va fi False.
Egal(n1,n2:Natural):Boolean {n1 = n2 ?}
Pre: n1,n2îN.
Post: Daca n1 = n2
atunci Egal(n1,n2) va fi True
altfel Egal(n1,n2) va fi False.
Aduna(n1,n2:Natural; var n:Natural) {calculeaza suma lui n1 si n2}
Pre: n1,n2îN.
Post: n = n1 + n2.
Scade(n1,n2:Natural; var n:Natural) {scade n2 din n1}
Pre: n1,n2îN si MaiMare(n1,n2).
Post: n = n1 - n2.
Ori(n1,n2:Natural; var n:Natural) {calculeaza produsul lui n1 si n2}
Pre: n1,n2îN.
Post: n = n1 * n2.
Cƒt(n1,n2:Natural; var n:Natural)
{calculeaza cƒtul Œmpartirii lui n1 la n2}
Pre: n1,n2îN si n2>0.
Post: n = partea Œntreaga a cƒtului n1 / n2.
Rest(n1,n2:Natural; var n:Natural)
{calculeaza restul Œmpartirii lui n1 la n2}
Pre: n1,n2îN si n2>0.
Post: n = n1 - Cƒt(n1,n2) * n2.

Specificarea 3. Tipul abstract simplu Sutime

Domeniu: multimea valorilor posibile este multimea numerelor naturale


82
cuprinse Œntre 0 si 99, notata prin DS.
Operatii:
MaiMare(s1,s2:Sutime):Boolean {s1 > s2 ?}
Pre: s1,s2îDS.
Post: Daca s1 > s2
atunci MaiMare(s1,s2) va fi True
altfel MaiMare(s1,s2) va fi False.
Egal(s1,s2:Sutime):Boolean {s1 = s2 ?}
Pre: s1,s2îDS.
Post: Daca s1 = s2
atunci Egal(s1,s2) va fi True
altfel Egal(s1,s2) va fi False.
Aduna(s1,s2:Sutime; var n:Natural; var s:Sutime)
{calculeaza suma lui s1 si s2}
Pre: s1,s2îDS.
Post: s va contine partea fractionara a sumei dintre s1 si s2
iar n va contine partea Œntreaga a acesteia (transportul).
Scade(s1,s2:Sutime; var s:Sutime) {scade s2 din s1}
Pre: s1,s2îDS si MaiMare(s1,s2) sau Egal(s1,s2).
Post: s = s1 - s2.

Specificarea 4. Tipul abstract structurat Monede

Elemente: Elementele componente sunt partea Œntreaga, respectiv


partea fractionara a unei sume monetare.
Structura: Se noteaza cu Lei partea Œntreaga a sumei, respectiv cu
Bani partea fractionara a acesteia.
Exista o corespondenta unu-la-unu Œntre fiecare
identificator si elementul component corespunzator.
Domeniu: Valoarea partii Œntregi a sumei, Lei, este un numar natural,
iar cea a partii fractionare, Bani, este Œn intervalul
natural 0..99. Domeniul tipului Monede, DM este vazut ca
produsul cartezian al acestor doua domenii ale elementelor
componente.
Operatii:

BaniInMonede(n:Sutime; var r:Monede)


{transforma suma n exprimata Œn Bani Œn Lei si Bani}
Pre: nîDS.
Post: r este valoarea lui n exprimata Œn Lei si Bani.
LeiInMonede(n:Natural; var r:Monede)
{transforma suma n exprimata Œn Lei Œn Lei si Bani}
Pre: nîN.
Post: r este valoarea lui n exprimata Œn Lei si Bani.
LeiDinMonede(s:Monede):Natural{partea Œntreaga a sumei}
Pre: sîDM.
Post: LeiDinMonede(s) este valoarea cƒmpului Lei al lui s.
BaniDinMonede(s:Monede):Sutime {partea fractionara a sumei}
Pre: sîDM.
Post: BaniDinMonede(s) este valoarea cƒmpului Bani al lui s.
MonedeInReal(s:Monede):Real {valoarea sumei s ca numar real}
Pre: sîDM.
83
Post: numarul real MonedeInReal(s) va avea partea intreaga
egala cu Lei si partea fractionara egala cu Bani / 100.
RealInMonede(r:Real; var s:Monede)
{valoarea numarului real r convertita la tipul Monede}
Pre: rîR.
Post: sîDM are componentele:
cƒmpul Lei este partea Œntreaga a numarului real r,
cƒmpul Bani are ca cifre primele doua zecimale din partea
fractionara a lui r.
MaiMare(s1,s2:Monede):Boolean {s1 > s2 ?}
Pre: s1,s2îDM.
Post: Daca s1 > s2
atunci MaiMare (s1,s2) va fi True
altfel MaiMare (s1,s2) va fi False.
Egal(s1,s2:Monede):Boolean {s1 = s2 ?}
Pre: s1,s2îDM.
Post: Daca s1 = s2
atunci Egal (s1,s2) va fi True
altfel Egal (s1,s2) va fi False.
Aduna(s1,s2:Monede; var r:Monede) {aduna s1 si s2}
Pre: s1,s2îDM.
Post: r = s1 + s2.
Scade(s1,s2:Monede; var r:Monede) {scade s2 din s1}
Pre: s1,s2îDM si MaiMare(s1,s2).
Post: r = s1 - s2.
Scrie(s:Monede) {scrie valoarea lui s la dispozitivul standard
de iesire}
Pre: sîDM.
Post: se va afi_a s Œn formatul '999999,99 lei'.
Dobƒnda(s:Monede; d:Real; var r:Monede)
{calculeaza dobƒnda r aferenta sumei s si ratei r}
Pre: s DM si dR.
Post: r = dobƒnda la suma s calculata cu rata dobƒnzii d.

2.3.2. Proiectarea operatiilor unui TAD

Cea mai importanta faza Œn specificarea unui TAD este proiectarea


multimii operatiilor. Multimea operatiilor Œn ansamblul ei trebuie sa
Œndeplineasca urmatoarele conditii:
(1) sa fie completa;
(2) sa fie minimala.

Dupa criteriul functionalitatii lor, clasele de operatii ale unui


TAD sunt urmatoarele (clasificare data de Riley, 1987):
(1) constructori;
(2) operatii de intrare/iesire;
(3) operatii de conversie;
(4) operatii de test;
(5) operatii de copiere;
(6) operatii de selectare.

84
Vom discuta Œn cele ce urmeaza caracteristicile fiecarei clase de
operatii. Exemplele furnizate apartin specificarii 4.

Constructorii sunt operatii prin care se atribuie valori unor


realizari ale TAD, adica creeaza sau modifica instante ale TAD. In cazul
Œn care operatia se realizeaza prin utilizarea unor date de alt tip,
constructorii se numesc initiali. Completitudinea setului de operatii
se poate formula astfel:

Constructorii unui TAD trebuie sa produca orice element din domeniul


acestuia.

Exemple de constructori: BaniInMonede, LeiInMonede (initiali),


Aduna, Scade, Dobƒnda.

Operatiile de intrare-iesire (numite prescurtat operatii de I/O)


permit citirea sau scrierea valorii unei realizari a TAD de pe/pe suport
extern. Acest lucru este necesar deoarece, cu putine exceptii, TAD sunt
structurate si majoritatea LP nu dispun de operatii de I/O cu care sa
se poata implementa TVD asociate TAD. Operatiile de intrare pot fi
considerate si constructori. Scrie este o operatie de I/O (de iesire mai
exact, deci nu este constructor).

Operatiile de conversie realizeaza transformarea valorii instantelor


TD Œn valori de instante ale altor TD. Operatiile MonedeInReal si
RealInMonede fac parte din aceasta clasa. RealInMonede se poate
considera si constructor pentru Monede.

Operatiile de test sunt necesare Œn primul rƒnd pentru testarea


preconditiilor, putƒnd fi folosite chiar Œn specificarea TAD (vezi
specificarile 2-4). MaiMare si Egal sunt astfel de operatii.

Operatiile de copiere sunt necesare cƒnd nu se poate folosi


instructiunea de atribuire pentru a copia valoarea unei instante de TAD
Œntr-o alta instanta. Ele sunt considerate si constructori, numiti uneori
chiar constructori de copiere (in C++). Pentru TAD Monede, o operatie

Copy(m1:Monede; var m2:Monede) { m2 := m1 }

ar fi o operatie de copiere care ar copia fiecare camp al instantei m1


in campul corespunzator al instantei m2.

Operatiile de selectare permit (Œn cazul TAD structurate) operarea


cu elementele componente ale unei instante a TAD. LeiDinMonede si
BaniDinMonede sunt astfel de operatii. Cu alte cuvinte, operatiile de
selectare permit aflarea valorii unei componente a instantei (pentru TAD
structurate), fara a permite modificarea ei.

O alta clasificare a operatiilor, data de Meyer (1988) distinge 3


clase:
- constructori (creeaza noi instante ale TAD),
- accesori (permit accesul la valorile anumitor componente, fara a le
85
modifica) si
- modificatori (modifica anumite componente ale instantei).

Legatura dintre aceste doua clasificari este sintetizata Œn tabelul 2.

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
ÄÄÄÄÄÄÄ¿
³ Meyer ³ Riley ³
ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
ÄÄÄÄÄÄÄ´
³ constructori ³ constructori initiali ³
³ ³ operatii de conversie spre TAD ³
³ ³ operatii de intrare ³
³ modificatori ³ constructori (altii decƒt cei ³
³ ³ initiali) ³
³ accesori ³ operatii de test ³
³ ³ operatii de conversie de la TAD ³
³ ³ operatii de selectare ³
³ ³ operatii de iesire ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
ÄÄÄÄÄÄÄÙ

Tabelul 2. Tipuri de operatii pentru TAD

2.3.3. Implementarea tipurilor abstracte de date

Asa cum am precizat anterior, implementarea unui TAD presupune


parcurgerea urmatoarelor etape:
- alegerea unui limbaj de programare LP,
- stabilirea reprezentarii domeniului TAD Œn termenii acestui limbaj,
- proiectarea algoritmilor necesari pentru implementarea operatiilor
(tinƒnd cont de reprezentare) si,
- scrierea de subprograme corespunzatoare fiecarei operatii, in LP.

In mod normal, faza de implementare mai contine o etapa de testare,


vazuta atƒt la nivelul fiecarei operatii, cƒt si al TAD Œn ansamblu
(testarea procedurala si testarea de modul). Rezultatul implementarii,
tipul virtual de date, TVD, va fi numit Œn cele ce urmeaza modul sau
pachet, Œn ideea ca el este o unitate sintactica de sine statatoare,
compilabila separat. In sectiunea 2.4. vom prezenta implementarile TAD
prezentate Œn specificarile 1 - 4.

2.3.4. Avantajele TAD

Abordarea la nivel abstract a TD ofera cƒteva avantaje importante


pentru activitatea de programare Œn general:
(1) specificatii precise;
(2) modularitate;
(3) Œncapsularea si ascunderea informatiei;
(4) simplitate Œn proiectare si utilizare;
(5) integritatea reprezentarii;
(6) independenta de implementare si
86
(7) modificabilitate.

Deoarece specificarea TAD se face folosind un limbaj precis, iar


semantica operatiilor nu face apel la reprezentarea domeniului,
definitiile obtinute sunt clare, neambigue si independente de contextul de
utilizare.
Despre modularitate: putem discuta de doua tipuri de module: TAD si
TVD. Este extrem de important ca definirea unui TAD (care utilizeaza
operatii sau elemente ale altor tipuri de date) sa utilizeze numai
definitiile TAD respective, fara vreo referire la TVD ce le implementeaza.
In exemplele prezentate de noi Œn 2.3.1, TAD Monede este construit cu
ajutorul TAD Natural si Sutime, Œnsa fara nici o referire la implementarea
acestora. In mod natural, implementarea unui astfel de TAD va trebui sa
apeleze operatiile TAD ale componententelor sale, prevazute Œn specificare.
Incapsularea reprezentarii si operatiilor (punerea lor Œmpreuna)
induce ordine si eficienta. Pentru o anumita structura de date este
natural sa se conceapa nu numai o operatie, ci toate operatiile de
manipulare a respectivei structuri. Acest lucru, facut independent de
utilizarea respectivei structuri, permite o testare usoara a operatiilor
proiectate si asigura flexibilitate, o noua operatie putƒnd fi introdusa
oricƒnd, daca e nevoie. Acordarea unei atentii egale reprezentarii si
operatiilor conduce la realizarea de module mai stabile, deschizƒnd
poarta reutilizarii lor Œn contexte adecvate.
Ascunderea informatiei este una dintre cerintele majore ale
modularizarii. Modulul client nu trebuie sa cunoasca decƒt modul de apelare
a serviciilor din modulul furnizor (server).
Abstractizarea datelor ofera simplitate. La prima vedere, termenul
abstract ar avea Œntelesul de teoretic, abscons, greu de Œnteles. In cazul
nostru, abstractizarea are o semnificatie opusa. Esenta ei consta tocmai
Œn surprinderea aspectelor si comportamentului esential al datelor
(ca structura si operatii) prin neglijarea detaliilor de reprezentare
si de implementare. Aceasta concentrare pe aspectele esentiale ofera
simplitate Œn proiectarea TAD de catre specialist. Pe de alta parte,
cerinta de minimalitate a setului de operatii ale TAD ofera simplitate Œn
utilizarea acestora, adica o Œntelegere usoara de catre utilizator a
comportamentului TAD. Din aceste afirmatii nu trebuie sa se Œnteleaga ca
aspectele de implementare sunt neglijate. La nivelul abstract acestea nu
sunt luate Œn considerare nu pentru ca ele nu sunt importante, ci pentru
ca aceste detalii vor fi discutate la vremea lor. Principiul rafinarii
succesive (stepwise refinement) aplicat TAD Œnseamna trei etape:
- abstractizare,
- definirea reprezentarii si
- implementare.

Un TVD poate fi vazut ca o cutie neagra, Œn care operatiile sunt


butoane ce realizeaza anumite functii (operatii) ale cutiei. Proiectantul
(implementatorul) cutiei cunoaste interiorul ei, dar utilizatorul
(programatorul client) nu o poate desface pentru a vedea ce e Œnauntru,
ci o poate folosi prin apasarea butoanelor ei. Integritatea reprezentarii
poate fi discutata pe doua nivele: abstract si virtual. La nivelul
abstract, integritatea TAD este asigurata de maniera Œn care sunt
concepute operatiile sale. La nivelul virtual, daca toate operatiile sunt
87
implementate corect, atunci nici una dintre ele nu poate afecta
integritatea reprezentarii. In plus, programatorul client nu va avea acces
la reprezentarea informatiei din interiorul modulului server (principiul
ascunderii informatiei). Notiunea de integritate (consistenta) a
reprezentarii se poate defini cu ajutorul invariantului acesteia, care
este un predicat: I : D Ä> {false,true} .

Spunem ca reprezentarea este consistenta daca I are valoarea true


pentru orice element din domeniul TAD. De exemplu, pentru TAD Monede,
invariantul reprezentarii este:

I(r:Monede) =
0 <= r.Lei <= MaxLongInt si
0 <= r.Bani <= 99 .

La nivelul abstract detaliile de reprezentare si implementare a


operatiilor sunt neglijate intentionat. De asemenea, specificarea se face
folosind un limbaj specializat si nu unul de programare. Prima decizie
Œn faza de implementare este alegerea LP. Acest lucru se bazeaza pe
cunoasterea TD si a instructiunilor cu care acest limbaj poate opera si
are o importanta decisiva Œn succesul implementarii. Urmatoarea etapa
importanta este stabilirea reprezentarii TD, concretizare a celei abstracte
prin utilizarea TD ale LP ales. Pentru acelasi TAD se pot alege
reprezentari structural diferite, ceea ce conduce la algoritmi diferiti.
Mai mult, pentru aceeasi reprezentare se pot concepe mai multi algoritmi
(deci implementari diferite) pentru o aceeasi operatie. Daca specificarea
operatiei o este corecta, modificarea codului de implementare al ei are
un efect local, aceasta neimplicƒnd modificari Œn alte module sau programe
ce apeleaza o. Multi autori recomanda o abordare gradata a ultimelor doua
faze de implementare a unui TAD, Œn maniera urmatoare, pentru o
reprezentare fixata:
(1) se stabilesc algoritmii cei mai simpli pentru fiecare operatie;
(2) se implementeaza operatiile;
(3) se testeaza TVD Œn ansamblu (teste de anduranta), evidentiindu-se
operatiile care sunt mari consumatoare de resurse (timp sau memorie);
(4) se reproiecteaza algoritmii Œn cauza si se reia ciclul de la faza (2).

Procesul se Œncheie cƒnd Œn faza (3) rezultatele obtinute satisfac


cerintele impuse. In cazul cƒnd nu se obtin rezultate satisfacatoare, se
Œncearca o alta reprezentare.

2.4. Exemple

In cele ce urmeaza vom ilustra implementarea Œn Turbo Pascal a TAD


specificate Œn paragraful 3.2.
Datorita restrictiilor limbajului, numele modulului va fi compus din
litera U (de la Unit) si numele TAD.

2.4.1. TVD Zile

TAD Zile poate fi implementat natural Œn Turbo Pascal, folosind TVD


enumerare. Remarcam parcurgerea circulara a domeniului SAP pentru
88
operatiile Ieri si Mƒine si implementarea acestora ca subprograme de tip
functie Œn Turbo Pascal.

Vezi UZile.PAS

2.4.2. TVD Natural

Implementarea TAD Natural pe care o prezentam corespunde numerelor


naturale ce se pot reprezenta Œn memoria calculatorului pe 4 octeti
(intervalul [0..MaxLongInt], MaxLongInt = 2^31-1 Œn Turbo Pascal).
Un numar natural este deci considerat Œntreg masina pe 4 octeti. Tinƒnd
cont de aceasta reprezentare, implementarea TAD Natural se face extrem de
simplu, folosindu-se operatiile tipului predefinit LongInt (aritmetice si
relationale) din Turbo Pascal. Sugeram alte doua modalitati de
reprezentare a TVD Natural, diferite de cea aleasa, care sa permita
manipularea de numere naturale n > MaxLongInt, cu:
- n reprezentat ca tablou cu MaxDim cifre (MaxDim constanta,
elementul de tablou sa fie de tip Byte si sa contina o cifra a
numarului scris Œntr-o baza de numeratie numita Baza;
(elementul de indice i va contine cifra de rang i = coeficientul lui
Baza^i);
- n reprezentat ca lista Œnlantuita, nodul i al listei continƒnd ca
informatie utila cifra i a numarului scris Œn baza Baza.

Pentru fiecare dintre cele doua reprezentari sugerate, implementarea


trebuie refacuta, plecƒnd de la specificatiile 2 si obtinƒndu-se astfel noi
variante ale TVD Natural, care vor diferi ca functionalitate de cea de mai
jos doar prin domeniu.

Vezi Unit-ul UNatural.PAS

Din motive de simplificare a implementarii, la operatiile Aduna si


Ori nu este testata depasirea valorii maxime reprezentabile, MaxLongInt.
Sugeram cititorului sa realizeze singur o alta implementare a acestor
operatii, Œn care sa se semnaleze eroare Œn cazul depasirii.

2.4.3. TVD Sutime

Implementarea TAD Sutime pe care o prezentam considera reprezentarea


domeniului acestui TAD ca subdomeniu al tipului predefinit Integer din
Turbo Pascal. Tinƒnd cont de aceasta reprezentare, implementarea se face
extrem de simplu, folosindu-se operatiile tipului predefinit Integer
(aritmetice si relationale) din Turbo Pascal. In interfata si implementarea
unit-ului se foloseste TVD Natural, prezentat Œn 2.4.2.

Vezi Unit-ul USutime.PAS

Analizƒnd implementarea operatiei Aduna, putem sugera si o alta


solutie, mai generala:

Procedure Aduna(s1,s2:Sutime; var n:Natural; var s:Sutime);


Var
89
n1,n2,n3 : Natural;
Begin
n1 := SutimeToNatural(s1);
n2 := SutimeToNatural(s2);
UNatural.Adun_(n1,n2,n3);
UNatural.Cƒt(n3,100,n);
UNatural.Rest(n3,100,n1);
s := NaturalToSutime(n1)
End; { Aduna }

Œn care am folosit operatii de conversie de la tipul Sutime la tipul Natural


(SutimeToNatural) si invers (NaturalToSutime), iar calculele s-au facut
folosind operatiile tipului Natural.

2.4.4. TVD Monede

Acest TVD este un exemplu de folosire intensiva a altor TVD (Natural,


Sutime) pentru implementarea TAD Monede. Se remarca calificarea operatiei
cu numele modulului (Œn situatia conflictului de nume). Folosirea
operatiilor relationale ale TVD Natural (MaiMare, Egal) Œn locul
operatorilor relationali (predefiniti) este un exemplu de apel independent
de implementare: daca TVD Natural ar fi altfel implementat (vezi sugestiile
facute Œn 3.2.3.1), n-ar putea fi folositi respectivii operatori, prin
urmare codul sursa al TVD Monede (operatiile MaiMare si Egal) ar trebui
rescris). Utilizƒnd operatiile cu care a fost dotat TAD Natural, se obtine
o implementare mult mai generala si mai flexibila, care nu este afectata
de modificarea TVD folosite Œn ea.

Vezi UMonede.PAS

4. PROGRAMAREA ORIENTATA PE OBIECTE

4.1. Concepte generale


4.2. Programarea orientata pe obiecte in Turbo Pascal

4.1. Concepte generale

Am vazut in lectia 'Tipuri de date utilizator' ca

programarea orientata pe obiecte = programarea bazata pe obiecte +


mostenirea +
polimorfismul.

Inainte de a defini conceptele de mostenire si polimorfism, vom face o scurta


trecere in revista a conceptelor de baza legate de programarea orientata pe
obiecte (Object-Oriented Programming, OOP sau POO).

4.1.1. Notiuni si concepte fundamentale

In programarea bazata pe obiecte (Object-Based Programming), obiectele


incapsuleaza date (ce caracterizeaza starea lor) si actiuni (care
caracterizeaza comportamentul obiectelor). Obiectele comunica intre ele prin
90
mesaje. Doua obiecte de acelasi tip (adica instante ale aceleiasi clase) vor
avea aceeasi multime a variabilelor de stare (numite campuri sau variabile de
instanta), dar vor avea valori diferite ale acestora, ce le va deosebi. In
schimb, din punct de vedere comportamental, ele vor fi capabile sa efectueze
aceleasi operatii (numite metode); natural, fiecare obiect va efectua o
anumitt operatie in conformitate cu starea sa. Cu alte cuvinte, doua obiecte
identice (ca tip) vor reactiona diferit la acelasi mesaj, deoarece raspunsul
tine cont de starea fiecaruia dintre ele.

In preambulul de mai sus am folosit notiunile:


- obiect,
- clasa,
- instanta sau realizare,
- camp sau variabila de instanta sau variabila de stare,
- metoda sau operatie,
- mesaj.
Sa le definim.

a) Clasa

Clasa este un tip de date reprezentand o multime de obiecte care au in comun


aceeasi reprezentare (aceleasi variabile de stare, numite si campuri sau
variabile de instanta) si acelasi comportament (aceleasi operatii, numite
metode). Conceptual, ea poate fi considerata la doua nivele:

1. La nivelul abstract:
clasa este un tip abstract de date;
campurile definesc reprezentarea sa;
operatiile definesc comportamentul obiectelor (instantelor) sale;

2. La nivelul virtual:
- clasa este un mecanism existent intr-un limbaj de programare, ce
permite:
- incapsularea datelor si operatiilor;
- stabilirea unor reguli de vizibilitate (acces la campuri din
mediul extern ei;
mecanism de protectie a datelor);
- asocierea unui nume entitatii respective (mecanism de tipizare);
- posibilitatea creerii si distrugerii instantelor (realizarilor)
sale (mecanism de instantiere, constructori, destructori);
- campurile clasei se numesc variabile de instanta sau variabile de
stare;
- operatiile (serviciile) se numesc metode; multimea metodelor clasei
formeaza protocolul de comunicatie al obiectelor ei

Deci, la nivelul abstract clasa este un tip abstract de date, iar in a doua
acceptiune, clasa este un tip virtual de date.

b) Obiectul

Obiectul este instanta (realizare) a clasei, in ambele acceptiuni discutate


mai sus. La nivelul virtual, putem considera obiectul ca o variabila de tipul
91
clasei. Obiectele comunica intre ele prin mesaje. In forma generala, un mesaj
se poate scrie astfel:

send(R,S[,A])
sau
R.S(A)

unde:
R(receptor) este obiectul destinatar al mesajului
S(selector) este metoda apelata
A(argumente, optional) contine parametrii actuali ai apelului

Obiectul receptor R, instanta a clasei C, poate raspunde la mesaje cu


selectorul S numai daca metoda desemnata de S face parte din protocolul de
comunicatie al clasei C. Altfel spus, S este numele unei metode si aceasta
metoda trebuie sa fie definita in clasa C.

4.1.2. Mostenirea si polimorfismul - o prima abordare

Definitiile date in acest paragraf sunt imprecise, dar constituie un punct de


plecare in discutia noastra. Ele vor fi detaliate in sectiunile urmatoare.

Mostenirea permite clasificarea obiectelor in concordanta cu caracteristicile


comune (de stare sau de comportament) ale lor. Polimorfismul dinamic inseamna
trimiterea de mesaje spre obiecte de tip necunoscut, dar care recunosc mesajul
(selectorul acestuia face parte din protocolul de comunicatie al clasei lor).
Numim obiecte polimorfice acele obiecte care au acelasi protocol de
comunicatie. Impreuna, mostenirea si polimorfismul sunt instrumente ce permit
realizarea a doua mari deziderate:
- organizarea ierarhiilor de clase;
- simplificarea comunicarii intre obiecte.

Cu aceste notiuni precizate, se pot formula doua (pseudo)definitii ale


programarii orientate pe obiecte (POO):
1. Esenta POO este trimiterea de mesaje spre obiecte de tip necunoscut,
care au in comun acelasi protocol de comunicatie;
2. POO este un stil de programare (mai bine zis de dezvoltare de programe)
ce incearca minimizarea complexitatii unui program prin reducerea
numarului de conexiuni intre componentele sale. Caile de reducere a
complexitatii sunt:
- transmiterea de mesaje;
- definirea unui protocol de comunicatie simplu si, in acelasi timp,
general.

Etapele de realizare a unui program orientat pe obiecte respecta in general


etapele prezentate pana acum, la programarea bazata pe obiecte. Ele sunt:

1. Crearea de clase, ce definesc reprezentarea si comportamentul obiectelor;


2. Crearea de obiecte, instante ale claselor;
3. Crearea programului, vazut ca o secventa de comunicari intre obiecte
(programul fiind un sir de mesaje). Cu alte cuvinte, programul este o
colectie structurata de obiecte care comunica intre ele.
92
Aceste etape vor fi detaliate in sectiunea 4.3, succesiunea lor bazandu-se pe
urmatoarele observatii:

1. In proiectarea unui program este nevoie de identificarea si clasificarea


obiectelor ce concura la realizarea functiunilor programului;
2. Clasificarea obiectelor impune ordine, atat pentru sistemul real (o
ordine naturala, proprie acestuia), cat si pentru modelele sale (logic
si fizic);
3. Obiectele se clasifica dupa caracteristicile lor comune;
4. In functie de locul de folosire a obiectelor, caracteristicile lor pot fi
accentuate sau ignorate;
5. Obiectele cu aceleasi caracteristici se grupeaza in clase.

Sa consideram un produs program destinat evidentei marfurilor intr-un


supermagazin.

Tinand cont de observatiile de mai sus, primul lucru care trebuie facut este
identificarea obiectelor. In cazul nostru, sistemul real este compus cel
putin din urmatoarele categorii de obiecte (fiinte, lucruri, compartimente):
- marfuri,
- magazii,
- case de marcaj,
- standuri de vanzare,
- personal,
- cumparatori.

Dintre toate aceste obiecte, vom discuta in continuare doar despre marfuri.
In sistemul real (supermagazinul), obiectele (reale) care sunt manipulate
(vandute) sunt marfuri. Prin urmare am identificat obiectele reale si urmeaza
sa le clasificam. Un supermagazin comercializeaza o mare diversitate de
marfuri. Din multe considerente (o gestiune mai usoara, atractivitate si
usurinta in gasirea marfii pentru cumparatori), spatiul comercial al
supermagazinului este impartit in raioane, a caror denumire caracterizeaza
genul de marfuri comercializate. Uzual putem gasi raioanele:
- alimentar,
- textile,
- librarie-papetarie,
- tutungerie,
- cosmetice,
- incaltaminte,
- menaj,
- electrice,
- electronice,
- articole de lux s.a.

Iata ca sistemul real ofera o clasificare a marfurilor, facuta dupa diverse


proprietati ale acestora (provenienta, utilizare). Este firesc ca aceasta
clasificare naturala sa se prelungeasca si in modelele logic si fizic ale
sistemului real. Prin urmare, putem discuta de obiectul MARFA, care
are ca descendenti obiectele ALIMENTARE, TEXTILE, LIBRARIE, TUTUNGERIE,
COSMETICE, INCALTAMINTE, MENAJ, ELECTRICE, ELECTRONICE, ARTICOLE DE LUX.
93
Caracteristicile comune ale tuturor marfurilor le vom grupa in obiectul MARFA.
In analiza care trebuie facuta, trebuie considerate obiectele din diverse
puncte de vedere. Pentru un cumparator sunt importante functionalitatea si
pretul de vanzare. Functionalitatea este o caracteristica mai greu de
cuantificat, motiv pentru care ea va fi tratata distinct la fiecare
clasa in parte, insa pretul este extrem de important: in supermagazine
diferite, aceeasi marfa poate sa aiba preturi diferite. Din punctul de vedere
al gestiunii magazinului sunt importante si alte caracteristici, ca de
exemplu:
- codul marfii (ce asigura o identificare unica a ei),
- denumirea,
- unitatea de masura,
- pretul de achizitie,
- pretul de transport,
- adaosul comercial aplicat,
- TVA,
- stocul din depozit,
- intrari (achizitie de marfa de la furnizori),
- iesiri (vanzare de marfa).

Unele dintre aceste caracteristici vor servi la determinarea pretului de


vanzare.

Caracteristicile de mai sus sunt comune tuturor marfurilor. Pentru clasele de


marfuri enumerate, se pot identifica si caracteristici specifice fiecareia,
ca de exemplu:

ALIMENTARE : termen de valabilitate


TEXTILE : tip fire
INCALTAMINTE : marime
ELECTRICE : putere consumata
ELECTRONICE : termen de garantie
ARTICOLE DE LUX : accize

Natural, fiecare din marfurile mai sus enumerate, fiind descendenti


ai obiectului MARFA, vor avea toate caracteristicile acestuia. Se observa deja
reutilizarea specificarii: obiectul de baza contine caracteristicile comune,
iar pentru descendentii acestuia se specifica numai ceea ce-i deosebeste de
parinte.

In explicatiile de mai sus, am identificat si clasificat obiecte din sistemul


real, determinand caracteristicile comune sau specializate ale lor. Conform
ultimei reguli (5), trebuie sa grupam aceste obiecte in clase. Gruparea se
poate face numai dupa ce caracteristicile obiectelor (grupurilor de obiecte)
sunt scoase in evidenta. Claselor nu le mai corespund obiecte reale; clasele
sunt descrieri abstracte ale obiectelor reale. Clasificarea acestor obiecte va
forma o ierarhie de clase. Pentru exemplul nostru, ierarhia clasei MARFA este
ilustrata in figura 1. Natural, fiecare subclasa a clasei MARFA poate fi
la randul ei dezvoltata in continuare.

ÚÄÄÄÄÄ¿ cod unitate de masura stoc intrari


94
³MARFA³ denumire pret achizitie TVA iesiri
ÀÄÄÂÄÄÙ pret pret transport adaos comercial
³
ÚÄÄÄÄÄÁÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄ
ÄÄÄÄÄÄÄ¿
ÚÄÄÄÄÄÁÄÄÄÄ¿ÚÄÄÄÁÄÄÄ¿ÚÄÄÄÄÄÄÁÄÄÄÄÄ¿ÚÄÄÄÄÁÄÄÄÄ¿ÚÄÄÄÄÄÁÄÄÄÄÄ¿
ÚÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄ¿
³ALIMENTARE³³TEXTILE³³INCALTAMINTE³³ELECTRICE³³ELECTRONICE³³ARTICOLE DE
LUX³
ÀÄÄÄÄÄÄÄÄÄÄÙÀÄÄÄÄÄÄÄÙÀÄÄÄÄÄÄÄÄÄÄÄÄÙÀÄÄÄÄÄÄÄÄÄÙÀÄÄÄÄÄÄÄÄÄÄ
ÄÙÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
termen de tip marime putere termen de accize
valabi- fire consumata garantie
litate

Figura 1. Ierarhia clasei MARFA

Odata clasele identificate, trebuie realizata specificarea lor, cand multe


dintre elementele discutate informal trebuiesc precizate. Astfel, unele
dintre caracteristici sunt date, ca de exemplu denumirea, codul, unitatea de
masura; altele, cum este pretul (de vanzare) se pot calcula (pe baza unui
algoritm de calcul). Primele sunt ceea ce am numit campuri sau variabile de
instanta, iar ultimele sunt metode ale claselor respective. Cand am facut
identificarea caracteristicilor claselor nu am precizat daca ele sunt campuri
sau metode. Specificarea unei clase va cuprinde, intr-o prima aproximatie
numele clasei, specificarea campurilor si specificarea metodelor. Schema
generala de specificare a unei clase este data in figura 2.

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ Clasa ³
³ nume, identificator ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ Campuri ³ ³ Specificare camp ³
³ specificare camp_1 ³ ³ nume si explicatie ³
³ specificare camp_2 ³ ³ tip de date ³
³ ... ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
³ specificare camp_n ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ Metode ³ ³ Specificare metoda ³
³ specificare metoda_1 ³ ³ nume si explicatie ³
³ specificare metoda_2 ³ ³ parametri formali ³
³ ... ³ ³ algoritm ³
³ specificare metoda_m ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ

Figura 2. Schema de specificare a clasei

Pentru clasa MARFA, schema de specificare este prezentata in figura 3.


Din ratiuni de spatiu, numele unor caracteristici este trunchiat.

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Ä¿
³ Clasa ³
³ Marfa ³
95
³ Campuri ³
³ Cod codul marfii, numar intreg ³
³ Denumire denumirea marfii, sir de caractere ³
³ UM unitatea de masura, sir de caractere ³
³ Pret_ach pretul de achizitie, numar real ³
³ TVA procent TVA, numar real ³
³ Adaos_c procent adaos comercial, numar real ³
³ Stoc cantitatea existenta in magazin, ³
³ numar intreg (real) ³
³ Metode ³
³ Pret_tr pret transport ³
³ Pret_tr := 0.1 * Pret_ach ³
³ Pret_v pret vanzare ³
³ Pret_v := Pret_ach (1+(TVA+Adaos_c)/100) ³
³ + Pret_tr ³
³ Intrare(c) intrarea unei cantitati c din marfa ³
³ Stoc := Stoc + c ³
³ Iesire(c) vanzarea unei cantitati c de marfa ³
³ Stoc := Stoc - c ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
ÄÙ

Figura 3. Schema de specificare a clasei MARFA

Revazad schema de specificare a unui tip abstract de date, prezentata intr-o


lectie anterioara putem constata ca specificarea claselor respecta aceasta
schema. Din motive de spatiu, in specificarile de mai sus nu am detaliat
partea aferenta operatiilor TAD (numite aici metode). In plus fata de
specificarea TAD, dupa cum se va vedea in paragraful urmator, specificarea
clasei contine si alte elemente.

4.1.3. Mostenirea

Mostenirea permite definirea de noi clase, pe baza celor existente (ultimele


sunt numite clase de baza sau clase parinti sau superclase). Noile clase
astfel obtinute se numesc clase derivate, subclase sau clase descendente ale
claselor de baza, ele fiind specializari ale acestora. Ca regula generala, o
clasa derivata mosteneste toate caracteristicile (starea si comportamentul)
clasei de baza, iar specializarea (diferentele in stare sau comportament fata
de clasa de baza) poate insemna:
- ignorarea unor caracteristici ale clasei de baza (mai rar intalnita);
- adaugarea unor noi caracteristici (variabile de stare sau metode), numita
specializare prin imbogatire;
- modificarea unor caracteristici ale clasei de baza (in general modificarea
unor metode), adica specializarea prin inlocuire.

Prin urmare, mostenirea permite ca in clasa derivata se se specifice doar


caracteristicile noi. Ca regula generala, definitia unei clase derivate
contine: precizarea parintelui (parintilor) si precizarea noilor
caracteristici. Acest lucru ofera doua prime avantaje:
- reutilizarea definitiilor si a codului: caracteristicile mostenite de la
parinti nu mai trebuie nici specificate, nici codificate;
96
- definitii mai simple ale claselor derivate (se specifica mai putine
elemente), deci o mai buna intelegere a specificarii si implementarii.

Exista doua tipuri de mostenire: simpla si multipla. Mostenirea simpla


corespunde unei singure clase parinte, iar mostenirea multipla presupune cel
putin doi parinti.

Relatia de mostenire este o legatura intre clase, de la fiu la parinte, ce


stabileste o relatie (partiala) de ordine pe multimea claselor. In cazul
mostenirii simple, ierarhia claselor se poate reprezenta ca un arbore
(arborele de mostenire, un nod corespunzand unei clase), ce are ca radacina
clasa de baza a ierarhiei, cea mai generala clasa (ce concentreaza
caracteristicile comune ale tuturor claselor din ierarhie). In cazul
mostenirii multiple, ierarhia claselor se reprezinta sub forma unui graf
(graful de mostenire). In arborele (respectiv graful) de mostenire se pot
considera subarbori (subgrafe), in care radacina este clasa de baza a
subarborelui (nodul initial al subgrafului). In cazul mostenirii simple,
legatura parinte-fiu este de tipul unul_la_mai multe (unui p_rinte ii
corespund mai multi descendenti), iar in cazul mostenirii multiple, legatura
este de tipul mai_multe_la_mai multe (unui parinte ii corespund mai multi
descendenti, iar un descendent poate avea mai multi parinti).

In ierarhia de mostenire, o clasa aflata intr-un nod intern sau terminal are
doua tipuri de caracteristici:
- caracteristici mostenite de la parinti (clasele aflate in aval de ea in
ierarhie);
- caracteristici proprii, specificate in definitia ei (fie caracteristici
noi, fie redefiniri ale caracteristicilor parintilor).

Reluand exemplele discutate in paragraful anterior, trebuie sa precizam ca


schema de specificare a unei clase trebuie sa contina, pe langa elementele
preluate de la specificarea TAD (nume, campuri si metode), si precizarea
parintilor (superclaselor) clasei in cauza. Prin urmare, o schema completa de
specificare a unei clase va avea structura prezentata in figura 4.

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ Clasa ³
³ nume ³
³ Superclasa ³
³ lista de superclase ³
³ Campuri ³
³ specificarea campurilor ³
³ Metode ³
³ specificarea metodelor ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ

Figura 4. Schema completa de specificare a unei clase

Fiecare dintre clasele specificate constituie o specializare a superclasei


sale, avand fie caracteristici noi (Putere_abs, Garan_ie, Accize, Tip_fire,
Capacitate, Standard_VCR, Tara_furnizoare, Provenienta, Tip_ingrediente),
fie redefinind caracteristici (metode) ale superclaselor (Pret_tr, Pret_v).
97
In figura 1 este prezentat arborele de mostenire cu radacina clasa MARFA.
Evident, avem de-a face in acest exemplu cu o mostenire simpla, toti
descendentii avand o singura superclasa, MARFA. Ramanand in acelasi context,
se pot da exemple mai elaborate de ierarhii de mostenire.

La inceput vom exemplifica mostenirea simpla, considerand trei descendenti ai


clasei MARFA: ELECTRICE, ARTICOL_DE_LUX si ALIMENTE, care vor avea la randul
lor descendentii FRIGIDER, VIDEO si HOMAR, respectiv OUA si BISCUITI.
Schemele de specificare ale acestor clase sunt date in continuare, in figura
5, iar arborele de mostenire in figura 6.

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄ¿


³ Clasa ³ ³ Clasa ³ ³ Clasa ³
³ ELECTRICE ³ ³ ARTICOL_DE_LUX ³ ³ ALIMENTE ³
³ Superclasa ³ ³ Superclasa ³ ³ Superclasa ³
³ MARFA ³ ³ MARFA ³ ³ MARFA ³
³ Campuri ³ ³ Campuri ³ ³ Campuri ³
³ Putere_abs ³ ³ Accize ³ ³ Tip_fire ³
³ Metode ³ ³ Metode ³ ³ Metode ³
³ Consum ³ ³ Pret_v ³ ³ ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ Clasa ³ ³ Clasa ³ ³ Clasa ³
³ FRIGIDER ³ ³ VIDEO ³ ³ HOMAR ³
³ Superclasa ³ ³ Superclasa ³ ³ Superclasa ³
³ ELECTRICE ³ ³ ARTICOL_DE_LUX ³ ³ ARTICOL_DE_LUX ³
³ Campuri ³ ³ Campuri ³ ³ Campuri ³
³ Capacitate ³ ³ Standard_VCR ³ ³ Tara_furnizoare ³
³ Metode ³ ³ Metode ³ ³ Metode ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ Clasa ³ ³ Clasa ³
³ OUA ³ ³ BISCUITI ³
³ Superclasa ³ ³ Superclasa ³
³ ALIMENTE ³ ³ ALIMENTE ³
³ Campuri ³ ³ Campuri ³
³ Provenienta ³ ³ Tip_ingrediente ³
³ Metode ³ ³ Metode ³
³ Pret_tr ³ ³ ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ

Figura 5. Specificari de clase

ÚÄÄÄÄÄÄÄ¿
³ MARFA ³
ÀÄÄÄÂÄÄÄÙ
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ-¿
ÚÄÄÄÄÄÁÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÁÄÄÄÄ¿
98
³ ELECTRICE ³ ³ ARTICOL_DE_LUX ³ ³ ALIMENTE ³
ÀÄÄÄÄÄÂÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÂÄÄÄÄÙ
³ ÚÄÄÄÄÁÄÄÄÄÄ¿ ÚÄÄÄÄÄÁÄÄ-¿
ÚÄÄÄÄÄÁÄÄÄÄ¿ ÚÄÄÄÄÁÄÄ¿ ÚÄÄÄÁÄÄÄ¿ ÚÄÄÁÄÄ¿ ÚÄÄÄÄÁÄÄÄÄÄ¿
³ FRIGIDER ³ ³ VIDEO ³ ³ HOMAR ³ ³ OUA ³ ³ BISCUITI ³
ÀÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÙ

Figura 6. Arbore de mostenire

Analizand arborele de mostenire din figura 6, remarcam simplitatea lui (fapt


pozitiv) pe de o parte, dar si rigiditatea ierarhiei (fapt negativ), pe de
alta parte. Ar parea normal ca VIDEO sa fie si descendent al clasei ELECTRICE,
pentru ca func_ioneaza cu curent electric, insa acest lucru nu este posibil
in mostenirea simpla.

Pentru a exemplifica mostenirea multipla, ramanem in acelasi context,


considerand in plus clasele PERISABILE, FRAGILE si ALTERABILE si rescriind
specificatiile claselor VIDEO, HOMAR si OUA. Ierarhia de clase astfel
obtinuta este mai complexa, insa relatiile dintre clase sunt acum mai
naturale. Am putut astfel sa consideram ca VIDEO are atat caracteristicile
unui aparat electric (ELECTRICE), cat si cele ale unui ARTICOL_DE_LUX. In
plus, cele trei noi clase introduse asigura o mai buna ierarhizare a
marfurilor: PERISABILE (marfuri care trebuie transportate cu mijloace de
transport adecvate, deci necesita conditii speciale de manipulare si
transport), FRAGILE (marfuri ce trebuie manipulate si transportate cu atentie,
ca sa nu se sparga) si ALTERABILE (care se depreciaza rapid in timp),
descendenta a clasei PERISABILE. In toate aceste situatii, pretul de
transport va include cheltuieli suplimentare (datorate fie mijloacelor
speciale de transport, fie restrictiilor de timp), deci in specificarea
claselor metoda cu numele Pret_tr va fi redefinita. Noile specificatii sunt
date in figura 7, iar graful de mostenire in figura 8.

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿


³ Clasa ³ ³ Clasa ³ ³ Clasa ³
³ PERISABILE ³ ³ FRAGILE ³ ³ ALTERABILE ³
³ Superclasa ³ ³ Superclasa ³ ³ Superclasa ³
³ MARFA ³ ³ MARFA ³ ³ MARFA ³
³ Campuri ³ ³ Campuri ³ ³ PERISABILE ³
³ Temperatura ³ ³ ³ ³ Campuri ³
³ Metode ³ ³ Metode ³ ³ Data_expir ³
³ Pret_tr ³ ³ Pret_tr ³ ³ ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ Clasa ³ ³ Clasa ³ ³ Clasa ³
³ OUA ³ ³ VIDEO ³ ³ HOMAR ³
³ Superclasa ³ ³ Superclasa ³ ³ Superclasa ³
³ ALIMENTE ³ ³ ARTICOL_DE_LUX ³ ³ ARTICOL_DE_LUX ³
³ ALTERABILE ³ ³ ELECTRICE ³ ³ PERISABILE ³
³ FRAGILE ³ ³ FRAGILE ³ ³ Câmpuri ³
³ Campuri ³ ³ Campuri ³ ³ Tara_furnizoare ³
³ Provenienta ³ ³ Standard_VCR ³ ³ Metode ³
99
³ Metode ³ ³ Metode ³ ³ ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ

Figura 7. Specificari de clase in cazul mostenirii multiple

ÚÄÄÄÄÄÄÄ¿
³ MARFA ³
ÀÄÄÄÂÄÄÄÙ
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄ
ÄÄÄÄÄÄ-¿
ÚÄÄÄÄÄÁÄÄÄÄÄ¿ ÚÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÁÄÄÄÄ¿ ÚÄÄÄÄÄÁÄÄÄÄÄÄ¿
ÚÄÄÄÄÄÁÄÄÄÄ¿
³ ELECTRICE ³ ³ ARTICOL_DE_LUX ³ ³ FRAGILE ³ ³ PERISABILE ³ ³ ALIMENTE ³
ÀÄÄÄÂÄÄÄÂÄÄÄÙ ÀÄÄÄÄÄÂÄÄÄÄÄÄÄÄÂÄÙ ÀÄÄÄÂÄÄÄÄÄÂÙ
ÀÄÄÂÄÄÄÄÄÂÄÄÄÙ ÀÄÄÂÄÄÄÄÂÄÄÙ
³ ³ ³ ³ ³ ³ ÚÄÄÄÄÙ ³ ³ ³
³ ³ ³ ³ ³ ³ ³ ÚÄÄÄÄÄÄÄÄÁÄÄÄ¿ ³ ³
³ ³ ³ ÚÄÄÄÄÄÄÅÄÄÄÄÄÄÄÙ ³ ³ ³ ALTERABILE ³ ³ ³
³ ³ ³³ ³ ³ ³ ÀÄÄÄÄÄÂÄÄÄÄÄÄÙ ³ ³
³ ÀÄÄÄÄÄÄÄÄÄ¿ ³ ³ ³ ÚÄÄÄÄÄÄÄÄÄÅÄÙ ³ ³ ³
³ ³³³ ³ ³ ³ ÚÄÄÄÄÄÄÄÙ ³ ³
³ ³³³ ³ ³ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³
ÚÄÄÁÄÄÄÄÄÄÄ¿ ÚÄÁÄÁÄÁÄ¿ ÚÁÄÄÄÁÄÄ¿ ÚÁÄÁÄÁ¿ ÚÄÄÄÄÄÄÄÁÄÄ¿
³ FRIGIDER ³ ³ VIDEO ³ ³ HOMAR ³ ³ OUA ³ ³ BISCUITI ³
ÀÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÙ

Figura 8. Graf de mostenire

Conform noilor specificatii, clasele ALTERABILE, VIDEO, HOMAR si OUA au mai


multe superclase directe, prin urmare ierarhia de mostenire se reprezinta sub
forma unui graf.

Fie C o class si A o caracteristica a ei. Daca A este mostenita, se pune


problema determinarii superclasei SC a lui C pentru care A este caracteristica
proprie (SC va fi clasa de la care C mosteneste caracteristica A). In cazul
mostenirii simple, exista un singur drum de la radacina arborelui de mostenire
la nodul corespunzator clasei C, relatia de mostenire inducand o relatie de
ordine pe multimea claselor existente in nodurile acestui drum; identificarea
superclasei SC se face prin parcurgerea respectivului drum in sens invers (de
la nodul lui C spre radacina) si inspectarea fiecarui nod intalnit. Primul
nod in care se gaseste definitia caracteristicii A va corespunde clasei SC.
In cazul mostenirii multiple, exista mai multe drumuri (in cazul general) de
la nodul clasei C la nodul initial al grafului de mostenire. Pot exista doua
situatii:
- caracteristica A apartine unui singur drum (C mosteneste caracteristica A
de la un singur parinte): determinarea clasei SC se face pe acest drum,
la fel ca in cazul mostenirii simple;
- caracteristica A apartine la cel putin doua drumuri (C mosteneste
caracteristica A de la cel putin doi parinti), situatie numita conflict
de mostenire; in acest caz trebuie (pe baza unei informatii suplimentare)
precizat parintele de la care C mosteneste caracteristica A; intre
100
modalitatile de rezolvare a conflictului de mostenire amintim:
- stabilirea unei ierarhii intre parinti (aceasta ierarhie va dicta
ordinea in care se iau in considerare drumurile);
- conflictele sunt de fapt conflicte de nume; se poate incerca o schimbare
a numelor sau o calificare a lor cu numele clasei parinte (neelegant);
- mostenirea se specifica explicit, in genul:

from SC inherits A.
(dela SC mosteneste A).

Revazand exemplul nostru, un asemenea conflict de mostenire apare in clasa OUA,


pentru care metoda Pret_tr este mostenita atat de la clasa FRAGILE, cat si de
la clasa PERISABILE. In astfel de situatii, in schema de specificare a clasei
trebuie precizat parintele de la care se mosteneste respectiva caracteristica.
O situatie analoaga poate sa apara si pentru campuri.

Relatia de instantiere este o legatura intre obiecte si clase: obiectul este


o instanta a unei singure clase. Prin urmare, aceasta legatura este de tipul
unu_la_unu (de la obiect la clasa). Invers, o clasa poate avea mai multe
instante. Intr-o ierarhie de clase, nu neaparat toate clasele pot avea
instante. Exista doua categorii de clase:
- clasele abstracte, ce nu genereaza instante; de obicei ele sunt in partea
superioara a ierarhiei (clase de baza), continand caracteristicile comune
ale tuturor claselor descendente;
- clasele generatoare de instante, ce se afla in nodurile interioare sau
terminale ale ierarhiei.

Revazand ierarhiile de mostenire ale clasei MARFA prezentate in figurile 6 si


8, putem conchide ca MARFA este o clasa abstracta (in cazul mostenirii simple),
respectiv ca MARFA, FRAGILE, PERISABILE si ALTERABILE sunt clase abstracte
(pentru mostenirea multipla). Clasele ELECTRICE, ARTICOL_DE_LUX si ALIMENTE
se pot considera fie abstracte, fie generatoare de instante, pe cand FRIGIDER,
VIDEO, HOMAR, OUA, BISCUITI sunt doar clase generatoare de instante.

Intre clasele abstracte, un loc aparte il ocup_ clasele generice sau clasele
parametrizate. Daca mostenirea permite rafinarea caracteristicilor unei
multimi de obiecte, plecand de la cele comune si specializand, obtinandu-se
o ierarhie in care fiecare clasa are cel putin o caracteristica diferit in
raport cu celelalte, genericitatea inseamna acelasi comportament pentru clase
diferite. De exemplu, o stiva de intregi, o stiva de siruri de caractere sau
o stiva de inregistrari PERSOANA vor avea (toate) comportamentul generic al
stivei (operatiile Push, Pop si Top), dictat de disciplina de servire LIFO
(Last In First Out). Ceea ce difera la cele trei exemple de stive este tipul
elementului supus manipularii: intreg, sir de caractere, inregistrare de tip
PERSOANA. Genericitatea (in acele limbaje in care este implementata) permite
parametrizarea claselor. Pentru exemplul nostru, va fi suficienta declararea
unei clase Stiva[Type] unde Type este tipul generic al elementului stivei.
Utilizarea acestei clase generice in cazurile particulare enumerate inseamna
instantierea acestei clase generice:

StivaInt = new Stiva[Integer];


StivaStr = new Stiva[String];
101
StivaPER = new Stiva[PERSOANA];

Instantele unei clase generice sunt vazute diferit in limbaje diferite. Spre
exemplu, in Ada (clasa=pachet), instanta unui pachet generic este tot un
pachet, generator de instante terminale (obiecte). Ca si in cazul mostenirii,
genericitatea are ca efect reutilizarea codului scris.

4.1.4. Polimorfismul

Intr-un program pot exista obiecte diferite, care sa fie instante ale unor
clase legate intre ele prin relatia de mostenire. Le numim obiecte inrudite
(polimorfice) deoarece:
- ierarhia claselor ale caror instante sunt are o clasa radacina, clasa de
baza;
- clasa de baza defineste protocolul de comunicatie comun tuturor obiectelor
inrudite: toate obiectele sunt capabile sa raspunda la aceleasi mesaje
(cum raspund e alta problema, important este ca inteleg aceleasi mesaje);
- sunt de tipuri (instante de clase) diferite.

Stricto sensu, polimorfism inseamna mai multe forme (aspecte, infatisari).


Termenul este imprumutat din biologie, unde se defineste astfel: variatie in
forma si functiile membrilor unor specii cu stramosi comuni in arborele de
evolutie (diversitate morfofiziologica in acelasi plan sau in planuri
diferite de structura). In terminologia POO, prin polimorfism intelegem
abilitatea de a:
1. pune obiecte inrudite intr-un tablou sau colectie;
2. utiliza protocolul de comunicatie pentru a transmite mesaje obiectelor
individuale, printr-o referire unitara (ca elemente de tablou sau
colectie).

In primul exemplu, toate marfurile ce se comercializeaza in supermagazin


formeaza o asemenea colectie de obiecte polimorfice. Ele au in comun acelasi
protocol de comunicatie (definit in clasa radacina a ierarhiei de mostenire,
MARFA), pe de o parte, fiind insa instante ale unor clase diferite, pe de
alta parte. Spre exemplu, orice marfa se manipuleaza (se achizitioneaza de
la furnizori si se vinde), deci asupra ei se executa operatii ca Intrare si
Iesire si are un pret (determinat cu metoda Pret_v). Pentru operatiile
Intrare si Iesire, metoda de calcul este descrisa in clasa MARFA (Figura 3),
ea fiind general valabila in cazul oricarei marfi. Nu acelasi lucru se
intampla in cazul metodei Pret_v, care este redefinita in cazul clasei
ARTICOL_DE_LUX, deoarece pentru aceste articole se aplica taxe fiscale mai
mari, numite accize. Un exemplu de algoritm pentru pretul de vanzare al
articolelor de lux ar putea fi:

ARTICOL_DE_LUX.Pret_v := MARFA.Pret_v + Pret_ach * Accize / 100

Prin urmare, daca avem o marfa M si dorim sa aflam pretul ei de vanzare,


pentru acest exemplu simplificat conventia de apelare ar putea fi:

M.Pret_v

unde Pret_v are semnificatia ARTICOL_DE_LUX.Pret_v daca M este articol de lux,


102
respectiv MARFA.Pret_v cand M este o alta marfa (M va mosteni Pret_v de la
clasa de baza MARFA). Se remarca folosirea aceleiasi notatii, deci
simplificarea scrierii. Acesta este un prim exemplu (mai simplu) de
polimorfism, realizat numai cu ajutorul mostenirii.

O situatie analoaga este oferita de metoda Pret_tr, care este redefinita in


clasa OUA (in cazul mostenirii simple), respectiv in clasele PERISABILE si
FRAGILE (la mostenirea multipla). Ce se intampla insa cand o metoda (cum este
Pret_tr) redefinita intr-o clasa fiu trebuie folosita in clasa de baza? Spre
exemplu, revazand algoritmul de calcul al pretului de vanzare pentru clasa
MARFA (figura 3),

MARFA.Pret_v := MARFA.Pret_ach +
(1 + (MARFA.TVA + MARFA.Adaos_c) / 100) +
MARFA.Pret_tr

ne punem intrebarea: cum se va calcula OUA.Pret_v? Raspunsul nu este asa de


simplu cum pare la prima vedere. Elementele de calcul ale pretului trebuie
sa fie caracteristici (campuri sau metode) ale clasei OUA, deci

OUA.Pret_v := OUA.Pret_ach +
(1 + (OUA.TVA + OUA.Adaos_c) / 100) +
OUA.Pret_tr

Dintre acestea, doar Pret_tr este metoda proprie clasei OUA (fiind redefinita
in specificarea 5, la mostenirea simpla, respectiv mostenita de la FRAGILE
sau PERISABILE in specificarea 7, la mostenirea multipla). Toate celelalte
elemente de calcul sunt proprii clasei MARFA, prin urmare pretul ar trebui
sa se calculeze astfel:

OUA.Pret_v := MARFA.Pret_ach +
(1 + (MARFA.TVA + MARFA.Adaos_c) / 100) +
OUA.Pret_tr

Suntem deci in situatia cand, pentru a se obtine un rezultat corect, o metoda


definita in clasa de baza trebuie sa apeleze o metoda dintr-o clasa derivata.
Aceasta este a doua fata a polimorfismului, mult mai atractiva, dar mai greu
de realizat. Din pacate, numai mostenirea nu este suficienta pentru a pune
in practica acest lucru, deoarece, asa cum am aratat in 4.1.3, relatia de
mostenire este o legatura de la fiu la parinte, deci de la clasa derivata la
clasa de baza. Definitia metodei Pret_v fiind facuta in clasa de baza, MARFA,
folosind (numai) relatia de mostenire se va ajunge la o definitie de forma:

OUA.Pret_v := MARFA.Pret_ach +
(1 + (MARFA.TVA + MARFA.Adaos_c) / 100) +
MARFA.Pret_tr

astfel:
1) pentru OUA.Pret_v se deduce (din arborele sau graful de mostenire) ca
MARFA este clasa in care Pret_v este metoda proprie;
2) Pentru elementele de calcul (Pret_ach, TVA, Adaos_c, Pret_tr) se deduce
ca toate sunt proprii clasei MARFA; daca n-ar fi asa, ele ar fi cautate
103
in ierarhia de mostenire, in superclasele clasei MARFA (daca ar exista
asemenea superclase) si nicidecum inapoi, in subclase.

In concluzie, putem rezuma caracteristicile polimorfismului astfel:

1. Polimorfismul necesita mostenire. Fara mostenire nu am avea o clasa de


baza, deci nu ar exista protocolul comun de comunicatie;
2. Mostenirea nu este suficienta pentru realizarea polimorfismului, fiind
nevoie de mecanisme suplimentare. In cele ce urmeaza vom discuta modul
in care interactioneaza aceste doua mecanisme si de ce mecanisme noi mai
e nevoie;
3. Polimorfismul simplifica munca programatorului, uniformizand sintaxa
mesajelor si micsorand complexitatea programelor.

Diferentele dintre polimorfism si mostenire le putem discuta in raport cu trei


aspecte: scop, arie de cuprindere si efecte. Mostenirea are ca scop
ierarhizarea claselor (tipurilor de date), in ideea unei mai bune structurari
a universului obiectelor, prin eliminarea redundantelor, iar polimorfismul
simplifica comunicarea cu sau intre obiectele inrudite. Din punctul de vedere
al ariei de cuprindere, mostenirea implica toate caracteristicile claselor
(campuri si metode), pe cand polimorfismul are ca obiect doar metodele ce
definesc protocolul de comunicatie (de fapt numai metodele virtuale, definite
in cele ce urmeaza, vezi 4.1.5). Mostenirea are ca efect reutilizarea codului
si permite manifestarea polimorfismului; polimorfismul utilizeaza mostenirea
pentru a construi ierarhii de tipuri polimorfice, ce au in comun acelasi
protocol de comunicatie, definit in clasa de baza.

4.1.5. Consideratii de specificare si implementare a POO

4.1.5.1. Clasa ca tip abstract de date

In sec_iunile anterioare, am asimilat o clasa de obiecte cu un TAD. Fata de


schema generala de specificare a unui TAD, trebuie considerate noi aspecte,
legate de:
- specificarea mostenirii;
- regulile noi de vizibilitate si acces induse de mostenire;
- specificarea operatiilor (metodelor).

Specificarea mostenirii apare la definirea unei subclase, cand trebuiesc


facute precizari privind:
- parintele (in mostenirea simpla);
- parintii si ierarhizarea lor (in mostenirea multipla).

Regulile de vizibilitate si drepturile de acces s-au rafinat. In functie de


vizibilitatea lor in exterior, caracteristicile unei clase se grupeaza in
(exemplu: limbajul C++):
- publice (se pot accesa si/sau modifica in exterior);
- private (nu se pot accesa si/sau modifica in exterior);
- protejate (se pot accesa si/sau modifica doar in subclase).

In plus, la specificarea mostenirii se pot prevedea modificatori de acces ai


caracteristicilor claselor parinti:
104
- public: pastreaza aceleasi drepturi de acces pentru descendenti la
caracteristicile mostenite;
- privat: interzice accesul pentru descendenti la caracteristicile
mostenite.

In sfarsit, o alta modalitate de acces este oferita de clasele prietene


(friend in limba engleza). Daca o clasa C este prietena a unei clase C',
atunci C va avea acces la campurile private ale clasei C'.

La TAD am discutat tipurile de operatii dupa functionalitatea lor. In cazul


claselor din POO, unele operatii capata noi valente (constructorii si
destructorii) si apare o alta clasificare a operatiilor, dupa criteriul
momentului legarii (metode statice si virtuale). In paragraful urmator vom
detalia noile aspecte mentionate.

4.1.5.2. Clasa in POO

Constructori si destructori

In POO, constructorii sunt specifici claselor. In afara functiilor cunoscute


deja de la programarea bazata pe obiecte (creare de obiecte, cu initializarea
unor variabile de stare), in POO constructorii au si o alta functie: pun
"semnatura" clasei in obiect. Practic, ne putem imagina acest lucru in felul
urmator: un obiect O al unei clase C va fi o variabila ce va contine:
- spatiu pentru variabilele de stare (in conformitate cu definitia clasei
C);
- o legatura spre clasa C (un pointer, de exemplu), ce reprezinta
materializarea relatiei de instantiere.

Avem o prima regula:

R1. Orice obiect O al unei clase C se construieste prin apelul unui


constructor.

(Daca regula R1 ar fi incalcata, ar putea exista obiecte care sa nu-si


recunoasca prototipul, adica clasa ale carei instante sunt).

Informatia de instantiere este utila la executie, cand cu ajutorul ei se


poate determina exact clasa instantei (obiectului). Aceasta regula este
valabila in cazul programarii orientate pe obiecte. In programarea bazata pe
obiecte nu este necesara existenta constructorilor in acceptiunea de mai sus.

Analog, un destructor (in afara functiilor deja cunoscute) trebuie sa


distruga si informatia de instantiere.

Legare statica si dinamica

Limbajele de programare folosesc din plin notiunea de identificator. Prin


identificatori, programatorul face notatii (intre altele) pentru variabile si
subprograme. Un identificator poate fi vazut in doua momente distincte:
declararea si referirea. In general, o declarare a unei variabile se face
folosind un tip de date cunoscut, iar declararea unui subprogram
105
respecta cerintele sintactice ale fiecarui limbaj in parte: se specifica de
obicei tipul subprogramului (procedura sau functie, la ultima si tipul
rezultatului obtinut) si lista parametrilor formali (nume si tip). Incepand
cu abstractizarea datelor, putem vorbi atat de declararea unui subprogram
(vazut ca o operatie a unui TAD), cat si de definirea acestuia.
Declararea se face in partea publica a modulului (interfata acestuia) si in
unele limbaje nu specifica decat tipul parametrilor; definirea precizeaza
codul, ea fiind proprie implementarii modulului.

Prin legare intelegem o operatie ce are loc intr-un program traducator


(compilator sau interpretor), ce consta in inlocuirea referirii unui
identificator (nume de variabila sau de procedura ce apare in textul sursa)
printr-o adresa (din codul programului, rezultat in urma traducerii). In
cazul compilatoarelor, fazele de compilare si executie ale aceluiasi program
se desfasoara la momente diferite de timp (intai compilarea integrala a
textului sursa si apoi executia codului rezultat in urma compilarii). In
schimb, in interpretoare aceste doua faze se desfasoara principial una dupa
alta pentru fiecare linie sursa (intai traducere, apoi executie).

In limbajele de programare dotate cu compilatoare, spunem ca legarea este


statica, in sensul ca, intr-o faza oarecare a traducerii, compilatorul poate
asocia referirii unui nume o adresa: adresa unei variabile (cand avem un nume
de variabila) sau adresa punctului de intrare a unui subprogram (cand avem
un apel de procedura sau de functie). Cum de obicei declararea unui
identificator precede referirea lui, declararea are ca efect fie alocarea de
spatiu in cod (pentru variabile), fie generarea de cod (pentru subprograme),
in ambele cazuri memorandu-se (sa zicem ca in tabela de simboluri)
corespondenta nume-adresa (de variabila sau de punct de intrare). Si ca sa
fim si mai exacti, ceea ce nu poate rezolva compilatorul (in cazul compilarii
separate a programelor) va rezolva editorul de legaturi, principiul ramanand
acelasi.

In cazul limbajelor puternic tipizate, in momentul legarii se mai fac si alte


verificari de compatibilitate:
- a tipurilor (pentru variabile);
- a listelor de parametri actuali (pentru apelurile de subprograme).

In limbajele de programare dotate cu interpretoare, spunem ca legarea este


dinamic, ea avand loc in momentul executiei.

Fiecare dintre tipurile de legare discutate prezinta avantaje si dezavantaje,


pe care nu le discutam aici.

Implementarea polimorfismului

Am vazut ca polimorfismul presupune tratarea unitara a obiectelor inrudite,


ce au in comun acelasi protocol de comunicatie, definit de clasa de baza. In
fapt, protocolul de comunicatie inseamna o lista de identificatori (nume de
metode definite in clasa de baza). Am vazut de asemenea ca obiectele inrudite
nu au comportament identic, deci un acelasi identificator de metoda poate sa
refere metode semantic diferite (deci actiuni diferite) pentru doua obiecte
din clase diferite.
106
Manipularea obiectelor polimorfice inseamna parcurgerea etapelor:
1. Se declara o colectie (lista, tablou) de obiecte in care un element
al colectiei are tipul clasei de baza;
2. Se creeaza si se introduc in colectie obiecte de tipuri descendente
ale clasei de baza (colectia va fi heterogena); crearea de obiecte se
va face apeland constructorii proprii claselor respective, care vor
pune semnatura clasei in instante;
3. Esenta polimorfismului este ca toate elementele colectiei se pot
trata unitar in maniera:

send(EC,S[,A])
sau
EC.S(A)

unde:
EC (receptorul) este un element al colectiei;
S (selectorul, numele metodei) apartine protocolului de
comunicatie definit de clasa de baza.

Consideram ca discutam despre un limbaj dotat cu compilator. Se pune


intrebarea:

Se poate folosi legarea statica pentru implementarea polimorfismului?

Raspunsul este UN NU CATEGORIC! Sa vedem de ce.

1. La compilare nu se cunoaste exact clasa receptorului EC, ci doar clasa


de baza a lui, pe baza instructiunii de declarare a colectiei. Ar fi
bine doar daca clasa EC coincide cu clasa de baza;
2. Mesajul de mai sus se traduce printr-un apel al metodei S; la compilare
se va alege in toate cazurile (indiferent carei clase ar apartine EC)
metoda S a clasei de baza. Din nou ar fi bine doar daca clasa EC
coincide cu clasa de baza sau daca metoda corespunzatoare clasei EC ar
fi mostenita din clasa de baza;
3. Ca sa se aleaga metoda adecvata clasei lui EC, ar trebui sa se cunoasca
clasa lui EC, deci compilatorul ar trebui sa aiba acces la
reprezentarea obiectului EC (unde se gaseste si "semnatura" clasei,
pusa de constructor). Indiferent daca EC este un obiect static sau
dinamic, el va exista numai dupa apelul constructorului, care apel
inseamna o actiune efectuata la executia programului, si nu la
compilare. Chiar daca la compilare avem acces la reprezentarea
obiectului (cand acesta este alocat in segmentul de date; n-am avea
acces daca este alocat in stiva sau in heap), informatia de instantiere
nu este completata, deci nu se poate deduce clasa obiectului;
4. Cum se poate face distinctie intre metode care se leaga static si cele
care se leaga dinamic? La aceasta intrebare nu putem inca raspunde,
dar trebuie sa avem in vedere o atare situatie.

Din discutia de mai sus rezulta ca (pentru compilatoare):


- polimorfismul se poate implementa numai prin legare dinamica;
- protocolul de comunicatie trebuie sa precizeze si tipul legarii (legare
107
statica sau legare dinamica).

Metode statice si virtuale

Am vazut ca pentru o colectie de obiecte polimorfice, clasa de baza defineste


protocolul de comunicatie. In acelasi timp, am constatat ca nu este suficienta
definirea metodelor comune, compilatorul trebuind sa cunoasca si tipul
legarii pentru fiecare dintre metodele ce formeaza protocolul de comunicatie
(celelalte metode se vor lega static).

Acesta este ultimul impediment in implementarea polimorfismului si el se


rezolva prin asa-numitele metode virtuale. Termenul de procedura virtuala
este introdus pentru prima data in Simula67, fiind preluat intre altele de
C++ (functie virtuala) si Turbo Pascal (metoda virtuala).

Avem, prin urmare, inca un criteriu de clasificare a metodelor unei clase,


dupa momentul legarii lor:
- metode statice (legare statica, la compilare);
- metode virtuale (legare dinamica, la executie).
Cu aceste precizari, putem defini mai exact protocolul de comunicatie, ca
fiind format din metodele virtuale ale clasei de baza.

Proiectarea protocolului de comunicatie trebuie sa respecte regulile:

R2. Constructorii nu pot fi metode virtuale;


(ei sunt responsabili cu "identitatea" obiectelor, deci trebuie legati
static);
R3. Destructorii pot fi metode virtuale;
R4. Daca o metoda V este declarata virtuala intr-o clasa C, toate
redefinirile ei din descendentii lui C vor fi virtuali;
R5. Specificarea metodelor virtuale proprii trebuie sa corespunda celei
din clasa de baza, pentru orice clasa descendenta a clasei de baza;
(optional, la limbajele puternic tipizate, cand compilatorul verifica
lista parametrilor actuali)
R6. O clasa ce are metode virtuale trebuie sa posede cel putin un
constructor.

Obiectele la munca

Triada constructor - metoda virtuala - legare dinamica asigura implementarea


corecta a polimorfismului. In linii generale, aceasta implementare presupune
urmatoarele etape si conventii:

1. Pentru fiecare clasa ce poseda metode virtuale, compilatorul


construieste tabela de metode virtuale (Virtual Method Table VMT in
Turbo Pascal, Virtual Function Table VFT in Borland C++), ce contine
pointeri la punctele de intrare ale acestora (perechi de forma
nume_metoda, adresa_punct_de_intrare);
2. Semnatura clasei, pe care constructorul o pune in fiecare obiect este
de fapt un pointer la tabela de metode virtuale a clasei obiectului;
3. In cazul legarii dinamice, obiectul este cel care ofera informatia
necesara legarii, in el existand referinta la tabela de metode
108
virtuale ce trebuie consultata pentru a se rezolva apelul.
4. Metodele sunt considerate implicit statice; declararea unei metode
virtuale se face explicit.

Revenind la mesajul:
send(R,S[,A])
sau
R.S(A)

rezolvarea lui se poate discuta in doua ipostaze:


- S este un nume de metoda statica;
- S este un nume de metoda virtuala.

A. Legarea statica

Legarea statica se realizeaza cand S este o metoda statica. Toate operatiile


de mai jos se executa la compilare:
1. se determina clasa C a lui R (din declararea lui);
2. se verifica daca S este o metoda a clasei C (proprie sau mostenita);
3. Daca NU, se va genera un mesaj de eroare (eroare de sintaxa);
4. Daca DA, se verifica lista parametrilor actuali cu declararea metodei
(daca exista o lista de parametri actuali si daca limbajul este puternic
tipizat);
5. Daca totul este OK, se face legarea statica (mesajul se traduce prin
salt la punctul de intrare al metodei gasite).

Observatie

Unele limbaje (C++, Ada) permit existenta unor proceduri cu acelasi nume,
care difera prin lista argumentelor (se spune ca numele sunt supraincarcate).
In cazul lor, trebuie facuta o verificare in plus: metoda este identificata
dupa nume si dupa lista argumentelor.

B. Legarea dinamica

Legarea dinamica este proprie colectiilor de obiecte polimorfice. Prin urmare,


se trateaza mesaje de forma:

send(EC,S[,A])
sau
EC.S(A)

unde:
EC este un element al unei colectii polimorfice;
S este o metoda virtuala.

Actiunile se desfasoara in doua momente distincte: compilare si executie.

La compilare:
1. se determina C - clasa de baza a elementelor colectiei EC;
2. se verifica daca S este o metoda (virtuala) a lui C, identificandu-se
punctul de intrare in VMT pentru S;
109
3. Daca DA, se verifica sintaxa apelului;
4. Daca sintaxa apelului este corecta, se pune in cod adresa relativa a
metodei S din VMT a lui C.

La executie:
1. se determina C - clasa efectiva a EC (din EC avem adresa VMT; de fapt se
identifica VMT a clasei efective, din semnatura pusa in obiectul EC de
constructor);
2. se identifica S in VMT determinata la pasul anterior (1), pe baza adresei
relative a lui S din VMT, determinata la compilare;
3. din VMT se ia punctul de intrare determinat la pasul 2 si se continua la
fel ca la legarea statica.

In explicatiile de mai sus, am considerat ca, deoarece clasa de baza este cea
care defineste protocolul de comunicatie (prin metodele sale virtuale), in
tabelele de metode virtuale ale claselor derivate o metoda virtuala S va ocupa
aceeasi locatie (va avea aceeasi adresa relativa a unui nume de metoda in
toate VMT ale unei ierarhii de clase). Prin urmare, la compilare se va
determina locatia din tabela (adresa relativa) corespunzatoare metodei
virtuale apelate, iar la executie se va determina in care tabela de metode
virtuale se face cautarea.

Revenind la exemplul discutat in 4.1.4, putem spune acum ca pentru a se obtine


un calcul corect al pretului de vanzare pentru clasa OUA, metoda Pret_tr
trebuie declarata virtuala, iar clasa OUA trebuie sa aiba un constructor
specific (analog clasele FRAGILE si PERISABILE in cazul mostenirii multiple).
In aceste conditii, legarea metodei Pret_tr se va face dinamic, cautarea
metodei virtuale in arborele sau graful de mostenire incepand din clasa OUA.
Se observa inca odata utilizarea mostenirii, deosebirea (fata de legarea
statica) fiind ca aceasta cautare are loc si in adancime (fiecare cautare
incepe din clasa obiectului si nu din clasa in care apare pentru prima data
referirea la caracteristica cautata, in cazul nostru Pret_tr).

4.1.6. Pasi spre POO

Bertrand Meyer [Mey88] defineste conditiile pe care trebuie sa le


indeplineasca un produs program si limbaj de programare orientat pe obiecte.
In afara primei cerinte, care da criteriul de proiectare, toate celelalte se
refera la programare, in sensul restrans al termenului:

Primul nivel corespunde observatiei: datele trebuie sa ofere criteriul


fundamental de structurare:

Nivelul 1 (Structura modulara bazata pe obiecte)

Sistemele sunt modularizate pe baza structurilor de date proprii,


sau
Proiectarea programelor are drept criteriu datele folosite si nu
functiile pe care programele trebuie sa le realizeze.

Urmatorul pas realizeaza conectarea cu TAD:

110
Nivelul 2 (Abstractizarea datelor)

Obiectele trebuie descrise ca implementari ale TAD.

Al treilea pas este de natura mai putin conceptuala si mai mult practica,
reflectand o cerinta importanta de implementare: cum se creeaza obiectele.
Programatorii nu trebuie sa se preocupe de alocarea sau dealocarea memoriei
pentru obiecte:

Nivelul 3 (Gestiunea automata a memoriei)

Obiectele neutilizate trebuie dealocate de sistemul de baza (substrat)


al limbajului, fara interventia programatorului.

Urmatorul pas este cel care face o separare clara a limbajelor bazate pe
obiecte de restul lumii. Se poate spune ca ecuatia de definire a acestor
limbaje este identitatea:

clasa = tip de date.

Nivelul 4 (Clase)

Orice tip de date non-simplu este un modul si orice modul de nivel


inalt este un tip de date.

Calificatorul "non-simplu" face posibila pastrarea tipurilor de date


predefinite, care nu sunt vazute ca module; cuvantul "nivel inalt" permite
existenta unitatilor de structurare a unui program (procedurile) care nu sunt
tipuri.

Urmatorul pas este o consecinta naturala a celui precedent: daca tipurile de


date se identifica cu modulele, suntem tentati sa identificam mecanismele de
reutilizare oferite de ambele concepte:
- pe de o parte, posibilitatea unui modul de a referi direct entitati
definite in alt modul;
- pe de alta parte, conceptul de subtip, prin care se poate defini un nou
tip adaugand proprietati noi unui tip existent (ca un subdomeniu Integer
din Pascal, subtip al lui Integer cu precizarea limitei inferioare si
superioare a domeniului sau).

Nivelul 5 (Mostenire)

O clasa poate fi definita ca extensie sau restrictie a alteia.

Tehnicile de mai sus deschid posibilitatea folosirii polimorfismului si


legarii dinamice:

Nivelul 6 (Polimorfism si legare dinamica)

Entitatile unui program trebuie sa poata sa refere obiecte din mai


multe clase, iar operatiile trebuie sa aiba realizari diferite in
clase diferite.
111
Urmatorul si ultimul pas extinde notiunea de mostenire pentru a permite
reutilizarea in mai multe contexte. Aceasta inseamna mostenirea multipla.

Nivelul 7 (Mostenirea multipla si repetata)

Trebuie sa se poata declara clase care sa mosteneasca de la mai multi


parinti si/sau de mai multe ori de la aceeasi clasa.

4.2. PROGRAMARE ORIENTATA PE OBIECTE IN TURBO PASCAL


4.2.1. Clase si obiecte in Turbo Pascal. Terminologie
4.2.2. Mostenire. Obiecte statice
4.2.3. Constructori si destructori. Metode statice si virtuale.
Legare statica si dinamica. Implementarea obiectelor
4.2.4. Proiectarea si implementarea metodelor virtuale
4.2.5. Clase abstracte
4.2.6. Colectii de obiecte polimorfice

4.2.1. Clase si obiecte in Turbo Pascal. Terminologie

Turbo Pascal (TP) este in acelasi timp un mediu si un limbaj de programare


puternic tipizat, ce poseda o extensie POO incepand de la versiunea 5.5.
Fiind un mediu de programare binecunoscut utilizatorilor de PC-uri, Turbo
Pascal este accesibil incepatorilor si capabil sa ofere suficiente
instrumente pentru realizarea de aplicatii profesionale. In plus, exista
un mare numar de carti si articole de specialitate legate de el. Deoarece
limbajul Pascal este folosit pe scara larga in procesul de invatare a
programarii calculatoarelor (in scoli si universitati), Turbo Pascal
cunoaste o mare popularitate in mediile academice, fiind, prin extensia POO,
un mijloc relativ facil de familiarizare cu conceptele programarii orientate
pe obiecte. Din punctul de vedere conceptual, extensia POO din Turbo Pascal
se inspira din limbajele C++ si Object Pascal.

La prima vedere, ar putea apare o confuzie in notatii. In TP, o clasa este


de tipul predefinit 'object' si se defineste (in versiunea 5.5) cu ajutorul
mecanismului 'type' (propriu limbajului Pascal), in forma:

type nume_clasa = object[(nume_superclasa)]


declaratii de campuri { publice }
specificari de metode
end;

Conceptual, o clasa este tratata ca un tip virtual de date, pentru ea fiind


definite campurile (ce formeaza domeniul TVD) si operatiile (metodele).
Definitia de mai sus corespunde partii de interfata a unui TVD, ea trebuind
completata cu partea de implementare a metodelor. La implementare, antetul
unei metode este calificat cu numele clasei:

procedure nume_clasa.nume_metoda[(parametri_formali)];
sau
function nume_clasa.nume_metoda[(parametri_formali)] : tip;
112
Instantele unei clase, numite obiecte, sunt declarate ca variabile de tip
nume_clasa:

var ob1,ob2 : nume_clasa;

Declaratia de mai sus corespunde unor obiecte statice (alocate in segmentul


de date sau in stiva de executie). Folosind pointerii, se poate lucra si cu
obiecte dinamice, alocate in heap:

type pointer_nume_clasa = ^nume_clasa;


var pob1,pob2 : pointer_nume_clasa;

Apelul unei metode se face printr-un mesaj de forma:

ob1.nume_metoda(lista_de_parametri_actuali);
sau

pob1^.nume_metoda(lista_de_parametri_actuali);

in care ob1 (respectiv pob1^) este receptorul, iar numele metodei este
selectorul.

Prin definitia unei clase nume_clasa se specifica si interfata clasei


respective cu mediul extern. Regulile de vizibilitate permit ca atat
campurile cat si metodele sa fie vizibile (deci utilizabile) in exterior.
Daca pentru metode acest lucru este normal, accesul din exterior la campuri
(inclusiv posibilitatea modificarii valorii lor) incalca una din regulile
de baza ale abstractizarii datelor:

modificarea valorii campurilor unui obiect trebuie sa se faca numai


prin operatiile proprii obiectului respectiv.

Pentru inlaturarea acestui neajuns, in versiunea 6.0 a limbajului Turbo


Pascal exista posibilitatea declararii de metode si campuri private, ce
sunt invizibile in exterior. Campurile private pot fi referite numai in
partea de implementare a metodelor (publice sau private) ale obiectului,
iar metodele private servesc la implementarea celor publice. Schema de
definire a unei clase de obiecte in Turbo Pascal 6.0 este:

type nume_clasa = object[(nume_superclasa)]


declaratii de campuri publice
specificari de metode publice
private
declaratii de campuri private
specificari de metode private
end;

Pentru protejarea eficienta a campurilor unui obiect, se recomanda ca ele


sa fie declarate in zona 'private'. Dezavantajul acestei metode de
proiectare a claselor de obiecte consta in aceea ca utilizatorul nu va avea
acces la aceste campuri, nici macar pentru a citi valoarea lor. Acest
113
impediment poate fi surmontat prin adaugarea de metode publice, care sa
furnizeze valoarea acestor campuri private. In acest fel, campurile devin
accesibile in citire (read only), dar valoarea lor nu se va putea modifica
decat prin metode. In specificarea 1 este definita clasa Locatie, cu
urmatoarele atribute:

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
ÄÄÄÄÄÄÄ¿
³ campuri: X,Y (private) ³
³ metode: Init {modifica campurile X si Y ale obiectului ³
³ la valorile specificate prin aX si aY} ³
³ XCoord {intoarce valoarea abscisei X} ³
³ YCoord {intoarce valoarea ordonatei Y} ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
ÄÄÄÄÄÄÄÙ

Type { unit-ul ULoc.PAS }


Locatie = Object
procedure Init(aX,aY:Integer);
function XCoord : Integer;
function YCoord : Integer;
private
X,Y : Integer;
End;

Specificarea 1. Campuri private si metode publice

Metodele publice XCoord si YCoord au fost concepute tocmai pentru a permite


mediului extern (clientului) sa cunoasca valoarea coordonatelor obiectului.

4.2.2. Mostenire. Obiecte statice

In Turbo Pascal mostenirea este simpla. Prin mostenire, campurile


superclasei se pastreaza si nu pot fi redefinite. In schimb, metodele unei
superclase se pot redefini in conformitate cu comportamentul specific al
subclasei careia le apartin. In specificarea 2, se considera clasa
Punct, descendenta a clasei Locatie definita anterior. Obiectul geometric
punct (in plan) poseda doua coordonate si un atribut de vizibilitate: el
poate fi desenat (este vizibil) sau nu.

type { unit-ul UPctS.PAS }


Punct = Object(Locatie)
procedure Init(aX,aY:Integer); { rescrie Locatie.Init }
procedure Deseneaza; { noua }
procedure Ascunde; { noua }
function EVizibil : Boolean; { noua }
procedure MutaIn(nouX,nouY:Integer); { noua }
procedure Translateaza(pas:Integer); { noua }
procedure Roteste(centru:Locatie; unghi:Integer); { noua }
function Caracteristici: String; { noua }
procedure Listeaza; { noua }
private
114
Vizibil : Boolean; { nou }
End;

Specificarea 2. Mostenire in Turbo Pascal

Atributele clasei Punct sunt urmatoarele:

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
ÄÄÄÄÄ¿
³ campuri:X,Y (private si mostenite de la Locatie) ³
³ Vizibil (privat) ³
³ metode: Init {redefineste metoda Init de la Locatie ³
³ initializeaza in plus campul Vizibil} ³
³ XCoord, YCoord {mostenite de la Locatie} ³
³ Deseneaza {desenarea obiectului pe ecran} ³
³ Ascunde {stergerea obiectului de pe ecran} ³
³ EVizibil {intoarce valoarea campului Vizibil} ³
³ MutaIn {muta obiectul in locatia nouX, nouY} ³
³ Translateaza {efectueaza o translatie, marind ³
³ valorile coordonatelor cu valoarea pas} ³
³ Roteste {roteste obiectul in jurul locatiei ³
³ centru, cu unghiul unghi} ³
³ Caracteristici {intoarce un sir de caractere ce ³
³ contine valorile coordonatelor} ³
³ Listeaza {afiseaza caracteristicile obiectului} ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
ÄÄÄÄÄÙ

In aceasta specificare, metoda Init a clasei Punct redefineste metoda


omonima de la superclasa Locatie, concretizand comportamentul specific al
descendentului, iar metoda EVizibil permite cunoasterea valorii campului
Vizibil in mediul extern.

Type
Cerc = Object(Punct)
procedure Init(aX,aY,aRaza:Integer);
procedure Deseneaza;
procedure Ascunde;
procedure MutaIn(nouX,nouY:Integer);
procedure Translateaza(pas:Integer);
procedure Roteste(centru:Locatie; unghi:Integer);
procedure Mareste(CuCit:Integer);
function Raza : Integer;
function Caracteristici : String;
private
R : Integer;
End;

Specificarea 3. Rafinarea mostenirii

O ilustrare mai cuprinzatoare a redefinirii metodelor este prezentata in


specificarea 3, corespunzatoare clasei Cerc, descendenta a clasei Punct.
115
Conceptual, obiectul geometric cerc are ca invariant un obiect geometric
punct, numit centrul cercului, prin urmare mostenirea este naturala. In
afara centrului, cercul poseda o raza (atribut specific). Atributele clasei
Cerc sunt urmatoarele:

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
ÄÄÄÄÄ¿
³ campuri: X,Y,Vizibil (private si mostenite de la Punct) ³
³ R (raza, privat) ³
³ metode: Init {redefineste metoda Init de la Punct ³
³ initializeaza in plus campul R} ³
³ XCoord {mostenita de la Locatie} ³
³ YCoord {mostenita de la Locatie} ³
³ EVizibil {mostenita de la Punct} ³
³ Listeaza {mostenita de la Punct} ³
³ Deseneaza {trasarea obiectului pe ecran ³
³ redefineste metoda omonima de la Punct} ³
³ Ascunde {stergerea obiectului de pe ecran ³
³ redefineste metoda omonima de la Punct} ³
³ MutaIn {muta obiectul in locatia definita ³
³ de coordonatele nouX, nouY ³
³ redefineste metoda omonima de la Punct} ³
³ Translateaza {efectueaza o translatie, marind ³
³ valorile coordonatelor cu pas ³
³ redefineste metoda omonima de la Punct} ³
³ Roteste {roteste obiectul in jurul locatiei ³
³ centru, cu unghiul unghi ³
³ redefineste metoda omonima de la Punct} ³
³ Mareste {mareste raza cercului cu ³
³ valoarea parametrului pas} ³
³ Raza {intoarce valoarea campului R} ³
³ Caracteristici {intoarce un sir de caractere ce ³
³ contine valorile coordonatelor si razei} ³
³ redefineste metoda omonima de la Punct} ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
ÄÄÄÄÄÙ

Metoda Raza permite cunoasterea valorii razei cercului in mediul extern.


Metodele Init, Deseneaza, Ascunde, MutaIn, Roteste si Translateaza sunt
redefiniri ale metodelor omonime ale superclasei Punct, ele concretizand
comportamentul specific al clasei Cerc, diferit de cel al parintelui sau
Punct. Astfel, desenarea unui cerc inseamna desenarea centrului si a
circumferintei; stergerea trebuie sa le ascunda. Mutarea unui cerc (de fapt
mutarea centrului sau) presupune stergerea vechii imagini, mutarea
centrului si desenarea noii imagini a obiectului. In mod analog se
procedeaza si in cazul translatiei sau rotatiei obiectului Cerc. Acest
lucru va rezulta cu exactitate din implementarea claselor Locatie, Punct si
Cerc, care este data in continuare. Se observa gradul ridicat de
simplitate al algoritmilor folositi.

Exemplul 1. Unit-ul ULOC.PAS: Implementarea metodelor clasei Locatie

116
Exemplul 2. Unit-ul UPctS.PAS: Implementarea metodelor clasei Punct

Exemplul 3. Unit-ul UCercS.PAS: Implementarea metodelor clasei Cerc

In implementarea metodelor claselor mai sus definite s-au folosit proceduri si


functii din doua unit-uri: Graph (unit sistem, ce contine extensia grafica a
limbajului Turbo Pascal) si UGraph (unit utilizator). Pentru o mai usoara
accesare a claselor enumerate, s-au conceput trei unit-uri Turbo Pascal: ULoc
pentru clasa Locatie, UPctS pentru clasa Punct si UCercS pentru clasa Cerc.
Fiecare unit contine in partea de interfata definitia clasei, iar in partea
de implementare textele sursa ale metodelor.

Utilizarea instantelor claselor Cerc si Punct este prezentata in programele


ExObSt1.PAS si ExObSt2.PAS. Primul dintre ele lucreaza cu obiecte statice, iar
al doilea cu obiecte dinamice.

Obiectele din clasele Punct si Cerc reactioneaza diferit la mesajele


Deseneaza, Ascunde, MutaIn, Roteste, Translateaza, Listeaza. Numele metodelor
apelate este acelasi pentru o actiune data, indiferent de obiect (punct sau
cerc), sugerand actiunea ce trebuie efectuata. Evident, metodele vor avea
implementari diferite pentru cele doua clase Punct si Cerc. Ar fi interesant
de vazut daca se poate lucra cu colectii de astfel de obiecte polimorfe,
colectii care contin pointeri la obiectele respective. Un exemplu simplu este
programul ExObSt3.PAS, ce reprezinta transpunerea programului ExObSt2.PAS,
colectia considerata aici fiind un tablou A de doua elemente, cu elementul de
tablou de tip PointerLaPunct.

Din pacate, desi programele ExObSt2 si ExObSt3 sunt echivalente semantic


(cel putin la prima vedere), la executie ele se comporta diferit, in sensul
ca al doilea obiect (referit de A[2]), chiar daca este instanta a clasei Cerc,
se comporta ca o instanta a clasei Punct. "Necazul" este generat de
instructiunea de atribuire:

A[2] := PC;

si nu poate fi evitat nici printr-un subterfugiu de forma:

New(A[2]); {alocarea obiectului dinamic referit de A[2]}


A[2]^.Init(150,230,50);

deoarece protesteaza compilatorul (A[2] este PointerLaPunct, iar metoda Init


din urmatoarea instructiune apartine clasei Cerc). Acest program este o prima
tentativa de lucru cu obiecte polimorfice. Reamintim ca aceste obiecte sunt
instante ale unor clase diferite, untre care exista relatia de mostenire si
care pot sa raspunda la aceleasi mesaje. Se pune intrebarea:

Cum se rezolva lucrul cu obiecte polimorfice in Turbo Pascal?

Analizand textele sursa ale metodelor clasei Cerc ce sunt redefiniri ale
metodelor clasei Punct, constatam ca MutaIn, Translateaza, Roteste si Listeaza
sunt identice din punctul de vedere al apelului (este clar ca, semantic,
actiunile sunt diferite). Se pune intrebarea:
117
Nu s-ar putea ca metodele amintite sa fie mostenite de la clasa Punct?

Raspunsul la aceste doua intrebari este afirmativ. In Turbo Pascal, ca in


toate limbajele orientate pe obiecte, acest lucru se realizeaza prin actiunea
conjugata a celor trei mecanisme discutate in sectiunea 4.1.5: constructorii
si destructorii, metodele virtuale si legarea dinamica. Pe langa acestea si
in stransa legatura cu folosirea constructorilor, s-a prevazut si o extensie
a procedurilor standard New si Dispose. Sa le discutam pe rand.

4.2.3. Constructori si destructori. Metode statice si virtuale.


Legare statica si dinamica. Implementarea obiectelor

In exemplele discutate pana acum, mostenirea a fost folosita pentru campuri


(clasele Punct si Cerc mostenesc de la Locatie campurile X si Y) si pentru
metode (ale superclasei, care nu sunt redefinite in subclase: XCoord, YCoord).
Metode omonime (Deseneaza, Ascunde, MutaIn, Translateaza, Roteste) au fost
definite in clasa Punct si au fost redefinite (toate) in clasa derivata, Cerc.

Mai mult, fiecare apel de metoda este rezolvat la compilare, in sensul ca se


deduce, din tipul receptorului, clasa a carei metoda este apelata. Din acest
motiv, in programul ExObSt3 nu functioneaza polimorfismul dinamic: desi PC
este pointer la cerc si obiectul referit de PC este initializat ca atare, prin
atribuirea A[2] := PC are loc o constrangere (elementele tabloului A sunt
pointeri la punct) si in continuare compilatorul va considera ca A[2] refera
un punct, selectand in consecinta metodele clasei Punct la intalnirea
receptorului A[2]^. Aceasta maniera de legare a metodei la tipul clasei
instantei a fost numita in 4.1.5.2 legare statica.

Toate metodele prevazute in exemplele discutate pana acum sunt metode statice
(implicit, orice metoda este statica in Turbo Pascal). Cand un mesaj contine
apelul unei metode statice cu selectorul nume_metoda, apelul se poate rezolva
la compilare prin parcurgerea arborelui de mostenire de la clasa obiectului
receptor (cunoscuta la compilare) spre radacina arborelui. Prima metoda gasita
ce are ca selector nume_metoda va fi metoda apelata si acest apel se rezolva
in mod obisnuit (ca apelul unei proceduri din Pascal Standard). Algoritmic,
legarea statica pentru mesaje de forma

ob1.nume_metoda_statica(lista_de_parametri_actuali);
sau
pob1^.nume_metoda_statica(lista_de_parametri_actuali);

inseamna:

1. Se determina clasa receptorului (ob1 sau pob1^), cunoscuta la compilare


(ob1 sau pob1 sunt declarate ca variabile);
2. Se selecteaza din dictionarul de metode al clasei respective adresa
punctului de intrare al metodei specificate;
3. Se pune in cod apelul metodei respective (salt la punctul de intrare
aferent procedurii in cauza).

Din punctul de vedere al implementarii, pentru o instanta a unei clase se


118
aloca un numar de octeti egal cu suma lungimilor de reprezentare ale
campurilor instantei (la fel ca la TD record). In cazul exemplelor de mai sus,
se vor aloca 5 octeti pentru o instanta a clasei Punct:

SizeOf(Punct) = SizeOf(X) + SizeOf(Y) + SizeOf(Vizibil),

respectiv 7 octeti pentru o instanta a clasei Cerc:

SizeOf(Cerc) := SizeOf(Punct) + SizeOf(R).

Prin urmare, in timpul executiei, o instanta nu-si "cunoaste" clasa sa. Acest
lucru este valabil in situatia in care clasele nu poseda metode constructor,
toate exemplele anterioare fiind din aceasta categorie. Rolul unui constructor
este in primul rand acela de a atasa fiecarei instante "semnatura" clasei la
care aceasta apartine. In Turbo Pascal acest lucru se realizeaza atasand
inca doi octeti reprezentarii fiecarei instante. In spatiul astfel rezervat,
constructorul va pune un pointer la tabela de metode virtuale (VMT) a clasei
la care apartine instanta. Informatia de instantiere este accesibila numai
in citire, ea putand fi modificata doar printr-un alt constructor. Reamintim
ca, pe langa aceasta functie (principala in POO), un constructor are de
obicei sarcina de a initializa campurile instantei (rolul metodelor Init din
exemplele discutate) sau de a aloca memorie dinamica pentru instanta sau
pentru acele campuri ale acesteia ce refera variabile dinamice.

Distrugerea informatiei de instantiere se face printr-o alta metoda speciala,


numita destructor in Turbo Pascal. Pe langa aceasta sarciba, un destructor
poate realiza dealocarea memoriei dinamice alocata de constructor.

O clasa poate avea mai multi constructori si destructori. Specificarea lor


se face in definitia clasei, in forma:

constructor nume_constructor[(parametri_formali)];
si
destructor nume_destructor[(parametri_formali)]; [virtual;]

iar implementarea lor se face in aceeasi maniera ca si implementarea


celorlalte metode, antetele fiind:

constructor nume_clasa.nume_constructor[(parametri_formali)]
respectiv
destructor nume_clasa.nume_destructor[(parametri_formali)];

Declararea metodelor virtuale se face atasand cuvantul cheie 'virtual' la


specificarea acestora, in maniera:

procedure nume_metoda[(parametri_formali)]; virtual;


sau
function nume_metoda[(parametri_formali)] : tip; virtual;

Inainte de a discuta rolul metodelor virtuale, sa precizam restrictiile de


proiectare si utilizare a lor, date sub forma urmatoarelor reguli (deduse din
regulile R1-R6 prezentate in sectiunea 4.1):
119
V1. Orice clasa ce poseda metode virtuale trebuie sa aiba cel putin un
constructor.
V2. In programul utilizator, o instanta isi va apela constructorul inaintea
oricarui apel de metoda virtuala.
V3. Orice specializare (rescriere) a unei metode virtuale in descendentii
clasei trebuie sa fie tot virtuala;
V4. Orice specializare (rescriere) a unei metode virtuale in descendentii
clasei trebuie sa aiba exact acelasi antet (acelasi nume si aceeasi
lista de parametri).
V5. Constructorii nu pot fi virtuali.

Pentru fiecare clasa ce poseda metode virtuale, compilatorul Turbo Pascal


creeaza o tabela de metode virtuale (Virtual Method Table, VMT), ce contine:
- dimensiunea unei instante a clasei;
- numarul de metode virtuale;
- pointeri la codul fiecarei metode (adrese de puncte de intrare).

Asadar, VMT este proprie clasei. Am vazut mai inainte ca fiecare instanta
creata cu un constructor poarta in ea "semnatura" clasei prototip, care nu
este altceva decat adresa VMT.

Apartenenta unei instante 'ob' la o clasa data 'nume_clasa' se poate testa


in Turbo Pascal astfel:

TypeOf(ob) = TypeOf(nume_clasa);

iar faptul ca doua obiecte ob1 si ob2 sunt instantee ale aceleiasi clase prin:

TypeOf(ob1) = TypeOf(ob2);

Am vazut in 4.1.5 ca metodele virtuale servesc la modelarea comportamentului


polimorfic al obiectelor. Daca apelul metodelor statice se rezolva la
compilare, apelul de metode virtuale se rezolva numai la executie. Astfel,
concretizand consideratiile prezentate in 4.1.5, un mesaj de forma:

ob1.nume_metoda_virtuala(lista_de_parametri_actuali);
sau
pob1^.nume_metoda_virtuala(lista_de_parametri_actuali);

se rezolva la executie in urmatoarele etape:

1. Se determina clasa receptorului (ob1 sau pob1^), de fapt adresa VMT


corespunzatoare clasei sale, din informatia existenta in instanta
respectiva;
2. Se selecteaza din VMT adresa codului metodei specificate (adresa
punctului de intrare);
3. Se lanseaza in executie respectiva metoda (se face salt la punctul de
intrare corespunzator acesteia).

Spre deosebire de legarea statica, unde conteaza doar tipul clasei instantei
(cunoscut la compilare), aceasta maniera de legare a selectorului de receptor
120
in timpul executiei a fost numita in 4.1.5.2 'legare dinamica'. Accentuam
faptul ca se apeleaza metoda adecvata instantei, in conformitate cu atributele
ei din timpul executiei, si nu metoda adecvata clasei statice a instantei,
declarata la compilare prin 'var'. Un obiect 'o', instanta a unei clase 'c',
poate fi considerat in dOUA ipostaze distincte:

a) la compilare, cand i se cunoaste clasa statica (declarata cu 'var o:c;'


in Turbo Pascal); in acest moment se pot rezolva toate apelurile de
metode statice 'ms' prin: 'o.ms(...);'
b) la executie, cand i se cunoaste clasa efectiva 's_c' (ca urmare a
apelului unui constructor, de exemplu 'Init' al clasei 's_c', care va
trebui sa fie o extensie a clasei 'c' (in arborele de mostenire al
clasei statice 'c'): 'o.Init(...);' dupa acest moment (al apelului de
constructor), orice apel de metoda virtuala 'mv' cu receptorul 'o':
'o.mv(...);' va tine cont de clasa efectiva 's_c' si nu de clasa statica
'c'.

Ipostaza a) corespunde legarii statice, pe cand b) corespunde legarii


dinamice. Cunoscand aceste lucruri, se poate explica necesitatea regulilor
V1-V5 enuntate anterior:

V1. Prezenta imperativa a constructorului pentru clasele ce poseda metode


virtuale: daca nu exista constructor, instanta nu va contine spatiu
pentru adresa VMT, deci nu se va putea cunoaste clasa efectiva a
instantei;
V2. Apelul constructorului unei instante 'o' trebuie sa preceada apelul
oricarei metode virtuale a lui o: numai dupa apelul constructorului se
cunoaste clasa efectiva a instantei;
V3. Orice rescriere a unei metode virtuale trebuie sa fie la randul ei
metoda virtuala: absenta acestei reguli ar provoca combinarea celor doua
modalitati de legare a metodelor, complicand implementarea legarii
statice si dinamice;
V4. O metoda virtuala cu numele 'mv' trebuie sa aiba aceeasi lista de
parametri in toate clasele din arborele de mostenire in care ea este
redefinita: acest lucru este impus de caracterul puternic (static)
tipizat al Pascal-ului (antetul metodei din clasa statica, de declarare,
asupra careia se face verificarea la compilare, trebuie sa fie identic
cu antetul metodei din clasa efectiva, dinamica care se va apela la
executie).
V5. Constructorii sunt proprii fiecarei clase, deci nu pot fi virtuali; doua
clase diferite au tabele de metode virtuale diferite.

4.2.4. Proiectarea si implementarea metodelor virtuale

Vom relua specificatiile claselor Punct si Cerc in care vom folosi metode
virtuale. Conform precizarilor V1-V5, aceasta inseamna ca in fiecare clasa vor
fi prevazute:
- un constructor;
- un destructor (necesar pentru dealocarea obiectelor dinamice);
- metode virtuale.
Inainte de a discuta noile specificatii, ne vom referi la lucrul cu obiecte
dinamice in Turbo Pascal.
121
Un obiect dinamic este alocat in memoria dinamica (heap), fiind referit de un
pointer ce contine adresa lui. Programele ExObSt2 si ExObSt3 contin exemple de
obiecte dinamice din clasele Punct si Cerc. Uzual, in situatia:

type pointer_nume_clasa = ^nume_clasa;


var pob : pointer_nume_clasa;

crearea unui obiect dinamic se face cu ajutorul unei proceduri de initializare


(numita Init in exemplul nostru), in maniera:

New(pob); {se aloca memorie dinamica pentru obiectul


dinamic referit de pob}
pob^.Init(...); {se apeleaza metoda Init}

In versiunea 6.0, Turbo Pascal introduce extensii ale procedurilor standard


New si Dispose, care se pot apela in cazul existentei constructorilor,
respectiv destructorilor. Astfel, extensiile au sintaxa (Init este
constructorul clasei nume_clasa, iar Done este destructorul acesteia):

New(pob,Init(...));

respectiv

Dispose(pob,Done(...));

Actiunile efectuate de procedura standard New in exemplul de mai sus sunt:


1. Se aloca in memoria dinamica spatiu pentru obiectul dinamic referit de
pob, in conformitate cu specificatiile clasei nume_clasa;
2. Se apeleaza cu pob^ ca receptor constructorul Init, care:
2.1. pune in instanta pob^ adresa VMT a clasei nume_clasa;
2.2. initializeaza (si eventual aloca dinamic unele dintre) campurile
obiectului pob^.
Reciproc, procedura standard Dispose (versiunea extinsa) realizeaza:
1. Apelarea destructorului Done (cu pob^ ca receptor), care:
1.1. reseteaza pe valorile implicite (si eventual dealoca unele dintre)
campurile pob^;
1.2. distruge in instanta pob^ adresa VMT a clasei nume_clasa;
2. Dealocarea din memoria dinamica a spatiului pentru obiectul pob^.

Procedura standard New mai are o extensie, cu sintaxa:

pob := New(pointer_nume_clasa,Init(...));

caz in care New va intoarce un pointer de tip pointer_nume_clasa, efectuand


actiunile precizate anterior. Bineinteles, Init trebuie sa fie constructorul
clasei nume_clasa. Se observa ca in aceasta situatie apelul lui New are
sintaxa apelului de functie si nu de procedura.

Revenind la specificatiile claselor Punct si Cerc si tinand cont de


observatiile facute, acestea se vor scrie astfel:

122
type
PointerLaPunct = ^Punct;
Punct = Object(Locatie)
constructor Init(aX,aY:Integer);
destructor Done; virtual;
procedure Deseneaza; virtual;
procedure Ascunde; virtual;
function EVizibil : Boolean;
procedure MutaIn(nouX,nouY:Integer);
procedure Translateaza(pas:Integer);
procedure Roteste(centru:Locatie; unghi:Integer);
function Caracteristici: String; virtual;
procedure Listeaza(c,l:Integer);
private
Vizibil : Boolean;
End;
PointerLaCerc = ^Cerc;
Cerc = Object(Punct)
constructor Init(aX,aY,aRaza:Integer);
procedure Deseneaza; virtual;
procedure Ascunde; virtual;
procedure Mareste(CuCit:Integer);
function Raza : Integer;
function Caracteristici : String; virtual;
private
R : Integer;
End;

Specificarea 4. Metode virtuale, constructori si destructori

Implementarea metodelor este data in unit-urile UPctD.PAS si UCercD.PAS.

Exemplul 4: unit-urile UPctD.PAS si UCercD.PAS

Comparand specificarea 4 cu specificarea 3 (pentru clasa Cerc), se constata un


grad mare de reutilizare a codului, in sensul ca metodele MutaIn, Roteste,
Translateaza si Listeaza nu mai apar ca metode proprii ale acestei clase, ele
fiind mostenite de la clasa parinte, Punct. Deoarece toate aceste metode
apeleaza la randul lor metode virtuale, legarea acestora se va face dinamic.
De exemplu, in situatia:

var P : Punct;
C : Cerc;

mesajul P.MutaIn(100,200) va avea ca efect (programul ExObSt2):

P.Ascunde; {Punct.Ascunde}
Locatie.Init(100,200);
P.Deseneaza {Punct.Deseneaza}

pe cand mesajul C.MutaIn(100,200) se va traduce astfel:

123
C.Ascunde; {Cerc.Ascunde}
Locatie.Init(100,200);
C.Deseneaza {Cerc.Deseneaza}

Mai mult, cand se lucreaza cu obiecte polimorfice, ca in situatia:

var A:Array[1..2] of PointerLaPunct;

si
A[1] := New(PointerLaPunct,Init(100,200));
A[2] := New(PointerLaCerc, Init(150,230,50));

mesajul A[1]^.MutaIn(100,200) va avea ca efect:

A[1]^.Ascunde; {Punct.Ascunde}
Locatie.Init(100,200);
A[1]^.Deseneaza {Punct.Deseneaza}

pe cand mesajul A[2]^.MutaIn(100,200) se va traduce astfel:

A[2]^.Ascunde; {Cerc.Ascunde}
Locatie.Init(100,200);
A[2]^.Deseneaza {Cerc.Deseneaza}

Se observa clar, in cazul obiectelor polimorfice, maniera in care se


stabileste la executie metoda virtuala adecvata instantei receptorului.

Programele ExObSt1, ExObSt2 si ExObSt3 prezentate anterior vor functiona


corect in prezenta noilor specificatii ale clasei Cerc. In fond, nu este vorba
de modificarea algoritmilor care implementeaza metodele acestei clase, ci de
o utilizare mai eficienta a mostenirii prin utilizarea metodelor virtuale.
De aceasta data si programul ExObSt3 va functiona corect (exemplul are
numele ExObSt4).

4.2.5. Clase abstracte

In proiectarea ierarhiei claselor, exista unele dintre ele care nu vor poseda
instante. Rolul lor este o mai buna structurare a acestei ierarhii, ele
colectand caracteristicile comune (campuri si metode) pentru mai multe clase
descendente. Clasa Locatie din specificarea 1 este un exemplu de clasa
abstracta. Rolul ei este definirea caracteristicilor (coordonatele, metodele
de initializare si de accesare a acestora) comune pentru clasele descendente.
Toate aceste clase, avand clasa Locatie in arborele lor de mostenire, vor
poseda atributele respective.

Pe de alta parte, Locatie poate fi considerata o clasa abstracta si deoarece


comportamentul ei nu este suficient de bogat pentru a fi utilizat de
instantele sale. Astfel, Locatie nu poseda metode de vizualizare sau
manipulare, cum poseda descendentul sau direct, clasa Punct.

4.2.6. Colectii de obiecte polimorfice

124
4.2.6.1. Liste

In programul 4.2.3 este prezentat un exemplu simplu de colectie de obiecte


polimorfice, depozitate in tabloul A cu doua elemente de tip PointerLaPunct.
O alta maniera de implementare a unei colectii de obiecte va folosi o lista
simplu inlantuita. Intreaga colectie poate fi considerata ca o noua clasa,
asupra careia se prelungesc operatiile proprii obiectelor ei. In unit-ul
UListF.PAS este prezentata specificarea si implementarea clasei Lista.

Exemplul 5: Unit-ul UListF.PAS

In afara claselor mentionate mai inainte, in acest exemplu se folosesc inca


doua:
Arc - clasa derivata a clasei Cerc, a carei specificare si implementare
este prezentata in unit-ul UArc;
Segment - clasa derivata a clasei Punct, prezentata in unitul USeg.

Exemplul 4.2.6: Unit-ul UArc.PAS

Exemplul 4.2.7. Unit-ul USeg.PAS

Metodele clasei Lista se impart in doua categorii:


- metode proprii listelor:
Init - constructor, creeaza o lista vida;
Done - destructor, dealoca elementele listei;
Adauga - adauga un nou element la coada listei;
- metode ce vor reflecta comportamentul obiectelor memorate
in elementele listei:
Listeaza - listeaza caracteristicile fiecarui element din lista;
Deseneaza - deseneaza fiecare obiect din lista;
Ascunde - ascunde fiecare obiect din lista;
Roteste - roteste fiecare obiect din lista cu acelasi unghi, in jurul
obiectului precedent din lista (prin parcurgerea circulara a
listei);
Translateaza - translateaza fiecare obiect din lista cu aceeasi marime;
Mareste - mareste raza fiecarui obiect rotund din lista cu aceeasi
marime.

A doua categorie de metode foloseste din plin apelul de metode virtuale, fie
direct, fie indirect. O atentie speciala trebuie acordata metodei Mareste,
care nu este comuna tuturor claselor mentionate, fiind proprie numai claselor
Cerc si Arc (prototipuri de obiecte rotunde). In implementarea ei se foloseste
functia TypeOf pentru determinarea clasei efective a obiectului. O alta
solutie de implementare ar fi adaugarea metodei virtuale Mareste la clasa
Punct (la care ea n-ar face nimic sau eventual ar desena obiectul), caz in
care implementarea metodei Mareste a clasei s-ar face in aceeasi maniera ca
si metodele anterioare. Aceasta se poate realiza astfel:
- la clasa Punct se declara metoda virtuala Mareste, care se implementeaza
astfel:

Procedure Punct.Mareste(CuCit:Integer);
Begin
125
Deseneaza
End; { Mareste }

- la clasa Cerc se declara Mareste ca virtuala;


- la clasa Lista se modifica implementarea metodei Mareste astfel:

Procedure Lista.Mareste(CuCit:Integer);
Var
N : PointerLaNod;
PP : PointerLaPunct;
Begin
N := Ultimul;
While N <> Nil do
Begin
PC := N^.Element;
PC^.Mareste(CuCit)
N := N^.Precedentul
End;
End; { Mareste }

Lucrul cu obiecte polimorfe este ilustrat in programul ExObDi.PAS

4.2.6.2. Unitul UGraph

Pentru implementarea unor functii grafice specifice exemplelor discutate anterior, s-a proiectat unit-ul
UGraph, ce exporta o serie de astfel de operatii. Textul sursa este prezentat in unit-ul UGraph.PAS.

8. Grafica in Turbo Pascal

Elemente de discutie

8.1. Utilizarea ecranului in mod grafic


8.2. Unit-ul Graph
8.3. Desenarea in plan a figurilor din planul real

8.1. Utilizarea ecranului in mod grafic

Etape
- initializarea modului grafic (trecerea de la modul text la modul grafic)
- lucrul in modul grafic
- terminarea modului grafic (revenirea la modul text)

Memoria ecran: zona de memorie interna ce contine ceea ce se vede pe ecran


- text
- grafica

Caracteristicile modului de lucru grafic


- primitive grafice
- culori
- rezolutie
- placa grafica
126
- Borland Graphics Interface (BGI)

0 x GetMaxX-1
0ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ ³ ³
³ ³ ³
³ ³ ³
yÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ.P(x, y) ³
³ ³
³ ³
³ ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
GetMaxY-1

Pixel
- picture element
- coordonate (numere intregi)
- culoare

Fereastra fizica ecran (viewport)


- portiune din ecranul grafic in care se realizeaza desenul

0 u1 u2 GetMaxX-1
0ÚÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ ³ P1(u1, v1) ³ ³
v1ÅÄÄÄÄÄÄÄ ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» ³
³ º º ³
³ º º ³
³ º º ³
v2ÅÄÄÄÄÄÄÄ ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ ³
³ P2(u2, v2) ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
GetMaxY-1

- caracteristicile ferestrei fizice


- coordonatele aparat: (u, v) sunt relative la originea P1
- initial: fereastra fizica este tot ecranul

8.2. Unit-ul Graph


8.2.1. Terminologie
8.2.2. Clase de subprograme
8.2.3. Exemple de folosire
Acest unit contine:
- subprograme
- constante
- variabile
specifice modului de lucru grafic.

8.2.1. Terminologie
- ecranul grafic corespunde unui driver grafic
- driverul grafic
- corespunde placii grafice din calculator
127
- este specificat printr-o constanta simbolica cu urmatoarele valori

Constanta ³ Valoare/comentariu
ÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ
CurrentDriver ³ -128 pentru GetModeRange
Detect ³ 0 Provoaca autodetectie
CGA ³ 1
MCGA ³ 2
EGA ³ 3
EGA64 ³ 4
EGAMono ³ 5
IBM8514 ³ 6
HercMono ³ 7
ATT400 ³ 8
VGA ³ 9
PC3270 ³ 10

- modul grafic
- un driver grafic poate avea mai multe moduri grafice
- la un moment dat pentru driverul grafic existent se lucreaza
intr-un singur mod grafic
- modul grafic defineste
- rezolutia pe verticala si pe orizontala (GetMaxX, GetMaxY)
- numarul de culori (GetMaxColor)
- modul grafic se reprezinta printr-un numar (constanta simbolica)

Constanta ³ Valoare³ Rezolutie Constanta ³Valoare³ Rezolutie


ÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍ ÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍ
CGAC0 ³ 0 ³ 320 x 200 EGALo ³ 0 ³ 640 x 200
CGAC ³ 1 ³ 320 x 200 EGAHi ³ 1 ³ 640 x 350
CGAC2 ³ 2 ³ 320 x 200 ³ ³
CGAC3 ³ 3 ³ 320 x 200 EGA64Lo ³ 0 ³ 640 x 200
CGAHi ³ 4 ³ 640 x 200 EGA64Hi ³ 1 ³ 640 x 350
³ ³ ³ ³
MCGAC0 ³ 0 ³ 320 x 200 ATT400C0 ³ 0 ³ 320 x 200
MCGAC1 ³ 1 ³ 320 x 200 ATT400C1 ³ 1 ³ 320 x 200
MCGAC2 ³ 2 ³ 320 x 200 ATT400C2 ³ 2 ³ 320 x 200
MCGAC3 ³ 3 ³ 320 x 200 ATT400C3 ³ 3 ³ 320 x 200
MCGAMed ³ 4 ³ 640 x 200 ATT400Med ³ 4 ³ 640 x 200
MCGAHi ³ 5 ³ 640 x 480 ATT400Hi ³ 5 ³ 640 x 400
³ ³ ³ ³
EGAMonoHi ³ 3 ³ 640 x 350 IBM8514Lo ³ 0 ³ 640 x 480
HercMonoHi ³ 0 ³ 720 x 348 IBM8514Hi ³ 1 ³ 1024 x 768
³ ³ ³ ³
VGALo ³ 0 ³ 640 x 200 PC3270Hi ³ 0 ³ 720 x 350
VGAMed ³ 1 ³ 640 x 350 VGAHi ³ 2 ³ 640 x 480

- informatii de desenare
- culoarea de fond - culoarea fondului (GetBkColor)
- culoarea cernelii - culoarea cu care se deseneaza (GetColor)
- punctul curent (GetX, GetY) - punctul in care se afla
cursorul grafic
128
- pagini grafice
- pentru driverele EGA (256K), VGA si Hercules, memoria grafica se
imparte in mai multe pagini
- fiecare pagina este identificata printr-un numar
- pagina curenta este pagina care este afisata pe ecran (SetVisualPage)
- pagina activa este pagina in care se face desenarea (SetActivePage)
- pagina curenta si pagina activa nu sunt neaparat una si aceeasi
- la animatie se procedeaza astfel:
- se seteaza ca pagina activa alta pagina decat cea curenta si se
deseneaza in ea
- cand pagina este desenata, se declara ca pagina curenta
- se pot folosi mai multe pagini, care se declara pe rand pagini
curente
- culorile pentru modul curent se codifica prin numere intregi
prin urmatoarele constante simbolice

Culori intunecate: Culori deschise:


(Cerneala si fond) (Fond)
ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ
Black 0 DarkGray 8
Blue 1 LightBlue 9
Green 2 LightGreen 10
Cyan 3 LightCyan 11
Red 4 LightRed 12
Magenta 5 LightMagenta 13
Brown 6 Yellow 14
LightGray 7 White 15

- paleta de culoare: un tabel de culori care contine codurile


acestora, intr-o anumita ordine
- numarul de culori depinde de driverul grafic si de modul grafic
- se poate modifica ordinea culorilor in paleta - se obtin
efecte speciale
- exista o paleta curenta, care se poate seta (SetPalette)
- factor de corectie (aspect ratio)
- este definit de doi parametri: XAsp si YAsp cu semnificatia
ca o linie orizontala de dimensiune XAsp are pe verticala
dimensiunea YAsp
- stil de umplere (fill style), se modifica cu SetFillStyle
- este folosit pentru umplerea zonelor ecranului grafic
- este definit de
- sablon de umplere (fill pattern)
- culoare de umplere (fill color)
- constante simbolice pentru stilul de umplere

Constanta ³ Valoarea³ Semnificatia


ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ
EmptyFill ³ 0 ³ Foloseste culoarea fondului
SolidFill ³ 1 ³ Foloseste culoarea cernelii
LineFill ³ 2 ³ Umplere cu ---
LtSlashFill ³ 3 ³ Hasura ///
SlashFill ³ 4 ³ Hasura /// fina
129
BkSlashFill ³ 5 ³ Hasura \\\ fina
LtBkSlashFill ³ 6 ³ Hasura \\\
HatchFill ³ 7 ³ Light hatch fill
XHatchFill ³ 8 ³ Heavy cross hatch
InterleaveFill ³ 9 ³ Linii intretesute
WideDotFill ³ 10 ³ Puncte spatiate larg
CloseDotFill ³ 11 ³ Puncte spatiate strans
UserFill ³ 12 ³ Definit de utilizator

- o linie desenata are urmatoarele caracteristici (GetLineSettings)


- stil de linie (line style) SetLineStyle
- sablon de linie (line pattern)
- grosime de linie (line thickness)
- constante simbolice:

Stiluri de linie Grosimi de linie


SolidLn 0 continua NormWidth 1 normala
DottedLn 1 puncte ThickWidth 3 subtire
CenterLn 2
DashedLn 3 intrerupta
UserBitLn 4 (Definit de utilizator)

- textul scris in modul grafic are urmatoarele caracteristici de stil


(GetTextSettings)
- font - fontul cu care se scrie
- directie - directia in care se scrie
- dimensiunea caracterelor
- proportia acestora pe orizontala si verticala

Constanta ³Valoare³ Semnificatie


ÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ
DefaultFont ³ 0 ³ font mapat 8x8 bit
TriplexFont ³ 1 ³ Stroked font
SmallFont ³ 2 ³ Stroked font
SansSerifFont ³ 3 ³ Stroked font
GothicFont ³ 4 ³ Stroked font
HorizDir ³ 0 ³ Orientare de la stanga la dreapta
VertDir ³ 1 ³ Orientare de jos in sus
UserCharSize ³ 0 ³ Dimensiunea caracterelor definita de
³ ³ utilizator

8.2.2. Clase de subprograme

- initializarea si terminarea modului grafic


- CloseGraph - termina modul grafic
- DetectGraph - determina driverul grafic
- GraphErrorMsg - intoarce mesajul de eroare pentru un cod specificat
- GraphResult - intoarce starea ultimei operatii grafice efectuate
= 0 OK, <> 0 - eroare
- InitGraph - initializeaza modul grafic
- RestoreCrtMode - restaureaza modul ecran dinainte de trecerea in
modul grafic
130
- setarea ferestrei fizice
- ClearDevice - sterge ecranul si seteaza punctul curent CP la (0,0)
- ClearViewPort - sterge fereastra fizica curenta
- GetViewSettings - obtine parametrii ferestrei fizice
- SetViewPort - seteaza fereastra fizica curenta
- SetVisualPage - stabileste pagina vizuala curenta
- setari de pagina, culoare, factor de corectie
- SetActivePage - stabileste pagina activa
- SetAllPalette - schimba paletele de culoare
- SetAspectRatio - seteaza factorul de corectie
- SetBkColor - seteaza culoarea fondului
- SetColor - seteaza culoarea cernelii
- SetGraphBufSize - modifica dimensiunea bufferului grafic folosit la
scanare sau umplere
- SetGraphMode - seteaza sistemul la modul grafic specificat si
sterge ecranul
- SetPalette - schimba o culoare a unei palete cu alta
- SetRGBPalette - seteaza paletele de culoare pt IBM8514
- setarea sau obtinerea punctului curent
- GetX - intoarce abscisa punctului curent
- GetY - intoarce ordonata punctului curent
- MoveRel - muta punctul curent cu un deplasament precizat
- MoveTo - schimba punctul curent la cel precizat
- primitive grafice
- Arc - traseaza un arc de cerc de centru si raza cunoscute, cuprins
intre doua raze ce formeaza unghiuri date cu axa Ox
- Bar - deseneaza o bara folosind stilul si culoarea de umplere curente
- Bar3D - deseneaza o bara 3D folosind stilul si culoarea de umplere curente
- Circle - traseaza un cerc de centru si raze cunoscute
- Drawpoly - traseaza un poligon cu n varfuri folosind stilul de linie
si culoarea curente
- Ellipse - traseaza un arc elipsa data prin centru, semiaxa mare si
mica si unghiurile razelor ce determina arcul cu semiaxa mare
- FillEllipse - traseaza si umple o elipsa de centru si semiaxe cunoscute
- FillPoly - traseaza un poligon si apoi il umple folosind stilul de
umplere si culoarea curente
- FloodFill - umple o regiune marginita cu sablonul de umplere si
culoarea curenta
- GetImage - salveaza o imagine (bitmap) dintr-o regiune specificata
intr-un buffer (variabila)
- PutImage - pune pe ecran o imagine dintr-un buffer
- GetPixel - intoarce culoarea pixelului de la locatia (X,Y) specificata
- PutPixel - schimba culoarea pixelului de la locatia specificata
cu culoarea specificata
- Line - traseaza o linie de la un punct la alt punct (ambele
specificate); punctul curent nu se modifica
- LineRel - traseaza o linie de la punctul curent (x0, y0) la punctul
(x1, y1), unde:
x1 = x0 + Dx
y1 = y0 + Dy
iar Dx si Dy sunt parametrii lui LineRel.
(x1,y1) devinde noul punct curent
131
- LineTo - traseaza o linie de la punctul curent (x0, y0) la punctul
(x1, y1), unde x1 si y1 sunt parametrii lui LineTo.
(x1,y1) devinde noul punct curent.
- PieSlice - traseaza si umple o felie de placinta de centru, raza si
unghiuri cunoscute
- Rectangle - traseaza dreptunghiul definit de coltul dreapta sus
si coltul stanga jos
- Sector - traseaza si umple un sector de elipsa de parametri cunoscuti
- parametri de umplere, sablon, stil
- GetFillPattern - intoarce sablonul si culoarea de umplere selectate
- GetFillSettings - intoarce sablonul si culoarea de umplere curente
- GetLineSettings - intoarce setarile curente de stil, sablon si
grosime de linie
- SetFillPattern - selecteaza un sablon de umplere definit de utilizator
- SetFillStyle - seteaza sablonul si culoarea de umplere
- SetLineStyle - seteaza stilul, sablonul si grosimea liniei
- informatii despre setarile curente
- GetArcCoords - intoarce coordonatele ultimei comenzi Arc executate
- GetAspectRatio - intoarce parametrii care determina factorul de
corectie
- GetBkColor - intoarce culoarea fondului
- GetColor - intoarce culoarea cernelii
- GetDefaultPalette - intoarce paleta de culoare cu care s-a initializat
modul grafic
- GetDriverName - intoarce un string continand numele driverului grafic
- GetGraphMode - intoarce modul grafic curent
- GetMaxColor - intoarce cel mai mare cod de culoare
- GetMaxMode - intoarce cel mai mare numar de mod grafic
- GetMaxX - intoarce rezolutia curenta pe orizontala
- GetMaxY - intoarce rezolutia curenta pe verticala
- GetModeName - intoarce numele modului grafic
- GetModeRange - intoarce subdomeniul numerelor de moduri grafice
valide pentru un anumit driver grafic
- GetPalette - intoarce paleta curenta si dimensiunea ei
- GetPaletteSize - intoarce dimensiunea paletei curente
- GraphDefaults - seteaza pe (0,0) punctul curent si reseteaza parametrii
grafici la valorile lor implicite
- ImageSize - intoarce numarul de octeti necesar pentru a memora o
regiune de ecran (bitmap) specificata
- scrierea de text in modul grafic
- GetTextSettings - intoarce setarile de text pentru scrierea in
modul grafic
- SetTextStyle - seteaza parametrii de stil de text
- SetTextJustify - seteaza parametrii de aliniere a textului scris
cu OutText sau OutTextXY in raport cu punctul curent
- SetUserCharSize - seteaza latimea si inaltimea caracterelor din
fontul curent
- SetWriteMode - seteaza modul de desenare a liniilor (scrie peste,
XOR, etc)
- TextHeight - intoarce inaltimea stringului argument, in pixeli
- TextWidth - intoarce latimea stringului argument, in pixeli
- OutText - scrie stringul argument in mod grafic, in raport cu
132
punctul curent
- OutTextXY - scrie stringul argument in mod grafic, in raport cu
un punct specificat
- instalari de drivere, fonturi
- InstallUserDriver
- InstallUserFont
- RegisterBGIdriver
- RegisterBGIfont

8.2.3. Exemple de folosire

8.2.3.1. Initializarea modului grafic


vezi UGraph.InitGr
foloseste
- DetectGraph
- InitGraph
- GraphResult
- GraphErrorMsg

8.2.3.2. Programul Cercuri


vezi Cercuri.PAS
foloseste
- UGraph.InitGr
- CloseGraph
- Circle

8.2.3.3. Programul Ferestre


vezi Ferestre.PAS
foloseste
- UGraph.InitGr
- CloseGraph
- Circle
- SetViewPort
- Rectangle

8.3. Desenarea in plan a figurilor din planul real

Coordonate ecran
- intregi definiti de rezolutia ecranului
- sunt coordonate absolute - originea este coltul din stanga sus
al ecranului (0, 0)
Coordonate reale
- coordonatele punctelor din planul real
- se reprezinta prin numere reale
Coordonate din fereastra fizica
- sunt relative la coltul din stanga sus al ferestrei
- se obtin din coordonatele ecran prin scaderea coordonatelor
coltului stanga sus

Fereastra fizica ecran V (u1, v1) (u2, v2)

0 GetMaxX-1
133
0ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ ³ M1(u1, v1) ³ ³
v1ÅÄÄÄÄÄÄÄ ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» ³
³ º º ³
v ÅÄÄÄÄÄÄÄĶÄÄÄÄÄÄÄÄÄÄ.M(u, v) º ³
³ º ³ º ³
v2ÅÄÄÄÄÄÄÄ ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ M2(u2, v2) ³
³ ³ ³ ³ ³
ÀÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
GetMaxY-1 u1 u u2

- u1, v1 si u2, v2 sunt coordonate ecran


- ordonata v creste in jos

Fereastra reala (logica): domeniul D din planul real D


- definit de doua colturi opuse
- (a, c) (b, d)
- (a, d) (b, c)
- ordonata y creste in sus

(a, d) x
dÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ (b , d)
³ ³ ³
³ ³ ³
³ ³ ³
yÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ.P(x, y) ³
³ ³
³ ³
³ ³
cÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ (b, c)
a b

Punctul P(x, y) din D se reprezinta in punctul M(u, v) din V

Problema reprezentarii punctului P in coordonate ecran este:


Date: domeniul real D (a, c) si (b, d)
fereastra ecran (0, 0) si (GetMaxX-1, GetMaxY-1)
punctul P(x, y) din D a <= x <= b, c <= y <= d
Rezultate: (u, v)

Relatii de transformare

x - a u - u1
----- = ------- u1 = 0, u2 = GetMaxX-1
b - a u2 - u1

y - d v - v1
----- = ------- v1 = 0, v2 = GetMaxY-1
c - d v2 - v1

de unde rezulta

134
u := Round((x-a)*(u2-u1)/(b-a)) + u1

v := Round((y-d)*(v2-v1)/(c-d)) + v1

u si v sunt coordonate ecran.

Daca se doreste reprezentarea numai in fereastra fizica V


(caz in care coordonatele trebuie calculate relativ la coltul din
stanga sus al acesteia), atunci formulele de mai sus devin:

u' := Round((x-a)*(u2-u1)/(b-a))

v' := Round((y-d)*(v2-v1)/(c-d))

in care (u1, v1) si (u2, v2) reprezinta colturile ferestrei fizice

Pascal (limbaj de programare)


De la Wikipedia, enciclopedia liberă

Salt la: Navigare, căutare

Pascal este unul dintre limbajele de programare de referinţă în ştiinţa calculatoarelor, fiind cel care a
definit programarea calculatoarelor. Pascal a fost dezvoltat de elveţianul Niklaus Wirth în 1970 pentru a
pune în practică programarea structurată, aceasta fiind mai uşor de compilat. Unul din marile sale
avantaje este asemănarea cu limbajul natural limba engleză, ceea ce îl face limbajul ideal pentru cei care
sunt la primul contact cu programarea. Pascal este bazat pe limbajul Algol şi a fost denumit astfel în
onoarea matematicianului Blaise Pascal, creditat pentru construirea primelor maşini de calcul numeric.
Wirth a mai dezvoltat limbajele Modula-2 şi Oberon, similare cu Pascal.

Cele mai populare implementări a acestui limbaj au fost Turbo Pascal şi Borland Pascal, ambele ale
firmei Borland cu versiuni pentru Macintosh şi DOS, care i-au adăugat limbajului obiecte şi au fost
continuate cu versiuni destinate programării vizuale pentru Microsoft Windows (limbajul Delphi) şi
pentru Linux (Kylix).

În prezent există şi alte implementări mai mult sau mai puţin populare, dar gratuite, printre care se
remarcă Free Pascal şi GNU Pascal.

Deşi în prezent este relativ rar folosit în industria software, el este încă util elevilor şi studenţilor care
doresc să se iniţieze în programare. Spre deosebire de BASIC, care a stat în trecut la baza învăţării
programării, Pascal este un limbaj structurat. De aceea, el formează un anumit tip de gândire, similar
limbajelor moderne, precum C++, dar nu deţine complexitatea şi abstractizarea acestuia din urmă, fiind
mai uşor de înţeles datorită sintaxei simple şi apropiate de pseudocod.

Cuprins
[ascunde]

135
 1 Tipuri de date de bază
 2 Operatori (clasificare după tipul de date)
o 2.1 Pentru datele numerice (integer, real, byte, word)
 2.1.1 Operatori relaţionali
o 2.2 Pentru datele de tip şir de caractere (string)
 2.2.1 Operatori relaţionali
 3 Instrucţiuni de bază
o 3.1 Instrucţiune multiplă
o 3.2 Condiţie
o 3.3 Cicluri
 3.3.1 Cu test iniţial
 3.3.2 Cu test final
 3.3.3 Cu număr cunoscut de paşi
 4 Structura unui program Pascal
 5 Câteva instrucţiuni
o 5.1 WRITE
 5.1.1 Exemplu
o 5.2 READ
 5.2.1 Exemple
o 5.3 READKEY
 5.3.1 Exemplu
o 5.4 STR
 5.4.1 Exemplu
o 5.5 VAL
 5.5.1 Exemplu
o 5.6 BREAK
o 5.7 EXIT
o 5.8 HALT
 5.8.1 Exemplu
o 5.9 DELETE
 5.9.1 Exemplu
o 5.10 INSERT

 5.10.1 Exemplu

[modifică] Tipuri de date de bază


 Integer (numere întregi)

 Real (numere reale)

 Char (caractere)

 String (şiruri de caractere)

 Boolean (valori logice)

 Text (fişiere text)

 File (fişiere binare)

136
 Array (vectori)

[modifică] Operatori (clasificare după tipul de date)


[modifică] Pentru datele numerice (integer, real, byte, word)

  ( )   grupează expresiile

  +,-   adunare şi scădere

  *,/   înmulţire şi împărţire (împărţirea cu virgulă se face în numere reale)

  mod   returnează restul (doar în întregi)

  div   returnează câtul (doar în întregi)

[modifică] Operatori relaţionali

 <  mai mic

 >  mai mare

 =  egal

  <>   diferit

  <=   mai mic sau egal

  >=   mai mare sau egal

[modifică] Pentru datele de tip şir de caractere (string)

  +   concatenarea a două şiruri

137
[modifică] Operatori relaţionali

  <,>   mai mare sau mai mic, pe baza ordinii lexicografice în funcţie de codurile ASCII

  =   cele două şiruri sunt identice

[modifică] Instrucţiuni de bază


[modifică] Instrucţiune multiplă
begin
<instrucţiuni>
end.

[modifică] Condiţie
if <condiţie> then
<instrucţiune>;

sau

if <condiţie> then
<instrucţiune>
else
<instrucţiune>;

[modifică] Cicluri

[modifică] Cu test iniţial

while <condiţie> do
<instrucţiune>;

[modifică] Cu test final

repeat
<instrucţiuni>
until <condiţie>;

[modifică] Cu număr cunoscut de paşi

for <variabilă>:=<valoare_iniţială> to <valoare_finală> do


<instrucţiune>;

sau

for <variabilă>:=<valoare_iniţială> downto <valoare_finală> do


<instrucţiune>;

[modifică] Structura unui program Pascal


138
program <nume_program>;
uses <biblioteci cu funcţii şi proceduri folosite în program>
type <tipuri de date definite de utilizator>
const <constante folosite în program>
var <variabile globale folosite în program>
<aici se pot scrie funcţii şi proceduri folosite în program>
begin
<aici se scrie programul principal>
end.
 Notă: După fiecare comandă se pune " ; " cu următoarele excepţii: înainte de "else" şi după
"do".

[modifică] Câteva instrucţiuni


[modifică] WRITE

Este o instrucţiune pentru afişarea pe ecran a unui text (şir de caractere) sau a valorii unor constante,
unor variabile sau unor expresii, exceptând date de tip vector sau fişier. Conţinutul a ceea ce urmează să
fie afişat pe ecran este încadrat de două paranteze rotunde. Lista a ceea ce urmează să fie afişat pe ecran
este despărţită prin virgulă. Instrucţiunea WRITELN face aceeaşi acţiune ca şi WRITE dar după afişare
mută cursorul la începutul rândului următor al ecranului.

[modifică] Exemplu

write ('a=', a);

Descriere: afişează pe ecran şirul a= după care afişează valoarea variabilei sau constantei a.

[modifică] READ

Este o instrucţiune care citeşte de la tastatură o variabilă, cu excepţia tipului boolean şi a vectorilor.
Variabilele se scriu între paranteze şi, dacă sunt mai multe variabile, se despart prin virgulă.
Instrucţiunea READLN face acelaşi lucru ca READ dar aşteaptă un <ENTER> înainte de a prelucra
datele primite. În caz că sunt citite mai multe variabile, introducerea acestora se va face prin separarea
valorilor prin spaţii albe (<Space>, <TAB> sau <ENTER>).

[modifică] Exemple

 write ('Dati x='); readln (x);

Descriere: afişează pe ecran Dati x= după care citeşte de la tastatură valoarea variabilei x.

 readln;

Descriere: aşteaptă apăsarea tastei <ENTER> după care continuă cu restul de instrucţiuni din program.

[modifică] READKEY

Este o instrucţiune de citire de la tastatură a caracterelor (valori de tip char) fără ca acestea să fie afişate
pe ecran. Variabila caracter citită se scrie între paranteze.

[modifică] Exemplu

139
write ('Parasiti aplicatia? (d/n)'); readkey (x);

Descriere: afişează pe ecran Parasiti aplicatia? apoi aşteaptă să fie introdusă de la tastatură o literă.

[modifică] STR

Transformă un număr într-un string (şir de caractere). Odată transformat în string, numărului nu i se mai
pot efectua operaţii matematice deoarece acum este considerat un cuvânt.

[modifică] Exemplu

str (x, s);

Descriere: Creează un string din numărul x în şirul s.

[modifică] VAL

Transformă un string într-un număr atât timp cât stringul conţine o reprezentare validă.

[modifică] Exemplu

val (s, x, er);

Descriere: creează un număr din stringul s în variabila x. Dacă în timpul execuţiei se întâlneşte o eroare,
variabila de tip integer er va conţine poziţia caracterului de la care s-a constatat că nu se poate
transforma stringul în număr. Spre exemplu, pentru şirul de caractere ' 1234a6 ' variabila er va conţine 5.

[modifică] BREAK

Opreşte forţat un ciclu (FOR, REPEAT sau WHILE), chiar dacă acesta nu s-a încheiat.

[modifică] EXIT

Opreşte automat programul.

[modifică] HALT

Opreşte automat programul cu posibilitatea de transmite sistemului de operare un cod de eroare.

[modifică] Exemplu

halt (5);

Descriere: opreşte programul şi transmite codul de eroare 5.

[modifică] DELETE

Şterge o porţiune dintr-un string.

[modifică] Exemplu

140
delete (s, 8, 4);

Descriere: şterge 4 caractere din şirul s începând cu poziţia 8.

[modifică] INSERT

Introduce un şir de caractere în altul.

[modifică] Exemplu

insert (s,'abc', 8);

Descriere: introduce în şirul s pe poziţia 8 caracterele abc.

141

S-ar putea să vă placă și