Sunteți pe pagina 1din 221

Cuprins

1. Tipuri dinamice de date. Lucrul cu adrese ...................................................... 3

2. Structura de listă în limbajul Pascal ................................................................ 15

3. Grafuri. Implementări în limbajul Pascal ........................................................ 28

4. Structuri arborescente ...................................................................................... 48

5. Algoritmi recursivi. Metodele divide et impera şi backtracking ............ 73

6. Reprezentarea vizuală a datelor ....................................................................... 92

7. Tehnici speciale în Pascal .................................................................................... 123

8. Prelucrarea etichetelor de fişier şi a altor informaţii externe

memorate în discuri .................................................................................................. 143

9. Unele aspecte tehnice referitoare la prelucrarea fişierelor .................... 162

10. Obiecte în Pascal ................................................................................................. 174

Anexa 1 – Unitatea DOS .......................................................................................... 208

Anexa 2 – Unitatea CRT .......................................................................................... 215

Anexa 3 – Erori de execuţie ................................................................................... 217

Bibliografie ................................................................................................................. 221


TIPURI DINAMICE DE DATE
LUCRUL CU ADRESE

Datele de tip static au caracteristici care limitează rezolvarea unor clase de


probleme. În primul rând, spaţiul de memorie aferent unor astfel de date se defineşte
şi se rezervă la dimensiune maximă, prestabilită, ca spaţiu propriu care nu poate fi
disponibilizat şi nici împărţit cu alte date, chiar dacă, în momentul diverselor execuţii
ale programului, nu este în întregime utilizat (rezervare statică sau la momentul
compilării). În al doilea rând, componentele structurilor statice ocupă locuri
prestabilite în spaţiul rezervat, determinate de relaţia de ordonare specifică fiecărei
structuri. În al treilea rând, limbajul defineşte operaţiile admise cu valorile
componentelor, potrivit tipului de bază al structurii, astfel încât numărul maxim şi
ordinea componentelor structurii nu pot fi modificate. În aceste condiţii, structurile
statice sunt dificil de utilizat în rezolvarea problemelor care prelucrează mulţimi de
date pentru care numărul şi ordinea componentelor se modifică frecvent în timpul
execuţiei programului. Pentru astfel de situaţii, limbajul PASCAL oferă posibilitatea
utilizării datelor de tip dinamic, cărora li se pot aloca şi elibera zone de memorie pe
parcursul execuţiei programului.

1.1 Lucrul cu adrese în Pascal

Adresarea memoriei se realizează prin registre ale unităţii centrale, care au


capacitatea de un cuvânt. La adresarea în modul real, pentru formarea unei adrese fizice
din spaţiul de 1Mo este necesară folosirea a două registre: de segment (segment), care
conţine adresa de început a segmentului, numită şi adresa de bază; de deplasare (offset),
care precizează distanţa la care se află octetul adresat faţă de începutul segmentului.
Astfel, orice adresă din memorie poate fi specificată în formatul segment:offset sau, în
alţi termeni, bază:deplasare. Întrucât deplasarea de 16 biţi nu poate accesa o locaţie de
memorie din afara domeniului 0..216-1, rezultă că dimensiunea maximă a unui segment
este de 64 Ko, restricţie valabilă pentru orice produs utilizat sub MS-DOS. Memoria
este împărţită în paragrafe de câte 16 octeţi, iar fiecare segment începe la graniţă de
paragraf, adică de la o adresă divizibilă cu 16. Într-un spaţiu de 1Mo sunt 216 paragrafe,
ceea ce înseamnă că adresa de început a unui segment, corespunzând unui număr de

3
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

paragraf, poate fi reprezentată ca o valoare pe 16 biţi. În calculul adresei fizice pe


20 biţi, se aplică următoarea relaţie: segment*16+offset, unde segment şi offset
desemnează conţinutul registrelor de segment (un număr de paragraf), respectiv de
deplasare. Adresele din segmentul curent se numesc apropiate (near), iar cele din afara
acestuia sunt îndepărtate (far). Accesul la o adresă apropiată presupune doar
schimbarea conţinutului registrului de deplasare, în timp ce pentru o adresă îndepărtată
trebuie schimbată atât valoarea registrului de segment, cât şi a celui de deplasare.
În unitatea System sunt definite funcţiile Seg(Var x):WORD, Ofs(VAR
x):WORD care furnizează adresa de segment şi deplasarea variabilei, procedurii sau
funcţiei x. În Pascal există tipul de date pointer, memorat pe două cuvinte, în care
cuvântul superior (high) conţine partea de segment, iar cuvântul inferior (low) pe cea de
deplasare asociate unei adrese. Pentru a se putea exemplifica modul de lucru cu
adrese, se precizează faptul că: • tipul pointer se defineşte prin construcţia de forma
^tip; • adresarea indirectă a unei variabilei se defineşte prin construcţia identificator^;
• referirea adresei unei variabile se defineşte prin construcţia @identificator.
Programatorul trebuie să facă distincţie între adresa şi conţinutul unei
variabile, precum şi între adresarea directă şi cea indirectă. În exemplul de mai jos,
liniile sursă numerotate de la 1 la 8 se generează următoarele tipuri de adresare: 1, 2,
4:adresare directă pentru ambii operanzi şi lucru cu conţinut; 3: adresare directă
pentru pa şi a, lucru cu conţinut pentru pa şi cu adresa a; 5: adresare directă pentru c
şi indirectă pentru pa, lucru cu conţinut; 6, 7, 8: adresare indirectă şi lucru cu
conţinut. De remarcat că pa este de tip pointer, pa^ este de tip REAL iar @a este
adresa a (are configuraţie de pointer).
În sintaxa @identificator, identificator se referă la o variabilă, procedură sau
funcţie. Efectul referirii @identificator este similar celui obţinut prin funcţia Addr
definită în unitatea System astfel: Addr(VAR x):pointer, unde x este variabilă,
funcţie sau procedură. Folosirea referirii identificator^ presupune existenţa unei
adrese valide în variabila identificator.

Exemplu:
1.1.
VAR a,b,c:REAL; {Se rezervă câte 6 octeţi pentru fiecare variabilă}
pa,pb:^REAL; {Se rezervă câte 4 octeţi pentru fiecare variabilă}
BEGIN
a:=20; {Se atribuie valoarea 20 variabilei de adresa a} 1
b:=a; {Se atribuie variabilei de adresa b, conţinutul variabilei
de adresa a} 2
pa:=@a; {Se atribuie variabilei de adresa pa, adresa a} 3

pb:=pa; {Se atribuie variabilei de adresa pb, conţinutul variabilei de


adresa pa} 4
c:=pa^; {Se atribuie variabilei c, conţinutul variabilei a cărei adresă
este memorată în pa; aceasta este adresare indirectă prin pa.
Lui c i se atribuie conţinutul lui a (20)} 5

4
Tipuri dinamice de date. Lucrul cu adrese

WriteLn( 'Valoarea ',pb^:10:2,');


{Se scrie continutul variabilei a carei adresa este în pb } 6
WriteLn( 'Adresa fizica a lui A : ,seg(pb^),':',ofs(pb^));
{Se scrie adresa a, sub forma segment:deplasare} 7
WriteLn( 'Adresa fizica a lui PB:',seg(pb),':',ofs(pb));
Π{Se scrie adresa pb, sub forma segment:deplasare} 8

1.2 Structura memoriei la execuţia unui program

După încărcarea programului executabil, memoria aferentă lui se structurează


în următoarele regiuni (segmente): segmentul prefix program, regiunea de cod,
segmentul de date, stiva şi zona heap (figura 1.1). Pentru adresarea acestora, unitatea
centrală de prelucrare foloseşte registre specializate (tabelul 1.1), la unele dintre ele
existând acces direct (vezi tipul Registers din unitatea DOS, anexa 1) sau indirect din
programe Pascal.

Tabelul 1.1 Registre de segment /deplasare şi funcţii standard asociate


Tipul segmentului Registrul de Registrul de Seg:Ofs
segment deplasare
Segment de cod CS (CSeg) IP CS:IP
Segment de date DS (DSeg) SI DS:SI
Segment de stivă SS (SSeg) SP (SPtr) SS:SP (SSeg:SPtr)
Observaţie: Cseg, DSeg, SSeg, SPtr sunt funcţii de tip WORD, nu au parametri şi
sunt definite în unitatea System.

• Segmentul prefix al programului (PSP) este o zonă de 256 de octeţi constituită


de MS-DOS la încărcarea în memorie a fişierului de tip .EXE. El conţine informaţii
necesare sistemului de operare pentru a transfera controlul către program, respectiv
pentru a returna controlul către sistemul de operare la încheierea execuţiei acestuia.
Adresa de segment este memorată în variabila publică PrefixSeg, de tip WORD,
definită în unitatea System.
• Regiunea de cod este constituită din mai multe segmente de cod: unul
corespunzând programului principal, respectiv câte unul pentru fiecare unitate referită în
program. Primul segment de cod este cel asociat programului principal, urmat de cele
ale unităţilor, în ordinea inversă specificărilor din clauza USES. Ultimul segment de
cod, introdus implicit în orice program executabil, corespunde unităţii System, care
conţine biblioteca de subprograme standard referite la momentul execuţiei (Run-time
library). În absenţa clauzei USES, zona de cod va conţine două segmente: cel al
programului principal şi cel al unităţii System. Codul poate fi împărţit într-un număr
oarecare de segmente, singura limitare fiind dată de memoria disponibilă. Registrul CS

5
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

conţine adresa de start a instrucţiunilor programului, iar registrul IP (registru pointer al


instrucţiunilor) precizează adresa următoarei instrucţiuni de executat. Programele Pascal
nu au acces la registrul IP, dar valoarea curentă a registrului CS poate fi obţinută cu
funcţia CSeg. Adresa de început a zonei de cod este CSeg:0000. Conţinutul registrului
CS se poate modifica pe parcursul execuţiei, depinzând de faptul că instrucţiunile
executate sunt din programul principal sau din una dintre unităţi.

(Limita superioară a memoriei RAM convenţionale)

Zona neutilizată în heap


Heap
HeapPtr
Variabile dinamice curent alocate în heap
HeapOrg
Date înregistrate în stiva
SSeg:SPtr Stiva
Zona neutilizată în stiva
SSeg:0000
Variabile globale
Date
Constante cu tip
DSeg:0000
Unitatea System
Unităţi specificate în Segment cod Crt
clauza USES Conţinut
Cod Segment cod A
Exemplu: fişier
Segment cod B
USES Crt,A,B,C; .EXE
Segment cod C
Segment cod program principal
CSeg:0000
Program Segment Prefix (PSP)
PrefixSeg:0000
Fig. 1.1 Harta memoriei la execuţia unui program Pascal

• Segmentul de date este unic şi conţine constantele cu tip urmate de variabilele


globale. Atunci când necesarul de memorie pentru datele interne depăşeşte 64Ko,
trebuie să se recurgă la folosirea unor tehnici adecvate (memorarea datelor în heap sau
pe medii externe, folosirea compactării etc.). Registrul DS conţine adresa de început a
segmentului de date şi nu se modifică pe parcursului execuţiei. SI reprezintă registrul
index al sursei, folosit pentru a puncta (a indexa) o dată în cadrul segmentului de date.
Pentru instrucţiunile asupra şirurilor de caractere, registrul SI punctează pe operandul
sursă, în timp ce un alt registru, index al destinaţiei (DI), punctează operandul
destinaţie. Funcţia DSeg returnează valoarea curentă a registrului DS. Registrele SI şi
DI pot fi accesate indirect, printr-un apel la o întrerupere. Adresa de început a
segmentului de date este DSeg:0000.

6
Tipuri dinamice de date. Lucrul cu adrese

• Segmentul de stivă, ca şi cel de date, poate avea maximum 64Ko, reducân-


du-se la unul singur. Stiva este folosită în lucrul cu subprograme pentru memorarea
parametrilor formali, variabilelor locale şi adreselor de revenire. Registrul SS conţine
adresa de început a stivei şi nu se modifică pe parcursul execuţiei. Registrul pointer al
stivei (SP) precizează deplasarea curentă în cadrul stivei. Funcţia SSeg returnează
valoarea registrului SS, iar SPtr returnează valoarea curentă a registrului SP. În cadrul
stivei, alocarea spaţiului se face începând de la adrese mai mari spre adrese mai mici.
Adresa curentă este definită de SS:SP sau, conform celor precizate anterior, de
SSeg:SPtr.
• Zona variabilelor dinamice poate corespunde întregii memorii convenţionale
a calculatorului, rămasă disponibilă după încărcarea programului. În heap se memorează
variabilele dinamice, buffer-ele pentru structuri de reacoperire şi pentru lucrul în modul
grafic. Adresa de început a zonei heap este dată de variabila publică HeapOrg, iar
adresa curentă este dată de variabila HeapPtr, ambele de tip pointer, definite în unitatea
System. Alocarea variabilelor începe de la adrese mai mici către adrese mai mari,
spaţiul maxim ce poate fi alocat unei variabile neputând depăşi 64Ko (strict riguros,
65520 octeţi), ca urmare a limitărilor impuse mecanismului de adresare a memoriei.
După modul lor de funcţionare, stiva şi heap-ul pot fi asimilate cu două stive aşezate
spate în spate.
Programatorul poate controla repartizarea memoriei disponibile între stivă şi
heap în faza de execuţie cu directiva de compilare {$M}, care are următoarea formă
sintactică:
{$M StackSize,HeapMin,HeapMax}
StackSize trebuie să fie un întreg din domeniul 1024 (1Ko) la 65520 (64 Ko),
prin care se specifică mărimea segmentului de stivă. HeapMin şi HeapMax specifică
dimensiunea minimă, respectiv maximă a heap-ului, teoretic cu valori între 0 şi 640 Ko.
Riguros, HeapMin poate avea valori de la 0 la 655360, iar HeapMax trebuie să fie în
domeniul de la HeapMin la 655360. Valorile implicite pentru aceşti parametri de
alocare sunt {$M 16384,0,655360}.
Rezultă că dimensiunea implicită a stivei este de 16 Ko, iar zona de heap se
extinde, teoretic, în tot spaţiul rămas liber în memoria convenţională. Practic, din
dimensiunea de 640 Ko trebuie scăzut, pe lângă spaţiul ocupat de programul însuşi, cel
corespunzător componentelor sistemului de operare rezidente în memorie pe parcursul
execuţiei.

1.3 Tipuri dinamice de date

În Pascal se operează cu două tipuri de date dinamice - referinţă şi pointer -


primele fiind "cu tip" iar celelalte "fără tip".
• Tipul referinţă are sintaxa: tip_referinţă=^tip_de_bază;. Simbolul ^ are
semnificaţia de "indirect". Datorită asocierii cu un tip de bază, variabilele tip_referinţă
se mai numesc şi variabile cu referinţă legată. La compilare, pentru astfel de variabile,
se vor rezerva în segmentul de date două cuvinte şi la referirea lor se vor genera

7
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

instrucţiuni cod maşină conform tipului de bază, dar cu adresare indirectă. Înainte de
referire, în variabilele de tip_referinţă trebuie să se încarce adresele variabilelor de tipul
tip_de_bază. Declararea unui tip referinţă permite referirea anterior declarării tipului de
bază. Astfel, următoarea secvenţă de declarări este corectă: TYPE pointer_a=^vector;
vector=ARRAY[1..20] OF REAL;
Construcţia sintactică a referirii unei variabile dinamice depinde de
caracteristicile tipului său de bază: este de forma identificator^ în cazul tipurilor
nestructurate sau celor structurate care permit referirea globală (STRING, RECORD şi
SET); conţine prefixul identificator^, urmat de elementele specifice modului de
referire a componentelor, în cazul tipurilor structurate care permit referirea pe
componente (ARRAY, STRING şi RECORD). Aceste posibilităţi de referire sunt
ilustrate în programul Alocare_dinamică_1.
• Tipul pointer este desemnat prin cuvântul rezervat pointer. Variabilele de tip
pointer pot fi denumite variabile cu referinţă liberă, deoarece pot fi folosite la
memorarea adreselor pentru variabile de orice tip. Tehnica de lucru cu astfel de variabile
este asemănătoare celei prezentate la tipul referinţă. Utilizarea efectivă presupune şi în
acest caz o asociere explicită cu un anumit tip de bază, dar soluţia folosită este diferită.
La tipul referinţă, asocierea se face prin declarare, iar în cazul tipului pointer asocierea
se realizează la utilizare, prin diverse tehnici. O posibilitate este asigurată de referinţa
typecasting (transfer de tip), care are forma generală: tip(variabilă), unde tip este un tip
standard sau declarat anterior de utilizator iar variabilă poate fi cu/fără tip sau o
referinţă prin pointer, de forma variabilă_pointer^.
Din punct de vedere fizic, variabilele de tip referinţă_legată şi pointer
memorează adrese sub forma segment:offset. De aceea, în limbajul curent de
specialitate, ambele tipuri se definesc prin termenul pointer. Următorul exemplu
evaluează expresia e:=a+b, folosind adresarea indirectă pentru toate variabilele (a şi e
prin referinţă_cu_tip iar b prin pointer):

VAR
a,b,e:REAL;
pa,pe:^REAL;
pb:POINTER;
BEGIN
pa:=addr(a); pb:=@b; pe:=@e;
Write(‘A=); ReadLn(pa^);
Write(‘B=’); ReadLn(REAL(pb^));
pe^:=pa^+REAL(pb^);
WriteLn(‘E= ‘,pe^:8:2))
END.

Variabilele pointer (referinţă sau pointer) pot fi puse în relaţie cu operatorii = şi


< >. Două variabile vor fi egale dacă au componentele segment, respectiv offset egale.
De remarcat faptul că două variabile de tip pointer care indică aceeaşi adresă pot fi
neegale, deoarece le diferă componentele. Variabilele pointer pot fi folosite în atribuiri.
Atât în relaţii cât şi în atribuiri sunt definite următoarele clase de compatibilitate: tipul
referinţă este compatibil cu orice alt tip dinamic; două variabile de tip referinţă sunt
compatibile dacă sunt de acelaşi tip.
Observaţie: pa şi pb nu sunt de acelaşi tip dacă sunt declarate astfel: pa:^real; pb:^real.

8
Tipuri dinamice de date. Lucrul cu adrese

Ele sunt de acelaşi tip dacă sunt declarate astfel: pa,pb:^real.


Este definită o constantă simbolică (nil) cu semnificaţie de valoare nulă a
tipului dinamic (valoarea nil nu punctează o zonă de memorie).

1.4 Utilizarea zonei heap

Noţiunea de dinamic este strâns legată de utilizarea zonei de memorie heap


(deşi tipul dinamic poate fi asociat variabilelor memorate în orice componentă a
memoriei principale). În unitatea System sunt definite următoarele variabile de tip
pointer, care pot fi folosite în gestionarea zonei heap: HeapOrg, HeapPtr, HeapEnd,
HeapError, FreeList.
HeapOrg punctează pe adresa de început a zonei heap, iar HeapEnd dă adresa
de sfârşit a heap-ului. HeapPtr conţine următoarea adresa disponibilă din heap. Ea este
variabila prin care se gestionează fiecare nouă alocare, punctând pe prima adresă
disponibilă din heap. Toate procedurile de alocare (New, GetMem, Mark) lucrează cu
această variabilă. HeapError corespunde adresei rutinei de tratare a erorilor de alocare
pentru variabile dinamice. FreeList serveşte la gestiunea blocurilor devenite libere în
interiorul heap-ului, punctând pe primul bloc liber în heap, care punctează pe al doilea
ş.a.m.d. Ultimul bloc liber punctează pe vârful heap-ului, adică pe locaţia dată de
HeapPtr, asigurându-se astfel posibilitatea realocării acestor spaţii. Dacă în interiorul
heap-ului nu există blocuri libere, atunci FreeList va fi egală cu HeapPtr.
De asemenea, în unitatea System sunt definite o serie de proceduri şi funcţii
care pot fi utilizate în lucrul cu variabilele dinamice. Procedurile GetMem(p,n),
FreeMem(p,n), respectiv New(p) şi Dispose(p) se folosesc pentru a aloca/elibera un
bloc a cărui adresă este dată de variabila pointer sau referinţă, p.
Deoarece zona heap este limitată, iar alocarea şi eliberarea dinamică
determină alternanţa unor zone libere cu cele ocupate, este necesară cunoaşterea
spaţiului disponibil şi a spaţiului contiguu maxim disponibil. În acest scop pot fi
folosite funcţiile (fără argumente, cu rezultat de tip LONGINT) MemAvail (pentru
spaţiul total disponibil) şi MaxAvail (pentru spaţiul contiguu maxim disponibil).
Iniţial, rezultatul furnizat de MemAvail corespunde dimensiunii totale a heap-ului, care
poate fi obţinută şi prin aplicarea formulei (Seg(HeapEnd^)-Seg(HeapOrg^))*16,
întrucât adresele de început şi de sfârşit ale heap-ului sunt exprimate ca numere de
paragraf. De asemenea, această dimensiune coincide iniţial cu cea furnizată de funcţia
MaxAvail, care precizează cel mai lung bloc de locaţii de memorie contigue disponibile
în heap. Se poate determina dacă spaţiul disponibil este contiguu, pe baza expresiei
relaţionale MemAvail = MaxAvail. Dimensiunea în octeţi ocupată de o variabilă
poate fi obţinută cu funcţia SizeOf(VAR x):WORD, unde x este identificator de
variabilă. Se poate stabili dacă spaţiul disponibil este acoperitor pentru o variabilă de
un anumit tip, scriind o relaţie de forma MaxAvail >= SizeOf (tip).
• Alocarea şi eliberarea zonelor pentru variabile referinţă_legată se face cu

9
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

procedurile New, respectiv Dispose, definite în unitatea System astfel: New(VAR


p:pointer), Dispose(VAR p:pointer). Procedura New rezervă în heap o zonă de
memorie de lungime egală cu cea indicată de tipul de bază şi încarcă în variabila p
adresa acestei zone.
Exemplu:
1.2.
VAR
px:^INTEGER; {La compilare se rezerva 4 octeti in segmentul de date}
…………………………………
BEGIN
…………………………………
New(px); {La executie se rezerva 2 octeti si se incarca adresa zonei
rezervate in
px}
px^:=21; {Se memoreaza valoarea 21 in zona de tip INTEGER din heap}
Dispose(px); {Se elibereaza spatiul rezervat in heap}

De remarcat că există două niveluri de rezervare: statică (corespunzătoare lui


px) şi dinamică (datorată procedurii New(px), care rezervă variabilă în heap, în
conformitate cu tipul de bază). Din punct de vedere fizic, operaţia realizează memorarea
în variabila p a valorii HeapPtr şi avansarea acesteia din urmă cu lungimea specifică
tipului de bază. Dacă nu există o zonă contiguă disponibilă de lungime mai mare sau
egală cu cea necesară, se generează eroare de execuţie. Procedura Dispose eliberează
zona alocată variabilei. Următorea alocare se poate face pe spaţiul eliberat, dacă este
acoperitor ca lungime. Fizic, se reface în HeapPtr valoarea de dinaintea alocării prin
New.
În programul Alocare_dinamică_1 se ilustrează prin exemple simple alocarea şi
eliberarea spaţiului din heap, precum şi referirile globale şi pe componente pentru
variabile dinamice ale căror tipuri de bază sunt nestructurate sau structurate. La execuţia
programului, pe lângă afişarea mesajelor care ilustrează corectitudinea folosirii tehnicii
de lucru cu variabile dinamice, se remarcă revenirea în final, după eliberarea tuturor
zonelor alocate, la dimensiunea iniţială a heap-ului.
Prin posibilitatea de generare a unor succesiuni de valori de acelaşi tip, de la
simpla alocare dinamică de spaţiu pentru o singură valoare a unei variabile se poate
trece la realizarea unor structuri dinamice de date. Acest lucru este posibil pe baza
înlănţuirii succesiunilor de valori, ca urmare a includerii la fiecare element al structurii a
două părţi: o parte de informaţii, corespunzând valorii propriu-zise a elementului; o
parte de legătură, care va conţine adresa următorului element.
Fără a se urmări, în acest context, abordarea problematicii implementării
structurilor de date complexe (liste, stive, cozi, arbori binari etc.), în programul
Alocare_dinamică_2 se prezintă realizarea unei liste înlănţuite pentru memorarea în
heap a unui vector. Aplicaţia are în vedere memorarea în heap a unui vector de mari
dimensiuni, cu elemente de tip întreg. Pentru fiecare element se construieşte o structură
de tip RECORD, în forma asociată tipului element. Pentru simplificare, se cere
utilizatorului să precizeze numărul total de valori şi valoarea de start, elementele fiind
generate automat, ca numere întregi consecutive. După construire se reia traversarea

10
Tipuri dinamice de date. Lucrul cu adrese

listei, cu afişarea pe monitor a elementelor vectorului.


Prin procedura New(leg) se va aloca o zonă de memorie de dimensiunea unei
date de tipul element, iar în variabila de tip leg se va memora adresa de început a acestei
zone. Zona de memorie a cărei adresă este conţinută în variabila de tip leg va fi referită
prin indirectare, folosind o construcţie sintactică formată din - sau începând cu -
variabila de tip leg^.
PROGRAM Alocare_dinamica_1;
USES Crt;
TYPE
pv=^v;
v=ARRAY[1..100] OF INTEGER;
ps=^s;
s=STRING[120];
pm=^m;
m=SET OF CHAR;
pc=^CHAR;
pa=^a;
a=RECORD
nume:STRING[15];
nota:ARRAY[1..5] OF 1..10;
end;
VAR
legv:pv; legs:ps; legm:pm; legc:pc; lega:pa;
BEGIN
ClrScr;
WriteLn('Adresa de inceput heap: ',
Seg(HeapOrg^),':',Ofs(HeapOrg^));
Writeln('Adresa de sfirsit heap: ',
Seg(HeapEnd^),':',Ofs(HeapEnd^));
WriteLn('Valoare pointer heap: ',
Seg(HeapPtr^),':',Ofs(HeapPtr^));
WriteLn('Dimensiune totala heap: ',MemAvail);
{Rezultat echivalent: (Seg(HeapEnd^)-Seg(HeapOrg^))*16 }
WriteLn('Bloc maxim in heap: ',MaxAvail);
New(legv);
WriteLn('Memorie alocata in heap: ',SizeOf(legv^));
WriteLn('Memorie libera in heap: ',MemAvail);
legv^[1]:=1;
WriteLn('v[1]=',legv^[1]);
Dispose(legv);
WriteLn('Memorie libera in heap: ',MemAvail);
New(legs); legs^:='PASCAL';
WriteLn('Al treilea caracter din sir este ',legs^[3]);
New(legm); legm^:=['a'..'c'];
IF 'c' IN legm^ THEN Writeln('Litera "c" apartine multimii');
New(legc); legc^:=#65; Writeln('Caracter atribuit: ',legc^);
New(lega); lega^.nume:='POPESCU ION'; lega^.nota[2]:=10;
WriteLn('Studentul ',lega^.nume,' are nota ',lega^.nota[2], '
la disciplina a doua');
Dispose(lega); Dispose(legs); Dispose(legc); Dispose(legm);
WriteLn('Memorie libera in heap: ',MemAvail);
END.
Din analiza programului Alocare_dinamică_2 se pot desprinde câteva din
cerinţele impuse programatorului în construirea şi prelucrarea structurilor dinamice.
Astfel, pentru a face posibilă traversarea ulterioară, se memorează în variabila de tip
referinţă inceput adresa primului element. La construirea unui nou element, inclusiv a
primului, câmpul de legătură urm (de tip referinţă) este iniţializat cu valoarea nil; la

11
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

traversarea ulterioară, regăsirea unei valori nil corespunde identificării ultimului


element din listă. Pentru toate elementele, începând cu al doilea, construirea presupune
revenirea la elementul anterior, a cărui informaţie de legătură trebuie să puncteze pe
elementul nou construit. Această balansare este asigurată prin “jocul” variabilelor de tip
referinţă curent, respectiv următor.

PROGRAM Alocare_dinamica_2;
leg=^element;
element=RECORD
v:INTEGER;
urm:leg;
END;
VAR
inceput,curent,urmator: leg;
n,vi,i:INTEGER;
BEGIN
Write('Nr. de elemente si valoare de inceput: ');
ReadLn(n,vi);
New(inceput);
inceput^.v:=vi;
inceput^.urm:=nil;
WriteLn('S-a creat elementul 1 = ',inceput^.v);
curent:=inceput;
FOR i:=2 TO n DO
BEGIN
New(urmator);
urmator^.v:=curent^.v+1;
urmator^.urm:=nil;
WriteLn('S-a creat elementul ',i,' = ',urmator^.v);
curent^.urm:=urmator;
curent:=urmator
END;
i:=1;
curent:=inceput;
WHILE curent^.urm <> nil DO
BEGIN
WriteLn('v[',i,']: ',curent^.v);
curent:=curent^.urm;
Inc(i)
END;
Writeln('v[',i,']: ',curent^.v)
END.

• Alocarea şi eliberarea zonelor pentru variabile de tip pointer se realizează cu


procedurile GetMem, respectiv FreeMem, definite în unitatea System astfel:
GetMem(VAR p:pointer; l:WORD), FreeMem(VAR p:pointer; l:WORD). Efectul
acestora este asemănător procedurilor New şi Dispose, cu precizarea că este necesară
specificarea lungimii, care nu poate fi dedusă implicit, neexistând un tip de bază. Un
exemplu simplu de folosire a variabilelor pointer din heap este ilustrat în programul
Alocare_dinamică_3.

PROGRAM Alocare_dinamica_3;

12
Tipuri dinamice de date. Lucrul cu adrese

VAR
p: POINTER;
BEGIN
GetMem(p,6);
REAL(p^):=5.25;
WriteLn(REAL(p^):4:2);
FreeMem(p,6)
END.

Reluând exemplul propus prin programul Alocare_dinamică_2, trecerea la


folosirea variabilelor pointer presupune unele adaptări, ilustrate în programul
Alocare_dinamică_4. Din program se constată că aplicarea tehnicii de typecasting
permite atât referirea globală, cât şi pe componente, ilustrată în acest caz pentru
câmpurile unui articol. Spaţiul alocat la un apel al procedurii GetMem corespunde
dimensiunii articolului (6 octeţi).

PROGRAM Alocare_dinamica_4;
TYPE
element=RECORD
v:INTEGER; urm:POINTER;
END;
VAR
inceput,curent,urmator: POINTER;
n,vi,i : INTEGER;
BEGIN
Write('Nr. de elemente si valoare de inceput: ');
ReadLn(n,vi);
GetMem(inceput,6);
element(inceput^).v:=vi;
element(inceput^).urm:=nil;
WriteLn('S-a creat elementul 1 = ',element(inceput^).v);
curent:=inceput;
FOR i:=2 TO n DO
BEGIN
GetMem(urmator,6);
element(urmator^).v:=element(curent^).v+1;
element(urmator^).urm:=nil;
WriteLn('S-a creat elementul ',i,' =
'element(urmator^).v);
element(curent^).urm:=urmator;
curent:=urmator
END;
i:=1;
curent:=inceput;
WHILE element(curent^).urm <> nil DO
BEGIN
WriteLn('v[',i,']: ',element(curent^).v);
curent:=element(curent^).urm;
Inc(i)
END;
Writeln('v[',i,']: ',element(curent^).v)
END.

• Ca alternative ale procedurilor New, GetMem, respectiv Dispose,


FreeMem, pot fi folosite procedurile Mark şi Release, definite în unitatea System
astfel: Mark(VAR p:pointer), Release(VAR p:pointer). Procedura Mark
memorează în variabila p valoarea din HeapPtr, iar procedura Release depune în

13
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

variabila HeapPtr conţinutul variabilei p. Folosirea în pereche a procedurilor Mark


şi Release oferă posibilitatea ca, după diverse alocări, să se restabilească valoarea
variabilei HeapPtr cu valoarea memorată prin Mark. Apelul Release(HeapOrg)
aduce HeapPtr pe începutul zonei heap (eliberează zona).
În exemplul următor, se memorează în y adresa de la un moment dat a lui
HeapPtr (fie ea a), se modifică HeapPtr prin rezervarea a 12 octeţi (pentru x2, x3),
se reface conţinutul lui HeapPtr cu adresa a, ceea ce înseamnă că x3^ se rezervă la
această adresă (în locul variabilei x2^).

VAR
x1,x2,x3.x4:^REAL;
y:POINTER;
BEGIN
New(x1); x1^:12;
Mark(y); {Memorarea valorii HeapPtr in Y}
New(x2); x2^:=10;
New(x3); x3^:=34;
Release(y); {Reincarcarea valorii din Y in Heapptr}
New(x4); x4:=46; {Se va memora peste valoare 10 din x2}
………………………

14
STRUCTURA DE LISTĂ
ÎN LIMBAJUL PASCAL

Unul din principalele atribute ale datelor este structura sau modul de
organizare care le caracterizează. Structurile rezultă prin gruparea într-un anumit
mod a colecţiilor de date primitive. Exemple de structuri de date sunt: tablourile,
mulţimile, înregistrările. În continuare vor fi prezentate noi tipuri de organizări, şi
anume: listele simplu şi dublu înlănţuite şi cazurile particulare cunoscute sub numele
de stive şi cozi.

2.1 Noţiuni introductive

Un tip de structură de date poate fi gândit logic, făcând abstracţie de modul


particular în care datele sunt reprezentate în memoria calculatorului. El se reprezintă
particularizat prin diverse implementări din limbajele de programare.
Specificarea unui anume tip de organizare pentru datele procesate de un
program presupune definirea mulţimii de valori permise pe care o variabilă sau
parametru aparţinând acestui tip de date le poate avea, precum şi a mulţimii de
operaţii care pot fi efectuate cu astfel de date. Eficienţa unei scheme de prelucrare a
datelor este determinată în mare măsură de tipurile particulare de date utilizate.
Limbajul de programare Pascal oferă suficientă flexibilitate pentru
reprezentarea unor tipuri complexe de date, dar controlul corectitudinii modului în
care sunt utilizate revine programatorului. Structurile de date Pascal pentru
reprezentarea colecţiilor de date pot fi clasificate în structuri statice şi dinamice.
Structurile statice cuprind: array, set şi file pentru reprezentarea datelor de acelaşi tip
(omogene) şi record, pentru reprezentarea datelor eterogene. Structurile dinamice se
construiesc pe baza tipurilor de date Pascal referinţă legată şi referinţă liberă
(desemnată prin tipul predefinit pointer).
Definirea unei date reprezentată printr-o structură statică presupune alocarea
unui spaţiu de memorie de dimensiune invariabilă pe durata evoluţiei programului, în
timp ce dimensiunea unei date reprezentată prin intermediul unei structuri dinamice
poate fi modificată pe durata execuţiei.

15
Programarea calculatoarelor - Tehnica programării în limbajul Pascal

Pentru anumite clase de probleme, structurile statice de date nu numai că nu


sunt suficiente, dar se dovedesc chiar imposibil de folosit, datorită limitărilor la care
sunt supuse. În primul rând, spaţiul de memorie aferent unor astfel de date se
defineşte şi se rezervă în momentul compilării programului (rezervare statică), la o
dimensiune maximă (cuprinzătoare). Acest spaţiu nu poate fi disponibilizat şi nici
împărţit cu alte date, chiar dacă nu este în întregime utilizat în anumite momente ale
execuţiei programului. În al doilea rând, componentele structurilor statice ocupă
locuri prestabilite în spaţiul rezervat, determinate de relaţia de ordonare specifică
fiecărei structuri. În al treilea rând, limbajul defineşte operaţiile admise cu valorile
componentelor, potrivit tipului de bază al structurii, astfel încât numărul maxim şi
ordinea componentelor structurii nu pot fi modificate.
În aceste condiţii, structurile statice sunt dificil de utilizat în rezolvarea
problemelor care prelucrează mulţimi de date pentru care numărul şi ordinea
componentelor se modifică frecvent în timpul execuţiei programului. Pentru astfel de
situaţii, există posibilitatea utilizării datelor de tip dinamic, cărora li se pot aloca şi
elibera zone de memorie pe parcursul execuţiei programului.

2.2 Structura de listă

Organizarea de tip listă corespunde unei structurări lineare a datelor, la


nivelul fiecărei componente dispunându-se de informaţie suficientă pentru
identificarea următoarei componente a colecţiei. Datele dintr-o colecţie astfel
structurată sunt referite de obicei prin termenii de noduri, celule, componente etc.
Reprezentarea unei liste în limbajul Pascal poate fi realizată prin intermediul
structurii de date array, ordinea componentelor fiind dată de ordinea pe domeniul
valorilor corespunzătoare indexării şi, în consecinţă, următoarea componentă este
implicit specificată. Memorarea unei mulţimi de date {d1, d2,…, dn} prin intermediul
unei structuri statice poate fi realizată în limbajul Pascal utilizând un masiv
unidimensional.
Definirea tipului de date Pascal pentru memorarea ca listă a datelor {d1,
d2,…, dn} este:
const max=500;
type lst=array[1..max] of tip_informatie;
unde n ≤ max şi tip_informatie este numele tipului de date Pascal utilizat pentru
memorarea fiecărei date din mulţimea {d1, d2,…, dn}. Dacă lista este variabila de
tip lst utilizată pentru memorarea colecţiei {d1, d2,…, dn}, atunci data di este
memorată în componenta lista[i], 1 ≤ i ≤ n .

16
Structura de listă în limbajul Pascal

Dezavantajele majore în utilizarea reprezentării statice rezidă în volumul de


calcule necesare efectuării operaţiilor de inserţie/eliminare de noduri şi în necesitatea
păstrării unei zone de memorie alocată, indiferent de lungimea efectivă a listei.
Aceste dezavantaje sunt eliminate prin opţiunea de utilizare a structurilor dinamice.
Componentele unei liste dinamice sunt eterogene, fiecare nod conţinând o parte de
informaţie şi câmpuri de legătură care permit identificarea celulelor vecine.
Câmpurile de legătură sunt reprezentate de date de tip referinţă (adresă). În cazul
listelor cu un singur câmp de legătură (simplu înlănţuite), valoarea câmpului indică
adresa nodului următor, în timp ce, în cazul listelor cu dublă legătură (dublu
înlănţuite), valorile memorate în câmpurile de legătură sunt adresele componentelor
care preced şi, respectiv, urmează celulei. În ambele situaţii, câmpul de legătură
pentru indicarea celulei următoare corespunzător ultimei componente a listei are
valoarea nil în cazul listelor “deschise” (lineare) şi respectiv indică adresa primei
componente din listă în cazul listelor “închise” (circulare).
Se presupune că mulţimea de date {d1, d2,…, dn} este memorată ca listă
lineară. Convenţional, structura rezultată poate fi reprezentată grafic fie prin a) (listă
simplu înlănţuită), fie prin b) (listă dublu înlănţuită), unde simbolul
desemnează valoarea nil a câmpului de legătură (figura 2.1). În cazul în care
pentru memorarea datelor {d1, d2,…, dn} se doreşte utilizarea unei structuri de listă
circulară, reprezentarea grafică este dată în figura 2.2.

dn dn-1 … d2 d1

C a)

dn dn-1 … d2 d1
b)

Fig. 2.1 Liste lineare

Declarările tipurilor de date Pascal pentru definirea structurilor de liste


dinamice simplu şi, respectiv, dublu înlănţuite sunt:
a) Listă simplu înlănţuită b) Listă dublu înlănţuită

type lista=^nod; type lista=^nod;


nod=record nod=record
inf:tip_informatie; inf:tip_informatie;
leg: lista; st,dr: lista;
end; end;
unde tip_informatie este numele tipului de date Pascal utilizat pentru memorarea
fiecărei date din mulţimea{d1, d2,…, dn}.

17
Programarea calculatoarelor - Tehnica programării în limbajul Pascal

dn dn-1 … d2 d1

a) ultim

dn dn-1 … d2 d1

ultim
b)

Fig. 2.2 Liste circulare

2.3 Operaţii primitive asupra listelor

Accesul la informaţia stocată într-o variabilă de tip listă revine efectuând una
sau mai multe dintre operaţiile primitive: regăsirea nodului (dacă există) care
corespunde unei chei date (condiţie impusă asupra valorii câmpului de informaţie),
inserarea unei noi componente în listă, eliminarea componentei (componentelor) cu
proprietatea că valorile câmpurilor de informaţie satisfac o anumită cerinţă şi
înlocuirea câmpului de informaţie corespunzător unei componente printr-o informaţie
dată.
Accesarea componentelor unei liste reprezentată printr-o structură statică
poate fi realizată atât secvenţial, cât şi direct, utilizând valorile indicelui considerat
pentru indexare, în timp ce accesarea componentelor unei liste dinamice se
realizează, de regulă, numai secvenţial, începând cu prima componentă şi continuând
cu următoarele, pe baza valorilor câmpurilor de legătură. Convenţional, numim cap
al listei dinamice pointerul a cărui valoare este adresa primei componente a listei.

1. Parcurgerea integrală a datelor memorate într-o listă. Se presupune că


declarările de tip pentru definirea structurilor de liste menţionate anterior sunt
globale, relativ la procedurile descrise în continuare.

18
Structura de listă în limbajul Pascal

a) Lista reprezentată prin structură statică (masiv unidimensional)


procedure parcurgere1(var l:lst; n:word);
var i:word;
begin
for i:=1 to n do
prelucrare(l[i]);
end;

b) Lista reprezentată prin structură dinamică simplu înlănţuită


procedure parcurgere2( var cap:lista);
var p:lista;
begin
p:=cap;
while(p<>nil) do
begin
prelucrare(p^.inf);
p:=p^.leg;
end;
end;

c) Lista reprezentată prin structură dinamică dublu înlănţuită


procedure parcurgere3( var cap:lista);
var p:lista;
begin
p:=cap;
while(p<>nil) do
begin
prelucrare(p^.inf);
p:=p^.dr;
end;
end;

2. Regăsirea unei date d într-o colecţie memorată într-o listă


În continuare se presupune că funcţia egal(a,b:tip_informatie):boolean
returnează valoarea true dacă a şi b coincid, altfel returnează false. Funcţiile Pascal
exista1, exista2 şi exista3 calculează valoarea true, dacă data d se află în colecţia
memorată în lista dată ca argument, altfel calculează valoarea false.

a) Lista reprezentată prin structură statică (masiv unidimensional)


function exista1(l:lst; n:word; d:tip_informatie):boolean;
var i:word;
c:boolean;
begin
c:=false;
i:=1;
while(i<=n)and(not c) do
if egal(d,l[i]) then c:=true
else i:=i+1;
exista1:=c;
end;
b) Lista reprezentată prin structură dinamică simplu înlănţuită

function exista2(cap:lista;d:tip_informatie):boolean;
var p:lista;

19
Programarea calculatoarelor - Tehnica programării în limbajul Pascal

c:boolean;
begin
p:=cap;
c:=false;
while(p<>nil)and(not c) do
if egal(d,p^.inf) then c:=true
else p:=p^.leg;
exista2:=c;
end;

c) Lista reprezentată prin structură dinamică dublu înlănţuită

function exista3(cap:lista;d:tip_informatie):boolean;
var p:lista;
c:boolean;
begin
p:=cap;
c:=false;
while(p<>nil)and(not c) do
if egal(d,p^.inf) then c:=true
else p:=p^.dr;
exista3:=c;
end;

3. Inserarea unei date, d, într-o listă


Includerea unei noi componente într-o listă poate fi realizată, în funcţie de
cerinţele problemei particulare, la începutul listei, după ultima componentă din listă,
înaintea/după o componentă cu proprietatea că valoarea câmpului de infor-
maţie îndeplineşte o anumită condiţie. Presupunem că funcţia condiţie
((a,b:tip_informatie):boolean calculează valoarea true dacă a şi b verifică condiţia
formulată pentru inserare, altfel calculează valoarea false. De asemenea, procedura
atribuie(var destinatie:tip_informatie; sursa:tip_informatie) realizează copierea datei
sursa în destinatie.
Deoarece prin inserarea unei componente se poate ajunge la depăşirea
spaţiului disponibil de memorie, este necesară verificarea prealabilă a posibilităţii
realizării operaţiei (dacă se poate aloca spaţiu de memorie pentru componenta de
inserat). În cazul listelor reprezentate prin structuri statice, această verificare va fi
realizată prin compararea lungimii efective n a listei cu dimensiunea declarată max.
În cazul listelor dinamice, este utilizată funcţia Pascal predefinită MaxAvail, care
calculează lungimea maximă a zonelor nealocate contigue din memoria Heap.
Valoarea returnată pentru parametrul test este true, dacă inserarea a fost posibilă,
altfel este false.
De exemplu, inserarea la începutul listei decurge astfel:

a) Lista reprezentată prin structură statică (masiv unidimensional)

procedure inserare_la_inceput1(var l:lst; var n:word;


d:tip_informatie;var test:boolean);

20
Structura de listă în limbajul Pascal

var i:word;
begin
test:=true;
if n=max then test:=false
else
begin
for i:=n downto 1 do atribuie(l[i+1],l[i]);
atribuie(l[1],d);
n:=n+1;
end;
end;

b) Lista reprezentată prin structură dinamică simplu înlănţuită

procedure inserare_la_inceput2(var
cap:lista;d:tip_informatie;var test:boolean);
var p:lista;
begin
test:=true;
if MaxAvail<sizeof(nod) then test:=false
else
begin
new(p);
atribuie(p^.inf,d);
p^.leg:=cap;
cap:=p;
end;
end;

c) Lista reprezentată prin structură dinamică dublu înlănţuită

procedure inserare_la_inceput3(var
cap:lista;d:tip_informatie;var test:boolean);
var p:lista;
begin
test:=true;
if MaxAvail<sizeof(nod) then test:=false
else
begin
new(p);
atribuie(p^.inf,d);
p^.dr:=cap;p^.st:=nil;
cap:=p;
end;
end;

Procedurile de inserare la începutul listei pot fi folosite şi pentru crearea unei


liste în care să fie memorată o colecţie de date {d1, d2,…, dn}. Procedura copiaza(var
d:tip_informatie; var i:word) realizează copierea în d a datei di din colecţia
considerată. Utilizând procedurile de inserare deja descrise, operaţia de creare a unei
liste poate fi realizată astfel:

a) Lista reprezentată prin structură statică (masiv unidimensional)

procedure creeaza1(var l:lst; n:word; var test:boolean);


var i,j:word;

21
Programarea calculatoarelor - Tehnica programării în limbajul Pascal

d:tip_informatie;
begin
test:=true;
if n>max then test:=false
else
begin
i:=1;
while(i<=n) do
begin
copiaza(d,i);
inserare_la_inceput1(l,i,d,test)
end;
end;
end;

b) Lista reprezentată prin structură dinamică simplu înlănţuită

procedure creeaza2(var cap:lista;n:word;var test:boolean);


var i:word;
begin
i:=0; cap:=nil;
test:=true;
while(i<n)and(test) do
begin
i:=i+1;copiaza(d,i);
inserare_la_inceput2(cap,d,test);
end;
end;

4. Eliminarea (ştergerea) unei date, d, dintr-o listă


Modificarea conţinutului unei liste prin eliminarea uneia sau mai multor
componente poate fi descrisă secvenţial, astfel încât este suficient să dispunem de o
procedură care realizează eliminarea unei singure componente.
Criteriile de eliminare pot fi formulate diferit, cele mai uzuale fiind: prima
componentă, ultima componentă, prima componentă care îndeplineşte o anumită
condiţie, respectiv componenta ce precede/urmează prima componentă care
îndeplineşte o condiţie dată.
În aceste cazuri este necesară verificarea existenţei în lista considerată a
componentei ce trebuie eliminată. Verificarea asigură şi testarea faptului că lista
prelucrată este vidă sau nu. Analog operaţiilor de inserare, în cadrul procedurilor
următoare, parametrul test returnează valoarea true, dacă eliminarea este efectivă,
altfel returnează false.
De exemplu, eliminarea primei componente dintr-o listă are loc astfel:
a) Lista reprezentată prin structură statică (masiv unidimensional)
procedure elimina_prima1(var l:lst; var n:word; var
d:tip_informatie;var test:boolean);
var i:word;
begin
test:=true;
if n=0 then test:=false
else
begin
atribuie(d,l[1]);

22
Structura de listă în limbajul Pascal

for i:=1 to n-1 do atribuie(l[i],l[i+1]);


n:=n-1;
end;
end;

b) Lista reprezentată prin structură dinamică simplu înlănţuită


procedure elimina_prima2(var cap:lista;var d:tip_informatie;var
test:boolean);
var p:lista;
begin
test:=true;
if cap=nil then test:=false
else
begin
p:=cap;
cap:=cap^.leg;
atribuie(d,p^.inf);
dispose(p);
end;
end;

c) Lista reprezentată prin structură dinamică dublu înlănţuită


procedure elimina_prima3(var cap:lista;var d:tip_informatie;var
test:boolean);
var p:lista;
begin
test:=true;
if cap=nil then test:=false
else
begin
p:=cap;
cap:=cap^.dr;
cap^.st:=nil;
atribuie(d,p^.inf);
dispose(p);
end;
end;
2.4 Liste circulare

În anumite cazuri, este preferabilă renunţarea la structura de tip linear a


listelor şi utilizarea unei legături de la ultima componentă către capul listei, rezultând
ceea ce se numeşte listă circulară (figura 2.2).
Avantajul utilizării acestui tip de structură este posibilitatea de accesare
dintr-un element al listei a oricărui alt element. În continuare sunt prezentate module
pentru realizarea unor operaţii de bază în lucrul cu liste circulare.

type
tip_informatie=string;
clista=^nod;
nod=record
inf:tip_informatie;
leg:clista;
end;

23
Programarea calculatoarelor - Tehnica programării în limbajul Pascal

procedure atribuie(var d:tip_informatie;s:tip_informatie);


begin
d:=s;
end;
procedure inserare_la_inceput(var
ultim:clista;d:tip_informatie;var test:boolean);
var
p:clista;
cap:clista;
begin
test:=true;
if MaxAvail<sizeof(nod) then test:=false
else
begin
new(p);
atribuie(p^.inf,d);
if ultim=nil then
begin
ultim:=p;
ultim^.leg:=ultim;
end
else
begin
cap:=ultim^.leg;
p^.leg:=cap;
ultim^.leg:=p;
end;
end;
end;
procedure inserare_la_sfarsit(var
ultim:clista;d:tip_informatie;var test:boolean);
var
p:clista;
cap:clista;
begin
test:=true;
if MaxAvail<sizeof(nod) then test:=false
else
begin
new(p);
atribuie(p^.inf,d);
if ultim=nil then
begin
ultim:=p;
ultim^.leg:=ultim;
end
else
begin
cap:=ultim^.leg;
p^.leg:=cap;
ultim^.leg:=p;
ultim:=p;
end;
end;
end;

procedure elimina_la_inceput(var ultim:clista;var


d:tip_informatie;var test:boolean);
var
cap:clista;

24
Structura de listă în limbajul Pascal

begin
test:=true;
if ultim=nil then test:=false
else
begin
cap:=ultim^.leg;
atribuie(d,cap^.inf);
if cap=ultim then
begin
dispose(ultim);
ultim:=nil;
end
else
begin
ultim^.leg:=cap^.leg;
dispose(cap);
end;
end;
end;
procedure elimina_la_sfarsit(var ultim:clista;var
d:tip_informatie;var test:boolean);
var
p:clista;
cap:clista;
begin
test:=true;
if ultim=nil then test:=false
else
begin
cap:=ultim^.leg;
atribuie(d,ultim^.inf);
if cap=ultim then
begin
dispose(ultim);
ultim:=nil;
end
else
begin
p:=cap;
while(p^.leg<>ultim) do p:=p^.leg;
p^.leg:=cap;
dispose(ultim);
ultim:=p
end;
end;
end;
procedure afisare(ultim:clista);
var p,cap:clista;
begin
if ultim=nil then writeln('Lista vida')
else
begin
cap:=ultim^.leg;
p:=cap;
repeat
write(p^.inf,' ');
p:=p^.leg;
until p=cap;
writeln;
end;
end;

25
Programarea calculatoarelor - Tehnica programării în limbajul Pascal

2.5 Stive şi cozi

Accesul la informaţia memorată într-o listă lineară pentru efectuarea


operaţiilor de inserare şi eliminare este permis la oricare dintre componentele
colecţiei. Aşa după cum a rezultat în §2.3, identificarea poziţiei în care trebuie
efectuată inserarea/eliminarea presupune iniţierea unei secvenţe de operaţii de
căutare, ceea ce determină creşterea timpului de lucru. Pe de altă parte, o mulţime de
aplicaţii pot fi modelate utilizând liste lineare în care introducerea şi, respectiv,
eliminarea informaţiilor sunt permise numai la capete. Astfel au fost definite tipurile
de listă stivă şi coadă, care impun un tip de organizare a aplicării operaţiilor de
inserare şi eliminare.
Stiva
Se numeşte stivă o listă organizată astfel încât operaţiile de inserare şi
eliminare sunt permise numai la prima componentă. Acest mod de organizare
corespunde unei gestiuni LIFO (Last In First Out) a informaţiei stocate.
Modelul corespunde unei stive de cărţi. Adăugarea unei noi cărţi în stivă se
face deasupra primei cărţi, iar extragerea este posibilă numai pentru prima carte.
Operaţiile de inserare şi eliminare într-o stivă pot fi descrise prin intermediul
procedurilor inserare_la_inceput1, inserare_la_inceput2, elimina_prima1,
elimina_prima2. Operaţiile elementare pentru gestiunea informaţiei memorate într-o
stivă sunt:
- push(S,d,test) – inserarea informaţiei d în stiva S;
- pop(d,S,test) – preluarea cu eliminare a informaţiei memorate în prima
celulă a stivei S;
- top(d,S,test) – preluarea fără eliminare a informaţiei deţinute de prima
componentă a stivei S.
Parametrul test returnează true dacă operaţia este posibilă, altfel false.

Coada
Se numeşte coadă o listă organizată astfel încât operaţia de inserare este
permisă la ultima componentă, iar operaţia de eliminare este permisă numai la prima
componentă. Acest mod de organizare corespunde unei gestiuni FIFO (First In First
Out) a informaţiei stocate.
Modelul corespunde unei cozi de aşteptare la un magazin. O nouă persoană
se aşază la coadă după ultimul cumpărător, iar persoana care îşi achită nota de plată
(primul cumpărător) părăseşte coada.
Implementarea unei liste coadă poate fi efectuată atât printr-o structură
statică (masiv unidimensional), cât şi printr-o structură dinamică de tip listă. În
scopul eficientizării operaţiilor de inserare/extragere, în cazul implementării cozilor
prin structuri dinamice lineare, este necesară utilizarea a două informaţii: adresa

26
Structura de listă în limbajul Pascal

primei componente şi adresa ultimei componente. Aceste informaţii pot fi menţinute


explicit prin utilizarea a doi pointeri sau prin utilizarea unui pointer şi a unei structuri
de listă circulară.
1. Operaţiile de inserare şi eliminare pentru listă coadă reprezentată static
sunt descrise de procedurile inserare_la_sfarsit1 şi eliminare_la_inceput prezentate
în cadrul secţiunii §2.3.
2. Reprezentarea unei liste coadă printr-o structură dinamică circulară
utilizează o singură variabilă adresă, ultim, pentru referirea ultimei componente a
listei. Operaţiile de inserare şi eliminare sunt descrise de procedurile inserare_coada
şi eliminare_coada.

procedure inserare_coada(var ultim:lista; d:tip_informatie; var


test:boolean);
var p:lista;
begin
test:=true;
if MaxAvail<sizeof(nod) then test:=false
else
begin
new(p);
atribuie(p^.inf,d);
if ultim=nil then
begin
ultim:=p;
ultim^.leg:=ultim;
end
else
begin
p^.leg:=ultim^.leg;
ultim^.leg:=p;
ultim:=p;
end;
end;
end;
procedure elimina_coada(var ultim:lista; var d:tip_informatie;
var test:boolean);
var prim:lista;
begin
test:=true;
if ultim=nil then test:=false
else
begin
prim:=ultim^.leg;
atribuie(d,prim^.inf);
if ultim^.leg=ultim then
begin
dispose(ultim);
ultim:=nil;
end
else
begin
ultim^.leg:=prim^.leg;
dispose(prim);
end;
end;
end;

27
GRAFURI. IMPLEMENTĂRI
ÎN LIMBAJUL PASCAL

Grafurile sunt structuri de date cu aplicaţii în multe domenii ale prelucrării


automate a datelor, algoritmii pentru reprezentarea şi prelucrarea grafurilor fiind
consideraţi fundamentali în acest domeniu.
În cadrul secţiunii 3.1 sunt prezentate principalele caracteristici ale
grafurilor, precum şi modalităţile uzuale de reprezentare a structurii de graf. În
continuare sunt descrise tehnicile de parcurgere a grafurilor în lăţime şi în adâncime.
Verificarea conexităţii şi calculul drumurilor în grafuri sunt tratate în secţiunea 3.3

3.1 Definiţii, caracteristici şi reprezentări ale grafurilor

Definiţia 3.1.1. Un graf (sau un graf neorientat) este o structură G=(V,E),


unde V este o mulţime nevidă, iar E este o submulţime (posibil vidă) a mulţimii
perechilor neordonate cu componente distincte din V.
Obiectele mulţimii V se numesc vârfuri, iar obiectele mulţimii E se numesc
muchii. Dacă e ∈ E, e = (u, v) = uv , se spune că muchia e are ca extremităţi u,v sau
că muchia e este determinată de vârfurile u şi v. Dacă e=uv ∈ E se spune că vârfurile
u, v sunt incidente cu muchia e.

Definiţia 3.1.2. Fie G = (V,E) graf. Vârfurile u, v sunt adiacente în G dacă


uv ∈ E.

Definiţia 3.1.3. Graful G = (V,E) este finit, dacă V este o mulţime finită.

În cadrul acestui capitol vor fi considerate în exclusivitate grafurile finite,


chiar dacă acest lucru nu va fi precizat în mod explicit.

Definiţia 3.1.4. Fie Gi =(Vi,Ei), i=1,2 grafuri. G2 este un subgraf al grafului


G1 dacă V2 ⊆ V1 şi E 2 ⊆ E 1 . Dacă G2 este un subgraf al lui G1, G2 este un graf
parţial al lui G1 dacă V2=V1.

28
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Definiţia 3.1.5. Un digraf este o structură D = (V,E), unde V este o mulţime


nevidă de obiecte numite convenţional vârfuri, iar E este o mulţime (posibil vidă) de
perechi ordonate cu componente elemente distincte din V. Convenţional, elementele
mulţimii E sunt numite arce sau muchii ordonate.
Terminologia utilizată relativ la digrafuri este similară celei corespunzătoare
grafurilor.

Definiţia 3.1.6. Se numeşte graf ponderat o structură (V,E,W), unde G =


= (V,E) este graf, W funcţie, W : E → (0, ∞ ) . Funcţia W este numită pondere şi ea
asociază fiecărei muchii a grafului un cost/câştig al parcurgerii ei.

Definiţia 3.1.7. Fie G=(V,E) un graf, u,v∈V.


Secvenţa de vârfuri Γ: u0, u1,..,un este un u-v drum dacă u0=u, un=v, uiui+1∈E
pentru toţi i, 0 ≤ i ≤ n .

Moduri de reprezentare a grafurilor

Cea mai simplă reprezentare a unui graf este cea intuitivă, grafică; fiecare
vârf este figurat printr-un punct, iar muchiile sunt reprezentate prin segmentele de
dreaptă, orientate (în cazul digrafurilor) sau nu şi etichetate (în cazul grafurilor
ponderate) sau nu, având ca extremităţi punctele corespunzătoare vârfurilor care le
determină.

Exemple:
3.1. Fie G = (V, E) graf, cu V = {1, 2, 3, 4, 5}, E = {(1,2),(1,3),(2,5),(3,5)}.
O posibilă reprezentare grafică este:

2 ● 4

3
5

29
Grafuri. Implementări în limbajul Pascal

3.2. Fie D = (V, E) digraf, cu V = {1, 2, 3, 4, 5}, E = {(1,2),(1,3),


(2,5),(3,5),(1,5)}. Digraful poate fi reprezentat grafic astfel:

2 ● 4

3
5

3.3. Fie G = (V, E, W) graf ponderat, cu V = {1, 2, 3, 4}, E =


{(1,2),(1,3),(2,4),(3,4)}, W((1,2))=5, W((1,3))=1, W((2,4))=2, W((1,4))=7. O
posibilă reprezentare grafică este:
1

5 1

2 3
7
2

4
Deşi acest mod de reprezentare este foarte comod şi sugestiv, în special în
cazul grafurilor cu număr mic de vârfuri, pentru prelucrări cu ajutorul calculatorului
sunt necesare reprezentări prin intermediul structurilor de date.
O modalitate de reprezentare este cea prin matrice de adiacenţă. Dacă
G=(V,E) este graf sau digraf cu V = n , atunci matricea de adiacenţă A ∈ Mnxn({0,1})
are componentele:
( )
1, dacă vi , v j ∈ E
a ij =  ,
0, altfel

30
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

unde vi, vj reprezintă cel de-al i-lea, respectiv cel de-al j-lea nod din V. Se observă că,
în cazul unui graf neorientat, matricea de adiacenţă este simetrică -
∀i, j = 1, n , a ij = a ji (perechile de vârfuri ce caracterizează muchiile sunt
neordonate, deci dacă uv ∈ E, atunci şi vu ∈ E), în timp ce, în cazul unui digraf, este
( ) ( )
posibil ca v i , v j ∈ E, v i , v j ∉ E , deci aij ≠ aji.

Exemplu:
3.4. Graful din exemplul 3.1 şi digraful din exemplul 3.2 sunt reprezentate
prin matricele de adiacenţă:
0 1 1 0 0  0 1 1 0 1
   
1 0 0 0 1  0 0 0 0 1
 
A = 1 0 0 0 1 pentru 3.1, A =  0 0 0 0 1 pentru 3.2
   
0 0 0 0 0 0 0 0 0 0
 0 1 1 0 0 0 0 0 0 0 
  

În cazul grafurilor ponderate, reprezentarea matriceală este asemănătoare


celei prezentate anterior. Matricea ponderilor unui graf ponderat G = (V, E, W),
V = n , W ∈ Mnxn((0, ∞ )) are componentele:
W (( v i , v j ) ), dacă (v i , v j ) ∈ E
w i, j =  ,
α, altfel
unde vi, vj reprezintă cel de-al i-lea, respectiv cel de-al j-lea nod din V, α =0
dacă ponderea are semnificaţia de câştig, respectiv α = ∞ în cazul în care se doreşte
reprezentarea costurilor ca ponderi ale grafului.

Exemplu:
3.5. Presupunând că ponderile reprezintă costuri, matricea de reprezentare a
grafului din exemplul 3.3. este:
∞ 5 1 7
 
5 ∞ ∞ 2
W= .
1 ∞ ∞ ∞
 
7 2 ∞ ∞ 

Reţinând numai “informaţia utilă”, şi anume existenţa unei muchii între două
vârfuri şi eventual valoarea ponderii ei, se obţine reprezentarea tabelară, mai
economică din punctul de vedere al spaţiului de memorare. În cazul în care există
vârfuri izolate în graf (ce nu sunt incidente cu nici o muchie), atunci este necesară
păstrarea acestora într-un vector suplimentar. Mulţimea muchiilor se memorează

31
Grafuri. Implementări în limbajul Pascal

într-o matrice cu E linii şi 2 coloane dacă graful nu este ponderat, respectiv cu


3 coloane, dacă graful este ponderat. În primele două coloane se scriu perechile de
vârfuri ce determină muchiile, în cazul grafurilor ponderate cea de-a treia coloană
conţine valoarea ponderii muchiei respective.
Exemple:
3.6. Graful din exemplul 3.1 poate fi reprezentat astfel: deoarece 4 este vârf
izolat, vectorul suplimentar este VS = (4), pentru reprezentarea muchiilor fiind
1 2
 
1 3
utilizată matricea A = 
2 5
 
3 5 

3.7. Digraful din exemplul 3.2 poate fi reprezentat astfel: deoarece 4 este
vârf izolat, vectorul suplimentar este VS = ( 4), arcele fiind reprezentate prin
1 2
 
1 3
A = 1 5
 
2 5
3 5 

O altă reprezentare este prin intermediul listelor. Reprezentarea permite
utilizarea economică a spaţiului de memorare şi, în anumite cazuri, implementări mai
eficiente pentru anumite clase de algoritmi. Vârfurile grafului se memorează într-o
listă, fiecare celulă a listei având o legătură către lista vecinilor acelui vârf (vârfurile
din graf adiacente cu vârful corespunzător acelei celule şi indicat ca informaţie utilă).
În situaţia în care graful nu este ponderat, el se reprezintă printr-o listă de
liste, şi anume: nodurile grafului se trec într-o listă L_nod, fiecare celulă având
structura
informaţie legătură vecini legătură nod următor
unde:
• câmpul informaţie conţine identificatorul nodului;
• legătură vecini reprezintă pointer către capul listei vecinilor;
• legătură nod următor conţine adresa următoarei celule din lista
L_nod.
Un graf ponderat poate fi reprezentat în mod similar, cu diferenţa că fiecare
celulă din lista vecinilor conţine şi ponderea muchiei respective (muchia care are ca
extremităţi vârful referit prin identificatorul de nod din lista vecinilor şi respectiv
vârful indicat de informaţia acelei celule din L_nod ce conţine adresa primului
element al listei vecinilor).

32
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

3.2 Modalităţi de parcurgere a grafurilor

Parcurgerea unui graf reprezintă o modalitate de vizitare a tuturor vârfurilor


grafului, fiecare vârf fiind vizitat o singură dată. În acest paragraf sunt prezentate
două modalităţi de parcurgere a grafurilor neorientate.
Ambele metode de parcurgere presupun selectarea unui vârf iniţial v0. Prin
aplicarea acestor metode sunt identificate numai vârfurile grafului cu proprietatea că
există cel puţin un drum de la vârful iniţial către acel vârf. Grafurile cu proprietatea
că oricare două vârfuri sunt conectate printr-un drum se numesc conexe şi sunt
prezentate în § 3.3. Dacă graful este conex, atunci prin aplicarea metodelor de
parcurgere vor fi identificate toate vârfurile grafului.

3.2.1 Metoda de parcurgere BF (Breadth First)

Ideea traversării BF este de parcurgere în lăţime a grafului, în sensul că


vârfurile grafului sunt prelucrate în ordinea crescătoare a “distanţelor” la vârful
iniţial. Prin distanţă se înţelege numărul de muchii din drumul identificat la acel
moment de la vârful iniţial către acel vârf. La momentul iniţial, vârful curent este v0.
Deoarece vârful curent la fiecare moment trebuie să fie unul aflat la distanţă minimă
de v0, se poate proceda în modul următor: iniţial lui v0 i se asociază valoarea 0 şi
fiecărui vârf diferit de v0 i se asociază valoarea –1. Dacă valoarea asociată vârfului
curent este m, atunci fiecăruia dintre vecinii acestuia de valoare –1 i se asociază
valoarea m+1. Se observă că dacă după ce toate vârfurile de valoare m au fost
considerate şi nici unui vârf nu i-a fost recalculată valoarea, atunci toate vârfurile
conectate cu v0 au fost găsite, deci calculul se încheie.

Exemple:
3.8 Fie graful:

2 3

4 6

5 7
şi v0=1.

33
Grafuri. Implementări în limbajul Pascal

Valorile calculate prin aplicarea metodei prezentate sunt:

vârf 1 2 3 4 5 6 7
m
0 0 -1 -1 -1 -1 -1 -1
1 0 1 1 -1 1 -1 1
2 0 1 1 2 1 2 1
0 1 1 2 1 2 1
Ordinea de vizitare a vârfurilor: 1,2,3,5,7,4,6.

3.9. Fie graful:

1 8

2 3

4 6 9 10

7
5
şi v0=1.
Se observă că vârfurile 8, 9 şi 10 nu sunt conectate cu vârful iniţial.
Valorile rezultate prin aplicarea metodei sunt:

vârf 1 2 3 4 5 6 7 8 9 10
m
0 0 -1 -1 -1 -1 -1 -1 -1 -1 -1
1 0 1 1 -1 1 -1 1 -1 -1 -1
2 0 1 1 2 1 2 1 -1 -1 -1
0 1 1 2 1 2 1 -1 -1 -1
Ordinea de vizitare a vârfurilor este 1,2,3,5,7,4,6.

Se observă că valorile lui m calculate în final reprezintă numărul de muchii


corespunzător celui mai scurt drum care conectează vârful iniţial cu vârful respectiv,
pentru vârfurile neconectate cu v0 valoarea lui m rezultată la terminarea calculului
este –1.
O implementare diferită, dar urmând aceeaşi idee, rezultă prin utilizarea
următoarelor structuri de date:
• A matricea de adiacenţă a grafului;
• o structură de tip coadă, C, în care sunt introduse vârfurile ce urmează a fi
vizitate şi procesate (în sensul cercetării vecinilor lor);

34
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

• Un vector c cu n componente, unde:


1, dacă i a fost introdus în coadă
ci = 
0, altfel
şi n este numărul vârfurilor grafului.
Componentele vectorului c vor fi iniţializate cu valoarea 0.
Descrierea metodei este:
• se iniţializează coada C cu vârful v0;
• cât timp coada este nevidă, se extrage un vârf i din coadă, se vizitează, apoi
se introduc în coadă numai vecinii acestuia care nu au fost deja introduşi (adică toate
vârfurile k având proprietatea că c[k]=0 şi a[i,k]=1). Vârfurile i ce au fost introduse
în coadă sunt marcate: ∀i introdus în coadă, c[i]=1.
În continuare este prezentat un program Pascal pentru parcurgerea în lăţime a
unui graf. Vârfurile grafului sunt numerotate de la 1 până la n, parcurgerea începând
cu vârful numerotat cu 1. Graful este reprezentat prin matricea de adiacenţă.
Structura de coadă este implementată ca o listă simplu înlănţuită, procedurile push şi
pop realizând introducerea, respectiv extragerea unui element din coadă.

uses crt;
type
ptcoada=^coada;
coada=record
inf:byte;
leg:ptcoada;
end;

var
prim,ultim:ptcoada;
c:array[1..20] of byte;
a:array[1..20,1..20] of byte;
i,j,k,n:byte;
procedure push(var p,u:ptcoada;i:byte);
var a:ptcoada;
begin
new(a); a^.inf:=i; a^.leg:=nil;
if p=nil then
begin
p:=a; u:=a;
end
else begin
u^.leg:=a;u:=a;
end;
end;
procedure pop(var p,u:ptcoada;var i:byte);
var a:ptcoada;
begin
if p<>nil then
begin
i:=p^.inf; a:=p; p:=p^.leg;
dispose(a);
if p=nil then u:=nil;
end;

35
Grafuri. Implementări în limbajul Pascal

end;
begin {program principal}
clrscr;
write('Numarul de varfuri:');
readln(n);
writeln('Matricea de adiacenta');
for i:=1 to n do
for j:=1 to n do
begin
write('a[',i,',',j,']=');
readln(a[i,j]);
end;
for i:=2 to n do c[i]:=0;
readln;
clrscr;
writeln('Incepem parcurgerea de la nodul 1');
c[1]:=1;
prim:=nil;ultim:=nil;
push(prim,ultim,1);
while prim<>nil do
begin
pop(prim,ultim,i);
write(i,' ');
for k:=1 to n do
if (a[i,k]=1)and(c[k]=0) then
begin
c[k]:=1;
push(prim,ultim,k);
end;
end;
readln;
end.

3.2.2 Metoda de parcurgere DF (Depth First)

Ideea metodei DF revine la parcurgerea în adâncime a grafurilor, în sensul


că, la fiecare moment, dacă M este mulţimea vârfurilor vizitate de procedură, pentru
vizitarea vecinilor este considerat unul din vârfurile din M cu proprietatea că
lungimea drumului calculat până la vârful iniţial v0 este maximă.
Implementarea metodei poate fi realizată în mai multe moduri, pentru
menţinerea mulţimii vârfurilor grafului disponibilizate până la momentul curent fiind
utilizată o structură de date de tip stivă, S.
La momentul iniţial se introduce în stivă v0. La fiecare pas, se preia cu
ştergere ca vârf curent vârful stivei S şi se introduc în stivă vecinii încă nevizitaţi ai
vârfului curent. Un vârf se marchează ca vizitat în momentul introducerii lui în S.
Calculul continuă până când este efectuat un acces de preluare din stivă şi se constată
că S este vidă. Pentru gestiunea vârfurilor vizitate, se utilizează un vector c cu
n componente, unde n reprezintă numărul vârfurilor grafului şi, la fiecare moment,
componentele sunt:

36
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

1, dacă i a fost vizitat


ci = 
0 , altfel

Componentele vectorului c vor fi iniţializate cu valoarea 0.

Exemple:
3.10. Pentru graful:

2 3

4 6

5 7

şi v0 = 1, ordinea în care sunt vizitate vârfurile este: 1, 2, 3, 4, 6, 7, 5.

3.11. Pentru graful

1 8

2 3

4 6 9 10

7
5

şi v0 = 1, ordinea în care sunt vizitate vârfurile este: 1, 2, 3, 4, 6, 7, 5. Vârfurile 8, 9 şi


10 nu sunt vizitate de procedură, pentru că nu sunt conectate de vârful iniţial selectat.
O variantă de implementare a metodei DF rezultă prin gestionarea stivei S
după cum urmează. Iniţial vârful v0 este unicul component al lui S . La fiecare etapă
se preia, fără ştergere, ca vârf curent vârful stivei. Se introduce în stivă unul dintre
vecinii vârfului curent încă nevizitat. Vizitarea unui vârf revine la introducerea lui în
S. Dacă vârful curent nu are vecini încă nevizitaţi, atunci este eliminat din stivă şi

37
Grafuri. Implementări în limbajul Pascal

este efectuat un nou acces de preluare a noului vârf al stivei ca vârf curent. Calculul
se încheie în momentul în care este efectuat un acces de preluare a vârfului stivei ca
vârf curent şi se constată că S este vidă. Evident, nici în cazul acestei variante nu vor
fi vizitate vârfurile care nu sunt conectate cu vârful ales iniţial.

3.3 Drumuri în grafuri. Conexitate

3.3.1 Drumuri; definiţii

Una dintre cele mai importante proprietăţi ale grafurilor o constituie


posibilitatea de accesare, prin intermediul unei secvenţe de muchii (arce), dintr-un
vârf dat a oricărui alt vârf al grafului, proprietate cunoscută sub numele de conexitate
sau conexiune. Aşa după cum a rezultat în §3.2., dacă G=(V,E) este un graf conex,
atunci pentru orice vârf iniţial v0 considerat, metodele BF şi DF permit vizitarea
tuturor vârfurilor din V.

Definiţia 3.3.1. Fie G=(V,E) un graf, u,v∈V. Secvenţa de vârfuri


Γ: u0, u1,..,un este un u-v drum dacă u0=u, un=v, uiui+1∈E pentru toţi i, 0 ≤ i ≤ n .
Lungimea drumului, notată l(Γ),este egală cu n. Convenţional, se numeşte trivial, un
drum Γ cu l(Γ)=0.
Definiţia 3.3.2. Fie Γ: u0, u1,..,un un drum în graful G=(V,E). Γ este un drum
închis dacă u0=un; în caz contrar, Γ este deschis. Drumul Γ este elementar dacă
oricare două vârfuri din Γ sunt distincte, cu excepţia, eventual, a extremităţilor.
Drumul Γ este proces dacă, pentru orice 0 ≤ i ≠ j ≤ n − 1 , uiui+1 ≠ ujuj+1.
Evident, orice drum elementar este un proces.

Definiţia 3.3.3. Fie Γ: u0, u1,..,un un drum în graful G = (V,E). Γ’: v0, v1,..,vm
este un subdrum al lui Γ dacă Γ’ este un drum şi pentru orice j, 0 ≤ j ≤ m , există i,
0 ≤ i ≤ n , astfel încât ui = vj.
Evident, orice drum cu lungime cel puţin 1 conţine cel puţin un drum
elementar cu aceleaşi extremităţi.
Într-adevăr, dacă Γ: u0, u1,..,un nu este elementar, atunci există
0 ≤ i < j ≤ n şi i ≠ 0 sau j ≠ n, astfel încât ui = uj.

Atunci drumul

38
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

u j u j+1 ...u n , dacă i = 0



Γ : u 0 u 1 ...u i , dacă j = 0
'

u u ...u u ...u , dacă i ≠ 0, j ≠ n


 0 1 i j+1 n
este, de asemenea, un u0-un drum. Aplicând în continuare eliminarea duplicatelor
vârfurilor în modul descris, rezultă în final un u0-un drum elementar.

3.3.2 Matricea existenţei drumurilor; algoritmul Roy-Warshall

Fie G=(V,E) un graf, V = n . Dacă A este matricea de adiacenţă asociată

grafului, atunci, pentru orice p≥1, a ij( p ) este numărul vi-vj drumurilor distincte de

lungime p din graful G, unde A p = a ij( p ) . ( )


Definiţia 3.3.4. Fie Mn({0,1)} mulţimea matricelor de dimensiuni nxn,
componentele fiind elemente din mulţimea {0,1}. Pe Mn({0,1)}se definesc operaţiile
binare, notate ⊕ şi ⊗ , astfel: pentru orice A=(aij), B=(bij) din Mn({0,1)},
A ⊕ B=(cij), A ⊗ B=(dij), unde
1 ≤ i, j ≤ n ,
cij=max{aij, bij}
dij=max{min{aik, bkj}, 1 ≤ k ≤ n }.

Dacă A=(aij) ∈ Mn({0,1)}, se notează {A = (a ); k ≥ 1} secvenţa de


k (k)
ij

matrice definită prin:


(1) k ( k −1)
A = A, A = A ⊗ A , ∀k ≥ 2 .
Dacă A este matricea de adiacenţă a unui graf G=(V,E), atunci pentru fiecare
(k ) 1, dacă există drum de la i la j de lungime k
k, 1 ≤ k ≤ n − 1 , a ij = 
0, altfel
(1) ( 2) ( n −1)
M=A ⊕A ⊕Κ ⊕ A se numeşte matricea existenţei drumurilor
în graful G. Semnificaţia componentelor matricei M este:
0, dacă nu există v i − v j drum în G
∀1 ≤ i, j ≤ n , m ij = 
1, altfel

39
Grafuri. Implementări în limbajul Pascal

Exemplu:
3.12. Pentru graful:

0 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1
       
1 0 0 0 2  0 1 1 1 3 1 0 1 1 1 1 1 1
A= ,A = ,A = ,M =
1 0 0 1 1 1 1 1 1 1 1 1  1 1 1 1
       
1 0 1 0  1 1 1 1    1 1
  1 1 1 1 1 1
Calculul matricei existenţei drumurilor permite verificarea faptului că un
graf dat este conex: graful este conex dacă şi numai dacă toate componentele
matricei M sunt egale cu 1.
Algoritmul Roy-Warshall calculează matricea existenţei drumurilor într-un
graf G cu n vârfuri.
procedure Roy_Warshall (a,n,m);
i,j,k:integer;
do-for i=1,n,1
do-for j=1,n,1
mij=aij;
do-for j=1,n,1
do-for i=1,n,1
if mij =1 then
do-for k=1,n,1
if mik<mkj then
mik=mkj;
endif;
enddo;
endif;
enddo;
enddo;
end;

40
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Datele de intrare sunt: n, numărul de noduri şi A, matricea de adiacenţă


corespunzătoare grafului. Matricea M calculată de algoritm constituie ieşirea şi este
matricea existenţei drumurilor în graful G.

3.3.3 Componente conexe ale unui graf

Definiţia 3.3.5. Fie G=(V,E) graf netrivial. Vârfurile u,v ∈ V sunt conectate
dacă există un u-v drum în G.

Definiţia 3.3.6. Dacă G este un graf, atunci o componentă conexă a lui G este
un subgraf conex al lui G, maximal în raport cu proprietatea de conexitate.
Evident, un graf este conex dacă şi numai dacă numărul componentelor sale
conexe este 1. Mulţimile vârfurilor corespunzătoare oricăror două componente
conexe distincte sunt disjuncte. Mulţimile vârfurilor corespunzătoare componentelor
conexe ale unui graf formează o partiţie a mulţimii vârfurilor grafului.
Multe aplicaţii modelate în termeni de grafuri impun determinarea
componentelor conexe corespunzătoare unui graf dat. Problema poate fi rezolvată în
modul următor: se selectează un vârf al grafului, se determină componenta conexă
care-l conţine; dacă există vârfuri care nu aparţin componentei conexe determinate,
se alege unul dintre acele vârfuri căruia i se determină componenta conexă care-l
conţine; în continuare, se repetă procedeul până când au fost găsite toate
componentele conexe ale grafului.
Pentru G=(V,E), V = n , n ≥ 1 şi v0 ∈ V, paşii algoritmului pentru
determinarea componentei conexe care conţine un vârf v0 dat sunt:

Pasul 1: V0={v0}; E0= Φ ; i=0;


Pasul 2: repetă Pas 3 până când Vi=Vi-1 şi Ei=Ei-1
Pasul 3: i=i+1;
Vi = Vi −1 ∪ {v / v ∈ V, ∃u ∈ Vi −1 , uv ∈ E};
E i = E i −1 ∪ {e / e ∈ E, ∃u ∈ Vi −1 , u incident cu e};

Ieşirea este G1=(Vi,Ei), componenta conexă din care face parte v0.

Exemplu:
3.13. Pentru graful
1 2 7 3

4 5 8 6

41
Grafuri. Implementări în limbajul Pascal

Aplicarea algoritmului descris, pentru v0=1, determină următoarea evoluţie:

i Vi Ei
i=0 {1} Ø
i=1 {1,2,4} {(1,2),(1,4)}
i=2 {1,2,4,7,5} {(1,2),(1,4),(2,7),(4,5),(4,7)}
i=3 {1,2,4,7,5,8} {(1,2),(1,4),(2,7),(4,5),(4,7),(5,8),(7,8)}
i=4 {1,2,4,7,5,8} {(1,2),(1,4),(2,7),(4,5),(4,7),(5,8),(7,8)}

3.3.4 Drumuri de cost minim

Definiţia 3.3.7. Fie G=(V, E, w) un graf ponderat. Costul drumului


Γ: u1, u2,..,un, notat L(Γ), este definit prin:
n −1
L(Γ ) = ∑ w (u i , u i +1 ) .
i =1
Pentru orice u şi v vârfuri conectate în G, u ≠ v, w-distanţa între u şi v, notată
D(u,v), este definită prin:
D(u, v ) = min{L(Γ ), Γ ∈ D uv }, unde Duv desemnează mulţimea tuturor u-v
drumurilor elementare din G. Dacă Γ ∈ D uv este astfel încât D(u,v)=L(Γ), drumul Γ
se numeşte de cost minim.
Cu toate că este utilizat termenul de w-distanţă, în general D nu este o
distanţă în sensul matematic al cuvântului.
În particular, dacă funcţia pondere asociază valoarea 1 fiecărei muchii a
grafului, atunci, pentru fiecare pereche de vârfuri distincte ale grafului, costul D(u,v)
este lungimea celui mai scurt drum între cele două vârfuri. În acest caz, D este o
distanţă pe mulţimea vârfurilor.
Dat fiind interesul pentru determinarea w-distanţelor şi a drumurilor de cost
minim în cadrul aplicaţiilor modelate pe grafuri, în continuare vor fi prezentaţi
algoritmi pentru rezolvarea acestor probleme.

Algoritmul Dijkstra
Algoritmul a fost propus de E. W. Dijkstra pentru determinarea w-distanţelor
D(u0,v) şi a câte unui u0-v drum de cost minim pentru fiecare vârf v≠u0 într-un graf
ponderat, unde u0 este prestabilit.
Fie (V,E,w) graf conex ponderat, u0∈V, S⊂V, u0∈S. Se notează S = V \ S şi
( ) { }
D u 0 , S = min D(u 0 , x ); x ∈ S . Fie v∈ S astfel încât D(u0,v)=D(u0, S ), Γ : u0,
u1,…,upv este un u0-v drum de cost minim. Evident, ∀0≤i≤p ui∈S şi Γ ’: u0, u1,…,up,
un u0- up drum de cost minim. De asemenea,
( ) {
D u 0 , S = min D(u 0 , u ) + w (uv); u ∈ S, v ∈ S, uv ∈ E . }

42
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Dacă x∈S, y∈ S astfel încât ( )


D u 0 , S = D(u 0 , x ) + w ( xy) , rezultă
D(u 0 , y ) = D(u 0 , x ) + w ( xy) . Pentru determinarea unui drum u0-v, cel mai ieftin,
algoritmul consideră o etichetare dinamică a vârfurilor grafului. Eticheta vârfului v
este (L(v),u), unde L(v) este lungimea celui mai ieftin drum u0-v determinat până la
momentul respectiv şi u este predecesorul lui v pe un astfel de drum.

Pentru (V,E,w) graf conex ponderat, V = n şi u0∈V, calculul implicat de


algoritmul Dijkstra poate fi descris astfel:
Pasul 1: i=0; S0={u0}; L(u0)=0, L(v)= ∞ pentru toţi v ∈ V, v≠u0. Dacă n=1
atunci stop
Pasul 2: Pentru toţi v∈ Si , dacă L(v)>L(ui)+w(uiv), atunci L(v)=L(ui)+w(uiv)
şi etichetează v cu (L(v),ui).
Pasul 3: Se determină d=min{L(v), v∈ S i } şi se alege ui+1∈ S i astfel încât
L(ui+1)=d.
Pasul 4: Si+1=Si ∪ {ui+1}
Pasul 5: i=i+1. Dacă i=n-1, atunci stop. Altfel, reia Pasul 2.

Evident, dacă (V,E,w) este graf ponderat neconex, atunci, pentru u0∈V,
algoritmul lui Dijkstra permite determinarea w-distanţelor D(u0,v) şi a câte unui u0-v
drum de cost minim pentru toate vârfurile v din componenta conexă căreia îi aparţine
u0.

Exemplu:

3.14. Fie graful ponderat


1

5 1
9
2 3

2 16
5
5
4

43
Grafuri. Implementări în limbajul Pascal

Considerând u0=1, etapele în aplicarea algoritmului Dijkstra sunt:


P1: i=0; S0={1}; L(1)=0, L(i)= ∞ pentru toţi i = 2,5 .
P2: S 0 ={2,3,4,5}, u0=1
L(2)= ∞ >L(1)+5=5 ⇒ L(2)=5, etichetează 2 cu 1
L(3)= ∞ >L(1)+1=1 ⇒ L(3)=1, etichetează 3 cu 1
L(4)= ∞ >L(1)+9=9 ⇒ L(4)=9, etichetează 4 cu 1
L(5)= ∞ , w(1,5)= ∞ , deci L(5) nu se modifică
P3: selectează u1=3, L(3)=1, cea mai mică dintre w-distanţele calculate la P2
P4: S1={1,3}
P5: i=i+1=1 ≠ 4, reia P2

P2: S1 ={2,4,5}, u1=3


Nu se modifică nici o etichetă şi nici o w-distanţă (w(3,i)= ∞ , pentru
toţi i din S1 )
P3: selectează u2=2, L(2)=5, cea mai mică dintre w-distanţele calculate la P2
P4: S2={1,3,2}
P5: i=i+1=2 ≠ 4, reia P2

P2: S 2 ={4,5}, u2=2


L(4)= 9>L(2)+2=7 ⇒ L(4)=7, etichetează 4 cu 2
L(5)= ∞ >L(2)+16=21, etichetează 5 cu 2
P3: selectează u3=4, L(4)=7, cea mai mică dintre w-distanţele calculate la P2
P4: S3={1,3,2,4}
P5: i=i+1=3 ≠ 4, reia P2

P2: S3 ={5}, u3=4


L(5)= 21>L(4)+5=12, etichetează 5 cu 4
P3: selectează u4=5, L(5)=12, cea mai mică dintre w-distanţele calculate la
P2
P4: S3={1,3,2,4,5}
P5: i=i+1=4, stop.
Algoritmul calculează următoarele rezultate:

Vârful v până la care se 1 2 3 4 5


calculează w-distanţa
D(1,v), eticheta lui v 0, 1 5, 1 1, 1 7, 2 12, 4

Drumurile de cost minim de la vârful 1 la fiecare dintre vârfurile grafului se


stabilesc pe baza sistemului de etichete astfel: drumul de la 1 la un vârf v este dat de:
v1, eticheta lui v, v2 eticheta lui v1 ş.a.m.d., până se ajunge la eticheta 1.

44
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Astfel, v0-drumurile de cost minim sunt:


până la 2: 2,1;
până la 3: 3,1;
până la 4: 4,2,1;
până la 5: 5,4,2,1.

Programul Pascal pentru algoritmul Dijkstra, graful ponderat fiind


reprezentat sub formă tabelară, este prezentat în continuare.
Variabilele folosite în program au următoarele semnificaţii:
- v: numărul de vârfuri ale grafului;
- m: numărul de muchii din graf;
- s: mulţimea Si la momentul curent;
- t: mulţimea Si la momentul curent;
- l: vectorul componentelor L din etichetele ataşate vârfurilor;
- dr: vectorul componentelor de tip vârf din etichetele ataşate vârfurilor.

uses crt;
var a:array[1..200,1..3] of integer;
s,dr,t,l:array[1..20] of integer;
i,j,k,p,v1,u,c,d:integer;
m,v:integer;
begin
clrscr;
readln(v);
readln(m);
for i:=1 to m do
for j:=1 to 3 do
begin
write('a[',i,',',j,']=');
readln(a[i,j]);
end;
i:=1;s[1]:=1;v1:=1;
for j:=2 to v do
begin
t[j-1]:=j; l[j]:=maxint;
end;
l[1]:=0;
for k:=1 to v-1 do
begin
for j:=1 to v-i do
begin
u:=1;
while((t[j]<>a[u,1])or(v1<>a[u,2]))and
((t[j]<>a[u,2])or(v1<>a[u,1]))and(u<=m)
do inc(u);
if(u<=m) and (l[t[j]]>l[v1]+a[u,3]) then
begin
l[t[j]]:=l[v1]+a[u,3];
dr[t[j]]:=v1;
end;
end;
d:=l[t[1]];v1:=t[1];
for j:=2 to v-i do
if l[t[j]]<d then begin
d:=l[t[j]];

45
Grafuri. Implementări în limbajul Pascal

v1:=t[j];
end;
inc(i);
s[i]:=v1;
u:=1;
while(u<=v-i+1)and(t[u]<>v1) do inc(u);
for j:=u to v-i do t[j]:=t[j+1];
end;
clrscr;
for i:=1 to v do
writeln(i,'--->',l[i]);
writeln('Drumurile minime de la fiecare varf la 1:');
for j:=2 to v do
begin
k:=j;write(k,' ');
while k<>1 do
begin
write(dr[k],' '); k:=dr[k];
end;
writeln;
end;
readln;
end.

În anumite cazuri se doreşte determinarea numai a w-distanţelor D(v0,v),


pentru toţi v∈V. În acest caz, algoritmul Roy-Floyd permite o rezolvare a acestei
probleme mai simplu de implementat decât algoritmul Dijkstra.

Algoritmul Roy-Floyd

Pentru (V,E,w) graf ponderat, V = n şi W matricea ponderilor, sistemul de


w-distanţe D(v0,v), v∈V, poate fi calculat pe baza următoarei proceduri (similară
algoritmului Roy-Warshall):

procedure Roy_Floyd (w,n,d);


i,j,k:integer;
do-for i=1,n,1
do-for j=1,n,1
dij=wij;
do-for j=1,n,1
do-for i=1,n,1
if dij ≠ ∞ then
do-for k=1,n,1
if dik>dij+ djk then
dik=dij+ djk;
endif;
enddo;
endif;
enddo;
enddo;

46
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

end;

Matricea D calculată de algoritm este a w-distanţelor D(u,v) în graful


ponderat conex (V,E,w); pentru orice 1 ≤ i, j ≤ n
D( v i , v j ), v i , v j sunt conectate
d ij = 
∞, altfel
Într-adevăr, procedura calculează dinamic w-distanţa între oricare două
vârfuri i şi k, astfel: dacă există un i-k drum ce trece prin j ( 1 ≤ j ≤ n ), cu costul
corespunzător (dij+ djk) inferior costului curent (dik), atunci noul drum de la i la k via
j este de cost mai mic decât cel al drumului vechi, deci w-distanţa între i şi k trebuie
reactualizată la dij+ djk.

Algoritmul Yen

Pentru calculul tuturor w-distanţelor într-un graf ponderat, algoritmul propus


de Yen rezolvă problema într-o manieră mai eficientă decât algoritmul Roy-Floyd,
din punctul de vedere al volumului operaţiilor. Fie (V,E,w) un graf ponderat, W
matricea ponderilor. Pentru determinarea w-distanţelor de la vârful vk fixat la
celelalte vârfuri ale grafului, algoritmul Yen iniţiază următoarele operaţii:

Pasul 1: D = W
Pasul 2: i = 1; λ(k) = 0, b(k) = 0; λ(j)=0, pentru toţi 1 ≤ j ≤ n , j ≠ k
Pasul 3: Calculează min{dkj; 1 ≤ j ≤ n , λ(j)=1}; determină j0 astfel încât
λ(j0) = 1 şi d kj0 = min{dkj; 1 ≤ j ≤ n , λ(j) = 1}
B(j0) = d kj0 , λ(j0) = 0
d[k,j] = min{d[k,j],d[k,j0]+d[j0,j]}, pentru toţi j, 1 ≤ j ≤ n
i=i+1
Pasul 4: Dacă i<n, reia Pasul 3, altfel stop.

La terminarea algoritmului, componentele vectorului B sunt egale cu


w-distanţa de la vârful vk la orice alt vârf al grafului: B(j)=D(vk,vj), 1 ≤ j ≤ n .
Într-adevăr, componentele egale cu 1 ale vectorului λ indică, la fiecare reluare a
pasului 3, vârfurile grafului pentru care nu s-a calculat încă w-distanţa la vârful vk.
După fiecare efectuare a etapei 3, dacă j0 a fost selectat, atunci B(j0)=D(vk, v j0 ).

47
STRUCTURI ARBORESCENTE

În clasa grafurilor conexe, structurile cele mai simple, dar care apar cel mai
frecvent în aplicaţii, sunt cele arborescente (arbori). În acest capitol sunt prezentate
principalele caracteristici ale arborilor, algoritmi pentru calculul arborelui parţial de
cost minim, arbori direcţionaţi, arbori cu rădăcină şi arbori binari. Pe lângă operaţiile
primitive asupra arborilor – căutarea unei informaţii, inserarea unui nod, extragerea
unui nod şi metode de parcurgere, sunt prezentate două clase importante de arbori
binari, şi anume arbori de sortare şi arbori de structură.

4.1 Grafuri de tip arbore

4.1.1 Definiţii şi caracterizări ale grafurilor de tip arbore

Definiţia 4.1.1. Graful G este arbore dacă G este aciclic şi conex.


Definiţia 4.1.2. Fie G=(V,E) graf arbore. Subgraful H=(V1,E1) al lui G este
un subarbore al lui G dacă H este graf arbore.

Exemple:
4.1. Graful

1 3

4 2

este arbore, deoarece pentru orice pereche de vârfuri i,j, 1 ≤ i,j ≤ 6, i≠j, există un i-j
drum şi graful nu conţine cicluri.

48
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

4.2. Graful

1 3

4 2

5
6

nu este arbore deoarece este conex, dar drumul Γ :1,4,3,2,1 este un ciclu.

4.3. Graful

1 3 7

4 2

6 8

nu este arbore deoarece are două componente conexe {1,2,3,4,5,6}, {7,8}.

Proprietatea unui graf de a fi arbore poate fi verificată prin algoritmi care


testează calităţile de conexitate şi aciclicitate. Verificarea proprietăţii unui graf de a fi
arbore poate fi realizată şi pe baza următoarelor proprietăţi:
Proprietatea 1.: Un graf G=(V,E), cu V = n , E = m este de tip arbore dacă
şi numai dacă G este aciclic şi n=m+1. Cu alte cuvinte, problema revine la
verificarea aciclicităţii grafului şi a relaţiei existente între numărul vârfurilor şi
numărul muchiilor grafului.
Proprietatea 2: Un graf G=(V,E), cu V = n , E = m este de tip arbore dacă
şi numai dacă G este conex şi n=m+1.
Fie G=(V,E) un graf. Următoarele afirmaţii sunt echivalente:
1. G este graf arbore;
2. G este graf conex minimal (oriare ar fi e∈E, prin eliminarea muchiei
e graful rezultat nu este conex);

49
Structuri arborescente

3. G este graf aciclic maximal (prin adăugarea unei noi muchii în graf
rezultă cel puţin un ciclu).
Definiţia 4.1.3. Un graf orientat D=(V,E), cu proprietatea că pentru
∀u, v ∈ E, u, v ∈ E , atunci vu ∉ E se numeşte graf asimetric. Digraful D este
simetric dacă u , v ∈ E, uv∈E, dacă şi numai dacă vu∈E.

Definiţia 4.1.4. Fie D=(V,E) digraf netrivial. Graful G=(V,E’), unde


E’={uv/ uv∈E sau vu∈E} se numeşte suport al digrafului D.

Definiţia 4.1.5. Un arbore direcţionat este un graf orientat asimetric cu


proprietatea că graful suport corespunzător lui este graf arbore. Arborele direcţionat
T=(V,E) este cu rădăcină dacă există r∈V astfel încât, pentru orice u∈V, u ≠ r, există
r-u drum în T. Vârful r se numeşte rădăcina arborelui direcţionat T.

Definiţia 4.1.6. Dacă T=(V,E) este arbore direcţionat, atunci T1=(V1,E1)


este subarbore al lui T dacă V1⊆V, E1⊆E şi T1 este arbore direcţionat.

Deoarece graful suport al unui arbore direcţionat este aciclic, rezultă că


pentru orice u∈V, u ≠ r, r-u drumul în T este unic. De asemenea, un arbore
direcţionat are cel mult o rădăcină. În consecinţă, pentru orice u∈V, u ≠ r, distanţa de
la rădăcină la vârful u este egală cu numărul de muchii ale r-u drumului în T.

Exemple:
4.4. Arborele direcţionat

u
w x

t r y

v z

este cu rădăcină (vârful r este rădăcina arborelui).

50
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

4.5. Arborele direcţionat

u v

z w
nu are rădăcină.

4.6. Arborele
y w

este un subarbore cu rădăcină x al arborelui din exemplul 4.5

4.1.2 Arbori orientaţi; reprezentări şi parcurgeri

Definiţia 4.1.7. Un arbore orientat este un arbore direcţionat cu rădăcină.


Deoarece un arbore orientat este un caz particular de digraf, pentru
reprezentarea unui arbore orientat poate fi utilizată oricare din modalităţile prezentate
în §3.1. În plus, există şi posibilitatea obţinerii unor reprezentări mai eficiente pentru
acest tip de graf.
Una dintre modalităţi este reprezentarea FIU-FRATE, care constă în
numerotarea convenţională a vârfurilor grafului şi reţinerea, pentru fiecare vârf i al
arborelui, a următoarelor informaţii:
- FIU(i), care reprezintă numărul ataşat primului descendent al vârfului i;
- FRATE(i), care reprezintă numărul ataşat vârfului descendent al tatălui
vârfului i şi care urmează imediat lui i;
- INF(i), care reprezintă informaţia ataşată vârfului i (de obicei valoarea i).
Pentru reprezentarea arborelui se reţin rădăcina şi numărul nodurilor.
Absenţa “fiului”, respectiv a “fratelui” unui vârf, este marcată printr-o valoare
diferită de numerele ataşate vârfurilor (de obicei valoarea 0).

51
Structuri arborescente

Exemplu:
4.7. Arborele orientat
1

2 3 4

5 6 7 8 9

10 11 12 13 14 15

este reprezentat astfel:


N=15 (numărul nodurilor arborelui)
R=1 (rădăcina), FIU=(2,5,7,9,0,10,0,0,13,0,0,0,0,0,0)

“fiul” lui 1 este 2 vârful 9 are “fiul” 13


FRATE=(0,3,4,0,6,0,8,0,0,11,12,0,14,15,0)

vârful 1 nu are frate vârful 14 are fratele 15

Utilizând structurile de date dinamice pentru arbori orientaţi, se obţine o


reprezentare descrisă în continuare. Presupunând că fiecare vârf al arborelui are cel
mult n descendenţi, fiecărui vârf îi este ataşată structura:

identificatorul vârfului vector de legături către descendenţii vârfului


legătură către fiul 1 legătură către fiul n
………

Dacă un vârf are p<n descendenţi, atunci primele p legături sunt către
descendenţi, iar ultimile n-p legături sunt nil. Pentru n ≤ 10, descrierea structurii de
date în limbajul Pascal este:
type ptnod=^nod;
nod=record
inf:integer;
leg:array[1..10] of ptnod;
end;

52
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

O parcurgere revine la aplicarea sistematică a unei reguli de vizitare a


vârfurilor grafului. Cele mai uzuale reguli de parcurgere a arborilor orientaţi sunt
prezentate în continuare.

A. Parcurgerea în A-preordine
Iniţial vârful curent este rădăcina arborelui. Se vizitează vârful curent şi sunt
identificaţi descendenţii lui. Se aplică aceeaşi regulă de vizitare pentru arborii care au
ca rădăcini descendenţii vârfului curent, arborii fiind vizitaţi în ordinea dată de
numerele ataşate vârfurilor rădăcină corespunzătoare.

Exemplu:
4.8. Pentru arborele orientat din exemplul 4.7, prin aplicarea parcurgerii în
A-preordine, rezultă: 1,2,5,6,10,11,12,3,7,8,4,9,13,14,15.
Pentru un arbore reprezentat FIU-FRATE, implementarea parcurgerii în
A-preordine se bazează pe următoarea procedură recursivă, având ca parametru de
intrare rădăcina arborelui curent (vârful curent în momentul apelului).

procedure A_preordine (R);


if R≠0 then
vizit (R);
A_preordine(FIU[R]);
A_preordine(FRATE[R]);
endif;
end;

B. Parcurgerea A-postordine
Regula de vizitare a vârfurilor în parcurgerea în A-postordine diferă de cea în
A-preordine numai prin faptul că rădăcina fiecărui arbore este vizitată după ce au fost
vizitate toate celelalte vârfuri ale arborelui.
Exemplu:
4.9. Pentru arborele orientat din exemplul 4.7, ordinea de vizitare a vârfurilor
este: 5,10,11,12,6,2,7,8,3,13,14,15,9,4,1.
Pentru arbori reprezentaţi prin structuri de date arborescente, implementarea
parcurgerii în A-postordine poate fi obţinută pe baza următoarei proceduri recursive.
Unicul parametru (de intrare) reprezintă rădăcina arborelui curent în momentul
apelului.
procedure A_postordine (R);
if R≠nil then
do-for i=1,n,1
A_postordine(R^.leg[i]);

53
Structuri arborescente

enddo;
vizit (R);
endif;
end;

Procedurile A-preordine şi A-postordine sunt variante de parcurgeri în


adâncime, fiind prioritare vârfurile aflate la distanţă maximă faţă de rădăcina
arborelui iniţial.

C. Parcurgerea pe niveluri

Definiţia 4.1.8. Un vârf v al unui arbore orientat cu rădăcină r se află pe


nivelul i al arborelui, dacă distanţa de la vârf la rădăcină (lungimea r-v drumului) este
egală cu i. Rădăcina arborelui este de nivel 0.
Parcurgerea unui arbore orientat pe niveluri constă în vizitarea vârfurilor sale
în ordinea crescătoare a distanţelor faţă de rădăcină.

Exemplu:
4.10. Pentru arborele din exemplul 4.7, prin aplicarea parcurgerii pe niveluri,
rezultă: 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15.

Implementarea parcurgerii pe niveluri se bazează pe utilizarea unei structuri


de coadă C. La momentul iniţial, rădăcina arborelui este unicul element din C. Atâta
timp cât coada este nevidă, se extrage (cu ştergere) un vârf din C, este vizitat şi sunt
introduşi în coadă descendenţii săi. Calculul se încheie în momentul în care, la
tentativa de extragere a unui vârf din C, se constată C=Ø.
Parcurgerea pe niveluri este realizată de următoarea procedură care are ca
parametri de intrare reprezentarea FIU-FRATE a grafului. Procedurile push şi pop
realizează operaţiile de acces introducere-extragere în C.

procedure parcurgere_pe_niveluri(R,FIU,FRATE,n)
C:ptcoada;
C=nil;push(C,R);
while C≠nil do
pop(C,v);
VIZIT(v);
v=FIU[v];
while v≠0 do
push(C,v);
v=FRATE[v];
endwhile;
endwhile;
end;

54
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Exemplu
4.11. Pentru arborele de la exemplul 4.7, evoluţia algoritmului este:

C
t
t=1 1
t=2 2 3 4
t=3 3 4 5 6
t=4 4 5 6 7 8
t=5 5 6 7 8 9
t=6 6 7 8 9
t=7 7 8 9 10 11 12
t=8 8 9 10 11 12
t=9 9 10 11 12
t=10 10 11 12 13 14 15
t=11 11 12 13 14 15
t=12 12 13 14 15
t=13 13 14 15
t=14 14 15
t=15 15
t=16

deci vârfurile sunt vizitate în ordinea: 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15.

Metoda BF pentru parcurgerea grafurilor este o generalizare a tehnicii de


parcurgere pe niveluri a arborilor orientaţi. O alternativă de implementare a
parcurgerii pe niveluri poate fi descrisă prin intermediul procedurilor recursive fraţi
şi parc. Coada C este o variabilă globală şi este iniţializată cu rădăcina arborelui.
Parcurgerea este obţinută prin apelul parc(C).

procedure fraţi(v);
if v≠0 then
push(C,v);fraţi(FRATE[v];
endif;
end;

procedure parc;
if C≠nil then
pop(C,v);VIZIT(v);
fraţi(FIU[v]); parc;
endif;
end;

55
Structuri arborescente

4.1.3 Arbori parţiali; algoritmul Kruskal

Definiţia 4.1.9. Fie G graf. Subgraful parţial H este un arbore parţial al lui G
dacă H este graf arbore.
Definiţia 4.1.10. Fie (V,E,w) un graf ponderat conex. Dacă T=(V,E0) este un
arbore parţial al grafului G=(V,E), ponderea arborelui T este definită prin:
W(T)= ∑ w ( e) .
e∈E 0

Definiţia 4.1.11. Fie T(G) mulţimea arborilor parţiali corespunzători grafului


G. T0∈T(G) este arbore parţial minim pentru G dacă W(T0)=min{W(T); T∈T(G)}.
Dacă G este graf finit, atunci T(G) este mulţime finită, deci orice graf finit
ponderat şi conex are cel puţin un arbore parţial minim.
Pentru calculul unui arbore parţial minim sunt cunoscuţi mai mulţi algoritmi.
În continuare este prezentat algoritmul Kruskal pentru determinarea unui arbore
parţial minim al unui graf ponderat conex G=(V,E,w).

Pasul 1: i=1; E0=∅


Pasul 2: Determină mulţimea
R={e/e∈E \ Ei-1 astfel încât graful (V,Ei-1 ∪ {e}) este aciclic}
Dacă R=∅, atunci stop;
altfel, selectează ei∈R cu w(ei)=min{w(e), e∈R};
Ei=Ei-1 ∪ {ei}
Pasul 3: i=i+1 şi reia pasul 2.

Structura (V,Ei-1) calculată de procedură este arbore parţial minim al grafului


conex ponderat G.

Ideea algoritmului Kruskal revine la alegerea şi includerea în mulţimea de


muchii curente a unei muchii de cost minim încă neselectate şi astfel încât să nu
formeze un ciclu cu muchiile selectate la etapele precedente. Algoritmul se încheie
atunci când nici o alegere nu mai este posibilă. Aplicarea metodei la grafuri neconexe
calculează o mulţime de arbori parţiali minimi, câte un arbore pentru fiecare
componentă conexă.
Pentru implementarea algoritmului Kruskal, graful conex ponderat este
reprezentat sub formă tabelară, muchiile fiind ordonate crescător după ponderi.
Muchiile selectate de algoritm pot fi menţinute, de asemenea, într-o structură
tabelară, sau doar marcate ca fiind incluse în mulţimea muchiilor arborelui parţial
minim a cărui construcţie este dorită. În varianta prezentată în continuare muchiile
selectate sunt afişate.
Pentru verificarea condiţiei ca muchia selectată să nu formeze nici un ciclu
cu muchiile selectate la etapele precedente, este utilizat un vector, TATA. Pentru
fiecare vârf i (vârfurile grafului fiind numerotate de la 1 la n, unde n este numărul
nodurilor grafului), componenta TATA [i] este predecesorul său în arborele care
conţine vârful i construit până la momentul curent, dacă i nu este rădăcina acelui

56
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

arbore, respectiv TATA[i] este egal cu –numărul de vârfuri ale arborelui de


rădăcină i, în caz contrar. Componentele vectorului TATA sunt iniţializate cu
valoarea -1. Calculul care realizează adăugarea unei noi muchii poate fi descris
astfel:
- este determinată o muchie de cost minim, e=v1v2, care nu a fost selectată
anterior;
- se verifică proprietatea de aciclicitate a grafului care rezultă prin eventuala
adăugare a muchiei selectate astfel: dacă vârfurile v1 şi v2 nu aparţin
aceluiaşi arbore, atunci proprietatea de aciclicitate este îndeplinită şi
muchia e este adăugată la structura curentă. Determinarea valorii k
reprezentând rădăcina arborelui care conţine vârful dat v rezultă prin
parcurgerea vectorului TATA:
k=v;
while TATA[k] >0 do k=TATA[k]
endwhile;
- adăugarea muchiei e selectate este realizată prin reunirea arborilor cu
rădăcini r1 şi r2, din care fac parte v1 şi respectiv v2, astfel: dacă
TATA[r1]<TATA[r2] (arborele de rădăcină r1 conţine mai multe vârfuri
decât arborele de rădăcină r2), atunci arborele rezultat prin reunirea celor
doi arbori are ca rădăcină vârful r1, iar vârful r2 devine fiu al lui r1
(TATA[r2]=r1 şi TATA[r1] =TATA[r2]+TATA[r1] (numărul muchiilor
arborelui cu rădăcină r1 este crescut cu numărul muchiilor arborelui cu
rădăcină r2). În caz contrar se procedează analog, rădăcina arborelui
rezultat prin reunire fiind r2, iar r1 devenind fiu al rădăcinii.
Calculul se încheie după ce a fost adăugată cea de-a (n-1)-a muchie.

4.2 Arbori binari

4.2.1 Reprezentare; parcurgeri

Definiţia 4.2.1. Un arbore binar este un arbore orientat cu proprietatea că


pentru orice vârf v, od(v)≤2. În cazul od(v)=2, cei doi descendenţi sunt desemnaţi ca
descendent stâng (fiu stânga) respectiv descendent drept (fiu dreapta). Pentru
vârfurile cu od(v)=1, unicul descendent este specificat fie ca fiu stânga, fie ca fiu
dreapta.
Definiţia 4.2.2. Se numeşte nod terminal orice vârf v al arborelui cu od(v)=0.
Nodul v este neterminal dacă od(v)>0.
Reprezentarea unui arbore binar este realizată prin reţinerea, pentru fiecare
nod, a legăturilor către descendenţii lui. Absenţa unui descendent este reprezentată
prin nil.

57
Structuri arborescente

identificator legătură fiu legătură fiu


nod stânga dreapta

Structura de date Pascal este:


type arb=^nod;
nod=record;
inf:integer;
fius,fiud:arb;
end;

Definiţia 4.2.3. Fie T=(V,E) un arbore binar cu rădăcina R. Subarborele


stâng al lui T este ST=(V\{R},E\{RS}), unde S este fiul stânga al rădăcinii.
Subarborele drept al lui T este DT=(V\{R},E\{RD}), unde D este fiul dreapta al
rădăcinii.
În plus faţă de metodele deja prezentate pentru parcurgerea arborilor generali
şi care sunt aplicabile şi în acest caz particular, parcurgerile în preordine(RSD),
inordine(SRD) şi respectiv postordine(SDR) sunt special considerate pentru arbori
binari şi au multiple aplicaţii. Regula de vizitare revine la parcurgerea subarborelui
stâng, a subarborelui drept corespunzători vârfului curent. La momentul iniţial, vârful
curent este rădăcina arborelui. Diferenţa între cele trei tipuri de parcurgere este dată
de momentul în care este vizitat fiecare vârf al arborelui. În parcurgerea RSD
(rădăcină-subarbore stâng-subarbore drept), fiecare vârf al arborelui este vizitat în
momentul în care devine vârf curent; în parcurgerea SRD (subarbore stâng-rădăcină-
subarbore drept), vizitarea vârfului este efectuată după ce a fost parcurs subarborele
stâng; în parcurgerea SDR (subarbore stâng-subarbore drept-rădăcină) vizitarea
fiecărui vârf este efectuată după ce au fost parcurşi subarborii aferenţi lui.
Procedura preordine realizează parcurgerea în preordine, metodele SRD şi
SDR fiind propuse cititorului ca exerciţii. Procedura preordine este recursivă şi are
ca parametru de intrare vârful curent în momentul apelului.

procedure preordine(r:arb);
begin
if r<>nil then
begin
write(r^.inf);
preordine(r^.fius);
preordine(r^.fiud);
end;
end;

58
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

4.2.2 Arbori binari de sortare

Definiţia 4.2.4. Un arbore de sortare este un arbore binar cu proprietăţile:


⇒ fiecărui nod i al arborelui îi este ataşată o informaţie INF(i) dintr-o
mulţime ordonată de valori;
⇒ pentru fiecare nod i, INF(i) este mai mare decât INF(j), pentru toate
nodurile j din subarborele stâng al arborelui cu rădăcină i;
⇒ pentru fiecare nod i, INF(i) este mai mică decât INF(j), pentru toate
nodurile j din subarborele drept al arborelui cu rădăcină i;
⇒ pentru orice vârfuri i,j, dacă i≠j, atunci INF(i)≠INF(j).

Exemplu:
4.12. Arborele binar

50

30 70

20 40 60 90

80

este de sortare.

Operaţiile uzuale efectuate asupra arborilor de sortare sunt inserarea unui


nod, ştergerea unui nod şi parcurgerea arborelui (în preordine, inordine sau
postordine). Inserarea şi ştergerea nodurilor într-un arbore de sortare trebuie realizate
astfel încât arborele rezultat să fie, de asemenea, arbore de sortare.
Parcurgerea în inordine a unui arbore de sortare determină secvenţa
vârfurilor arborelui în ordinea crescătoare a informaţiilor ataşate.

Exemplu:
4.13. Pentru arborele de sortare descris în exemplul 4.12, parcurgerea în
inordine determină secvenţa de valori: 20,30,40,50,60,70,80,90, adică exact vectorul
de informaţii asociate nodurilor ordonat crescător.

59
Structuri arborescente

Inserarea unui nod într-un arbore de sortare

Algoritmul de inserare a unei informaţii nr în arborele de sortare de rădăcină


rad este recursiv şi constă în efectuarea operaţiilor. Vârful curent v la momentul
iniţial este rădăcina arborelui.
- 1. dacă arborele de rădăcină v este vid (v=nil), este generat arborele cu un
singur nod, având nr ca informaţie ataşată;
- 2. altfel
- a) dacă informaţia ataşată nodului v este mai mare decât nr,
atunci vârful curent devine fiul stânga al lui v;
- b) dacă informaţia ataşată nodului v este egală cu nr, atunci stop
(se previne duplicarea informaţiilor ataşate vârfurilor arborelui);
- c) dacă informaţia ataşată nodului v este mai mică decât nr,
atunci vârful curent devine fiul dreapta al lui v.

Exemplu:
4.14. Aplicarea algoritmul descris pentru inserarea informaţiei 55 în arborele
de sortare din exemplul 4.12 determină următoarele operaţii:
INF(v)=50<55: se decide inserarea în subarborele drept cu rădăcina având
informaţia ataşată 70 (cazul 2.c);
INF(v)=70>55: se decide inserarea în subarborele stâng cu rădăcina având
informaţia ataşată 60 (cazul 2.a);
INF(v)=60>55: se decide inserarea în subarborele stâng cu rădăcina nil
(situaţia de la 1). Se decide crearea nodului cu informaţie 55, fiu stâng al nodului de
informaţie 60.
Arborele rezultat este:

50

30 70

20 40 60 90

55 80

60
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Ştergerea unei informaţii dintr-un arbore de sortare


Algoritmul pentru ştergerea unei informaţii nr din arborele de sortare de
rădăcină rad este recursiv este descris în continuare. Vârful curent v la momentul
iniţial este rădăcina arborelui.
- 1. dacă arborele este vid (v=nil) atunci stop (nr nu se află în mulţimea
informaţiilor ataşate nodurilor arborelui);
- 2. altfel
- a) dacă informaţia ataşată nodului v este mai mare decât nr,
atunci vârful curent devine fiul stânga al lui v ;
- b) dacă informaţia ataşată nodului v este mai mică decât nr,
vârful curent devine fiul dreapta al lui v ;
- c) dacă INF(v)=nr atunci:
- c1) dacă subarborele stâng este vid (v^.fius=nil), atunci
adresa vârfului v este memorată într-o celulă suplimentară
aux, v devine fiul dreapta al lui v, iar celula aux este
eliberată din memorie (este disponibilizată celula
corespunzătoare vârfului din arbore de informaţie nr);
- c2) dacă subarborele stâng este nevid, atunci se determină
cel mai mare element din subarborele stâng (este parcurs
subarborele stâng pe legăturile din dreapta, cât timp acest
lucru este posibil, cu păstrarea şi a adresei nodului părinte
corespunzător fiecărui vârf atins) :
p:=v^.fius;
while p^.fiud<> nil do
begin
p1:=p;
p:=p^.fiud;
end;
c2.1) dacă fiul stânga al lui v nu are subarbore drept
(v^.fius^.fiud=nil), atunci informaţia ataşată fiului stânga se
transferă în vârful curent, iar fiul stânga (v^.fius) este
înlocuit cu fiul său stânga (v^.fius^.fius) şi este eliberată
memoria corespunzătoare celulei v^.fius.
c2.2) altfel, se transferă în rădăcină informaţia ataşată
ultimului nod p determinat la c2), nodul p este înlocuit cu
fiul său stâng şi celula corespunzătoare lui p este eliberată
din memorie.

Exemple:
4.15. Ştergerea informaţiei 70 în arborele de sortare din exemplul 4.12 este
realizată astfel:
70>50, decide ştergerea din subarborele drept (cu rădăcină 70) - situaţia 2.b;
70=70, decide ştergerea din arborele curent: rădăcina etichetată cu 70 -
situaţia 2.c;

61
Structuri arborescente

Există subarbore stâng (rădăcina lui p este etichetată cu 60) iar acesta nu are
subarbore drept - situaţia 2.c.1.: nodul cu informaţie 70 este etichetat cu 60, iar p este
înlocuit cu subarborele său stâng (vid)
Arborele de sortare rezultat este:

50

30 60

20 40 90

80

4.16. Ştergerea informaţiei 30 din arborele de sortare:

50

30 70

20 40 60 90

10 25

24

este realizată astfel:


30>50, decide ştergerea din subarborele stâng (cu rădăcină 30) - situaţia 2.a;
30=30, decide ştergerea din arborele curent: rădăcina etichetată cu 70 –
situaţia 2.c;

62
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Există subarbore stâng (rădăcina este etichetată cu 20), iar acesta are
subarbore drept - situaţia 2.c.2: nodul cu informaţie 30 este etichetat cu 25
(informaţia ultimului nod p detectat la c2)), iar p este înlocuit cu subarborele său
stâng (cu rădăcină 24).
Arborele rezultat este:

50

25 70

20 40 60 90

10 24

Punctul c) de la pasul 2 poate fi înlocuit cu:


c) dacă INF(v)=nr atunci:
- c1) dacă subarborele drept este vid (v^.fiud=nil), atunci adresa
vârfului v este memorată într-o celulă suplimentară aux, v devine fiul stânga al lui v,
iar celula aux este eliberată din memorie (este eliberat vârful de informaţie nr);
- c2) dacă subarborele drept este nevid, atunci se determină cel mai
mic element din subarborele drept (este parcurs subarborele drept pe legăturile din
stânga, cât timp acest lucru este posibil, cu păstrarea şi a adresei nodului părinte
corespunzător fiecărui vârf atins) :
p:=v^.fiud;
while p^.fius<> nil do
begin
p1:=p;p:=p^.fius;
end;
c2.1.) dacă fiul dreapta al lui v nu are subarbore stâng
(v^.fiud^.fius=nil), atunci informaţia ataşată fiului dreapta se
transferă în vârful curent, iar fiul dreapta este înlocuit cu fiul
său dreapta (v^.fiud^.fiud) şi este eliberată memoria
corespunzătoare celulei v^.fiud.
c2.2) altfel, se transferă în rădăcină informaţia ataşată
ultimului nod p determinat la c2), nodul p este înlocuit cu
fiul său dreapta şi celula corespunzătoare lui p este eliberată
din memorie.

63
Structuri arborescente

4.2.3 Arbori de structură

Expresiile aritmetice în care intervin numai operatori binari pot fi


reprezentate prin intermediul arborilor strict binari (fiecare nod neterminal are doi
fii). Un arbore de structură are vârfurile etichetate astfel:
- fiecare nod neterminal este etichetat cu un simbol corespunzător unuia
dintre operatori;
- fiecare nod terminal este etichetat cu un operand (variabilă sau constantă);
Construcţia arborelui de structură corespunzător unei expresii aritmetice date
se realizează pe baza “parantezării” existente în expresie şi a priorităţilor
convenţional asociate operatorilor (ordinea operaţiilor) astfel încât rădăcina fiecărui
subarbore este etichetată cu operatorul care se execută ultimul în evaluarea
subexpresiei corespunzătoare acelui subarbore.

Exemplu:
4.17. Pentru expresia matematică (a+b)*(c-d)+e/g, arborele de structură este:

∗ /

+
− e g

a b c d

Construcţia arborelui de structură pentru o expresie s se face în două etape, şi


anume:
1. Ataşarea priorităţilor operatorilor şi operanzilor (toţi operanzii
au aceeaşi prioritate, egală cu prioritatea maximă). Priorităţile ataşate
permit eliminarea parantezelor fără ca semnificaţia expresiei să se
modifice;
2. Construcţia propriu-zisă.

64
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Prima etapă este realizată astfel:


- prioritatea iniţială a operatorilor ‘+’,’-‘ este 1 (dacă expresia nu conţine
paranteze, atunci în construcţie operatorii vor fi primii luaţi în considerare,
în ordinea de la dreapta la stânga);
- prioritatea iniţială a operatorilor ‘/’,’*‘ este 10 (dacă expresia nu conţine
paranteze, aceştia sunt consideraţi după operatorii de prioritate 1 în
ordinea de la dreapta la stânga);
- prioritatea fiecărui operator este incrementată cu valoarea 10 pentru
fiecare pereche de paranteze în interiorul cărora se află;
- prioritatea ataşată fiecărui operand este maxint.
După stabilirea sistemului de priorităţi, se elimină parantezele din expresie
(ordinea de efectuare a operaţiilor în cadrul expresiei este indicată de vectorul de
priorităţi ataşat).

Exemplu:
4.18. Etapele calculului sistemului de priorităţi pentru expresia de la
exemplul 4.17 sunt:

i j dim vectorul prioritate


1 10 0
2 10 1 (maxint)
3 10 2 (maxint,11)
4 10 3 (maxint,11,maxint)
5 0 3 (maxint,11,maxint)
6 0 4 (maxint,11,maxint,10)
7 10 4 (maxint,11,maxint,10)
8 10 5 (maxint,11,maxint,10,maxint)
9 10 6 (maxint,11,maxint,10,maxint,11)
10 10 7 (maxint,11,maxint,10,maxint,11,maxint)
11 0 7 (maxint,11,maxint,10,maxint,11,maxint)
12 0 8 (maxint,11,maxint,10,maxint,11,maxint,1)
13 0 9 (maxint,11,maxint,10,maxint,11,maxint,1,maxint)
14 0 10 (maxint,11,maxint,10,maxint,11,maxint,1,maxint,10)
15 0 11 (maxint,11,maxint,10,maxint,11,maxint,1,maxint,10,maxint)

După eliminarea parantezelor, expresia rezultată este s=a+b*c-d+e/g, având


ca vector de priorităţi (maxint,11,maxint,10,maxint,11,maxint,1,maxint,10,maxint).

Construcţia arborelui de structură pe baza expresiei s, din care au fost


eliminate parantezele şi a vectorului de priorităţi, poate fi realizează recursiv în
modul descris în continuare. La momentul iniţial expresia curentă este cea dată.

65
Structuri arborescente

- Pentru expresia curentă se determină operatorul/operandul de prioritate


minimă care se ataşează ca etichetă a rădăcinii r a subarborelui de
structură corespunzător ei; fie i poziţia acestuia în cadrul expresiei;
- Dacă expresia are un singur simbol (operand) atunci r^.fius=r^.fiud=nil;
- Altfel, se consideră subexpresiile s1 şi s2, constând din simbolurile de pe
poziţiile 1 până la i-1 şi respectiv i+1 până la length(s). Arborii de
structură corespunzători subexpresiilor s1 şi s2 se ataşează ca subarbore
stâng, respectiv subarbore drept vârfului r.

Exemplu:
4.19. Pentru expresia de la exemplul 4.17, după determinarea vectorului de
priorităţi şi a expresiei neparantezate corespunzătoare, procedura cr_der realizează
următoarea construcţie:

s=a+b*c-d+e/g,
prioritate=(maxint,11,maxint,10,maxint,11,maxint,1,maxint,10,maxint)

p=1,u=11 ⇒ min=1, i=8, arborele:

în construcţie în construcţie

p=1,u=7 ⇒ min=10, i=4, arborele:

* în construcţie

în construcţie în construcţie

66
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

p=1,u=3 ⇒ min=11, i=2, arborele:

∗ în construcţie

+ în construcţie

în construcţie în construcţie

p=u=1 ⇒ min=maxint, i=1, arborele:

∗ în construcţie

+ în construcţie

a în construcţie

67
Structuri arborescente

p=3,u=3 ⇒min=maxint, i=3, arborele:

∗ în construcţie

+ în construcţie

a b

p=5,u=7 ⇒ min=11, i=6, arborele:

∗ în construcţie

+ −

a b în construcţie în construcţie

68
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

p=5,u=5 ⇒ min=maxint, i=5, arborele:

∗ în construcţie

+ −

a b c în construcţie

p=7,u=7 ⇒ min=maxint, i=7, arborele:

∗ în construcţie

+ −

a b c d

69
Structuri arborescente

p=9,u=11 ⇒ min=10,i=10, arborele:

∗ /

+ în construcţie în construcţie

a b c d

p=9,u=9 ⇒ min=maxint,i=9, arborele:

∗ /

+ în construcţie
− e

a b c d

70
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

p=11,u=11 ⇒ min=maxint,i=11, arborele:

∗ /

+
− e g

a b c d

şi construcţia se termină.

Construcţia arborelui de structură se face în ipoteza în care expresia este


corectă. Procedura cr_der se apelează cu:nil,1,length(s),s,prioritate, unde s şi
prioritate sunt cei rezultaţi după apelarea procedurii priorităţi.

Definiţia 4.2.5. Se numeşte forma poloneză directă a unei expresii, expresia


rezultată în urma parcurgerii RSD a arborelui de structură. . Se numeşte forma
poloneză inversă a unei expresii, expresia rezultată în urma parcurgerii SDR a
arborelui de structură.

Exemplu:
4.20. Pentru expresia considerată la exemplul 4.17, forma poloneză directă
este +*+ab-cd/eg. Forma poloneză inversă a expresiei date este ab+cd-*eg/+.

Parcurgerea arborelui în inordine determină secvenţa de simboluri rezultată


prin eliminarea parantezelor din expresia dată. Restaurarea unei forme parantezate
poate fi realizată printr-o parcurgere SRD în modul următor. La momentul iniţial,
vârful curent este rădăcina arborelui de structură. Dacă vârful curent v nu este vârf
terminal, atunci se generează (s1) eticheta(v) (s2), unde eticheta(v) este operatorul
etichetă a vârfului, s1 este secvenţa rezultată prin traversarea SRD a subarborelui
stâng, s2 este secvenţa rezultată prin traversarea SRD a subarborelui drept. Dacă v
este vârf terminal, atunci este generată secvenţa eticheta(v).

71
Structuri arborescente

Exemplu:
4.21. Prin aplicarea traversării SRD a arborelui de structură al expresiei din
4.2.6, rezultă:
s=(((a)+(b))*((c)-(d)))+((e)/(g)).
Se observă că, prin aplicarea traversării SRD şi a parantezării descrise,
expresia rezultată are aceeaşi semnificaţie cu expresia iniţială, dar apar multe
paranteze “inutile”. Propunem ca exerciţiu scrierea unei proceduri Pascal pentru
eliminarea parantezelor inutile.

Evaluarea expresiilor aritmetice pe baza arborilor de structură

Traversarea SRD a arborelui de structură ataşat unei expresii aritmetice


permite evaluarea expresiei pentru valorile curente corespunzătoare variabilelor.
Evaluarea poate fi efectuată în mod recursiv. La momentul iniţial, vârful curent este
rădăcina arborelui. Dacă v este vârf curent, atunci noua informaţie asociată lui v este:
- val(eticheta(v)), dacă v este vârf terminal;
- val(s1)eticheta(v)val(s2), dacă v este neterminal,
unde val(s1), val(s2) sunt valorile rezultate prin evaluările subarborilor stâng
şi respectiv drept ai lui v; val(eticheta(v)) este valoarea curentă a variabilei, dacă
eticheta lui v este variabilă, respectiv valoarea constantei, dacă eticheta lui v este o
constantă.
Dacă v este vârf neterminal, atunci noua informaţie asociată lui v este
val(s1)eticheta(v)val(s2), ce reprezintă rezultatul operaţiei eticheta(v) aplicată
valorilor val(s1), val(s2).

Exemplu:
4.22. Prin aplicarea metodei de evaluare descrise, se obţine:

18

15 3

5 3 6 2

3 2 5 2

72
ALGORITMI RECURSIVI
METODELE DIVIDE ET IMPERA
ŞI BACKTRACKING

Recursivitatea este o tehnică de programare bazată pe apelarea unui


subprogram de către el însuşi. În cadrul capitolului sunt prezentate calculul recursiv,
metoda „divide et impera” şi metoda backtracking implementată recursiv.

5.1 Calcul recursiv

Calculul valorii n! pentru n dat poate fi efectuat pe baza formulei n!=n(n-1)!,


pentru n ≥ 1 şi 0!=1. Dacă Fact(n) este funcţia Pascal care calculează n!, atunci, dacă
n ≥ 1 evaluarea lui Fact(n) rezultă prin multiplicarea cu n a valorii calculate de apelul
Fact(n-1), cu Fact(0)=1. Cu alte cuvinte, apelul funcţiei Fact(n) realizează calculul
“imediat” dacă n=0, altfel presupune un nou apel al aceleiaşi funcţii pentru valoarea
argumentului egală cu n-1. Cazurile în care este posibilă evaluarea “imediată” se
numesc condiţii terminale.
În Pascal, funcţia Fact, este:

function Fact(n:byte):word;
begin
if n=0 then Fact:=1
else Fact:=n*Fact(n-1);
end;

n!
Utilizarea formulei C kn = pentru calculul combinărilor (n, k date)
k!(n − k )!
ridică dificultăţi deoarece n!, pentru n ≥ 13, nu poate fi reprezentat în calculator ca
dată de un tip întreg, chiar dacă numărul C nk este relativ mic şi poate fi reprezentat
ca întreg. Pe baza relaţiei de recurenţă C nk = C nk−1 + C nk−−11 rezultă ceea ce este
cunoscut sub numele de triunghiul lui Pascal.
De exemplu, triunghiul lui Pascal, pentru n = 7, este:

73
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

1 1

1 2 1

1 3 3 1

1 4 6 4 1

1 5 10 10 5 1

1 6 15 20 15 6 1

1 7 21 35 35 21 7 1

Secvenţa de program pentru calculul C nk , 0 ≤ k ≤ n ≤ 13 este:

var
n,i,j:word;
x:array[0..13,0..13] of word;
begin
write(‘Valoarea pentru n=’);
readln(n);
if n>13 then
writeln(‘Eroare’)
else
begin
x[0,0]:=1;
for i:=1 to n do
begin
x[i,0]:=1;
x[i,i]:=1;
end;
for i:=1 to n-1 do
for j:=1 to i do
x[i+1,j]= x[i,j-1]+x[i,j];
end;
for i:=0 to n do
begin
for j:=0 to i do write(x[i,j],’ ‘);
writeln;
end;
end.

Valorile combinărilor sunt calculate în componentele tabelei x, fiind utilizată


(n + 1)(n + 2)
numai din cele 142 celule de memorie rezervate.
2

74
Algoritmi recursivi. Metodele divide et impera şi backtracking

Se poate proceda, însă, altfel. Se presupune că function comb(n,k) calculează


C nk . Conform relaţiei de recurenţă, dacă n ≥ k ≥ 1, atunci evaluarea corespunzătoare
apelului comb(n,k) revine la însumarea rezultatelor obţinute prin apelurile comb(n-
1,k) şi comb(n-1, k-1), unde comb(n,0)=1, n ≥ 0. Dacă evaluările comb(n-1,k) şi
comb(n-1, k-1) sunt realizate în acelaşi mod, rezultă că apelul comb(n,k) va
determina o secvenţă de apeluri ale aceleiaşi funcţii pentru valori ale argumentelor
din ce în ce mai mici, până când este îndeplinită una din condiţiile terminale
comb(n,0)=1, comb(k,k)=1.
Soluţia recursivă a evaluării C nk este:

function comb(n,k:byte):word;
begin
if k>n then comb:=0
else
if (k=0) or (k=n) then comb:=1
else comb:=comb(n-1,k)+comb(n-1,k-1);
end;

Un alt exemplu este următorul. Se presupune că f0, f1, α , β sunt numere


reale date şi pentru p ≥ 2, f p = αf p −1 + βf p − 2 . Se cere să se calculeze fn pentru n dat.
Şirul definit de relaţia precedentă pentru f0=f1= α = β =1 se numeşte şirul lui
Fibonacci. Dacă Fib(n) este funcţia Pascal care calculează cel de-al n-lea element din
şirul considerat, atunci evaluarea lui Fib(n) revine la însumarea valorilor lui Fib(n-1)
şi Fib(n-2) ponderate de constantele α şi β , adică rezolvarea problemei Fib(n)
poate fi redusă la rezolvarea problemelor Fib(n-1), Fib(n-2) cu condiţiile terminale
Fib(0)=f0, Fib(1)=f1.
Funcţia Pascal Fib pentru calculul celui de-al n-lea termen al şirului definit
anterior este:

function Fib(n:word;alfa, beta, f0,f1:real):real;


begin
if n=0 then Fib:=f0
else if n=1 then Fib:=f1
else Fib:=alfa*Fib(n-1)+beta*Fib(n-2);
end;

Fiecare apel Fib(n) pentru n>1 determină încă două apeluri: Fib(n-1) şi
Fib(n-2). Numărul total de apeluri efectuate până la rezolvarea problemei Fib(n)
creşte exponenţial, în funcţie de parametrul n. Mai mult, rezolvarea problemei
Fib(n-2) are loc atât la apelul Fib(n-1), cât şi la apelul determinat de Fib(n). Datorită
acestor inconveniente, este preferată o soluţie iterativă pentru calculul unui termen de
rang dat al şirului lui Fibonacci.

75
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Se spune că astfel de apeluri sunt recursive directe. Schema unui apel


recursiv poate fi descrisă în modul următor. Se verifică dacă este îndeplinită cel puţin
una din condiţiile terminale. Dacă este îndeplinită o condiţie terminală, atunci
calculul se încheie şi se revine la unitatea apelantă. În caz contrar, este iniţiat calculul
pentru noile valori ale parametrilor, calcul care presupune unul sau mai multe apeluri
recursive.
Mecanismul prin care este efectuat apelul unui subprogram se bazează pe
utilizarea stivei memoriei calculatorului. Fiecare apel determină introducerea în stivă
(operaţia push) a valorilor/adreselor parametrilor formali, adresei de revenire şi a
variabilelor locale. La momentul execuţiei, aceste informaţii sunt extrase cu
eliminare din stivă (operaţia pop), eliberându-se spaţiul ocupat.
În cazul subprogramelor recursive, mecanismul funcţionează astfel: se
generează un număr de apeluri succesive cu ocuparea spaţiului din stivă necesar
efectuării acestor apeluri până la îndeplinirea unei condiţii terminale; apelurile sunt
executate în ordinea inversă celei în care au fost generate, iar operaţia push poate
produce depăşirea spaţiului de memorie rezervat în stivă.
Astfel, în cazul apelului Fact(3), secvenţa de apeluri recursive iniţiate este:
Fact(2), Fact(1), Fact(0). În continuare execuţia determină Fact(0)=1,
Fact(1)=1*Fact(0)=1, Fact(2)=2*Fact(1)=2, Fact(3)=3*Fact(2)=6. Evoluţia
determinată de apelul Fact(3) în stivă este ilustrată în figurile 5.1 şi 5.2, unde (○)
reprezintă adresa de revenire în punctul de unde a fost efectuat apelul Fact(3).
Apelurile recursive ale unei proceduri sau funcţii pot fi şi indirecte, în sensul
că este efectuat un apel al unei alte proceduri sau funcţii care, la rândul ei, iniţiază un
apel al procedurii sau funcţiei iniţiale.
Un exemplu simplu îl reprezintă calculul valorilor funcţiei h=f◦g◦f , unde
f,g:R→R sunt funcţii date.Un mod de a rezolva această problemă este descris în
continuare. Dacă f(x), g(x) sunt funcţiile Pascal care descriu calculul necesar
evaluării funcţiilor date în punctul x, atunci pentru calculul valorii h(x) este necesar
apelul funcţiei f , urmat de apelul funcţiei g care, la rândul ei, apelează din nou
funcţia f.
Pentru funcţiile f, g definite prin

2 x + 1, x < 3 x 2 − 3x + 2, x ≤ 1
f (x) =  2 , g( x ) = 
x + 2, x ≥ 3 3x + 5, x > 1

funcţia Pascal h(x) este:

76
Algoritmi recursivi. Metodele divide et impera şi backtracking

3 2

Fact=3*Fact(2) Fact=2*Fact(1)

Adresa de revenire Adresa de revenire

Fact=3*Fact(2)
(o)
Adresa de revenire (o)

1 0
Fact=1*Fact(0) Fact=1
Adresa de revenire Adresa de revenire
2 1

Fact=2*Fact(1) Fact=1*Fact(0)

Adresa de revenire Adresa de revenire


3 2

Fact=3*Fact(2) Fact=2*Fact(1)

Adresa de revenire Adresa de revenire

(o) Fact=3*Fact(2)

Adresa de revenire (o)

Fig. 5.1 Evoluţia în stivă până la condiţia terminală Fact(0):=1

77
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

1 2

Fact=1 Fact=2

Adresa de revenire Adresa de revenire

2 3

Fact=2*Fact(1) Fact=3*Fact(2)

Adresa de revenire Adresa de revenire (○)


3
3

Fact=3*Fact(2)
Fact=6
(○)
Adresa de revenire
Adresa de revenire (○)

Stiva vidă

Fig. 5.2 Eliberarea stivei după execuţia determinată de condiţia terminală

function f(x:real):real;
begin
if x<3 then f:=2*x+1
else f:=x*x+2
end;

function g(x:real):real;
begin
if x<=1 then g:=x*x-3*x+2
else g:=3*x+5
end;

function h(x:real):real;
begin
h:=f(g(f(x)));
end;

78
Algoritmi recursivi. Metodele divide et impera şi backtracking

5.2 Metoda “divide et impera”


Metoda “divide et impera” presupune descompunerea problemei de rezolvat
în două sau mai multe subprobleme (probleme “mai simple”), determinarea soluţiilor
acestora care, apoi, compuse după reguli simple, furnizează soluţia problemei
iniţiale.
De exemplu, se presupune că se doreşte aflarea valorii maxime dintr-o
secvenţă {a 1 ,..., a n } de n numere. Pentru rezolvarea problemei se poate proceda în
mai multe moduri: se determină valoarea cea mai mare din prima jumătate, fie
aceasta x1, apoi se determină valoarea cea mai mare din a doua jumătate a secvenţei,
fie aceasta x2. Soluţia problemei este max(x1, x2). Problema iniţială a fost
descompusă în două subprobleme de acelaşi tip, dar “mai simple” deoarece lungimea
fiecărei secvenţe este jumătate din lungimea secvenţei iniţiale. Problema poate fi
rezolvată pe baza unei metode care o “reduce” succesiv la o problemă “mai simplă”,
determinându-se valoarea maximă din primele n-1 componente ale secvenţei (fie
aceasta x1), valoarea maximă fiind max(x1,an). Ambele soluţii sunt recursive şi sunt
reprezentate prin funcţiile Pascal max1 şi max2.

function max1(var a:vector; s,d:byte):real;


var x1, x2:real;
begin
if s=d then max1=a[s]
else
begin
x1:=max1(a,s,(s+d)div 2);
x2:=max1(a,(s+d)div 2+1,d);
if x1>x2 then max1:=x1
else max1:=x2;
end;
end;
function max2(var a:vector; n:byte):real;
var x1:real;
begin
if n=1 then max2=a[1]
else
begin
x1:=max1(a,n-1);
if x1>a[n] then max1:=x1
else max1:=a[n];
end;
end;

Un alt exemplu în care, pentru rezolvarea problemei, se poate efectua un


raţionament similar este următorul. Se presupune că ecuaţia f(x)=0 are o singură
soluţie x0 în intervalul (a,b). Se doreşte obţinerea unei valori aproximative x̂ astfel
încât x0 − x̂ < ε , pentru ε > 0 dat. Deoarece ecuaţia f(x)=0 are o singură soluţie x0
în intervalul [a,b], rezultă că f(a)f(b)<0 şi, de asemenea, dacă pentru a<α<β<b
f(α)f(β)<0, atunci x 0 ∈ (α, β) . Pe baza aceastei proprietăţi, dacă c este mijlocul

79
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

intervalului (a,b), atunci este îndeplinită una şi numai una dintre relaţiile f(a)f(c)<0,
f(c)=0, f(b)f(c)<0. Dacă f(c)=0, atunci x0=c. Dacă f(a)f(c)<0 atunci x 0 ∈ (a , c) ,
altfel x 0 ∈ (c, b) . Se presupune că f(a)f(c)<0. Se poate aplica acelaşi procedeu
intervalului (a,c) şi se continuă până când sau este obţinută soluţia exactă sau
intervalul care conţine soluţia este de lungime inferioară lui ε . În cazul în care
terminarea calculului se realizează prin identificarea unui interval de lungime
inferioară lui ε , x̂ poate fi considerat oricare dintre numerele din acel interval, de
exemplu mijlocul intervalului. Evident, numărul maxim de iteraţii N pentru obţinerea
b−a   b − a 
preciziei ε rezultă din inegalitatea < ε , adică N = log 2   + 1 .
 ε 
N
2 
Se presupune că funcţia f este calculată prin apelul funcţiei Pascal
f(a:real):real. O variantă recursivă a metodei descrise este:
uses crt;
{$F+}
type
fct=function(x:real):real;
var
eps,a,b,x:real;
functie:fct;
function f(x:real):real;
begin
f:=x*x*x-8;
end;

procedure bisectie(a,b,eps:real;var f:fct;var x:real);


var xx:real;
begin
if f(a)=0 then x:=a
else if f(b)=0 then x:=b
else if b-a<eps then x:=(a+b)/2
else
begin
xx:=(a+b)/2;
if f(xx)*f(a)<0 then bisectie(a,xx,eps,f,x)
else bisectie(xx,b,eps,f,x)
end;
end;
begin
clrscr;
write('Introduceti a ');
readln(a);
write('Introduceti b ');
readln(b);
eps:=exp(-20);
functie:=f;
bisectie(a,b,eps,functie,x);
writeln('Solutia este:',x:5:2);
readln;
end.

80
Algoritmi recursivi. Metodele divide et impera şi backtracking

De asemenea, rezolvarea problemei turnurilor din Hanoi a fost realizată tot


pe baza unei metode de reducere şi anume: problema deplasării a n discuri P(n) a fost
redusă succesiv la rezolvarea problemelor mai simple P(n-1), P(n-2),…,P(1).

Unul dintre cei mai eficienţi algoritmi pentru sortarea crescătoare a unei
secvenţe de numere reale este cunoscut sub numele de algoritmul de quicksort
(sortare rapidă) şi reprezintă, de asemenea, un exemplu de aplicare a metodei divide
et impera. Fie secvenţa (vp , vp+1 , …,vu ), unde iniţial p=1, u=n (n=dimensiunea
vectorului).
Dacă p = u, secvenţa este sortată.
Altfel, se poziţionează vp în această secvenţă astfel încât toate elementele ce
ajung în faţa lui să fie mai mici decât el şi toate cele care îi urmează să fie mai mari
decât el; fie poz poziţia lui corectă în secvenţa (vp , vp+1 , …, vpoz , vpoz+1 , …,vn).
Procedeul se reia pentru secvenţele (vp , vp+1 , …,v poz-1) şi (vpoz+1, vpoz+2, …,vn), deci
p ≤ poz-1 şi poz+1 ≤ u.
Poziţionarea elementului vp se face astfel: se utilizează doi indicatori, i şi j;
iniţial i = p şi j = u. Se compară vi cu vj, dacă nu este necesară interschimbarea, se
micşorează j cu 1, repetându-se procesul; dacă apare o interschimbare, se măreşte i
cu 1 şi se continuă compararea, mărind i până la apariţia unei noi interschimbări.
Apoi se micşorează din nou j, continuându-se în acelaşi mod până când i = j.

Exemplu:
5.1. Aplicarea algoritmului de sortare rapidă pentru
v = (10, 12, 19, 15, 3, 17, 4, 18)
1 2 3 4 5 6 7 8
p = 1, u = n = 8
a) poziţionarea lui vp = v1 = 10
i=1, j=8
Sensul de parcurgere: ←
i=1, j=8 10<18 ⇒ j=j-1
i=1,j=7 10>4 ⇒ se efectuează interschimbarea şi se atribuie i=i+1=2
v=(4, 12, 19, 15, 3, 17, 10, 18)
i=2, j=7
Sensul de parcurgere: →
i=2, j=7 12>10 ⇒ se efectuează interschimbarea şi se atribuie j=j-1=6
v=(4, 10, 19, 15, 3, 17, 12, 18)
i=2, j=6
Sensul de parcurgere: ←
i=2, j=6 10<17 ⇒ j=j-1
i=2, j=5 10>3 ⇒ se efectuează interschimbarea şi se atribuie i=i+1=3
v = (4, 3, 19, 15, 10, 17, 12, 18)
i=3, j=5
Sensul de parcurgere: →
i=3, j=5 19>10 ⇒ se efectuează interschimbarea şi se atribuie j=j-1=4

81
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

v=(4, 3, 10, 15, 19, 17, 12, 18)


i=3, j=4
Sensul de parcurgere: ←
i=3, j=4 10<15 ⇒ j=j-1
i=3, j=3 i=j, poziţionare realizată
b) Se obţine vectorul:
v=(4, 3, 10, 15, 19, 17, 12, 18)
1 2 3 4 5 6 7 8
poz=3, se lucrează cu secvenţele
(4, 3) şi (15, 19, 17, 12, 18)

Primul nivel de recursie:


Pentru Sv1=(4, 3) p=1, u=2
a1) poziţionarea lui 4:
i=1, j=2
Sensul de parcurgere: ←
i=1,j=2 4>3 ⇒ se efectuează interschimbarea şi se atribuie i=i+1=2
sv1 (3, 4) , poz=2
i=j=2, stop
b1) se ajunge la scevenţele (3) şi ∅ ; acestea sunt sortate
Pentru sv2 = (15, 19, 17, 12, 18)
4 5 6 7 8
a1) poziţionarea lui 15
i=4, j=8
Sensul de parcurgere: ←
i=4, j=8 15<18 ⇒ j=j-1=7
i=4, j=7 15>12 ⇒ se efectuează interschimbarea şi se atribuie i=i+1=5
sv2=(12, 19, 17, 15, 18)
4 5 6 7 8
i=5, j=7
Sensul de parcurgere: →
i=5, j=7
19>15 ⇒ se efectuează interschimbarea şi se atribuie j=j-1=6
sv2=(12, 15, 17, 19, 18)
4 5 6 7 8
i=5, j=6
Sensul de parcurgere: ←
i=5, j=6 15<17 ⇒ j=j-1
i=5, j=5 i=j poziţionare realizată
sv2=(12, 15, 17, 19, 18)
4 5 6 7 8 poz = 5
b1)Se obţine secvenţa sv2=(12, 15, 17, 19, 18)
4 5 6 7 8
poz=5; se lucrează mai departe cu secvenţele

82
Algoritmi recursivi. Metodele divide et impera şi backtracking

sv3=(12) sortată
sv4=(17, 19, 18)
6 7 8
În acest moment, v=(3, 4, 10, 12, 15, 17, 19, 18)

Al doilea nivel de recursie - avem numai secvenţa


sv4=(17, 19, 18)
6 7 8
a2) poziţionarea lui 17
i=6, j=8
Sensul de parcurgere: ←
i=6, j=8 17<18 ⇒ j=j-1=7
i=6, j=7 19<19 ⇒ j=j-1=6
i=j=6 - poziţionare realizată
b2) Se obţine sv4=(17, 19, 18), poz=6,
6 7 8
se lucreauă mai departe cu secvenţele
sv5= sortată
sv6=(19, 18)
7 8
În acest moment v=(3, 4, 10, 12, 15, 17, 19, 18)

Al treilea nivel de recursie - avem numai secvenţa


sv6=(19, 18)
7 8
a3) poziţionarea lui 19
i=7, j=8
Sensul de parcurgere: ←
i=7, j=8 19>18 ⇒ se interschimbă şi i=i+1=8
sv6=(18,19)
i=j=8, poziţionare realizată
b3) S-a obţinut secvenţa sv6=(18, 19), poz=8
Următoarele secvenţe sunt sv7=(18), sv8= ∅ , ambele sortate
Calculul se încheie, v=(3, 4, 10, 12, 15, 17, 18, 19) este sortat crescător.
program quick_sort;
uses crt;
var
x:array[1..100] of integer;
n,i:byte;
procedure poz(p,u:byte; var k:byte);
var
i,j:byte;
l,di,dj:shortint;
{di, dj: pasii de incrementare pentru i si j;
ei indica sensul parcurgerii}
v:integer;

83
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

begin
i:=p;j:=u;di:=0;dj:=-1;
while i<j do
if x[i]>x[j] then
begin
v:=x[i];
x[i]:=x[j];
x[j]:=v;
l:=di;di:=-dj;dj:=-l;
i:=i+di;j:=j+dj;
end
else begin
i:=i+di;j:=j+dj;
end;
k:=i;
end;

procedure quick(p,u:byte);
var
i:byte;
begin
if p>=u then
else begin
poz(p,u,i);
quick(p,i-1);
quick(i+1,u);
end;
end;
begin{ program principal}
clrscr;
write('Dimensiunea vectorului:');
readln(n);
for i:=1 to n do
read(x[i]);
quick(1,n);
for i:=1 to n do
write(x[i],' ');
end.

În programul quick_sort, pentru simularea sensurilor de parcurgere, s-a


lucrat cu incremenţi pentru i şi j, desemnaţi prin di, respectiv dj. Pentru sensul de
parcurgere “←“ (de la sfârşitul secvenţei spre începutul ei) i rămâne constant (di=0)
şi j este decrementat cu o unitate (dj=-1). Pentru celălalt sens, j rămâne constant
(dj=0) şi este incrementat i (di=1). Trecerea de la un sens la celălalt, efectuată în
momentul unei interschimbări, determină secvenţa de operaţii:
l=dj;
di=dj;
dj=-l;
adică di este interschimbat cu – dj. Vectorul de sortat x este variabilă globală
procedurii quick.

Soluţiile recursive propuse în exemplele precedente se bazează fie pe o metodă


de tip reducere, fie pe o metodă de descompunere. În toate cazurile a fost posibilă
“sinteza” unei soluţii a problemei date din soluţiile subproblemelor la care problema

84
Algoritmi recursivi. Metodele divide et impera şi backtracking

s-a redus, respectiv în care s-a descompus. De asemenea, pentru fiecare dintre
problemele considerate au fost definite subproblemele primitive (condiţiilor
terminale) a căror soluţie este “cunoscută” sau dată. Metoda de rezolvare se numeşte
divide et impera (dezbină şi stăpâneşte) şi semnifică ideea prin care este realizată
construcţia soluţiei.

5.3 Metoda backtracking

Pentru rezolvarea anumitor probleme este necesară desfăşurarea unui proces


de căutare a soluţiei aflate într-o anumită mulţime, numită spaţiul stărilor. Pentru
fiecare element din spaţiul stărilor este definită o mulţime de acţiuni sau alternative.
Momentul iniţial în rezolvarea problemei corespunde unei stări, numită iniţială, iar
soluţiile corespund drumurilor în spaţiul stărilor, de la cea iniţială până la una finală.
Procesul de rezolvare a problemei poate fi imaginat ca o secvenţă de acţiuni care
asigură “deplasarea” (prin intermediul unei secvenţe de stări) în spaţiul stărilor din
starea iniţială la cea finală. În cazul anumitor probleme se doreşte obţinerea unei
singure soluţii, altele solicită determinarea tuturor soluţiilor sau determinarea unei
soluţii optime, dintr-un anumit punct de vedere (soluţie optimală).
Se presupune că problema constă în a ajunge în vârful unui munte pornind de
la baza lui. În general, există mai multe puncte din care se desprind mai multe poteci,
dar nu neapărat toate conduc spre vârful muntelui. În acest caz, starea iniţială este
baza muntelui, există o singură stare finală şi anume vârful muntelui, spaţiul stărilor
incluzând şi toate punctele de ramificare a drumului. O soluţie poate fi apreciată ca
optimală din mai multe puncte de vedere: un drum care solicită cel mai mic efort din
partea celui care-l urmează; un cel mai scurt drum; un drum care trece printr-un
punct preferat etc.
Un alt exemplu este un labirint având una sau mai multe ieşiri. Starea iniţială
poate fi considerată orice cameră a labirintului, problema revenind la găsirea unui
drum din camera respectivă către una dintre ieşiri. Desfăşurarea procesului de căutare
a unei stări finale presupune, la fiecare etapă, alegerea opţiunii pentru o alternativă
posibilă a stării curente şi detectarea acelor stări “capcană” din care nu mai este
posibilă continuarea procesului, sau deja se cunoaşte excluderea atingerii unei stări
finale. Detectarea stării “capcană” trebuie să determine revenirea la starea din care
s-a ajuns la ea şi selectarea unei noi opţiuni de continuare. În cazul în care nu mai
există alternative care să nu fi fost selectate anterior, o astfel de stare devine la rândul
ei “capcană” şi pentru ea se aplică acelaşi tratament.
Prin soluţie a problemei se înţelege o secvenţă de acţiuni care determină
tranziţia din starea iniţială într-o stare finală, fiecare componentă a unui drum soluţie
reprezentând o alternativă din mulţimea de variante posibile. Cu alte cuvinte, x1 este
alternativa aleasă pentru starea iniţială, x2 este alternativa selectată pentru starea în

85
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

care s-a ajuns pe baza opţiunii x1 ş.a.m.d. După efectuarea acţiunii corespunzătoare
alegerii alternativei xn rezultă o stare finală.
Forma standard a metodei corespunde unei probleme în care trebuie găsit un
drum soluţie x=(x1, x2, ..., xn) cu xi ∈ Si, unde fiecare mulţime Si este finită şi conţine
si elemente. În plus, se presupune că fiecare Si este ordonată şi reprezintă mulţimea
alternativelor existente la momentul i al căutării.
În anumite cazuri interesează obţinerea unei singure soluţii, în altele sunt
căutate toate soluţiile problemei sau cele care îndeplinesc un criteriu dat (de
exemplu, se maximizează sau minimizează o funcţie f definită pe mulţimea
drumurilor soluţie din spaţiul stărilor).
Procesul de căutare a unui drum soluţie revine la tentativa de extindere a
porţiunii de drum construit, alegând prima alternativă disponibilă pentru starea
curentă atinsă. Continuarea drumului poate fi realizată până la atingerea unei stări
finale sau până la întâlnirea unei stări capcană (mulţimea vidă de alternative). Dacă
este atinsă o stare capcană, atunci este necesară revenirea la starea anterioară şi
selectarea următoarei alternative disponibile acestei stări. Dacă nu mai există
alternative disponibile, atunci se iniţiază o nouă revenire ş.a.m.d. În cazul în care
există cel puţin încă o alternativă disponibilă, atunci se reia procesul de extindere a
drumului rezultat. În condiţiile în care revenirea poate conduce la atingerea stării
iniţiale şi pentru ea nu mai există alternative disponibile, se consideră că problema nu
are soluţie.
Pentru implementarea căutării este necesară reţinerea alternativei selectate
pentru fiecare stare atinsă până la cea curentă, astfel încât, în cazul unei reveniri să
fie posibilă alegerea alternativei următoare. Cu alte cuvinte, procesul de căutare
revine la tentativa de extindere a drumului curent (pasul de continuare), cu eventuala
revenire în cazul atingerii unei stări capcană (pasul de revenire - back), memorând
alternativele selectate pentru fiecare stare intermediară atinsă (track). De aici îşi are
geneza numele metodei backtracking.

Pentru determinarea unei singure soluţii, descrierea pe paşi a metodei este:


• starea iniţială a problemei este prima alternativă posibilă pentru starea
curentă ; fie aceasta x1 ∈ S1;
• dacă starea curentă rezultată prin alternativa x1 este finală, atunci x=(x1)
este soluţie; stop;
• altfel, este selectată prima alternativă din mulţimea de acţiuni posibile
pentru starea curentă; fie aceasta x2 ∈ S2;
• dacă secvenţa de alternative care a condus la starea curentă este
x=(x1, x2,..., xk), atunci:
• dacă starea curentă este finală, soluţia este x=(x1, x2,..., xk); stop;
• altfel
• B1: dacă pentru starea curentă există alternative disponibile,
atunci se alege prima dintre ele şi se continuă;

86
Algoritmi recursivi. Metodele divide et impera şi backtracking

• B2: altfel, se revine la starea anterioară celei curente, soluţia


parţial construită devine x=(x1, x2,..., xk-1) şi se efectuează B1.
• dacă, în urma unui pas de revenire, s-a ajuns la starea iniţială şi nu
mai sunt alternative disponibile, atunci problema nu are soluţie; stop.

În cazul în care trebuie determinate toate soluţiile problemei, căutarea


continuă după determinarea fiecărei soluţii prin efectuarea de reveniri succesive.
Terminarea căutării este decisă în momentul în care s-a revenit la starea iniţială şi nu
mai există alternative disponibile.
Dacă se doreşte obţinerea numai a soluţiilor care optimizează o funcţie
criteriu f, atunci metoda se aplică pentru determinarea tuturor soluţiilor problemei,
fiecare nouă soluţie rezultată fiind comparată cu “cea mai bună” soluţie determinată
anterior. Pentru aceasta este necesară reţinerea “celei mai bune” soluţii calculate la
fiecare moment.
Forma generală a metodei backtracking este implementată de procedura
back.

procedure back(k:byte);
begin
if k=n+1 then final
else
begin
x[k]:=init(k);
while succ(k) do
if continuare(k) then back(k+1);
end;
end;

în care:
− final este o procedură care descrie prelucrarea dorită pentru o soluţie
determinată (se afişează rezultatul, se testează o funcţie criteriu pentru soluţia
obţinută samd);
− init(k) efectuează iniţializarea lui xk cu o valoare prin care se indică faptul
că, până la acel moment, nu a fost selectată nici o alternativă pentru poziţia k;
− succ(k) este o funcţie booleană care calculează true, dacă şi numai dacă
există succesor pentru xk în Sk;
− continuare(k) este o funcţie booleană pentru testarea condiţiilor de
continuare; calculează true dacă şi numai dacă este posibilă extinderea drumului
curent.

În continuare sunt prezentate câteva probleme rezolvate prin metoda


backtracking.

1. Să se genereze toate permutările mulţimii {1, 2,..., n}.


În acest caz, S1 = S2 = ... = Sn = {1, 2,..., n}. Alternativele posibile pentru
starea iniţială corespund alegerilor pentru prima poziţie dintr-un vector soluţie.
Pentru fiecare k, 1 ≤ k ≤ n − 1 , dacă x=(x1, x2,..., xk) este drumul calculat până la

87
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

momentul k, atunci xi ≠ x j , ∀1 ≤ i ≠ j ≤ k şi alternativele posibile pentru starea


curentă sunt elementele xk+1 din mulţimea {1, 2,..., n}care îndeplinesc cerinţa
xi ≠ x k +1 , ∀1 ≤ i ≤ k .
De exemplu, pentru n=3, soluţiile problemei sunt:
x1=(1,2,3), x2=(1,3,2), x3=(2,1,3), x4=(2,3,1), x5=(3,1,2), x6=(3,2,1).

Funcţia init(k) realizează iniţializarea elementului x[k] cu valoarea 0, pentru


a marca faptul că, până la momentul curent, nu a fost selectată nici o alternativă
pentru x[k]. Funcţia succ(k) calculează true dacă elementul x[k] are succesor în
mulţimea {1, 2,..., n}, caz în care acesta este determinat prin incrementare. Altfel,
funcţia calculează false. Funcţia continuare(k) returnează true dacă şi numai dacă
secvenţa (x1, x2,..., xk) calculată până la momentul curent este corectă, conform
regulilor descrise anterior.
Conform schemei generale, programul Pascal este:

program permutare;
uses crt;
type tip_elem=0..7;
var x:array[1..7] of tip_elem;
n:byte;

function init:byte;
begin
init:=0;
end;
function succ(k:byte):boolean;
begin
succ:=x[k]<n; {atribuire de valoare logica}
inc(x[k]);
end;
function continuare(k:byte):boolean;
var i:byte;
begin
i:=1;
while(i<k)and(x[i]<>x[k]) do inc(i);
continuare:=i=k; {atribuire de valoare logica}
end;

procedure final;
var i:byte;
begin
for i:=1 to n do write(x[i],' ');
readln;
end;

procedure back(k:byte);
begin
if k=n+1 then final
else
begin
x[k]:=init;
while succ(k) do
if continuare(k) then back(k+1);

88
Algoritmi recursivi. Metodele divide et impera şi backtracking

end;
end;

begin
clrscr;
write('Numarul de elemente ale permutarii: ');
readln(n);
back(1);
end.
În cazul acestei probleme este posibilă operarea unor simplificări în scrierea
codului, pe baza observaţiilor:
- funcţia init(k) nu depinde de valoarea lui k şi returnează întotdeauna
valoarea 0;
- funcţia succ(k) nu depinde de valoarea parametrului k şi realizează
întotdeauna o incrementare (S1 = S2 = ... =Sn = {1, 2,..., n}).
Procedura back(k) poate fi descrisă fără a utiliza funcţiile init şi succ, astfel:

procedure back(k:byte);
var i:byte;
begin
if k=n+1 then final
else
for i:=1 to n do
begin
x[k]:=i;
if continuare(k) then back(k+1);
end;
end;
Înlocuirea în procedura back a structurii repetitive while cu ciclul for este
posibilă datorită faptului că funcţia succ realiza incrementarea valorii elementului
x[k].

Varianta de program Pascal rezultată în urma acestor simplificări este:

program permutare_1;
uses crt;
type tip_elem=0..7;
var x:array[1..7] of tip_elem;
n:byte;
procedure final;
var i:byte;
begin
for i:=1 to n do write(x[i],' ');
readln;
end;
function continuare(k:byte):boolean;
var i:byte;
begin
i:=1;
while(i<k)and(x[i]<>x[k]) do inc(i);
continuare:=i=k;
end;
procedure back(k:byte);

89
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

var i:byte;
begin
if k=n+1 then final
else
for i:=1 to n do
begin
x[k]:=i;
if continuare(k) then back(k+1);
end;
end;
begin
clrscr;
write('Numarul de elemente ale permutarii: ');
readln(n);
back(1);
end.

2. Se presupune că se dispune de n tipuri de bancnote, n ≤ 20, cu valori


diferite; din fiecare tip se dispune de un număr cunoscut de bancnote. Considerân-
du-se dată o sumă de bani s, să se determine o modalitatea de plată a sa utilizându-se
un număr minim de bancnote.
Valorile şi numărul de bancnote disponibile din fiecare tip sunt memorate
într-o tabelă y cu 2 linii şi n coloane, astfel: pentru fiecare 1 ≤ i ≤ n , y[1,i] reprezintă
valoarea unei bancnote de tipul i şi y[2,i] este numărul de bancnote disponibile din
tipul i. Vectorul xb memorează modalitatea optimă de plată a sumei s, adică xb[i]
reprezintă numărul de bancnote alese din tipul i într-o descompunere optimă;
vectorul x memorează descompunerea curentă. Numărul minim de bancnote utilizate
în plata sumei s este nrb.
Metoda utilizată în rezolvarea problemei este backtracking, urmărindu-se
n n
minimizarea funcţiei f ( x ) = ∑ x[i] ,
i =1
în condiţiile în care ∑ x[i]y[1, i] = s
i =1
şi

0 ≤ x[i] ≤ y[2, i], i = 1,..., n . Sunt generate toate descompunerile posibile ale sumei
s funcţie de bancnotele disponibile şi, la fiecare moment în care este determinată o
astfel de descompunere, aceasta este comparată cu precedenta. În vectorul xb este
memorată o cea mai bună descompunere pe baza criteriului f, după fiecare astfel de
comparaţie.

program bancnote;
uses crt;
var x,xb:array[1..100] of longint;
y:array[1..2,1..20]of longint;
n,i,nrb:word;
s:longint;
function init(k:word):longint;
begin
init:=-1;
end;

function urm(k:word):boolean;
begin

90
Algoritmi recursivi. Metodele divide et impera şi backtracking

urm:=x[k]<y[2,k];inc(x[k]);
end;
function continuare(k:word):boolean;
var
sc,i:longint;
begin
sc:=0;
for i:=1 to k do sc:=sc+x[i]*y[1,i];
if k<n then continuare:=sc<=s
else continuare:=sc=s;
end;
procedure final(k:word);
var i:byte;
nr:longint;
begin
nr:=0;
for i:=1 to n do
begin
nr:=nr+x[i];
end;
if nr<nrb then
begin
for i:=1 to n do xb[i]:=x[i];
nrb:=nr;
end;
end;
procedure back(k:byte);
begin
if k=n+1 then final(k)
else
begin
x[k]:=init(k);
while urm(k) do
if continuare(k) then back(k+1)
end;
end;
begin
clrscr;
write('Dati numarul de bancnote');
readln(n);
writeln('Valorile si numarul de exemplare:');
for i:=1 to n do
begin
write('Valoarea:');readln(y[1,i]);
write('Numar de bancnote:');readln(y[2,i]);
end;
write('Suma schimbata:');
readln(s);
nrb:=maxint;
back(1);
writeln('Numarul minim de bancnote:',nrb);
writeln;
writeln('Numarul de bancnote alese din fiecare tip:');
for i:=1 to n do
writeln('Tip ',i,' numar de bancnote alese:',xb[i]);
end.

91
REPREZENTAREA VIZUALĂ
A DATELOR

Forma şi formatul afişării datelor pe monitor (ca ecou al introducerii de la


tastatură sau ca rezultat al scrierii) precum şi modul în care se desfăşoară conversaţia
în timpul acestui proces constituie interfaţa programului cu utilizatorul. Realizarea
unei interfeţe eficiente şi atractive trebuie să constituie pentru programator un
obiectiv la fel de important ca şi cel al atingerii performanţelor ridicate de eficienţă a
programului însuşi.

6.1 Resursele şi modurile de lucru video


ale unui microcalculator

În procesul afişării video a datelor sunt implicate componente hardware şi


software, care interacţionează pentru crearea imaginii pe ecranul monitorului
(figura 6.1).

Placa grafică

Interfaţa video
Monitor
(Logică de comandă)
Software
Memoria ecran

Fig. 6.1 Componente implicate în afişarea video a datelor

• Memoria ecran, de tipul RAM, are rolul de a stoca, sub formă binară,
imaginea care se afişează pe ecran. Ea poate avea diferite capacităţi - uzual, până la 1

92
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

MB − şi este împărţită în pagini de o anumită mărime, în care se pot memora


concomitent una sau mai multe imagini de ecran. În plus, se pot memora definiţiile
binare ale unor seturi de caractere pentru afişarea textelor cu modele de caractere
(fonturi) ale utilizatorului, altele decât cele definite de generatorul de caractere al
interfeţei video.
• Interfaţa video (adaptorul video) are rolul de a comanda şi controla
monitorul pentru crearea imaginii pe tub catodic. Ea comunică cu UC a
microcalculatorului şi cu memoria ecran prin magistrale de date şi de adrese. UC şi
interfaţa pot utiliza simultan memoria ecran (prin porturi diferite), astfel încât
microprocesorul poate înscrie date în memoria ecran în timp ce interfaţa citeşte
memoria pentru a realiza afişarea (figura 6.2).

Magistrala de date
Microproceso Memoria
r Magistrala de adrese ecran

Interfaţă video
Sincronizare
Controler Generator Registru de Generator
video de caractere deplasare de semnale

Semnale de comandă

Monitor
Fig. 6.2 Modul de interacţiune a interfeţei video

Controlerul video (logica de comandă) al interfeţei citeşte periodic, cu o


anumită frecvenţă, memoria de ecran şi formează semnalele de comandă pentru
monitor în vederea creării şi menţinerii imaginii video. Dacă frecvenţa de refacere a
imaginii pe ecran (frecvenţa de reîmprospătare) este de peste 50 Hz (de obicei
50-70 Hz), atunci, datorită inerţiei ochiului, imaginea apare ca fiind continuă şi fără
pâlpâiri.
Generatorul de caractere este dispozitivul interfeţei care produce imaginile
(matricele) punctiforme ale caracterelor ASCII de afişat. Prin comanda dată de
controler şi utilizând caracterele ASCII citite din memoria ecran, generatorul de
caractere produce, la ieşire, matricele discrete care definesc caracterele. Pe baza lor şi
a atributelor de afişare asociate, aduse de controler din memoria de ecran, generatorul
de semnale produce semnalele seriale care comandă monitorul. Serializarea în
producerea semnalelor este asigurată prin intermediul unui registru de deplasare care
furnizează, pe rând, câte un element de imagine.

93
Reprezentarea vizuală a datelor

• Monitorul utilizează un tub catodic monocrom sau color şi creează


imaginea pe ecran, de sus în jos. Fascicolul de electroni “aprinde”, pe rânduri sau
linii, puncte elementare din suprafaţa fluorescentă a tubului catodic, în conformitate
cu semnalele discrete de comandă trimise de sistemul de baleere a ecranului.
Punctele elementare de imagine sunt denumite pixeli (picture elements) şi
apar pe monitor organizate matriceal, definind spaţiul fizic sau spaţiul ecran
(figura 6.3). În acest spaţiu, pixelii pot fi adresaţi printr-o pereche de numere naturale
(x, y), unde x este numărul coloanei şi y este numărul liniei. De remarcat faptul că, în
acest spaţiu, abscisa creşte normal, de la stânga spre dreapta, dar ordonata creşte de
sus în jos.
(0,0) x
•••• ••••
•••• ••••
•••• ••••
• (x,y)

•••• ••••
•••• ••••

y (xmax,ymax)
Fig. 6.3 Spaţiul ecran

Numărul total de pixeli care pot fi trataţi independent constituie o


caracteristică a monitorului, denumită rezoluţia ecranului. Rezoluţia poate fi joasă
(cca.320 x 200 pixeli), medie (cca.640 x 200 pixeli) şi înaltă (peste 640 x 350 pixeli).
Interfeţele video sunt proiectate astfel încât pot lucra cu monitoare de diferite
rezoluţii, pentru un monitor simulând toate rezoluţiile inferioare celei date. Imaginea
este cu atât mai clară cu cât rezoluţia utilizată în realizarea ei este mai mare. De
regulă, în construcţia microcal culatoarelor, interfaţa video şi memoria de ecran sunt
realizate pe o placă unică, numită placă grafică. “Puterea” plăcii grafice este cea care
dă, în principal, puterea de afişare a datelor pe ecran. În prezent, microcalculatoarele
sunt echipate cu plăci grafice de mare putere, de tipul SVGA (Super Video Graphic
Array), realizate după standardul firmei IBM pentru utilizatori profesionali. Ele sunt
compatibile cu plăci realizate anterior (MDA, CGA şi EGA), al căror mod de lucru îl
pot realiza, oferind în plus o rezoluţie înaltă şi o paletă bogată de culori şi nuanţe de
gri (pentru monitoare monocrome).
• Partea de software implicată în afişarea video are rolul de a comanda
interfaţa video în realizarea diferitelor operaţii necesare afişării: alegerea modului de
afişare - text sau grafic - definirea poziţiei spaţiului ecran pentru afişare, definirea
atributelor de afişare etc. Ea este constituită din rutine BIOS multifuncţionale (cu mai
multe servicii) care se apelează prin intermediul tehnicii întreruperilor software.
Tipică este INT 10h, care oferă o serie de servicii video standard. În program, înainte
de cererea întreruperii, codul serviciului dorit trebuie încărcat într-un registru special

94
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

(AH), iar alţi parametri, dacă sunt necesari, se încarcă în alte registre (AL, BH, BL,
DH, DL).

6.2 Moduri video

Din punctul de vedere al constituirii imaginii, interfaţa video poate lucra în


două moduri: text şi grafic.
• În modul text, ecranul este văzut ca o matrice de linii şi coloane de text.
Locul de afişare a unui caracter în spaţiul ecran este dat de o pereche (x,y) de numere
naturale, cu x=numărul coloanei şi y=numărul liniei (figura 6.4.a).

012 cmax x
0
1 (nr. coloană)

caracter 8
#•#####•
#•#####•
#••####•
#••####•
lmax #•#•###•
#•#••##•
#•##•##•
14 #•##••#•
lmax =24 #•###•#•
y (nr. linie) ecran 25*80 #•####••
#•####••
cmax =79 #•#####•
#•#####•
########
lmax =24
ecran 25*40
cmax =39
a) Spaţiul b) Matricea de caractere

Fig. 6.4 Ecranul în modul text

Deoarece spaţiul text rezultă prin gruparea corespunzătoare a liniilor şi


coloanelor de pixeli, afişarea caracterelor se face, în mod natural, prin matrice de
caractere (figura 6.4.b). O astfel de matrice, de regulă 8x14 sau 8x8, defineşte forma
caracterului prin pixelii aprinşi, iar fondul prin pixelii stinşi. În fondul caracterului se
cuprind pixeli de separare a caracterelor pe coloane şi linii. Caracterele realizate
astfel se numesc caractere matriceale sau rastru. De regulă, în memoria ROM a
calculatorului se definesc patru seturi de caractere, având forme (fonturi) tipice
(scriere standard, caractere italice etc). La pornirea calculatorului sau la cererea

95
Reprezentarea vizuală a datelor

programatorului, ele sunt încărcate în memoria de ecran pentru a fi accesibile


generatorului de caractere. Un anumit set, implicit sau cerut de utilizator (via INT
10h), va deveni set curent pentru generator. Există posibilitatea ca programatorul
să-şi definească în memoria calculatorului propriile seturi de caractere (cel mult patru
odată), pe care să le încarce în memoria ecran şi din care, apoi, să definească setul
curent (ambele sunt servicii INT 10h).
Datorită adresabilităţii spaţiului text, există posibilitatea de a comanda (INT
10h) poziţia de afişare a unui caracter. În acest scop, interfaţa păstrează în registre
speciale coordonatele locului de afişare, iar pe ecran acesta este marcat prin afişarea
unui caracter special, denumit cursor (de regulă, un dreptunghi umplut sau luminos).
Atunci când se afişează un caracter în poziţia de la cursor (poziţia curentă), acesta se
suprascrie peste cursor, iar cursorul este mutat automat cu o poziţie spre dreapta şi în
jos, la începutul noului rând, dacă este cazul. Prin servicii ale rutinei INT 10h se
poate defini o formă proprie a cursorului în modul text şi se poate afla locul acestuia
pe ecran.
Fiecărui caracter de afişat i se asociază un caracater de atribute (figura
6.5.a).
a) caracter de atribute b) registru de paletă
byte 0 1 2 ... 15
76543210
culoare negru albastru ... alb

cod 0 1 ... 53
Culoare de Culoare de
fond caracter
Intensitatea
Clipire
culorii
(1=da, 0=nu)
(1=da, 0=nu)

Fig. 6.5 Definirea atributelor de afişare

Caracterul de atribute, prin valorile biţilor săi, defineşte culoarea de fond a


caracterului (background color), culoarea de caracter (foreground color) şi faptul că
imaginea caracterului se va “stinge intermitent” (clipire - blinking). Se observă că se
pot defini cel mult 8 culori pentru fond şi 16 culori de caracter, dacă se consideră că
afişarea normală şi intensă definesc două culori diferite. Dacă ecranul este
monocrom, atunci se pot combina culorile alb şi negru.
Pentru variabilitate în exprimarea culorilor şi pentru asigurarea
independenţei faţă de convenţiile de notare a lor la diverse plăci grafice, s-a adoptat
ideea specificării indirecte a culorilor. Interfaţa posedă un registru, de regulă de
16 octeţi, denumit registru de paletă, prin care se specifică culorile care pot fi
utilizate la un moment dat (figura 6.5.b). Fiecare poziţie din registrul de paletă
defineşte, prin cod, o culoare şi, în ansamblu, constituie paleta curentă de culori.

96
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Astfel, în caracterele de atribute se specifică numărul intrării din paleta curentă


(index), de unde trebuie extras codul culorii dorite. Pe baza acestei idei, în orice
moment, poate fi modificat conţinutul registrului de paletă (serviciu INT 10h), ceea
ce conduce, în mod automat, la modificarea culorilor pentru întregul text afişat pe
ecran. La pornirea calculatorului, registrul de paletă este încărcat cu o paletă iniţială
(implicită), definită în memoria ROM. Programatorul poate schimba, total sau
parţial, culorile în registrul de paletă, depinzând de tipul plăcii grafice şi aceasta este
unica operaţie pentru care trebuie cunoscute codurile culorilor la placa respectivă.
Trebuie remarcat că unele monitoare nu pot afişa toate culorile, deşi interfaţa cu care
sunt cuplate acceptă specificarea oricărui atribut.
Definirea caracterului de atribute se face prin serviciul INT 10h, care
realizează scrierea caracterului text ca parametru într-un registru (registrul BL).
Scrierea unui caracter poate fi însoţită de operaţia SAU EXCLUSIV (suma
modulo-2, notată ⊕) între culoarea caracterului afişat într-o anumită poziţie şi
culoarea noului caracter, destinat a fi afişat în aceeaşi poziţie. Operaţia poate fi
utilizată pentru scrierea şi ştergerea caracterelor (ştergerea selectivă). De exemplu,
pentru ştergere, dacă culoarea este 0101 şi se aplică o suprascriere cu aceeaşi culoare,
atunci, cum 0101⊕0101=0000, se obţine un cod care desemnează culoarea fondului
curent, adică, de fapt, “dispariţia” caracterului de pe ecran.
Având în vedere că necesarul de memorie ecran este în jur de 4KB 25*80,
depinzând de rezoluţia în modulul text, în memoria video se pot focaliza una sau mai
multe pagini video (ecrane). Se creează posibilitatea constituirii textului într-o
anumită pagină, denumită pagină video, selectată din mulţimea paginilor acceptate
de interfaţa respectivă prin serviciul INT 10h. Orice poziţie şi deplasare a cursorului
pe ecran îşi are echivalentul într-o poziţie şi o deplasare corespunzătoare în pagina
video selectată. Din mulţimea paginilor definite, programatorul poate selecta una, ca
pagină de afişare pe ecran, denumită pagină activă. Astfel creşte viteza de lucru prin
suprapunerea operaţiei de afişare cu operaţia de pregătire a imaginii, într-o nouă
pagină video. Interogarea, prin INT 10h, în legătură cu atributele unui anumit
caracter al imaginii este, de fapt, o citire din pagina video a memoriei ecran, eventual
pagina activă.
Modul text permite definirea, în pagina activă, a ferestrelor de text. O
fereastră text este o zonă de afişare, formată din una sau mai multe linii şi coloane
consecutive. Ferestrele text se definesc şi se selectează prin INT 10h. Definirea se
realizează prin precizarea coordonatelor din spaţiul text ale colţului stânga sus şi
respectiv ale colţului dreapta jos. În fereastră se poate realiza o operaţie de defilare în
sus (jos) care deplasează textul rândurilor cu un număr de poziţii în sus (jos) şi umple
rândurile eliberate, la partea de jos (sus), cu caracterul blank (ştergere). Se pot scrie
rânduri noi de text în rândurile eliberate, eventual în toată fereastra activă. Rândurile
de text care ies din fereastră, prin defilare, se pierd. La limită şi în mod implicit,
fereastra text este întreaga pagină activă.
În modul grafic, imaginea se constituie prin aprinderea mulţimilor de pixeli
(desen prin puncte - pixel mode). Imaginea se creează în memoria ecran, în pagini,
dacă placa grafică permite mai multe pagini şi se afişează pe ecran, pagină cu pagină.

97
Reprezentarea vizuală a datelor

O pagină video utilizează un bit pentru a defini starea fiecărui pixel al ecranului
(1=aprins, 0=stins) şi un număr de biţi suplimentari pentru a defini culoarea
acestora. Deoarece culorile se definesc pe baza registului de paletă, dacă se
utilizează concomitent 16 culori, sunt necesari 4 biţi suplimentari. Rezultă că o
pagină video trebuie să aibă cel puţin 4∗rezoluţie biţi. De exemplu, pentru o placă
EGA, cu rezoluţie înaltă, sunt necesari 4∗640∗350=896000 biţi≈110 Kb şi deci,
într-o memorie ecran de 256 Kb, pot fi create două pagini video.
Modul grafic utilizează posibilitatea de adresare a pixelilor într-o pagină
video şi un cursor grafic invizibil. Cursorul grafic are coordonatele ultimului pixel
adresat în pagina video selectată şi poate fi deplasat în orice punct al acestui spaţiu.
Se menţin, de asemenea, facilităţile de interogare asupra poziţiei cursorului şi asupra
culorii pixelului curent.
Ca şi la modul text, la definirea unui pixel există posibilitatea realizării
operaţiei SAU EXCLUSIV între biţii de culoare ai unui pixel dintr-o pagină şi
valorile noi ale acestora. În acest fel, pixelii pot fi scrişi sau şterşi.
Toate aceste operaţii se realizează ca servicii ale rutinei INT 10h. În modul
grafic, se selectează un anumit submod acceptat de placa grafică, submodurile
diferind prin rezoluţie, număr de culori şi număr de pagini de ecran pe care le
acceptă.

6.3 Facilităţi de scriere a textelor prin unitatea CRT

În Pascal, operaţiile de afişare pe monitor, în modul text, au fost


implementate în două unităţi de program: SYSTEM şi CRT. Unitul SYSTEM
defineşte operaţia de afişare pe monitor în mod standard, adică cu atribute de scriere
implicite şi începând din poziţia curentă a cursorului. Unitul CRT (anexa 3) defineşte
variabile, funcţii şi proceduri prin care programatorul poate defini şi controla
contextul de scriere (ferestre, culori, poziţie de scriere etc.)
• Alegerea modului text. Programatorul are posibilitatea de a alege un
anumit mod de scriere, caracterizat, în principal, prin rezoluţia de afişare şi tipul de
monitor: monocrom sau color. Sunt acceptate modurile prezentate în anexa 3. Pentru
alegerea modului se apelează procedura:

TextMode (Mode);

în care parametrul Mode, de tip Word, defineşte modul dorit, prin numărul asociat.
Procedura salvează vechiul mod în variabila predefinită LastMode, de unde, prin
aceeaşi procedură, poate fi restaurat. Procedura setează culoarea de fond zero
(culoarea neagră). Dacă nu se apelează TextMode, atunci, implicit, se consideră
modul de mare rezoluţie al plăcii grafice.

98
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Exemplu:
6.1.
. . .
User CRT;
. . .
TextMode(C080);
. . .
TextMode(LastMode);
. . .
TextMode(BW40)

• Stabilirea culorii de fond şi de scriere se face prin referire la paleta


implicită. Intrările în paletă, desemnate prin constante întregi sau constante
simbolice, sunt asociate cu diferitele culori (anexa 3). Definirea culorii fondului se
face prin apelul procedurii:
TextBackGround(culoare);
în care parametrul culoare este de tipul Byte şi poate avea valorile 0-7. Procedura
tratează biţii 4-6 ai unei variabile predefinite TextAtrr de tip Byte, prin care se
modifică corespunzător octetul de atribute din memoria video.
Culoarea de scriere se defineşte prin apelul procedurii:
TextColor (culoare);
în care parametrul culoare poate să aibă o valoare între 0 şi 15 (anexa 2), eventual
mărită cu 128. Procedura modifică biţii 0-3 şi 7 ai variabilei TextAtrr, scriind în
bitul 7, pentru definirea clipirii, 1 sau 0, după cum a fost adunată sau nu constanta
128 (constanta simbolică corespunzătoare este Blink).

Exemplu:
6.2.
. . .
TextColor (Red+128);
. . .
TextColor(Green+Blink);
. . .
Culoare:=6
. . .
TextColor(Culoare);

Programatorul poate controla intensitatea culorii, prin modificarea bitului 3


din variabila TextAtrr (şters de către TextColor) prin procedurile fără parametri:
HighVideo - intensitate mărită (bitul 3 are valoare 1);
LowVideo - intensitate mică (bitul 3 are valoare 0);
NormVideo - intensitate implicită.
Execuţia procedurilor pentru stabilirea culorii de fond şi de scriere afectează
numai fereastra curentă. Atributele rămân valabile până la o nouă definire sau până la
terminarea execuţiei programului.
• Definirea unei ferestre de scriere. Scrierea textului pe ecran se poate
realiza într-o anumită zonă a acestuia, denumită fereastră curentă de scriere (text

99
Reprezentarea vizuală a datelor

window). O fereastră se defineşte ca un dreptunghi, prin coordonatele colţurilor


stânga-sus, dreapta-jos. Ecranul întreg este o fereastră de coordonate (1, 1, 80, 25)
sau (1, 1, 40, 25) etc., depinzând de modul text. Declararea ferestrei se face prin
procedura:
Window(x1, y1, x2, y2);
în care parametrii (de tipul Byte) definesc, în ordine, cele două colţuri: (x1, y1) -
colţul stânga-sus, (x2, y2) - colţul dreapta-jos (în exprimare matriceală, abscisele
x1,x2 reprezintă numerele coloanelor, iar ordonatele y1, y2 sunt numerele de linii).
Exemplu:
6.3. Window (10, 5, 70, 20); defineşte o fereastră care se întinde între liniile
5-20 şi coloanele 10-70.
Unitatea CRT defineşte variabilele WindMin, WindMax, de tipul Word,
care conţin coordonatele colţurilor ferestrei curente. Octetul cel mai semnificativ
conţine numărul liniei (y), iar cel mai puţin semnificativ conţine numărul coloanei
(x). Coordonatele ferestrei curente pot fi scrise ca:
(Lo(WindMin), Hi(WindMin)) - colţul stânga-sus
(Lo(WindMax), Hi(WindMax)) - colţul dreapta-jos
Dacă nu se apelează procedura Window, fereastra implicită este întregul
ecran, variabilele WindMin şi WindMax fiind iniţializate corespunzător modului
text selectat sau implicit. Fereastra selectată rămâne activă până la un nou apel al
procedurii Window, care poate defini o nouă fereastră curentă, eventual una utilizată
anterior.
• Poziţionarea cursorului. În fereastra curentă, programatorul poate să
gestioneze poziţia cursorului după necesităţile programului, impunând locul pe ecran
începând cu care se va realiza scrierea următorului text. Poziţia curentă a cursorului
poate fi aflată prin apelul funcţiilor, fără parametri, cu rezultat de tipul Byte:
WhereX (pentru abscisă) şi WhereY (pentru ordonată). Mutarea cursorului într-o
anumită poziţie a fereastrei curente poate fi comandată prin apelul procedurii:
GotoXY(X, Y);
Exemplu:
6.4.
. . .
Window(20, 3, 100, 15); TextBackGround(Red); TextColor(Blue);
ClrScr; {sterge fereastra}
Writeln (’Text 1’); {Se scrie pe linia 1, coloana 1 a
ferestrei (coordonatele absolute: linia 3, coloana 20)}
GotoXY(5, 6); Write (’Text 2’);
{Se scrie pe linia 6, coloana 5 a ferestrei (coordonatele
absolute: linia 9, coloana 25)}
X:=WhereX;
Y:=WhereY;
Write;
Writeln (’X=’, X, ’Y=’, Y); {Se scrie X=11, Y=6}
Dacă programatorul nu gestionează poziţia cursorului, atunci acesta se
deplasează, implicit, de sus în jos şi de la stânga la dreapta, pe măsură ce se scrie

100
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

textul în fereastră. Când cursorul este la sfârşitul ultimului rând al ferestrei, se


produce defilarea textului în fereastra curentă.

• Ştergerea şi inserarea în fereastra curentă se pot realiza utilizând


următoarele proceduri fără parametri:
ClrScr - şterge ferestra curentă şi poziţionează cursorul pe prima linie şi
coloană a acesteia;
ClrEol - şterge linia curentă, de la cursor până la sfârşitul ei, fără a muta
cursorul;
DelLine - şterge rândul pe care se află cursorul şi mută, în sus cu un rând,
textul de sub linia ştearsă; nu este afectată poziţia cursorului;
InsLine - deplasează în jos, cu un rând, textul, începând cu cel pe care este
cursorul şi inserează o linie goală, în linia din fereastră pe care este cursorul. Se
“pierde” ultimul rând din fereastră.
• Citirea caracterelor fără ecou. Prin intermediul a două funcţii, fără
parametri, programatorul poate citi fără ecou caracterul produs de ultima tastă
apăsată sau poate verifica dacă a fost apăsată o tastă. Se utilizează funcţiile:
ReadKey - funcţie de tipul CHAR care reîntoarce caracterul existent în
buffer-ul tastaturii; dacă nu există un astfel de caracter, atunci aşteaptă până la
tastarea unui caracter;
KeyPressed - funcţie de tipul BOOLEAN care reîntoarce valoarea True dacă
a fost apăsată o tastă şi False altminteri.
Funcţia ReadKey întoarce codul caracterului rezultat prin apăsarea tastei,
dacă aceasta produce un singur cod sau primul cod (valoare zero binar), dacă tasta
apăsată produce două coduri. În acest ultim caz, se obţine caracterul complet dacă se
fac două apeluri succesive ale funcţiei Readkey.
În general, tastele F1-F12, tastele de deplasare cursor (săgeţi), Home, Ins,
Del, PgUp, PgDwn când sunt apăsate singure sau simultan cu CTRL, Shift ori
ALT, produc două coduri. Pot, de asemenea, produce două coduri tastele de litere
sau cifre, atunci când sunt apăsate simultan cu tasta ALT.
În legătură cu funcţia KeyPressed, trebuie menţionat că răspunsul este
furnizat potrivit stării buffer-ului tastaturii de la acel moment (funcţia nu aşteaptă
apăsarea pe o tastă). Dacă KeyPressed este TRUE, atunci ReadKey returnează
imediat caracterul.

101
Reprezentarea vizuală a datelor

• Pornirea şi oprirea difuzorului. Programul poate provoca producerea


sunetelor de o anumită frecvenţă pe o durată controlabilă. În acest scop sunt
prevăzute procedurile:
Sound(Hz) - pornirea difuzorului care emite un sunet continuu de frecvenţă
dată de parametrul Hz, în Hertz;
NoSound - oprirea difuzorului;
Delay(Ms) - realizarea unei întârzieri de Ms milisecunde până la execuţia
următoarei instrucţiuni.
Controlând corespunzător frecvenţa Hz şi intervalul de emisie Ms al
difuzorului se poate realiza o anumită linie melodică.
Pentru cei care doresc să se iniţieze în tehnica compunerii muzicii cu ajutorul
calculatorului, sunt prezentate în continuare câteva elemente de bază.
În tabelul 6.1 sunt prezentate frecvenţele notelor, în patru octave consecutive
(în total sunt 7 octave numerotate 0÷6). Se poate constata că frecvenţa unei note
într-o octavă se poate estima prin dublarea frecvenţei acesteia din octava imediat
inferioară. Similar, se poate deduce frecvenţa notelor dintr-o octavă inferioară prin
înjumătăţirea frecvenţelor notelor din octava imediat superioară.
Tonul unei note poate fi alterat (mărit sau micşorat) cu o jumătate de ton în
octava respectivă (note cu diez şi bemol). Aceasta înseamnă că frecvenţa unei note
oarecare i cu diez se calculează cu relaţia: notai:=0.5(notai+1+notai); notai=1,2,4,5,6.
Notele pot avea diferite durate: întreagă, doime, pătrime, optime etc. Dacă se
stabileşte durata notei întregi ca durată iniţială, atunci duratele celorlalte note se
calculează ca o fracţie corespunzătoare din aceasta (1/2, 1/4, 1/8 etc.).

Tabelul 6.1 - Frecvenţele notelor muzicale în diverse octave

Octava 1 Octava 2 Octava 3 Octava 4


(mijlocie)
Nota Frecvenţa Nota Frecvenţa Nota Frecvenţa Nota Frecvenţa
Do 130.81 Do 261.63 Do 523.25 Do 1046.5
Re 146.83 Re 293.66 Re 587.33 Re 1174.7
Mi 164.81 Mi 329.63 Mi 659.26 Mi 1318.5
Fa 174.61 Fa 349.23 Fa 698.46 Fa 1396.9
Sol 186.00 Sol 392.00 Sol 783.99 Sol 1568.0
La 220.00 La 440.00 La 880.00 La 1760.0
Si 246.94 Si 493.88 Si 987.77 Si 1976.5

102
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Se ştie că durata unitară este determinată de tempo-ul în care trebuie


“cântate” notele. În tabelul 6.2, sunt redate câteva tempo-uri, exprimate în
pătrimi/minut. Din tabel rezultă duratele diferitelor note, în minute, pentru un tempo,
T, dat:
D1=4/T; D8=1/2T;
D2=2/T; D16=1/4T;
D4=1/T; D32=1/8T etc.
Durata unei note poate fi modificată prin punct. Astfel, durata reală este 3/2
din durata notei fără punct. Pentru a obţine stilul legato şi stacatto, durata pe care se
cântă o notă se modifică astfel:
normal: 7/8 din durată; 1/8 pauză;
legato: 1/1 din durată;
stacatto: 3/4 din durată; 1/4 pauză.
Trebuie observat că, datorită dependenţei duratei notelor de ceasul intern,
pentru unele tempo-uri nu se obţin rezultate satisfăcătoare. De aceea, este necesar ca
durata notelor şi tempo-urile să se stabilească prin experimentări.

Tabelul 6.2 Duratele notelor în diverse tempo-uri

Tempo Denumire Pătrimi/minut


Largo 40-60
foarte încet Larghetto 60-66
Adagio 66-76
încet Andante 76-108
mediu Moderato 108-120
repede Alegro 120-168
Presto 168-208

6.4 Implementarea modului grafic în Pascal

Subprogramele care implementează modul grafic se bazează pe


caracteristicile plăcilor existente şi se împart în: primitive grafice şi subprograme
ajutătoare. Ele sunt declarate în unitatea Graph şi sunt memorate în următoarele
fişiere:
• GRAPH.TPU - care conţine codul obiect al subprogramelor definite în
unitatea Graph;
• *.BGI - câte un fişier pentru fiecare tip de placă grafică, care
conţine codul obiect al rutinelor de tratare a
întreruperii pentru operaţii grafice (driver de placă), ca
soft specific pentru comanda interfeţelor grafice;

103
Reprezentarea vizuală a datelor

• *.CHR - fişiere care conţin seturi de caractere suplimentare cu


diferite stiluri (fonturi).
Unitatea Graph defineşte, de asemenea, o serie de constante, variabile şi
structuri de date care să-i uşureze programatorului apelul acestor subprograme
(anexa 4). Pentru utilizarea subprogramelor de grafică, programatorul trebuie să
procedeze astfel:
a) Să declare faptul că utilizează unitatea: uses Graph;
b) Să selecteze modul (submodul) grafic dorit, prin utilizarea procedurii:
InitGraph (GraphDriver, GraphMode, PathToDriver)
Primii doi parametri sunt variabile de tipul INTEGER, iar ultimul este o
constantă sau variabilă de tipul STRING. Parametrii au următoarea semnificaţie:
• GraphDriver precizează tipul plăcii grafice considerate. Variabila poate lua una
din valorile întregi 0-10 sau constantele predefinite echivalente. Constanta Detect
(valoare 0) cere subprogramului să identifice tipul de placă a calculatorului.
• GraphMode selectează unul din submodurile acceptate de placa grafică. Valoarea
parametrului nu este luată în considerare dacă GraphDriver a fost Detect, caz în
care se selectează automat submodul cu cea mai mare rezoluţie.
• PathToDriver defineşte calea spre directorul de rezidenţă a driver-ului de placă
(fişier de tip BGI), dacă acesta nu este în director curent. Şirul vid defineşte o
căutare în directorul curent.

Exemple:
6.5.
GraphDriver:=EGA;
GraphMode:=EGALo;
InitGraph ('GraphDriver, GraphMode,');
6.6.
GraphDriver:=Detect;
GraphMode:=0;
InitGraph (GraphDriver, GraphMode,'C:\TP\BIN');

c) Să verifice dacă iniţializarea modului grafic dorit, din pasul anterior, s-a desfăşurat
cu succes. În acest sens, se utilizează funcţia specială GraphResult, de tipul
INTEGER şi fără parametri, care returnează valoarea zero (GrOK) pentru succes.
Funcţia poate fi utilizată după execuţia oricărei operaţii grafice.
d) Să apeleze subrutinele grafice necesare, dacă iniţializarea s-a derulat cu succes.
e) Să revină în modul text, la terminarea operaţiilor grafice, prin utilizarea procedurii
fără parametri, CloseGraph. Iniţializarea sistemului grafic alocă memoria dinamică
necesară, iar închiderea o eliberează.

104
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

6.4.1 Fereastră şi vizor

În grafica computerizată se consideră că înregistrarea pe ecran este o


transpunere, micşorată sau mărită, a desenelor din spaţiul real (spaţiul utilizator).
Partea imaginii reale care se desenează este cuprinsă într-un dreptunghi, numit
fereastră (window), cu laturile paralele cu axele de coordonate ale acestui spaţiu.
Fereastra se proiectează pe spaţiul ecran într-un dreptunghi, asemenea cu fereastra,
denumit vizor (viewport). Astfel, pe ecran, pot coexista mai multe imagini
independente. La limită, vizorul poate fi întregul ecran.
Relaţia între fereastră şi vizor este evidenţiată în figura 6.6. De aici se deduce
o transformare de coordonate, care proiectează orice punct (xr,yr) din spaţiul real, în
punctul (xe,ye) al spaţiului ecran. Dacă fereastra şi vizorul se definesc prin
coordonatele colţurilor stânga-sus şi dreapta-jos, iar centrele celor două dreptunghiuri
sunt notate ( x 0r , y 0r ) , ( x e0 , y e0 ) , din asemănarea imaginii din cele două spaţii se pot
scrie relaţiile:

x e − x 0e v − v1 
= 2 
x r − x r w 2 − w1 
0

 (1)
y e − y 0e v 4 − v3 
=
y 0r − y r w 3 − w 4 

x
(v1,v3)
(x 0e , y e0 )

y •
(xe,ye)
y
(v2,v4)
vizor
(w1,w3)


(x 0r , y 0r ) • x
(xr,yr)
fereastră (w2,w4)
Fig. 6.6 Relaţia fereastră-vizor

105
Reprezentarea vizuală a datelor

Din (1) se explicitează coordonatele (xe,ye) şi se obţin relaţiile:

v 2 − v1 v − v1 0 
x e = x 0e + xr − 2 xr
w 2 − w1 w 2 − w 4 
 (2)
v − v3 v − v3 0 
y e = y 0e − 4 yr + 4 yr
w3 − w4 w 3 − w 4 
din care, prin înlocuirea coordonatelor centrelor ferestrelor şi vizorului, în funcţie de
coordonatele colţurilor acestora, se obţin relaţiile:
v1 + v 2 v − v1 v − v1 w 1 + w 2 
xe = + 2 xr − 2 ⋅ 
2 w 2 − w1 w 2 − w1 2 
 (3)
v3 + v4 v4 − v3 v4 − v3 w 3 + w 4 
ye = − yr + ⋅
2 w3 − w4 w3 − w4 2 

Realizând calculul din (3) se obţin relaţiile:


v 2 − v1 v w − v 2 w1 
xe = xr + 1 2 
w 2 − w1 w 2 − w1 
 (4)
v 4 − v3 v4 w 3 − v3w 4 
ye = − yr +
w3 − w4 w 3 − w 4 
Notând:
v 2 − v1 v − v3 
a1 = > a2 = − 4 
w 2 − w1 w3 − w4 
 (5)
v1 w 2 − v 2 w 1 v 4 w 3 − v3w 4 
b1 = > b2 =
w 2 − w1 w 3 − w 4 
se obţin relaţiile de transformare:
x e = Round(a 1 x r + b 1 )
(6)
y e = Round(a 2 y r + b 2 )

Relaţiile (6) dau coordonatele absolute, în spaţiul ecran, pentru orice punct
(xr,yr). Este, de multe ori, preferabil să se exprime punctele în spaţiul ecran relativ la
vizor, adică într-un subspaţiu cu originea în colţul stânga-sus al acestuia. Dacă se
notează (dxe,dye) coordonatele absolute (xe, ye), se obţin relaţiile:
x e = v 1 + dx e 
 (7)
y e = v 3 + dy e 

106
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Utilizând relaţiile (4), din (6) se obţin relaţiile:

v 2 − v1 
dx e = (x r − w 1 ) 
w 2 − w1 
 (8)
v4 − v3
dy e = − ( y r + w 3 )
w3 − w4 

Dacă se notează:

v 2 − v1
k 1 =
w 2 − w 1
v 4 − v3
k 2 = −
w 3 − w 4

atunci se pot considera coordonatele relative sub forma:

dx e = round(k 1 ( x r − w 1 )) 
 (9)
dy e = round(k 2 ( y r − w 3 ))
Utilizarea ferestrei şi/sau vizorului în tehnica realizării imaginilor grafice
oferă, în plus, posibilitatea de verificare a apartenenţei punctelor la desenul curent.
Se pot implementa măsuri de eliminare a punctelor străine (tehnica clipping-ului sau
tăierii), pentru a asigura protecţia imaginilor vecine împotriva distrugerilor
accidentale, desenându-se numai imaginea din fereastră.
În sistemul grafic Pascal, ideile tehnicii fereastră-vizor sunt implementate
parţial, aşa după cum rezultă din cele ce urmează.
• Există posibilitatea definirii vizorului ca spaţiu curent pentru un anumit
desen (vizor curent). Declararea lui se face prin apelul procedurii SetViewport, în
forma:
SetViewport(v1,v3,v2,v4,Clip);
în care (v1,v3) şi (v2,v4) sunt constante sau variabile de tipul INTEGER care dau
coordonatele colţurilor vizorului, iar Clip este un parametru boolean care activează
sau inhibă aplicarea clipping-ului.
Unitatea Graph defineşte funcţiile de tipul INTEGER fără parametri:
GetMaxX şi GetMaxY care returnează dimensiunile maxime ale spaţiului ecran
pentru placa grafică cu care este echipat calculatorul. Pentru a crea independenţă
programului faţă de calculator, se recomandă utilizarea lor în definirea vizorului. De
asemenea, unitatea Graph defineşte constantele simbolice ClipOn (True) şi ClipOff
(False) care pot fi folosite pentru parametrul Clip.

107
Reprezentarea vizuală a datelor

Exemplu:
6.7.
SetViewport(10,25,GetMaxX-150,GetMaxY-50,ClipOn);
Vizorul implicit, stabilit la iniţializarea modului grafic, este întregul ecran, echivalent
cu un apel de forma:
SetViewport (0,0,GetMaxX,GetMaxY,ClipOn) ;

• Vizorul curent poate fi şters, la culoarea de font curentă, prin apelul


procedurii ClearViewport, care este fără parametri. Pentru a fi pus în evidenţă faţă
de zonele vecine, vizorul poate fi şters cu o culoare de fond adecvată, stabilită în
prealabil (vezi § 6.4.2.) şi poate fi încadrat într-un dreptunghi.

Exemplu:
6.8. Pentru un ecran monocrom (dar nu numai) se poate utiliza o secvenţă de
forma:
Rectangle(9,24,GetMaxX-149,GetMaxY-49);
SetViewport(10,25,GetMaxX-150,GetMaxY-50,ClipOn)
ClearViewport;
în care procedura Rectangle desenează un dreptunghi cu liniile în culoarea curentă de
desen (alb), iar SetViewport îl şterge, la culoarea fondului.

Procedura ClearDevice şterge întregul ecran şi determină revenirea la


parametrii de lucru impliciţi, stabiliţi la iniţializare.
• Declararea vizorului determină rutinele sistemului grafic să lucreze în
coordonate relative. Aceasta înseamnă că orice procedură sau funcţie care primeşte
ca parametru de intrare coordonatele (dxe,dye) ’înţelege’ să le utilizeze pentru a
calcula coordonatele absolute (xe,ye), prin relaţii de forma (6), iar cele care au aceste
coordonate ca parametru de ieşire le determină prin relaţiile:

dx e = x e − v1 

dy e = y e − v 3 

• Sistemul nu prevede posibilitatea declarării ferestrelor în spaţiul utilizator


şi, de aici, lipsa facilităţii de exprimare a coordonatelor, pentru rutinele de grafică,
direct în acest spaţiu şi transformarea lor automată în coordonatele ecran.
Programatorul trebuie să-şi construiască proceduri proprii care să-i transforme
coordonatele din spaţiul utilizator în spaţiul ecran, relative la vizorul curent. Deşi pot
fi aplicate procedee diverse, după natura desenului, pentru generalitate şi
uniformitate în realizarea programelor de grafică se aplică tehnica fereastră-vizor
descrisă anterior.

108
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

6.4.2 Grafică în culori

Sistemul grafic Pascal implementează culorile pe principiul paletei. Sistemul


posedă o serie de proceduri, funcţii şi date prin care se pot accesa facilităţile de
culoare ale plăcii grafice şi modului grafic selectat.
• Determinarea paletei. Deoarece caracteristicile de culoare depind de placa
grafică şi modul grafic selectat, se poate obţine un program relativ portabil, dacă
acesta se scrie astfel încât să-şi determine singur facilităţile pe care le poate utiliza.
Pentru aceasta pot fi utilizate rutinele:
- GetMaxColor - funcţie fără parametri care întoarce o valoare de tipul WORD,
reprezentând indexul maxim al paletei;
- GetPaletteSize - funcţie fără parametri care întoarce o valoare de tipul
INTEGER reprezentând numărul maxim de culori care pot fi
utilizate simultan;
- GetDefaultPalette(paleta) - procedură care returnează, în parametrul paleta,
numărul şi culorile paletei implicate;
- GetPalette(paleta) - procedură care returnează aceleaşi date ca procedura
anterioară, dar pentru paleta curentă.
Parametrul paleta trebuie să fie declarat ca o variabilă de tipul PaletteType, definit în
unitatea Graph ca un articol cu câmpurile: Size (Byte) şi Colors (Shortint).
• Modificarea paletei curente. Programatorul poate modifica una sau toate
intrările paletei curente, dar pantru aceasta trebuie să cunoască codurile de culoare.
Procedurile care se utilizează sunt:
- SetPalette(Index,Color) - care permite modificarea intrării date de parametrul
Index (WORD), cu o culoare de cod Color (INTEGER). Atât
pentru Index, cât şi pentru Color pot fi utilizate constantele
simbolice predefinite în unitatea Graph.
- SetAllPalette(Palette) - înlocuieşte paleta curentă cu cea dată de parametrul
Palette. Parametrul este de tipul PaletteType şi trebuie să fi
fost încărcat corect cu codurile de culori.
În ambele situaţii, procedurile modifică automat culorilor desenului de pe
ecran, în concordanţă cu noua configuraţie a paletei curente. Procedurile pot fi
utilizate şi cu scopul de a produce efecte de culoare deosebite sau pentru a releva
treptat, prin modificări repetate, un desen ’ascuns’.
• Determinarea şi modificarea culorilor de fond şi de desen. Funcţiile şi
procedurile din această categorie sunt cel mai frecvent utilizate deoarece definesc
fondul şi culoarea de desen pe baza paletei curente.
-SetBkColor(Color) - procedură pentru selecţia culorii de fond. Color este
parametrul de tip WORD care precizează indexul culorii
dorite. Implicit, se utilizează indexul zero care corespunde, în
general, culorii negre;
- GetBkColor - funcţie fără parametri, de tipul WORD, care returnează inde-
xul culorii curente pentru fond;

109
Reprezentarea vizuală a datelor

- SetColor(Color) - procedură pentru selecţia culorii de desen. Color are


aceeaşi semnificaţie ca la procedura anterioară. Implicit, se
utilizează ultimul Index care, în general, corespunde culorii
alb;
- GetColor - funcţie similară cu GetBkColor pentru a obţine culoarea curentă
de desen.
Modificarea culorii de fond atrage după sine modificarea culorii pe ecran.
Culorile selectate, denumite culori curente, rămân active până la o nouă selecţie sau
până la ieşirea din program.

Exemplu:
6.9. Presupunând o placă EGA, secvenţa care urmează stabileşte un vizor cu
fond albastru în care desenează un dreptunghi umplut. După 2000 ms se refac
culorile anterioare.

Uses Graph,Crt;
VAR
ColorBk,ColorFg:Word;
……………………..
ColorBk:=GetBkColor;
ColorFg:=GetColor;
SetViewport(20,50,250,150,ClipOn);
SetColor(EGARed);
SetBkColor(EGABlue);
ClearViewport;
Bar(20,10,60,50);
Delay(2000);
SetBkColor(ColorBk);
SetColor(ColorFg);
. . . . . . . . . . . . . .

6.4.3 Desen prin puncte şi vectori


Desenul prin puncte este modul ’natural’ de realizare a imaginii video.
Biblioteca de subprograme grafice Pascal conţine pentru acest mod de lucru
procedura PutPixel(x,y,Color) şi funcţia de tipul WORD GetPixel(x, y).
Procedura PutPixel stabileşte culoarea Color de aprindere a pixelului de
coordonate (x,y) din vizorul curent, iar funcţia GetPixel întoarce indexul culorii cu
care este aprins un astfel de pixel. Pe baza subprogramelor, se pot construi desene
complexe, dar cu dificultăţile pe care le implică gestionarea mulţimii de pixeli care
compun imaginea video. Pentru a facilita realizarea desenelor, sistemul grafic
PASCAL implementează şi alte moduri de lucru, cum este desenul prin vectori. În
acest mod, pot fi trasate segmente de dreaptă, independente sau legate (linii frânte),
utilizând pentru forma liniilor stilurile şi culorile curente (setate). Culoarea de desen
şi culoarea fondului sunt curente, setate prin SetBkColor şi SetColor. Stilul de linie
este cel definit de procedura SetLineStyle,care se apelează astfel:
SetLineStyle(TipStil,Model,Grosime);

110
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Stilul definit rămâne valabil până la o nouă setare. Stilul implicit este linia
continuă normală. Parametrii, de tipul WORD, constante sau variabile, definesc
atributele liniei (stilul liniei):
- TipStil - defineşte tipul de linie din punctul de vedere al continuităţii.
Parametrul poate avea valorile 0-4 sau constanta simbolică
corespunzătoare;
- Grosime - precizează grosimea, în număr de pixeli şi poate avea valorile:
1 (linie normală - NormWidth) şi 2 (linie groasă - ThickWidth);
- Model - defineşte un stil de utilizator printr-un fragment de 16 pixeli. Un pixel
este aprins, dacă bitul corespunzător, din numărul dat de acest
parametru, este unu. De exemplu, dacă Model=$F053 (în binar 1111
0000 0101 0011) atunci se defineşte modelul •••• xxxx x•x• xx••, în care •
este pixel aprins.
Desenarea unui segment de dreaptă se face prin procedura
Line(x1,y1,x2,y2). Prin parametrii x1, y1, x2, y2, de tip INTEGER, se precizează
coordonatele capetelor segmentului de dreaptă. Coordonatele sunt relative la vizorul
curent şi se pot exprima ca variabile sau constante. Se desemnează numai porţiunea
de segment care se află în interiorul vizorului.

Exemplu:
6.10. Secvenţa care urmează:

SetViewport(20,20,150,100,ClipOn);
Line(10,10,150,150);

desenează un segment cu originea absolută în (30,30) şi extremitatea finală în


(170,120) din care se vede numai porţiunea de coordonate absolute (30,30),
(138,100) - figura 6.7.

Desenarea segmentelor de dreaptă cu procedura Line este incomodă, când


trebuie desenate linii frânte, datorită redundanţei de informaţii, având în vedere
coincidenţa punctului final al unui segment cu originea segmentului următor.
Desenarea liniilor frânte este facilitată de introducerea în sistemul grafic a
noţiunii de punct curent şi de existenţa unor rutine care desenează linii relativ la
acesta. Punctul curent, definit prin coordonatele sale în spaţiul ecran, este înţeles de
rutinele sistemului ca origine a următorului segment de dreaptă posibil de desenat.
Sistemul prevede rutine pentru aflarea şi modificarea poziţiei punctului curent,
precum şi reguli de comportare a rutinelor de trasare de linii, cu privire la
actualizarea acestuia. Locul punctului curent poate fi aflat cu ajutorul funcţiilor, fără
parametri, GetX şi GetY, care returnează abscisa şi ordonata absolută, ca numere
întregi.

111
Reprezentarea vizuală a datelor

(0,0) x
(20,20)

(30,30)

(138,100
• • (150,100)

• (170,120)
y
Fig. 6.7. Desenarea liniilor relativ la vizor

Punctul curent poate fi mutat într-un alt loc din spaţiul fizic, prin procedurile
MoveTo şi MoveRel care se apelează sub forma: MoveTo(x,y); MoveRel(x,y).
Procedura MoveTo consideră valorile (x,y) drept coordonate relative la vizorul curent
şi calculează coordonatele absolute (xe,ye) ale punctului curent prin relaţiile: xe=v1+x;
ye=v3+y, unde (v1,v3) sunt coordonatele originii vizorului. Procedura MoveRel
deplasează punctul curent relativ la poziţia sa anterioară, adică xe=GetX+x;
ye=GetY+y, în care x,y apar ca deplasări. La intrarea în regim grafic, la definirea
şi/sau ştergerea vizoarelor, precum şi la schimbarea modului grafic, punctul grafic
curent este nedefinit. Este sarcina programului să iniţializeze punctul curent prin
procedura MoveTo. Toate procedurile de desenare de linii şi scriere de text
deplasează poziţia punctului curent în ultimul punct utilizat.
Desenarea liniilor care îşi au originea în punctul curent se realizează cu
ajutorul procedurilor LineTo(x,y); LineRel(x,y). Pentru LineTo, coordonatele
extremităţii liniei sunt relative la vizorul curent şi deci xe=v1+x, ye=v3+y, unde (xe,ye)
sunt coordonatele absolute ale extremităţii liniei relativ la punctul curent xe=GetX+x;
ye=GetY+y. După trasare, punctul curent este deplasat în punctul xc=xe, yc=ye. Dacă
la trasare a fost necesară operaţia de clipping, punctul final al segmentului vizibil este
recalculat ca (x ~ , y ~ ) , însă punctul curent este mutat tot în (xe,ye). Se evită astfel
e e
deformarea desenelor.

Exemplu:
6.11. Dacă se consideră linia frântă P1, P2, P3, P4 din figura 6.8, atunci
secvenţele de instrucţiuni de trasare, având în vedere coordonate relative la vizor şi
relative la punctul curent, pot fi scrise astfel:

Relativ la vizor Relativ la punctul curent


MoveTo(50,60) MoveTo(50,60)
LineTo(70,40) LineRel(20,-20)
LineTo(85,45) LineRel(15,5)
LineTo(140,30) LineRel(25,-15)

112
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

(0,0) x
(20,10)
(70,40) (110,30)
[20,-20] [25,-15]


(50,60) •
(85,45)
[15,5]
y
Fig. 6.8. Desenarea segmentelor relativ la vizor şi la punctul curent

În plus, trebuie menţionat faptul că desenarea unei linii simple sau frânte
poate fi făcută astfel încât să distrugă sau să conserve, într-un anumit sens, culorile
pixelilor care se suprapun. Comportamentul pixelilor depinde de modul de scriere a
noii imagini în raport cu imaginea existentă pe ecran (setarea modului Write);

6.4.4 Raportul de aspect

În realizarea desenelor prin segmente apare un fenomen de deformare care


poate să fie deranjant (un pătrat apare ca dreptunghi, un cerc ca o elipsă etc).
Fenomenul se datorează formei pixelilor care, cu excepţia unor plăci de foarte mare
rezoluţie, nu sunt de formă pătratică, ci dreptunghiulară. Aceasta face să se aplice,
implicit, unităţi diferite de măsură pe cele două axe. Deformarea poate fi atenuată sau
chiar eliminată, dacă în realizarea desenului se aplică un anumit raport între
lungimile segmentelor de pe orizontală şi ale celor de pe verticală.
Dacă se notează cu Lpx lungimea unui pixel, în sensul axei ox şi cu Ipy
înălţimea sa, în sensul axei oy, atunci raportul Ra=Ipy/Lpx este denumit raport de
aspect. El poate fi utilizat pentru a determina, în mod adecvat, lungimile segmentelor
(în pixeli) care se desenează. Pentru a obţine dimensiunile pixelului plăcii grafice se
utilizează procedura:
GetAspectRatio(Lpx, Ipy);
în care cei doi parametri sunt variabile de tipul WORD.
Dacă se notează cu AB un segment vertical de lungime n pixeli şi cu CD un
segment orizontal, de lungime m pixeli, atunci, pentru ca raportul AB/CD să fie k,
trebuie ca m, dacă se cunoaşte n (respectiv n, dacă se cunoaşte m), să se determine
cu relaţiile:
m = Round(Ra * n k ) 

n = Round(m * k Ra ) 

113
Reprezentarea vizuală a datelor

Exemplu:
6.12. Desenarea unui pătrat, cu latura orizontală CD de m=50 pixeli şi vârful
stânga sus în (30,60), pe un vizor egal cu întregul ecran. Deoarece k=1, se poate
utiliza secvenţa:

GetAspectRatio(Lpx,Ipy);
Ra:=Ipy/Lpx;
n=Round(50/Ra);
MoveTo(30,60);
LineTo(80,60);
LineTo(80,60+n);
LineTo(30,60+n);
LineTo(30,60);

Există, de asemenea, rutina SetAspectRatio care permite definirea software a


raportului de aspect pentru a fi utilizat de rutinele sistemului grafic. Programatorul
care utilizează rutinele sistemului pentru desenare de figuri poate să reducă
fenomenul de deformare, modificând repetat, eventual interactiv, raportul de aspect.
Apelul acestei proceduri este similar procedurii GetAspectRatio.

6.4.5 Scrierea textelor în modul grafic

În mod uzual, imaginile de pe ecran sunt însoţite de texte explicative. Având


în vedere că, de multe ori, lucrul în modul text nu satisface cerinţele de mărime,
formă, culoare şi direcţie de scriere, în unit-ul Graph au fost introduse primitive
specifice prelucrării textelor. Unitatea conţine două grupe de rutine: pentru definirea
atributelor textelor şi pentru scrierea propriu-zisă.
• Rutinele de declarare a atributelor textelor trebuie apelate ori de câte ori
este necesar să se definească alte caracteristici de scriere decât cele curente.
Atributele astfel definite (setate) rămân active până la o nouă setare sau până la
sfârşitul programului. Rutinele de declarare sunt proiectate pentru a putea trata
următoarele atribute de scriere:
a) Stilul de caractere, adică forma caracterelor imprimabile. Sistemul
recunoaşte cinci stiluri (fonturi), codificate 0-4, cărora li s-au asociat constante
simbolice;
b) Culoarea: caracterele se afişează pe fondul şi culoarea de desen curentă;
c) Mărimea se referă la lăţimea şi înălţimea caracterelor, caracteristici care se
definesc pentru toate fonturile, cu excepţia stilului DefaultFont, la care mărimea nu
poate fi modificată. Fonturile cu mărime de caracter modificabilă definesc
caracterele prin vectori (caractere vectoriale). Mărimea se poate referi la spaţiul
dreptunghiular pentru un caracter, în sensul micşorării sau creşterii proporţionale a
acestuia pentru ambele dimensiuni sau pentru fiecare dimensiune în parte
(dimensiuni de utilizator).

114
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

d) Direcţia de scriere poate fi: orizontală sau verticală. Pentru direcţia orizontal
(constanta simbolică HorizDir) scrierea se face de la stânga spre dreapta, iar pentru
direcţia vertical (VertDir), de jos în sus;
e) Alinierea: precizează, pentru fiecare din cele două direcţii, modul de
amplasare a textului în raport cu punctul de scriere (punct de referinţă).
Pentru definirea atributelor se pot utiliza procedurile:
SetTextStyle (Font, Directie, Marime);
SetTextJustify (FtHorizDir, FtVertDir);
SetUserCharSize (MultX, DivX, MultY, DivY);
Parametrul de mărime, la procedura SetTextStyle, poate lua valori naturale
mai mari sau egale cu zero şi precizează de câte ori să fie mărite, proporţional,
caracterele fontului ales faţă de dimensiunea normală a acestora. Pentru definirea
dimensiunilor de utilizator (independente pe cele două direcţii), valoare zero a
parametrului Marime declară intenţia programatorului de a apela procedura
SetUserCharSize. La această procedură, se utilizează ideea că dimensiunea se
modifică prin înmulţire cu un raport. Raportul MultX/DivX va fi utilizat pentru a
modifica lăţimea caracterelor, iar MultY/DivY pentru modificarea înălţimii. Atunci
când raportul este unu, dimensiunea pe direcţia respectivă va rămâne nemodificată.
La procedura de definire a alinierii se cere să se precizeze modul de aliniere
a textului pe ambele direcţii, chiar dacă scrierea utilizează numai una din ele.

Exemple:
6.13. Se defineşte fontul SmallFont, cu mărire proporţională, de 3 ori şi cu
afişare centrată pe orizontală:

SetTextJustify(CenterText,CenterText);
SetTextStyle(SmallFont,HorizDir,3);

6.14. Se stabileşte o creştere a dimensiunii caracterelor fontului


SansSerifFont numai în înălţime, de 1,5 ori; scrierea se face pe direcţia verticală, cu
aliniere la partea de jos:

SetTextStyle(SansSerifFont,VerDir,0);
SetUserCharSize(1,1,3,2);
SetTextJustify(LeftText,BottomText);

Dacă atributele de scriere au fost deja definite, adică au devenit atribute


curente, programatorul poate utiliza funcţiile întregi (de tipul WORD):
TextHeight(Text), TextWidth(Text), pentru a afla înălţimea, respectiv lăţimea, în
număr de pixeli, necesare pentru a afişa întregul text dat de parametrul Text. Pe baza
acestor informaţii, se poate alege punctul de referinţă astfel încât scrierea textului să
fie posibilă (totul să înceapă în vizorul de scriere) sau acesta să fie mai bine amplasat,
pe direcţia curentă.

115
Reprezentarea vizuală a datelor

• Pentru scrierea efectivă a textelor se utilizează una din următoarele două


proceduri:
OutText(Text);
OutTextXY(x,y,Text);
Textul de scris (parametrul Text) se poate prezenta ca o constantă, variabilă
sau expresie de tipul STRING. Scrierea se face în raport cu punctul de referinţă cu
coordonate (x,y) sau “la cursor”, dacă nu se defineşte explicit un astfel de punct.
După scriere, cursorul se găseşte pe poziţia următoare celei utilizate pentru scrierea
ultimului caracter al textului, dacă scrierea s-a făcut prin procedura OutText sau
rămâne nemodificat, atunci când s-a utilizat procedura OutTextXY.
Dacă există definit vizor curent, cu acceptarea clipping-ului, textul este
decupat la limitele vizorului. Nu se aplică procedeul de clipping dacă fontul este
DefaultFont şi comportarea rutinei de scriere este nedefinită când survine o depăşire
a vizorului. În plus, trebuie menţionat că primitivele de afişare ţin cont de setarea
modului de scriere.

Exemplu:
6.15. Se scrie orizontal textul "TURBO PASCAL", de 4 ori, cu cele patru
fonturi vectoriale. Fiecare rând, centrat pe linia verticală a centrului ecranului, se
scrie cu o altă culoare.
Clear Device;
SetTextJustify(CenterText,CenterText);
y:=20;
For i:=1 to 4 do
Begin
SetColor (i);
SetTextStyle(i,HorizDir,2+i);
Y:=y+TextHeight('TURBO PASCAL')+8;
OutTextXY(GetMaxX Div 2,Y,'TURBO PASCAL');
End;

6.4.6 Primitive pentru figuri

Pentru a facilita realizarea imaginilor, sistemul grafic cuprinde câteva


primitive pentru desenarea unor figuri, majoritatea în plan. Unele din aceste
proceduri umplu figura desenată cu un anumit model de culoare şi haşură. Toate
rutinele au fost concepute să lucreze în condiţiile existenţei unei vizor curent, adică
în coordonate relative la vizor şi să aplice clipping-ul, dacă acesta a fost acceptat.
Câteva din ele ţin seama de modul de scriere selectat. Ca şi rutinele prezentate
anterior, primitivele pentru figuri utilizează, pentru contur şi linii, culorile şi
atributele curente. Dacă o primitivă umple figura, atunci ea foloseşte un model de
haşurare şi o anumită culoare. Modelul de haşurare poate fi ales dintr-o mulţime
predefinită sau poate fi definit de utilizator.

116
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Pentru a preciza stilul şi culoarea de umplere se apelează procedura:


SetFillStyle(TipStil,Culoare);
în care parametrul Culoare este un index în paleta curentă, iar TipStil o constantă
între 0-12. Dacă TipStil este 12 (UserFill), atunci el trebuie să fie definit ca un model
binar pe 8 byte (un vector) care este interpretat de rutinele respective ca o matrice de
8x8 biţi. Matricea este traversată circular pe linii şi pixelul curent este aprins, la
culoarea definită prin parametrul culoare, dacă bitul din model este unu. În plus, tipul
definit rămâne ca model curent.

Exemplu:
6.16.

CONST
MyPattern:Array[1..8]of Byte=($01,$01,$FF,$01,$01,$FF,$01,$01);
. . . . . . . . . .
SetFillStyle(MyPattern,3);

În figura 6.9 se prezintă toate tipurile de haşuri considerând numai culorile


alb şi negru.

Empty Solid Line LtSlash Slash BkSlash


•••••••
•••••••
•••••••
•••••••

Hatch XHatch Interline Wide Dot Close Dot User

Fig. 6.9 Tipuri de haşuri

Principalele primitive pentru figuri sunt prezentate în continuare.


a) Proceduri pentru dreptunghiuri:
Rectangle (x1,y1,x2,y2);
Bar(x1,y1,x2,y2);
Prima procedură desenează un dreptunghi simplu, neumplut, iar a doua un
dreptunghi umplut. Procedura Rectangle ţine seama de setarea modului de scriere
(WriteMode). Parametrii de apel sunt coordonatele întregi ale colţului stânga-sus
(x1,y1) şi dreapta jos (x2,y2) ale dreptunghiului.
b) Proceduri pentru linii poligonale şi poligoane oarecare:

117
Reprezentarea vizuală a datelor

DrawPoly(NumarVarfuri,CoordonateVarfuri);

FillPoly(NumarVarfuri,CoordonateVarfuri);
Procedurile sunt similare perechii de proceduri pentru desenarea
dreptunghiurilor. Coordonatele punctelor liniei poligonale trebuie să fie declarate ca
o variabilă de tip masiv, la care tipul de bază trebuie să fie PointType, adică un
articol cu două câmpuri: X şi Y. Dacă se doreşte trasarea unui poligon, atunci faţă de
linia poligonală cu n vârfuri trebuie declarate n+1 vârfuri şi coordonatele ultimului
vârf trebuie să coincidă cu cele ale primului. Procedura FillPoly se aplică numai
pentru poligoane, iar interiorul acestora este umplut cu haşura şi culoarea cerute.

Exemplu:
6.17.
CONST
Pentagon: Array[1…6]of PointType=
((x:20;y:45),(x:50;y:25),(x:100;y:10),
(x:120;y:40),(x:80;y:60),(x:20;y:45));
FillPoly (6,Pentagon);

c) Proceduri pentru arce, cercuri şi elipse. Procedurile din această categorie


desenează marginile figurilor utilizând culoarea curentă, stabilită de SetColor. Cele
care haşurează sau umplu figura desenată utilizează atributele date de SetFillStyle.
Procedurile nu ţin seama de setarea modului de scriere.
Procedurile care desenează arce utilizează, ca parametri, unghiuri în grade.
Trebuie avut în vedere că acestea sunt considerate în sens trigonometric (sens invers
acelor de ceasornic, cu 0 grade la ora 3, 90 grade la ora 12, 180 grade la ora 9 etc.).
De asemenea, raza cercului şi razele elipselor (raza orizontală - RazaX şi raza
verticală - RazaY) se dau în număr de pixeli. Coordonatele centrului figurii
constituie, de asemenea, un parametru (x,y). Pentru asigurarea circularităţii pe ecran,
procedurile utilizează raportul de aspect al plăcii, eventual setat soft, prin
SetAspectRatio.
Procedurile de bază sunt:
Arc
(x,y,UnghiStart,UnghiFinal,Raza);
PieSlice
(x,y,UnghiStart,UnghiFinal,Raza);
Circle (x,y,Raza);
Ellipse
((x,y,UnghiStart,UnghiFinal,RazaX,RazaY);
Sector
(x,y,UnghiStart,UnghiFinal,Raza);
FillEllipse (x,y,RazaX,RazaY);

118
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Procedura Circle desenează un cerc (neumplut), iar procedura Arc desenează


un arc care, la limită, poate fi un cerc, depinzând de alegerea unghiurilor. Procedura
PieSlice desenează un sector umplut care poate fi, la limită, un cerc umplut.
Aproape similar este cazul elipselor. Procedura Ellipse desenează un arc de
elipsă sau o elipsă (neumplută), procedura Sector desenează un sector de elipsă sau o
elipsă umplută, iar procedura FillEllipse desenează o elipsă umplută.

Exemplu:
6.18.
Arc (150,100,0,270,50);
PieSlice (GetMaxX Div 2,GetMaxY Div 2,0,360,GetMaxX Div 4);
FillEllipse (150,150,300,75).

Trebuie remarcat şi faptul că sistemul grafic posedă o procedură de


interogare asupra centrului punctului de start şi punctului final care au fost utilizate
în timpul execuţiei uneia din procedurile: Arc, PieSlice, Ellipse, Sector. Aceste
informaţii sunt furnizate prin apelul procedurii:
GetArcCoords(ArcCoords);
în care parametrul ArcCoords are tipul ArcCoordsType definit public în unitatea
Graph astfel:
Type
ArcCoords=Record
x,y:Integer; {Punct de centru}
xStart,yStart:Integer; {Punct de inceput}
xEnd,yEnd:Integer; {Punct de sfarsit}
End;
Procedura este utilă pentru racordarea arcelor cu segmente de dreaptă sau
alte arce.

d) Procedura pentru paralelipiped dreptunghic


Bar3D (x1,y1,x2,y2,Grosime,Top);
Procedura permite desenarea paralelipipedelor dreptunghice colorate
(umplute). Parametrii au următoarele semnificaţii:
- (x1,y1),(x2,y2) sunt coordonatele colţurilor stânga-sus, dreapta-jos ale feţei
(dreptunghiului din faţă);
- Grosime, de tipul WORD, indică grosimea paralelipipedului;
- Top, de tip BOOLEAN, arată că faţa superioară trebuie să fie vizibilă (valoare
True sau constanta TopOn) sau să fie ascunsă (valoare False sau TopOff). Parametrul
permite realizarea paralelipipedelor suprapuse, caz în care trebuie să fie vizibilă
numai faţa superioară a ultimului paralelipiped.
Procedura se poate utiliza pentru desenarea histogramelor prin bare (grafice
din bare) care prezintă, comparativ, valorile unei (unor) caracteristici (mărimi).

119
Reprezentarea vizuală a datelor

6.4.7 Elemente de animaţie


Animaţia poate fi considerată ca tehnică de realizare a mişcării obiectelor
grafice pe ecran. Ea presupune refacerea imaginii video, de peste 30 de ori pe
secundă, cu obiectele în diferite poziţii, astfel încât să se asigure persistenţa imaginii
pe retina ochiului. Reuşita în realizarea animaţiei este strâns condiţionată de resursele
calculatorului, cum sunt: viteza microprocesorului, existenţa coprocesorului
matematic, numărul de pagini ale memoriei video etc.
1. Aspecte generale. Animaţia este o tehnică complexă prin multitudinea de
probleme pe care le pune realizatorului de programe.
• În primul rând, programul trebuie să ia în considerare comportarea
obiectului de animat în “mediu”. În cazurile simple, imaginea pe ecran se reduce la
obiectul în mişcare, adică nu există mediu ambiant. Uzual, însă, pe ecran există o
imagine “peste” care trebuie să se deplaseze obiectul. Această imagine este, pentru
obiectul animat, un mediu ambiant, pe care acesta trebuie să îl conserve. Rezultă de
aici că imaginea obiectului, în diferitele sale poziţii, trebuie scrisă peste imaginea
mediu într-un anumit mod. De regulă, regimul grafic al unui calculator acceptă cel
puţin două moduri de scriere (WriteMode): unul care distruge imaginea veche (modul
PUT) şi altul care suprapune noua imagine peste cea veche (modul XOR). Trebuie
remarcat că realizarea a două operaţii logice XOR (sau exclusiv) succesive ale unei
imagini peste acelaşi mediu înseamnă, de fapt, ştergerea noii imagini din poziţia
respectivă.
• În al doilea rând, programul creşte în complexitate dacă obiectul animat nu
rămâne identic cu el însuşi în toate poziţiile sale de mişcare. Este mai simplu de
animat un obiect pentru care imaginea formei sale se conservă, deoarece această
imagine poate fi realizată o singură dată, memorată şi, apoi, afişată în diferite poziţii.
Dacă obiectul animat îşi schimbă forma de la o poziţie la alta, aşa cum este cazul
general, atunci imaginea sa trebuie refăcută continuu. În acest caz, consumul de timp
este mult mai mare şi animaţia reuşeşte numai în prezenţa unor resurse suplimentare
ale calculatorului care să asigure viteza necesară de afişare.
• În al treilea rând, obiectul în mişcare poate să aibă un anumit
comportament la atingerea limitelor vizorului de afişare. Problema este simplă atunci
când obiectul poate ieşi şi intra în vizor, deoarece se rezolvă prin tehnica
clipping-ului. Dacă obiectul nu poate părăsi vizorul sau atingerea limitelor acestuia îi
imprimă o anumită traiectorie, atunci programul trebuie să prevadă teste pentru a
sesiza situaţia şi să includă relaţii adecvate de calcul.
• În sfârşit, probleme complexe ridică situaţiile în care mediul însuşi poate
modifica forma şi/sau traiectoria obiectului sau când trebuie animate mai multe
obiecte concomitent. Din punct de vedere algoritmic, orice program de animaţie
trebuie să aibă încorporaţi următorii paşi:
a) Se afişează pe ecran imaginea, cu obiectul în poziţia a;
b) Se construieşte imaginea cu obiectul într-o poziţie nouă, b (dacă este
cazul);
c) Se şterge obiectul din poziţia a (eventual, prin afişarea a doua oară a
imaginii acestuia în a, cu scriere XOR);

120
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

d) Se atribuie lui a valoarea lui b şi se reia, dacă este cazul, de la punctul a.

2. Facilităţi pentru realizarea animaţiei în PASCAL. Sistemul grafic Turbo


Pascal prevede câteva rutine care facilitează realizarea animaţiei.
• Moduri de scriere. Pot fi definite moduri de determinare a culorii unui
pixel din noua imagine, în funcţie de culoarea curentă de scriere şi culoarea pe care
acelaşi pixel o are în imaginea existentă pe ecran (culoarea veche). Având în vedere
că noua imagine se constituie în memoria video, peste cea existentă, sunt posibile
diferite operaţii logice între biţii care determină culoarea unui pixel şi noile valori
dorite. Aceste operaţii sunt denumite moduri de scriere şi se definesc prin procedura:
SetWriteMode(Mode);
unde parametrul Mode poate lua valorile întregi între 0-4. În mod implicit se aplică
NormalPut, când pixelul capătă culoarea curentă, indiferent de culoarea pe care o
avea (imaginea veche dispare). Cel mai frecvent se utilizează XOR, care determină
culorile după o funcţie ”sau exclusiv”. Se are în vedere că specificarea culorilor se
face prin index în paletă şi, deci, rezultatul este tot o astfel de valoare.
Din exemplul următor se poate urmări şi afirmaţia că (A XOR B) XOR B = A.

Culoare veche Culoare curentă Culoare nouă (A XOR B)


(A) (B) (A XOR B) XOR B
0011 (3) 0101 (5) 0110 (6) 0011 (3)
1011 (11) 0010 (2) 1001 (9) 1011 (11)
1111 (15) 1101 (13) 0010 (2) 1111 (15)
0000 (0) 1111 (15) 1111 (15) 0000 (0)

Aşa după cum s-a mai remarcat, nu toate procedurile grafice ţin seama de
setarea modului de scriere. Rutinele care determină culoarea nouă după regulile
descrise sunt:
Line LineTo LineRel OutTextXY
DrawPoly Rectangle OutText
• Salvarea şi restaurarea unei imagini. Sistemul prevede salvarea, din
memoria ecran, a unei imagini încadrată într-o zonă dreptunghiulară, într-un buffer
definit de utilizator în memoria internă, de regulă în yona heap. Se utilizează
procedura:
GetImage(X1,Y1,X2,Y2,Buffer);
în care (X1,Y1), (X2,Y2) dau coorodnatele colţurilor stânga-sus şi dreapta-jos ale
drptunghiului care defineşte imaginea de salvat. Variabila Buffer trebuie să aibă un
număr acoperitor de cuvinte pentru imagine, plus două cuvinte, (la începutul
acesteia) în care procedura înscrie lăţimea şi lungimea dreptunghiului de imagine.
Mărimea variabilei Buffer este returnată de funcţia ImageSize(X1,Y1,X2,Y2) de
tipul WORD şi nu poate depăşi 64KB. Imaginea salvată în buffer poate să fie afişată
din nou utilizând procedura:
PutImage (X1,Y1,Buffer,WriteMode);

121
Reprezentarea vizuală a datelor

În acest apel, (X1,Y1) sunt coordonatele punctului în care se va suprapune


colţul stânga-sus al imaginii, iar WriteMode defineşte modul de scriere a imaginii şi
are una din valorile definite pentru procedura SetWriteMode.

Exemplu:
6.19. Se desenează un cerc umplut, cu centrul în (50,50) şi raza de 40 pixeli.
Imaginea se salvează şi apoi este afişată (restaurată) în punctul (200,50), fiind ştearsă
din vechea sa poziţie. Imaginea nouă conservă imaginea existentă pe ecran.

. . . . . . .
Uses CRT,Graph
. . . . . . .
p:Pointer;
Sz:Word;
. . . . . . . . .
PieSlice(50,50,0,360,40);
Sz:=ImageSize(0,0,50,100);
GetMem(p,Sz);
GetImage(0,0,50,100,p^);
Delay(2000);
PutImage(0,0,p^,XORPut);
PutImage(200,50,p^,XORPut);
. . . . . . . . .

• Pagina activă şi pagina video. Pentru modurile grafice care suportă mai
multe pagini (EGA, VGA etc.), imaginea se poate constitui în pagina activă, în timp
ce pe ecran este afişată pagina video (vezi §6.2). Pentru selecţia paginilor se
utilizează procedurile:
SetActivePage(NumarPagina);
SetVisualPage(NumarPagina);
în care parametrul NumarPagina, de tipul WORD, poate lua valori naturale,
începând cu zero. Facilitatea de paginare în memoria video asigură o creştere de
viteză şi uşurinţă în realizarea imaginilor, mai ales, în animaţie.

122
TEHNICI SPECIALE
ÎN PASCAL

Pentru practica proiectării, punerii la punct şi dezvoltării programelor,


mediul integrat Turbo Pascal oferă programatorului o serie de tehnici de lucru
speciale şi instrumente puternice care să îl asiste în acest proces complex.

7.1 Tehnici de reacoperire în construirea


programelor complexe

Programele complexe, de dimensiuni mari (uneori peste spaţiul de memorie


disponibil), pot fi concepute modularizat, într-o structură arborescentă, în care se
disting un modul monitor şi mai multe module operaţionale. Modulul monitor este
rezident în memorie pe parcursul execuţiei programului şi apelează succesiv
modulele operaţionale care pot sau nu să fie prezente simultan în memoria principală.
Reacoperirea este tehnica prin care două sau mai multe unităţi se încarcă la
aceeaşi adresă de memorie, evident nu simultan. Construirea structurilor de
reacoperire este posibilă numai pentru modulele constituite ca unităţi ale
utilizatorului (unităţile standard nu pot face obiectul reacoperirii).
În general, un program realizat cu unităţi poate fi construit ca program
executabil în două forme: cu structură liniară, respectiv cu structură arborescentă. La
programele cu structură liniară, spaţiul de memorie necesar este dat de suma
lungimilor tuturor unităţilor, la care se adaugă lungimea programului principal.
Programul, cu toate componentele sale, va fi încărcat complet în memorie şi va
rămâne rezident pe tot parcursul execuţiei. La programele cu structură arborescentă
se poate realiza o structură cu reacoperire (overlay), în care se disting două tipuri de
componente: componenta rezidentă (rădăcina arborelui), care rămâne în memorie pe
tot parcursul execuţiei programului; componentele reacoperibile (ramurile arborelui),
care folosesc pe rând aceeaşi arie de memorie, în care se încarcă succesiv pe măsura
referirii lor, cu eventuala suprascriere (reacoperire) a celor încărcate anterior.
Componentele reacoperibile sunt formate din unităţi ale utilizatorului.
Pe exemplul din figura 7.1, presupunând că partea rezidentă este formată din
programul principal şi unitatea C, iar celelalte unităţi se reacoperă între ele, necesarul

123
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

de memorie internă este de 55 Ko, dat de suma lungimilor componentei rezidente şi a


celei mai mari ramuri.

Programul principal şi
unităţile standard (30 KO)

Unitatea C (5 KO)
Unitatea A (10 KO)

Unitatea B (20 KO)

Fig. 7.1 Exemplu de structură cu reacoperire

Pentru construirea şi gestiunea structurilor cu reacoperire, mediul de


dezvoltare Turbo Pascal are o componentă specializată (Overlay Manager), ale cărei
proceduri şi funcţii sunt reunite în unitatea Overlay. Folosirea acestei componente
presupune:
a) Pe planul declaraţiilor:
• Includerea unităţii Overlay în clauza USES a programului principal, cu
obligaţia de a fi prima referită.
• Introducerea directivelor {$O nume_unitate}, după clauza USES. Fiecare
directivă nominalizează unitatea care va fi inclusă în structura de reacoperire (se
generează instrucţiuni care vor include unitatea în fişierul Overlay).
• Fiecare unitate care va fi inclusă în reacoperire se va compila cu directiva
{$O+}. Prezenţa acesteia nu realizează includerea unităţii în reacoperire, ci doar
semnalează compilatorului să asigure o structură a codului obiect, care să permită
eventuala sa includere într-o astfel de structură. În concluzie, o aceeaşi versiune de
unitate, compilată cu {$O+}, va putea fi folosită atât în structuri cu reacoperire, cât şi
în structuri liniare.
• Pentru asigurarea generării tuturor apelurilor după modelul FAR (cu adresă
de segment şi cu deplasare), se va folosi directiva de compilare {$F+}, atât în
programul principal, cât şi în unităţile care urmează a fi incluse în reacoperire.
b) Pe planul instrucţiunilor executabile, folosirea tehnicii de reacoperire
presupune apelul unor proceduri şi funcţii ale unităţii Overlay, în cadrul programului
principal (în componenta rezidentă).
• În primul rând este necesară iniţierea programului de gestiune a reacoperirii
segmentelor, înaintea execuţiei altor instrucţiuni (inclusiv a celor de iniţializare din
unităţi). Acest lucru se realizează cu procedura OvrInit, definită astfel:
OvrInit(nume_fisier:STRING), unde nume_fisier este numele fişierului, cu
extensia implicită .OVR, în care sistemul încarcă unităţile incluse în structura de
reacoperire.

124
Tehnici speciale în Pascal

Pentru exemplul din figura 7.1, pot fi concepute, principial, structurile din
figura 7.2. În cazul în care una dintre unităţile apelate conţine parte de iniţializare,
datorită restricţiei ca procedura OvrInit să se execute prima, corelat cu faptul că
partea de iniţializare a unei unităţi se lansează în execuţie cu prioritate, va trebui
construită o unitate separată, care să conţină în partea de iniţializare apelul
procedurii OvrInit şi care să fie invocată prima în clauza USES a programului
principal (această unitate va conţine USES Overlay).

{$O+,F+} {$O+,F+} {$O+,F+} (* Optional *)


UNIT A; UNIT B; UNIT C;
Interface Interface Interface
……………. ……………. …………….
Implementation Implementation Implementation
……………. ……………. …………….
END. END. END.

a) Unităţile

{$O+,F+}
PROGRAM PP; {Apelatorul}
USES Overlay, A,B,C
{$O A}
{$O B}
……………….
BEGIN
OvrInit(‘ALFA.OVR’);
……………….
END.
b) Programul

Fig. 7.2 Exemplu de principiu de realizare a unei structuri cu reacoperire

Pe parcursul execuţiei programului, folosirea tehnicii de reacoperire se


traduce în încărcarea succesivă a unităţilor, pe măsura referirii lor, într-un buffer al
structurii de reacoperire, căruia i se asociază memorie la începutul zonei heap. La
apelul procedurii OvrInit, mărimea buffer-ului de reacoperire este setată implicit la
mărimea celei mai mari unităţi incluse în reacoperire, la care se adaugă o zonă
suplimentară pentru informaţii de interfaţă. Dimensiunea curentă a buffer-ului de
reacoperire în număr de octeţi poate fi determinată, în orice moment pe parcursul
execuţiei programului, prin apelul funcţiei OvrGetBuf, definită astfel:
OvrGetBuf:Longint.
Referitor la mecanismul de folosire a buffer-ului, trebuie menţionat că o
unitate deja încărcată va fi ştearsă (suprascrisă), numai dacă nu există suficient spaţiu
liber în restul buffer-ului pentru a încărca următoarea unitate. Dacă există spaţiu

125
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

suficient, noua unitate va fi încărcată, evitând reluarea încărcării unora dintre unităţi
de fiecare dată când sunt referite.
Dacă utilizatorul doreşte să mărească dimensiunea buffer-ului, tocmai pentru
a rezolva asemenea probleme, se poate folosi procedura OvrSetBuf, definită astfel:
OvrSetBuf(bufsize:Longint), unde bufsize precizează noua mărime a buffer-ului,
care trebuie să fie mai mare decât cea implicită, fără a depăşi valoarea returnată de
funcţia MemAvail: bufsize<=MemAvail+OvrGetBuf (MemAvail returnează volumul
total de memorie nealocată în heap). Procedura OvrClearBuf eliberează buffer-ul,
pentru utilizări în alte scopuri.
Pentru a elimina repetarea operaţiilor de I/E necesare încărcării unităţilor din
fişierul asociat structurii de reacoperire, este posibilă folosirea memoriei EMS
(Expanded Memory System), prin apelul procedurii OvrInitEMS (fără parametri). În
acest caz, se va asigura încărcarea fişierului în memoria EMS (dacă aceasta are spaţiu
suficient), preluarea unităţilor în buffer-ul structurii de reacoperire realizân-du-se
foarte rapid, direct din această memorie, fără operaţii de I/E. Dacă nu există spaţiu
suficient, procedura este inefectivă.
Execuţia procedurilor şi funcţiilor de gestiune a structurilor de reacoperire se
poate încheia cu succes sau cu eroare, situaţie care poate fi determinată pe baza
folosirii variabilei predefinite OvrResult. În unitatea Overlay sunt declarate mai
multe constante simbolice asociate unor valori ale variabilei OvrResult (anexa 5).

7.2 Tehnica gestiunii proceselor

În cele ce urmează, prin proces se desemnează un program executabil. În


cazul unor sisteme de programe, utilizatorul are de ales între două variante de lansare
în lucru a programelor componente:
- lansare independentă, caz în care intercondiţionările dintre programe se
rezolvă prin ordinea de execuţie a acestora. Întrucât procesele nu pot comunica între
ele, eventuale informaţii privind modul lor de încheiere vor fi preluate şi prelucrate
de sistemul de operare.
- lansare din interiorul altor programe, stabilindu-se raporturi de tipul
proces părinte - proces fiu. Pentru sistemele de programe se poate constitui, la limită,
un singur proces părinte, cu rol de monitor, care lansează succesiv în execuţie
procesele fiu. Programul monitor poate prelucra informaţiile referitoare la modul de
încheiere a fiecărui proces fiu şi poate lua decizii privind modul de continuare.
Unitatea Dos oferă posibilitatea implementării acestei soluţii prin intermediul unor
proceduri şi funcţii specifice.
• Lansarea în execuţie a unui proces fiu se poate realiza cu procedura al
cărei antet este: Exec(specif,parametru:STRING). Procedura încarcă în memorie şi
lansează în execuţie programul al cărui format executabil se găseşte în fişierul cu

126
Tehnici speciale în Pascal

specificatorul specif. Argumentul parametru conţine şirul de caractere corespunzând


eventualilor parametri daţi prin linia de comandă la lansarea acestui program.
Întrucât este necesară asigurarea de spaţiu în memorie şi pentru procesul fiu, în
programul corespunzând procesului părinte trebuie să se reducă dimensiunea zonei
heap, folosind directiva $M.
• Salvarea vectorilor de întrerupere se realizează prin procedura
SwapVectors. Procedura este destinată asigurării independenţei dintre procesul
părinte şi un proces fiu în folosirea unor rutine de întrerupere instalate de către
celălalt proces. Se apelează înainte şi după fiecare apel al procedurii Exec şi schimbă
între ei vectorii de întrerupere curenţi cu pointerii SaveIntxx din unitatea System.
• Preluarea codului de retur al unui proces fiu se poate realiza prin funcţia:
DosExitCode:WORD. Codul de retur al procesului fiu poate fi poziţionat prin
parametrul folosit la apelul rutinei Halt.

Exemplu:
7.1. Se consideră un program director (Monitor) care lansează în execuţie trei
programe, în cadrul unei aplicaţii multifunţionale. Cele trei programe sunt memorate
în fişierele Fis1.EXE, Fis2.EXE, Fis3.EXE.

PROGRAM Monitor;
{$M 2048,0,0}
USES CRT;
VAR
c:CHAR;
BEGIN
REPEAT
ClrScr;
WriteLn(’ Functiile realizate de program sunt:’);
WriteLn(’ ’:5,’1 - Semnificatie functia 1’);
WriteLn(’ ’:5,’2 - Semnificatie functia 2);
WriteLn(’ ’:5,’3 - Semnificatie functia 3’);
WriteLn(’ ’:5,’4 - STOP);
Write (’Tastati functia dorita [1-4] : ’); Readln( c );
CASE c OF
’1’: Exec(’Fis1’,’’);
’2’: Exec(’Fis2’,’’);
’3’: Exec(’Fis3’,’’);
’4’: WriteLn(‘<< Program terminat>>’)
ELSE
WriteLn(’***Cod eronat de functie***’)
END
UNTIL c=’4’
END.

127
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

7.3 Lansarea programelor în execuţie


prin linie de comandă

În general, lansarea în execuţie a programelor Turbo Pascal este posibilă în


două moduri: din interiorul mediului de programare, prin una din comenzile meniului
Run; la nivelul sistemului de operare, printr-o linie de comandă. Prima variantă este
utilă pe parcursul procesului de punere la punct a programelor, iar a doua este
preponderentă în exploatarea curentă a unui produs-program.
Pentru lansarea prin linie de comandă, utilizatorul are posibilitatea includerii,
după numele fişierului .EXE, a unor parametri destinaţi programului. Linia de
comandă are formatul general:
...>nume parametru [parametru]...
Parametrii sunt şiruri de caractere, separate prin spaţii sau tab-uri, care sunt
accesibili din programele Turbo Pascal prin două funcţii aparţinând unităţii System.
Numărul de parametri din linia de comandă poate fi determinat folosind funcţia
ParamCount, definită astfel: ParamCount:WORD. Accesul la un anumit parametru
este posibil prin funcţia ParamStr, al cărei antet are forma:
ParamStr(n:WORD):STRING. Funcţia returnează un şir de caractere care conţine
al n-lea parametru de pe linia de comandă. Este posibil şi apelul de forma
ParamStr(0), caz în care şirul returnat conţine calea şi numele fişierului .EXE asociat
programului.

Exemplu:
7.2. Se consideră programul ExDir, care poate prelua din linia de comandă
directorul în care se găsesc fişierele aplicaţiei, transformându-l în director curent. Se
asigură salvarea directorului în uz de la momentul lansării, respectiv restaurarea lui la
încheierea execuţiei programului. Dacă noul director nu există, este prevăzută
posibilitatea creării şi activării lui ca director curent. În cadrul programului,
procedura OpDir asigură următoarele operaţii: determinarea directorului curent ('D'),
schimbarea directorului curent ('S'), crearea unui nou director ('C'). Procedura
returnează o variabilă de eroare (rez) când operaţiile asupra directoarelor nu se pot
executa.

PROGRAM ExDir;
VAR
r : INTEGER; c : CHAR;
dinc,dnou: STRING;
PROCEDURE OpDir(op:CHAR; VAR d:STRING; VAR rez:INTEGER);
BEGIN
CASE UpCase(op) OF
'D': GetDir(0,d);
'S': BEGIN {$I-} ChDir(d); {$I+} rez:=IOResult END;
'C': BEGIN {$I-} MkDir(d); {$I+} rez:=IOResult END
END
END;
PROCEDURE DetDir;

128
Tehnici speciale în Pascal

VAR
dir: STRING;
BEGIN
OpDir('d',dir,r);
WriteLn('Director curent: ',dir)
END;
BEGIN
WriteLn('Program in executie curenta: ',ParamStr(0));
DetDir;
IF ParamCount <> 0 THEN
BEGIN
dnou:=ParamStr(1);
OpDir('d',dinc,r);
OpDir('s',dnou,r);
IF r <> 0 THEN
BEGIN
WriteLn('Director inexistent: ',ParamStr(1));
Write('Se creeaza ca director nou?(Y/N): ');
ReadLn(c);
IF UpCase(c) = 'Y' THEN
BEGIN
OpDir('c',dnou,r);
IF r = 0 THEN
OpDir('s',dnou,r)
ELSE
BEGIN
WriteLn('Eroare la creare director. Executie
intrerupta!' );
RunError(r)
END
END
END
END;
DetDir
END; {restul programului se scrie normal}
IF ParamCount <> 0 THEN OpDir('s',dinc,r);
DetDir
END.

7.4 Tehnica tratării întreruperilor

Microprocesorul gestionează un sistem de întreruperi. Datorită complexităţii


proceselor ce se pot desfăşura în cadrul sistemului, unitatea centrală de prelucrare
(UCP) nu poate asigura simultan un control complet, fiind focalizată la un moment
dat pe o singură activitate. Pentru a suplini această lipsă, calculatorul este proiectat
astfel încât, la apariţia oricărui eveniment ce presupune o anumită acţiune, să se
genereze un semnal de întrerupere prin care este informată UCP. Aceasta suspendă
temporar alte activităţi pentru a trata evenimentul apărut, transferând controlul la o
rutină de tratare a întreruperii, care poate provoca afişarea unui mesaj de avertizare,
reluarea sau încheierea execuţiei programului (normală, cu eroare sau forţată) etc. La
încheierea execuţiei rutinei, UCP revine la activitatea iniţială.
Microcalculatoarele IBM PC şi compatibile acceptă 256 de întreruperi, cu
coduri din intervalul 0..255 (în hexa, $00..$FF). În literatura de specialitate,
întreruperile se regăsesc clasificate în diverse moduri: • hardware, software; • ale

129
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

microprocesorului, hardware, software, DOS, Basic, de adresă, de uz general; • ale


microprocesorului, BIOS, ale sistemului de operare (din care fac parte şi cele
referitoare la funcţiile DOS) etc.
Pentru asigurarea accesului la rutinele de întrerupere, prima zonă de 1 Ko din
memoria internă conţine o tabelă de pointeri către fiecare din rutinele disponibile,
dimensionată pentru a putea memora toate cele 256 de adrese teoretic posibile şi
numită tabela vectorilor de întrerupere. Considerând o rutină cu codul i din
intervalul 0..255, pointerul asociat ei în tabelă se află la adresa 4*i.
Utilizatorul poate determina adresa unei rutine de întrerupere prin procedura
GetIntVec definită în unitatea DOS astfel: GetIntVec(int:BYTE;VAR
adrint:POINTER), unde int reprezintă codul întreruperii precizat în apelator, iar în
adrint se returnează adresa rutinei de întrerupere corespunzătoare.
Pentru ilustrarea utilizării acestei proceduri, se prezintă programul
TabVectInt care afişează, în mai multe ecrane succesive, întreaga tabelă a vectorilor
de întrerupere, cu precizarea codului şi a adresei rutinei asociate fiecărei întreruperi,
cu componentele de segment şi deplasare, exprimate în hexazecimal (conversia se
realizează de către funcţiile HexOctet şi HexCuvint incluse în unitatea Hex).

PROGRAM TabVectInt;
USES Dos,Crt,Hex;
VAR
i : BYTE;
adrint: POINTER;
PROCEDURE Defilare;
VAR
car: CHAR;
BEGIN
GotoXY(1,25);
Write('Press any key ...');
car:=ReadKey;
ClrScr
END;
BEGIN
ClrScr;
FOR i:=0 TO 255 DO
BEGIN
GetIntVec(i,adrint);
IF (i+1) MOD 24 = 0 THEN Defilare;

WriteLn(HexOctet(i):5,HexCuvant(Seg(adrint^)):5,
HexCuvant(Ofs(adrint^)):5)
END;
Defilare
END.

UNIT Hex;
INTERFACE
FUNCTION HexOctet(nroct:BYTE):STRING;
FUNCTION HexCuvint(nrcuv:WORD):STRING;
IMPLEMENTATION
FUNCTION CifraHex(cifra:BYTE):CHAR;

130
Tehnici speciale în Pascal

BEGIN
IF cifra < 10 THEN CifraHex:=Chr(48+cifra)
ELSE CifraHex:=Chr(55+cifra)
END;
FUNCTION HexOctet;
BEGIN
HexOctet:=CifraHex(nroct DIV 16) + CifraHex(nroct
MOD 16);
END;
FUNCTION HexCuvant;
BEGIN HexCuvint:=HexOctet(Hi(nrcuv))+HexOctet(Lo(nrcuv))
END
END.

Apelul rutinei de întrerupere se realizează prin procedura Intr, definită în


unitatea System astfel: Intr(ni:BYTE; VAR reg:Register), unde ni este codul
întreruperii, iar reg este o variabilă care specifică, într-o formă standard, diverşi
parametri. Tipul Registers, definit în unitatea Dos (anexa 1), permite accesul la
registrele unităţii centrale. Înainte de apel, trebuie pregătite anumite registre, în
funcţie de parametrii solicitaţi de întrerupere. Astfel, în semiregistrul Reg.AH trebuie
încărcat codul funcţiei, în Reg.AL codul subfuncţiei (când există) etc.
Principalele caracteristici ale întreruperilor (folosindu-se ultima clasificare
prezentată anterior) sunt:
• întreruperile microprocesorului sunt generate de el însuşi: 0 corespunde
situaţiei de împărţire la zero, 2 corespunde erorii de paritate în memorie, 1 şi 3 sunt
folosite în depanarea programelor; 5 provoacă tipărirea conţinutului ecranului pe
imprimantă etc. Alte întreruperi sunt generate de diverse componente hardware din
configuraţia sistemului.
• rutinele BIOS sunt realizate de către producătorul echipamentului şi
memorate în ROM. La începutul unei sesiuni de lucru cu calculatorul, adresele din
ROM ale rutinelor BIOS sunt încărcate în tabela vectorilor de întrerupere. Când un
program cheamă o rutină BIOS, Turbo Pascal preia adresa acesteia din tabelă şi o
execută. Exemple de întreruperi BIOS: $09 - apăsarea unei taste; $10 - servicii video;
$12 - determinarea dimensiunii memoriei existente în sistem; $13 - gestiunea
discurilor; $15 - determinarea unor caracteristici ale memoriei etc.
În programele Int15 şi Int12 sunt prezentate exemple de activare a
întreruperilor $15 şi $12. Pentru întreruperea $15, se apelează funcţia $88, care
returnează în registrul AX dimensiunea memoriei extinse exprimată în Ko.
Întreruperea $12 nu foloseşte registrul AH.

PROGRAM Int15; PROGRAM Int12;


USES Dos; USES Dos;
FUNCTION FUNCTION MemorySize: WORD;
ExtendedMemory:WORD; VAR
VAR reg: Registers;
reg: Registers; BEGIN
BEGIN Intr($15,Reg);
reg.AH:=$88; MemorySize:=reg.AX
Intr($15,Reg); END;
BEGIN
ExtendedMemory:=reg.AX WriteLn('Memorie interna= ',
END; MemorySize,' Ko')
BEGIN END.
WriteLn('Memorie
extinsa= ',
ExtendedMemory,' Ko')
131
END.
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

• rutinele de întrerupere ale sistemului de operare sunt memorate pe disc şi


sunt încărcate în memoria internă la începutul fiecărei sesiuni de lucru. Acestea
corespund întreruperilor software. Exemple de întreruperi SO: $20 - terminare
program; $21 - funcţii DOS; $22 - furnizarea adresei de terminare; $24 - rutina de
tratare a erorilor critice.
Una dintre cele mai utilizate întreruperi este $21 (rutine DOS). Adresele
rutinelor, corespunzând diferitelor servicii DOS, sunt memorate în tabela vectorilor
de întrerupere, în poziţiile neocupate de rutinele BIOS. Exemple de funcţii DOS:
$00 - terminare program; $01 - citire cu ecou de la tastatură; $08 - citire fără ecou de
la tastatură; $25 - setarea vectorului de întrerupere; $2B - setarea datei din sistem;
$2C - citirea datei din sistem; $2D setarea orei din sistem etc.
Apelul unei rutine DOS se poate realiza cu Intr($21,Reg) sau cu o procedură
specială definită în unitatea DOS astfel: MsDos(VAR Reg:Registers). Întrucât toate
rutinele corespund unor funcţii, este obligatorie încărcarea prealabilă a codului
acestora în semiregistrul AH, înaintea apelului procedurii MsDos sau Intr.
Aproape toate cele 80 de funcţii DOS se regăsesc în cele peste 200 de
proceduri şi funcţii ale unităţilor standard din Turbo Pascal. Analizând evantaiul
posibilităţilor ce se deschid programatorului care foloseşte Turbo Pascal, la extreme
se constată următoarele variante de abordare: folosirea exclusivă a procedurilor şi
funcţiilor din unităţile standard ale mediului Turbo Pascal (care apelează, la rândul
lor, funcţiile DOS); folosirea directă a rutinelor de întrerupere asociate funcţiilor
DOS prin apeluri adecvate ale procedurilor MsDos (sau Intr). Prima soluţie este mai
simplu de utilizat în scrierea programului sursă, dar cea de-a doua conduce la
creşterea vitezei de execuţie a programelor. În concluzie, se poate reţine
recomandarea ca a doua soluţie, care presupune cunoaşterea a o serie de detalii
tehnice, să fie folosită doar atunci când este importantă asigurarea unui timp de
răspuns cât mai scurt. Evident, la limită, este posibilă "ocolirea" integrală a
procedurilor şi funcţiilor din bibliotecile Turbo Pascal, dar nici una din variante nu
poate fi absolutizată.
Pentru analiza comparată a celor două variante se ilustrează folosirea lor pe
exemplul funcţiilor DOS de acces la data din sistem. Accesul din Turbo Pascal se
poate realiza cu procedurile GetDate, pentru preluarea datei curente, respectiv
SetDate, pentru modificarea acesteia. Procedura GetDate are următorii parametri,
pentru care se precizează şi limitele domeniului de valori admis: anul (1980..2099),
luna (1..12), ziua în cadrul lunii (1..31), ziua în cadrul săptămânii (0..6, cu
duminică=0, luni=1, ..., sâmbătă=6). Procedura SetDate foloseşte numai primii trei
parametri.
În programul Calendar_1 se ilustrează apelul acestor proceduri pentru
determinarea calendarului unei luni oarecare, din intervalul de ani acceptat
(1980..2099).

PROGRAM Calendar_1;
USES Dos,Crt;
VAR
ac,lc,zc,zsc: WORD;

132
Tehnici speciale în Pascal

an,ln,zn,zsn: WORD;
i,nrz: BYTE;
BEGIN
REPEAT
Write('Luna, an: '); ReadLn(ln,an);
UNTIL (an > 1979) AND (an < 2100) AND (ln IN [1..12]);
zn:=1;
GetDate(ac,lc,zc,zsc);
SetDate(an,ln,zn);
GetDate(an,ln,zn,zsn);
SetDate(ac,lc,zc);
CASE ln OF
1,3,5,7,8,10,12: nrz:=31;
4,6,9,11 : nrz:=30;
2 : IF an MOD 4 =0 THEN nrz:=29 ELSE nrz:=28
END;
ClrScr;
WriteLn(' ',ln:2,' - ',an:4,#13#10);
WriteLn(' L M M J V S D');
WriteLn(' ---------------------');
IF zsn = 0 THEN zsn:=7;
FOR i:=1 TO nrz+zsn-1 DO
BEGIN
IF i <= zsn-1 THEN Write(' ')
ELSE Write(i-zsn+1:3);
IF i MOD 7 = 0 THEN WriteLn
END;
WriteLn;
WriteLn(' ---------------------')
END.

Primul apel al procedurii GetDate asigură salvarea datei curente (în


variabilele ac, lc, zc, respectiv zsc), care se reface prin al doilea apel al procedurii
SetDate. Algoritmul folosit se bazează pe faptul că, intern, se asigură determinarea
zilei din săptămână asociată oricărei date din intervalul de ani acceptat. Astfel, după
solicitarea lunii (ln) şi a anului (an) pentru care se cere afişarea calendarului, se va
determina ziua din săptămână (zsn) pentru prima zi a lunii, urmată de toate celelalte.
Rutinele de întrerupere DOS corespunzătoare celor două proceduri au
codurile $2A, respectiv $2B. După apelul rutinei $2A, rezultatele sunt furnizate în
următoarele (semi)registre: anul în CX, luna în DH, ziua în cadrul lunii în DL, iar
ziua în cadrul săptămânii în AL. Corespunzător, apelul rutinei $2B trebuie precedat
de încărcarea valorilor asociate noii date din sistem în CX, DH şi DL.
Folosirea acestei soluţii presupune: adăugarea unei declaraţii de forma:
reg1,reg2: Registers; înlocuirea celor patru instrucţiuni de apel al procedurilor
GetDate şi SetDate cu următoarea secvenţă (restul programului rămânând
nemodificat):

reg1.AH:=$2A; reg1.AH:=$2A;
MsDos(reg1); MsDos(reg1);
ac:=reg1.CX; zsn:=reg1.AL;
lc:=reg1.DH; reg2.AH:=$2B;
zc:=reg1.DL; reg2.CX:=ac;
zsc:=reg1.AL; reg2.DH:=lc;
reg2.AH:=$2B; reg2.DL:=zc;
reg2.CX:=an; MsDos(reg2);
reg2.DH:=ln;
reg2.DL:=zn;
MsDos(reg2);

133
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

7.5 Tehnici de lucru la încheierea execuţiei programelor

Încheierea execuţiei unui program este un eveniment important, care trebuie


tratat de programator cu toată atenţia. Turbo Pascal asigură utilizatorului posibilitatea
implicării directe, atât în declanşarea acestui eveniment, cât şi în realizarea unor
prelucrări suplimentare asociate lui.
Terminarea unui program poate fi de mai multe categorii: normală, cu eroare
la execuţie, forţată. În primele două cazuri, decizia de terminare a execuţiei aparţine
sistemului, iar în ultimul terminarea se execută înaintea atingerii sfârşitului logic al
programului, la solicitarea expresă a utilizatorului.
Variabilele ExitCode şi ErrorAddr, definite în unitatea System, conţin
informaţii privind modul de încheiere a execuţiei. La încheierea cu eroare la execuţie,
ExitCode va conţine numărul erorii, iar ErrorAddr punctează pe codul asociat
instrucţiunii care a generat această eroare. La lansarea în execuţie a unui program,
cele două variabile au valorile iniţiale 0, respectiv nil. La încheiere normală, situaţia
celor două variabile este ExitCode=0 şi ErrorAddr=nil. Mediul Turbo Pascal
determină dacă este necesară afişarea unui mesaj de eroare la execuţie, pe baza
valorii variabilei pointer ErrorAddr. Mesajul de eroare se afişează numai dacă
ErrorAddr ≠ nil.
Teoretic, încheierea forţată a execuţiei, înaintea atingerii sfârşitului logic al
programului, contravine principiilor programării structurate. Totuşi, practica
programării conduce la situaţii când o asemenea încheiere devine necesară, dar se
recomandă limitarea folosirii ei numai la cazurile când nu sunt posibile şi alte soluţii.
• Procedura de încheiere a execuţiei blocului curent are antetul de forma:
Exit. În cazul subprogramelor, apelul procedurii provoacă revenirea în apelator
(blocul din care a fost apelat subprogramul), iar pentru programe determină
încheierea execuţiei.
• Procedura de încheiere a execuţiei cu un cod de retur precizat de utilizator
este definită astfel: Halt[(ExitCode:WORD)]. La apelul procedurii cu parametru,
ExitCode va primi valoarea transferată la execuţia apelului, iar în cazul apelului fără
parametru rămâne pe valoarea 0; în ambele cazuri, ErrorAddr rămâne cu valoarea nil.
Prin apelul procedurii (care poate fi plasat chiar la sfârşitul logic al programului, fără
a încălca principiile programării structurate), se poate face distincţia între mai multe
moduri de terminare normală a unui program.

Exemplu:
7.3. Fiind dat un vector de mari dimensiuni, memorat într-un fişier binar, să
se realizeze programul de ventilare a fişierului cu obţinerea a două fişiere binare, cu
valorile pozitive, respectiv negative ale fişierului (valorile nule vor fi neglijate).
Programul Ventilare realizează încheierea execuţiei cu apelul procedurii Halt, care,

134
Tehnici speciale în Pascal

prin poziţionarea codului de retur, permite distincţia între diversele moduri de


încheiere a execuţiei: ambele fişiere nevide, ambele fişiere vide, respectiv unul sau
altul dintre fişierele de ieşire vid.

PROGRAM Ventilare;
VAR
fi,fp,fn: FILE OF INTEGER;
v : INTEGER;
np,nn:WORD;
BEGIN
Assign(fi,'FI.DAT'); Assign(fp,'FP.DAT');
Assign(fn,'FN.DAT');
Reset(fi); Rewrite(fp); Rewrite(fn);
WHILE NOT Eof(fi) DO
BEGIN
Read(fi,v);
IF v > 0 THEN Write(fp,v)
ELSE IF v < 0 THEN Write(fn,v)
END;
np:=FileSize(fp); nn:=FileSize(fn);
IF (np <> 0) AND (nn <> 0)
THEN Halt {0 - ambele fisiere nevide}
ELSE IF np <> 0
THEN Halt(1) {1 - fisierul FN vid}
ELSE IF nn <> 0
THEN Halt(2) {2 - fisierul FP vid}
ELSE Halt(3) {3 - ambele fisiere vide}
END.

La revenirea în contextul de lansare a programului în execuţie, există soluţii,


oferite de mediul Turbo Pascal (prin variabila DosExitCode) sau de sistemul de
operare DOS, pentru testarea valorii codului de retur şi luarea unor decizii adecvate
de continuare a prelucrărilor. În programul GestProc se creează fişierul binar iniţial,
se lansează în execuţie programul Ventilare (considerat memorat în fişierul
VENT.EXE) şi se prelucrează codul de retur.

PROGRAM GestProc;
USES Dos;
{$M 2048,0,0}
VAR
fi : FILE OF INTEGER;
vi,n,i: INTEGER;
r : WORD;
BEGIN
Write('Numar de elemente si valoare initiala:');
ReadLn(vi,n);
Assign(fi,'FI.DAT');
Rewrite(fi);
FOR i:=vi TO vi+n-1 DO Write(fi,i);
Close(fi);
SwapVectors;
Exec('VENT','');
SwapVectors;
r:=DosExitCode;
IF r = 0 THEN
WriteLn('OK!')
ELSE IF r = 1 THEN

135
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

WriteLn('Fisierul FN vid')
ELSE IF r = 2 THEN
WriteLn('Fisierul FP vid')
ELSE
WriteLn('Ambele fisiere vide')
END.

• Procedura de încheiere cu eroare de execuţie este definită astfel:


RunError[(ExitCode:WORD)]. La apelul fără parametru, ExitCode rămâne cu
valoarea 0, iar în cazul apelului cu parametru valoarea parametrului real va fi
atribuită variabilei ExitCode.

Exemplu:
7.4. În programul Valid se realizează introducerea cu validare a unei valori
numerice, cu reluare până la furnizarea unei valori corecte, respectiv până la
depăşirea unui număr limită de reluări.
PROGRAM Valid;
VAR n,i,er : BYTE; x : REAL;
BEGIN
Write('Numar maxim admis de reluari:'); ReadLn(n); i:=0;
REPEAT
er:=0;
Write('x:'); {$I-} ReadLn(x); {$I+}
IF IOResult <> 0 THEN
BEGIN
Write('Valoare nenumerica');
er:=1
END;
Inc(i)
UNTIL (er = 0) OR (i > n);
IF i > n THEN
BEGIN
Write('Depasire numar de reluari admis');
RunError(106);
END
END.

Soluţia propusă conduce la întreruperea forţată prin apelul RunError, la


depăşirea numărului de repetări admis, cu mesajul standard de eroare corespunzător
codului dat de utilizator:
Runtime error 106: Invalid numeric format

• Includerea rutinelor proprii de încheiere a execuţiei programelor. Pe lângă


operaţiile realizate de sistemul de operare MS-DOS, Turbo Pascal este prevăzut cu
câteva rutine de serviciu, a căror lansare, într-o anumită succesiune, este declanşată
implicit la terminarea execuţiei unui program. La apariţia evenimentului de terminare
a unui program, controlul este transferat primei proceduri din lanţul rutinelor de
încheiere a execuţiei (exit procedures), a cărei adresă este memorată în variabila
ExitProc.
Utilizatorul are posibilitatea să includă propria rutină de tratare a terminării,
al cărei apel se va insera la începutul lanţului de apeluri. În acest scop, variabila

136
Tehnici speciale în Pascal

ExitProc va fi setată pe adresa procedurii proprii, cu asigurarea refacerii valorii


iniţiale după încheierea execuţiei acestei proceduri, asigurându-se astfel continuarea
cu procedurile implicite de încheiere.
Procedurile de încheiere proprii oferă programatorului un mijloc de a
controla - nu şi de a preveni - terminarea unui program. De exemplu, dacă programul
foloseşte o imprimantă, în acest mod se poate asigura sesizarea momentului pentru
extragerea ultimei pagini. Tehnica de includere în lanţ a procedurii proprii este
ilustrată în programul Ex_ExitProgram.

PROGRAM Ex_ExitProgram;
VAR
ExitOrig: POINTER;
{F+}
PROCEDURE ExitPropriu;
{$F-}
BEGIN
{Instructiuni ale utilizatorului la terminarea programului }
WriteLn('Terminarea programului...');
ExitProc:= ExitOrig; {restaurarea adresei rutinei implicite
de incheiere a executiei}
END;
BEGIN
ExitOrig:=ExitProc; {salvarea adresei rutinei implicite de
incheiere a executiei}
ExitProc:=@ExitPropriu; {includerea rutinei proprii de
incheiere la inceputul lantului de apeluri}
{Restul programului se scrie normal}
END.

Deşi procedura ExitPropriu nu este chemată explicit, ea va fi prima


procedură apelată la terminarea execuţiei programului, efectul vizibil fiind afişarea
mesajului inclus în cadrul ei. Se remarcă folosirea directivei {$F}, necesară pentru a
forţa tratarea procedurii ExitPropriu ca "îndepărtată" (far). Acest lucru este necesar
deoarece rutinele de încheiere implicite aparţin segmentului de cod al unităţii
System, în timp ce procedura ExitPropriu este inclusă în segmentul de cod al
programului principal.

7.6 Punerea la punct a programelor Turbo Pascal

În raport de momentul în care se manifestă, pot fi identificate trei categorii


de erori: de compilare, la execuţie şi de logică.
Erorile de compilare (Compile-Time Errors) sunt, în general, rezultatul unor
greşeli lexicale sau sintactice. Erorile lexicale apar când un identificator, un literal
sau un simbol folosit în program este ilegal. De exemplu, considerând declaraţia:
CONST hex1=$FFHH;

137
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

se produce eroare lexicală, deoarece în specificarea valorii constantei hexa apar


caractere nepermise (HH). Eroarea este semnalată printr-un mesaj de forma: Error 7:
Error in integer constant. O eroare similară ar produce şi folosirea unui literal hexa
$-12, deoarece aceşti literali nu pot avea semn.
Declaraţia de constantă simbolică:
CONST sir='pret unitar=;
generează eroare lexicală, compilatorul interpretând absenţa apostrofului de închidere
a şirului, în sensul următorului mesaj: Error 8: String constant exceeds line.
Erorile sintactice corespund cazurilor în care unele construcţii folosite în
program încalcă regulile formale ale limbajului, cum ar fi specificarea unui număr
greşit sau de tipuri neadecvate de parametri în apelul unor proceduri sau funcţii,
omiterea caracterului ";" la încheierea unei instrucţiuni sau folosirea lui într-un
context nepermis etc. Mesajele nu sunt întotdeauna foarte explicite, dar deplasarea
cursorului în fereastra Turbo Pascal, exact în locul în care a fost sesizată eroarea, îl
va ajuta pe utilizator să determine cauza acesteia. În plus, la folosirea tastei F1 se
afişează o fereastră de informare cu explicaţii suplimentare privind semnificaţia
erorii. De semnalat faptul că unele erori, care la o primă analiză par lexicale, sunt
interpretate de compilator ca erori de sintaxă. Se consideră declaraţia:
VAR pret-unitar: REAL;
În sens strict, s-a produs o eroare lexicală, identificatorul fiind construit
incorect ("-" în loc de "_"). Totuşi, mesajul de eroare al compilatorului este Error 86:
":" expected şi nici cel suplimentar, afişat la apăsarea tastei F1, A colon doesn't
appear where it should nu este cu mult mai explicit. Compilatorul interpretează că
identificatorul este pret, după care, fiind în secţiunea VAR a unui program, aşteaptă
caracterul ":", care ar trebui să urmeze în sintaxa declaraţiei. Deoarece cursorul se
plasează sub caracterul "-", este de presupus că utilizatorul va sesiza uşor adevărata
cauză a erorii.
În multe cazuri, eroarea sintactică este sesizată după locul strict în care s-a
produs, în momentul în care compilatorul constată absenţa unui element pe care îl
aştepta în contextul respectiv. De exemplu, considerând secvenţa:

VAR
pret_unitar: REAL
BEGIN
absenţa caracterului ";" de încheiere a declaraţiei variabilei se sesizează doar la
începutul liniei următoare, mesajul de eroare fiind: Error 85: ";" expected, iar
cursorul se va plasa la începutul cuvântului BEGIN.
Pentru o instrucţiune de forma:
IF a > b THEN WriteLn(a,' > ',b);
ELSE WriteLn(a,' <= ',b);
mesajul de eroare este: Error 113: Error in statement. Cauza erorii este folosirea
incorectă a terminatorului ";" la încheierea ramurii THEN a instrucţiunii IF, iar
eroarea este sesizată la întâlnirea lui ELSE, cu plasarea cursorului la începutul

138
Tehnici speciale în Pascal

acestuia. La folosirea tastei F1 se afişează fereastra de informare cu mesajul This


symbol can't start a statement, care, de această dată, este suficient de explicit.
Erorile la execuţie (Run-Time Errors) se mai numesc şi semantice şi apar
atunci când instrucţiuni valide pe plan sintactic sunt combinate incorect. Printre
cazurile mai frecvente din această categorie pot fi menţionate: încercarea de a realiza
operaţii I/O cu fişiere nedeschise, tentativa de citire dincolo de sfârşitul unui fişier,
depăşirea domeniului de valori permis pentru unele tipuri de variabile, depăşirea
stivei, împărţire la zero etc. În toate cazurile, execuţia programului se încheie
anormal, se afişează un mesaj de eroare, iar cursorul se deplasează în fereastra de
editare a mediului Turbo Pascal, în contextul din care s-a generat eroarea,
aşteptându-se intervenţia utilizatorului. Trebuie menţionat că, depinzând de modul de
folosire a unor directive de compilare, programatorul poate inhiba sau nu semnalarea
unora dintre erori sau poate prelua controlul în caz de eroare, evitând încheierea
anormală şi prematură a programului.
Un exemplu tipic din prima categorie este directiva {$R}, pentru controlul
depăşirii domeniului de valori al datelor de tip întreg. Întrucât valoarea ei implicită
este {$R-}, execuţia programului:
VAR a: BYTE;
BEGIN
a:=255; a:=a+1;
END.
se va încheiea fără eroare, deşi la a doua operaţie de atribuire se depăseşte domeniul
de valori admis pentru tipul BYTE. Introducând în program o primă linie cu directiva
{$R+}, execuţia se încheie cu eroare, afişându-se mesajul Error 201: Range check
error, iar cursorul se va plasa la începutul instrucţiunii care a generat eroarea.
Un exemplu pentru evitarea încheierii anormale îl poate constitui directiva
{$I}, care permite programatorului preluarea controlului în caz de eroare la realizarea
operaţiilor de intrare/ieşire (programul Valid).
Mesajele erorilor de execuţie au forma: Runtime error 106 at ssss:dddd,
unde ssss reprezintă segmentul, iar dddd deplasarea corespunzând adresei
instrucţiunii la care s-a produs eroarea. Execuţia se întrerupe, iar cursorul se va
deplasa pe linia din programul sursă cu instrucţiunea care a generat eroarea (în
exemplul considerat, linia cu instrucţiunea de apel al procedurii ReadLn).
Erorile de logică (Logic Errors) sunt cel mai greu de îndepărtat, deoarece
programul se poate încheia "normal", dar rezultatele nu sunt cele aşteptate. Acestea
sunt generate de greşelile de proiectare a programelor. Pentru depistarea lor este
necesară, de multe ori, folosirea unor tehnici speciale de depanare.
Depanarea interactivă se realizează în cadrul unei sesiuni de depanare, care
presupune execuţia supravegheată a programului. Utilizatorul are posibilitatea să
aleagă între: folosirea unui depanator integrat (Integrated Debugger), inclus în
mediul de programare; folosirea unui depanator autonom (Standalone Debugger),
respectiv pachetul de programe Turbo Debugger al firmei Borland.
Folosirea depanatorului integrat, abordată în cele ce urmează, presupune
setarea comutatorului Options|Debugger|Integrated. În procesul de depanare

139
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

interactivă pot fi identificate două etape: pregătirea sesiunii de depanare, respectiv


desfăşurarea acesteia.
• Pregătirea sesiunii de depanare interactivă corespunde operaţiilor ce se
desfăşoară pe parcursul fazelor de compilare şi editare de legături şi care, prin
informaţiile produse, condiţionează desfăşurarea sesiunii propriu-zise. Informaţiile
necesare procesului de depanare sunt generate de directive de compilare sau de
comenzi echivalente din meniul Options. Folosirea depanatorului integrat depinde de
modul de specificare a directivelor de compilare {$D} şi {$L}, cărora le corespund
comutatoarele Options|Compile|Debug Information, respectiv
Options|Compile|Local Symbols.
Directiva {$D} activează sau inhibă generarea unor informaţii de depanare,
care constau dintr-o tabelă ce precizează corespondenţa dintre numerele liniilor din
programul sursă ce conţin instrucţiuni executabile şi adresa codului obiect generat
pentru fiecare dintre ele. Valoarea implicită a acestei directive este {$D+} şi folosirea
ei permite execuţia pas cu pas şi definirea punctelor de întrerupere în programele
compilate în acest mod. În plus, la apariţia unor erori la execuţie, comanda
Compile|Find Error asigură localizarea automată a instrucţiunii ce a produs eroarea.
Directiva condiţionează şi obţinerea aşa numitei "mape" a editorului de legături,
respectiv un fişier cu extensia .MAP, ale cărui caracteristici şi conţinut depind de
modul de setare a butoanelor radio din grupul Options|Linker|Map File:
(.) Off
( ) Segments
( ) Public
( ) Detailed
Butonul Off este selectat implicit şi are ca efect neproducerea fişierului
.MAP. La selectarea butonului Segments, fişierul va conţine informaţii privind adresa
de start, lungimea şi numele segmentelor de cod, date şi stivă, precum şi adresa de
început a zonei heap. Alegerea butonului Public face ca, pe lângă informaţiile privind
segmentele, în fişierul .MAP să se includă un tabel cu adresele obiectelor "publice",
adică ale subprogramelor interne, ale constantelor cu tip şi ale variabilelor globale
definite în program, precum şi ale variabilelor globale ale unităţilor folosite (în
absenţa clauzei USES, numai cele ale unităţii System). De asemenea, se precizează
adresa punctului de lansare în execuţie a programului (program entry point). Cu
butonul Detailed se afişează, în plus, un tabel cu numerele liniilor cu instrucţiuni
executabile din programul sursă, urmate de adresa codului obiect generat în cadrul
segmentului de cod al programului principal. Sunt incluse şi instrucţiunile
executabile ale eventualelor subprograme interne.
Directiva {$L} activează sau inhibă generarea unor informaţii despre
simbolurile locale, adică variabilele şi constantele declarate în cadrul subprogramelor
interne ale unui program. Valoarea implicită a directivei este {$L+} şi folosirea ei
condiţionează examinarea şi modificarea variabilelor locale, precum şi urmărirea
succesiunii de generare şi execuţie a apelurilor de proceduri şi funcţii interne.
Directivele {$D} şi {$L} se folosesc de obicei împreună, cu precizarea că
{$L} este ignorată dacă {$D} nu este activă. Pentru depanare, pentru a nu fi

140
Tehnici speciale în Pascal

condiţionaţi de eventualele setări implicite pe parcursul sesiunii de lucru, se


recomandă includerea la începutul programului a configuraţiei: {$D+,L+}. Deoarece
atât {$D+} cât şi {$L+} generează spaţiu de memorie suplimentar, după punerea la
punct a programelor se recomandă resetarea acestora.
• Desfăşurarea sesiunii de depanare interactivă. Depanatorul integrat oferă
utilizatorului mai multe comenzi incluse în meniurile Run, Debug şi Window ale
mediului Turbo Pascal, utile în procesul de depanare: execuţia pas cu pas a
programelor, cu sau fără execuţia în aceeaşi manieră a subprogramelor; vizualizarea
valorilor unor variabile (mai general, expresii), cu eventuala modificare a acestora;
crearea unor puncte de întrerupere; vizualizarea conţinutului stivei. În practica
programării, comenzile se grupează pe parcursul sesiunii de depanare în funcţie de
obiectivele urmărite. Sesiunea de depanare este formată din următoarele componente:
deschiderea sesiunii; sesiunea propriu-zisă; închiderea sesiunii. Chiar dacă tehnicile
de depanare sunt diferite, în cadrul lor se regăsesc cel puţin două elemente comune:
execuţia pas cu pas a instrucţiunilor, urmărirea în paralel a rezultatelor obţinute.

a) Deschiderea sesiunii se realizează cu una din comenzile meniului Run:


- Run|Trace Into (F7) sau Run|Step Over (F8), când se urmăreşte
depanarea de la începutul programului;
- Run|Go to Cursor (F4), în cazul unei depanări dintr-un anumit punct.
În primul caz, efectul comenzii este afişarea unei bare de execuţie (run bar),
care supraluminează linia BEGIN a programului principal. În al doilea caz, anterior
deschiderii sesiunii are loc deplasarea cursorului pe o anumită linie, iar la folosirea
comenzii se execută instrucţiunile în mod normal pînă la linia marcată, după care se
intră într-o manieră de lucru asemănătoare variantei anterioare.
O variantă a depanării dintr-un anumit punct o constituie crearea punctelor
de întrerupere (breakpoints), cu ajutorul cărora se selectează instrucţiunile din
program corespunzând locurilor unor prezumtive greşeli de logică. Crearea punctelor
de întrerupere se realizează anterior deschiderii sesiunii de depanare propriu-zise,
prin deplasarea cursorului în textul sursă în contextul respectiv şi folosirea comenzii
Debug|Toggle breakpoint (sau <Ctrl><F8>). Punctul de întrerupere va fi marcat
printr-o bară luminoasă, iar în timpul sesiunii de depanare execuţia programului se va
întrerupe înainte de executarea instrucţiunilor de pe linia marcată. Comanda poate
realiza şi eliminarea punctelor de întrerupere, dacă este folosită pentru linii unde
există deja definite asemenea puncte. Punctele de întrerupere pot fi gestionate şi cu
comanda Debug|Breakpoints, care, prin intermediul unei ferestre de dialog, permite
ştergerea unor puncte (butonul de comandă Delete), definirea unor noi puncte de
întrerupere sau modificarea caracteristicilor celor existente (butonul de comandă
Edit).
b) Sesiunea propriu-zisă corespunde aplicării unor tehnici de depanare,
bazate pe folosirea într-o anumită succesiune a unor comenzi din meniurile Debug şi
Window, după deschiderea acesteia cu una din comenzile menţionate ale meniului

141
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Run. Urmărirea valorilor variabilelor este asociată cu execuţia pas cu pas, de la


început sau dintr-un anumit punct, fiind posibilă urmărirea modului cum evoluează
valorile unor variabile (mai general, expresii) pe măsura execuţiei instrucţiunilor. În
consecinţă, fie de la început, fie pe parcursul sesiunii, este necesară precizarea
variabilelor (expresiilor) ale căror valori vor fi afişate într-o fereastră de urmărire
(watch window). Se folosesc comenzile din submeniul Debug|Watch, prin care se
poate specifica: includerea unui nou element (Add watch); ştergerea unui element
(Delete watch); modificarea unui element (Edit Watch).
Submeniul are o fereastră proprie de dialog cu un câmp de intrare în care se
realizează operaţii asupra elementului curent din lista expresiilor de urmărit, marcat
printr-o bară luminoasă. La prima introducere a unui element în listă are loc automat
deschiderea ferestrei de urmărire şi amplasarea acestuia la începutul listei, cu afişarea
valorii sale (la începutul sesiunii valoarea este nedefinită).
Desfăşurarea sesiunii presupune execuţia pas cu pas a instrucţiunilor, de la
început sau din punctul ales, cu afişarea valorilor variabilelor (expresiilor) incluse în
fereastra de urmărire. Activarea ferestrei de urmărire, atât pentru urmărirea prin
defilare a tuturor valorilor cât şi pentru operaţii de editare asupra listei, se realizează
cu comanda Window|Watch.
Urmărirea conţinutului stivei este legată de lucrul cu subprograme proprii,
permiţând controlul succesiunii apelurilor de proceduri şi funcţii, până la atingerea
apelului procedurii aflată curent în execuţie. Se bazează pe folosirea comenzii
Window|Call stack (<Ctrl><F3>), care deschide o fereastră pentru afişarea
succesiunii apelurilor de subprograme.
c) Închiderea sesiunii se realizează cu comanda Run|Program reset
(<Ctrl><F2>).

142
PRELUCRAREA ETICHETELOR
DE FIŞIER ŞI A ALTOR INFORMAŢII
EXTERNE MEMORATE ÎN DISCURI

Sistemul de operare gestionează o structură arborescentă de [sub]directoare şi


fişiere. La fiecare nivel din arborescenţă există tabele care încorporează informaţii de
legătură şi identificare a ascendenţilor şi descendenţilor. În suportul extern, un fişier
are, pe lângă partea de date, o intrare în [sub]directorul părinte, numită în continuare şi
etichetă. Aceasta memorează caracteristicile externe ale fişierului, cum ar fi: numele
extern, extensia, atributele, data şi ora ultimei scrieri în fişier etc. Programele
utilizatorului pot avea acces la unele informaţii memorate în etichetele fişierelor şi în
alte tabele din structura DOS a discurilor, prin funcţii şi proceduri adecvate.

8.1 Structura fizică a discurilor DOS

Un disc magnetic, în structură DOS, este compus din următoarele părţi: tabele
de alocare a fişierelor, tabela directorului rădăcină, zona de date a fişierelor. În plus,
discul sistem mai conţine zona programului de încărcare a sistemului de operare
(BOOT), localizată pe primul sector al primei piste. Fiecare fişier (subdirector) are o
intrare (tip etichetă) în directorul (subdirectorul) părinte.
♦ Tabela de alocare a fişierelor (FAT) este împărţită în câmpuri care co-
respund cluster-elor de pe disc. Cluster-ul este format din unul sau mai multe sectoare
şi reprezintă unitatea de alocare pe disc. Numărul de sectoare ale unui cluster este
memorat în blocul de parametri ai BIOS şi diferă de la un tip de disc la altul (flexibil
sau Winchester) şi de la un tip de calculator la altul. Oricum, el este o putere a lui doi.

Exemplu:
8.1. Disc flexibil simplă faţă - 20 sectoare/cluster; disc flexibil dublă faţă - 21 sec-
toare/cluster; disc Winchester pentru PC/AT - 22 sectoare/cluster; disc Winchester
pentru PC/XT - 23 sectoare/cluster.

143
Programarea calculatoarelor– Tehnica programării în limbajul Pascal

Câmpurile din FAT au 16 biţi (un cuvânt), prin care se pot adresa 216 clustere
(la unele sisteme, câmpurile din FAT au 12 biţi).
Primul cuvânt din FAT (cu numărul zero) conţine identificatorul tabelei de
alocare, numit şi descriptorul suportului. El indică, codificat, tipul de disc utilizat. Al
doilea cuvânt din FAT (cu numărul unu) conţine FFFF16. Începând de la cuvântul cu
numărul doi, sunt memorate lanţuri de alocare pentru fiecare fişier. Un cuvânt din FAT
poate avea următorul conţinut:
000016 - cluster-ul corespunzător este disponibil;
FFF016-FFF616 - cluster-ul corespunzător este rezervat;
FFF716 - cluster-ul corespunzător este defect;
FFF816-FFFF16 - sfârşitul lanţului de alocare;
000216-FFEF16 - adresa următorului cuvânt din FAT, corespunzător următorului clus-
ter ocupat de datele fişierului.
Lanţul de legături este realizat prin aceea că fiecare cuvânt indică numărul
următorului cuvânt din lanţ. Fişierul nu ocupă o zonă continuă în disc. El are cel puţin
un cluster (când are un singur cluster, cuvântul corespunzător din FAT este FFFF16).

Exemplu:
8.2. În figura 8.1 se prezintă o tabelă de alocare şi lanţul de legături realizat
pentru un fişier, Fis.

Fig. 8.1 Exemplu de tabelă de alocare

Pentru fişierul Fis sunt alocate cluster-ele 09-0C, 14-16, 18-19. Cluster-ul 17
este defect. Cluster-ele 0D-13, 1A-1F sunt disponibile. Un alt lanţ începe cu cluster-ul
02 şi se termină cu cluster-ul 07.
Prin algoritmi corespunzători, MS-DOS face conversia de la adresă de cluster
la adresă de sector. Pentru a evita pierderea informaţiilor, în cazul deteriorării unor
câmpuri, în disc sunt păstrate două exemplare (adiacente) din FAT.
♦ Zona tabelei directorului rădăcină (DIR) urmează, în suport, tabelei de
alocare a fişierelor şi conţine câte o intrare pentru fiecare fişier sau subdirector
descendent. Intrarea este o zonă de 32 octeţi cu o structură prestabilită. Fiecare disc are

144
Prelucrarea etichetelor de fişier şi a altor informaţii externe memorate în discuri

un singur director rădăcină, căruia îi este asociată zona DIR. Dimensiunea şi poziţia
tabelei DIR sunt fixe, prestabilite prin comanda FORMAT, în momentul formatării
discului. Numărul de intrări în DIR şi poziţia tabelei în disc sunt memorate în blocul de
parametri ai BIOS-ului. Dacă discul este de sistem, primele două intrări din DIR se
referă la fişiere care conţin sistemul de operare (aceste fişiere sunt încărcate în memoria
principală de către încărcător, care se află în sectorul zero al discului).
♦ Zona de date a fişierelor (FILE) conţine partea de date a acestora şi
subdirectoarele. Subdirectoarele sunt tratate ca fişiere, cu articole speciale, de câte
32 octeţi. Structura intrărilor în subdirectoare este identică cu cea a intrărilor în director.
Numărul intrărilor în subdirectoare este nelimitat (spre deosebire de intrările în
director). Sectoarele din zona de date sunt grupate în clustere care au câte un cuvânt
corespondent în FAT. Când un fişier (subdirector) este extins, se caută în FAT clustere
libere (cuvinte cu conţinutul 000016) şi se prelungeşte lanţul de alocare. Dacă discul este
de sistem, fişierele IO.SYS şi MSDOS.SYS sunt primele în zona de date.

8.2 Structura intrărilor în [sub]directoare

Fiecărui fişier sau subdirector îi corespunde o intrare (etichetă) în [sub]directo-


rul părinte. Intrările au lungimea 32 octeţi. Subdirectoarele se comportă ca nişte fişiere
cu articole speciale, gestionate de DOS. Structura unei intrări este prezentată în
tabelul 8.1.
♦ Numele fişierului este format din 8 caractere, primul având următoarele
semnificaţii:
• 00 - Intrarea în director nu a fost folosită;
• 2E - Intrarea corespunde unui [sub]director. 2E16 reprezintă caracterul
ASCII ".". Zona 1A-1B a intrării conţine mărimea subdi-
rectorului, exprimată în numere de clustere. Dacă şi octetul 01
al intrării este tot 2E16, zona 1A-1B conţine adresa de început
în FAT a lanţului de alocare a subdirectorului părinte (zona
1A-1B conţine 000016 dacă părintele este directorul rădăcină).
În ambele situaţii, octeţii 01-0A, respectiv 02-0A conţin
caracterul spaţiu;
• E5 - Fişierul a fost şters;
• Altă valoare - Primul caracter al numelui de fişier (subdirector).
Din analiza structurii numelui se poate deduce că se selectează următoarele
tipuri de intrări: intrare normală de fişier (subdirector) fiu; intrare de tip "."; intrare de
tip "..". O mai bună precizare a tipului de intrare se realizează prin câmpul de atribute.

145
Programarea calculatoarelor– Tehnica programării în limbajul Pascal

Tabelul 8.1 Structura intrărilor în [sub]director


Octeţi Semnificaţie

00 - 07 Numele fişierului
08 - 0A Extensia fişierului
0B Atributele fişierului
0C - 15 Octeţi rezervaţi
16 - 17 Ora ultimei scrieri în fişier

18 - 19 Data ultimei scrieri în fişier


1A - 1B Adresa de început în FAT a lanţului de alocare a fişierului
1C - 1F Dimensiunea fişierului, în număr de octeţi

♦ Atributele fişierului au semnificaţia din tabelul 8.2. Prin setarea sau şter-
gerea corespunzătoare a biţilor octetului 0B pot fi obţinute şi alte combinaţii de atribute
(cea mai utilizată este valoarea 0316, care ar corespunde unui fişier readonly ascuns).
Însumarea tuturor valorilor (3F16) desemnează un fişier oarecare (orice fişier).

♦ Ora ultimei scrieri în fişier este memorată pe doi octeţi şi are structura din
figura 8.2, unde:
O reprezintă ora, cu valori binare cuprinse în intervalul [0,23];
M reprezintă minutul, cu valori binare cuprinse în intervalul [0,59];
S reprezintă numărul de incremente de două secunde, exprimat în binar.

♦ Data ultimei scrieri în fişier este memorată pe doi octeţi şi are structura din
figura 8.3, unde:

Fig. 8.2 Structura orei ultimei scrieri în fişier

Fig. 8.3 Structura datei ultimei scrieri în fişier

146
Prelucrarea etichetelor de fişier şi a altor informaţii externe memorate în discuri

• A este anul relativ la 1980, cu o valoare binară din intervalul [0-119];

• L este luna exprimată în binar (valori din intervalul [1,12]);

• Z este ziua exprimată în binar (valori din intevalul [1,31]).


Atât pentru ora, cât şi pentru data ultimei scrieri există proceduri PASCAL care
"împachetează", respectiv "despachetează", informaţia.

Tabelul 8.2 Atributele unui fişier (octetul 0B al intrării în [sub]director)

Valoarea octetului
0B Semnificaţie
Biţi Hexa

76543210

00000000 00 Fişier obişnuit. Asupra lui se pot realiza operaţii de


citire şi scriere.
00000001 01 Fişier readonly. Fişierul este protejat la scriere. Din
el se poate doar citi. Fişierul nu poate fi şters.
00000010 02 Fişier ascuns (hidden). Fişierul nu poate fi găsit
prin operaţii obişnuite de căutare în directori. Nu
poate fi afişat prin comanda DIR din DOS şi nu
este disponibil prin comenzile COPY, DEL, REN,
XCOPY.
00000100 04 Fişier de sistem (system). Se referă la sistemul de
operare şi este un fişier ascuns.
00001000 08 Identificator de volum. Un singur fişier, aflat în
directorul rădăcină, are acest atribut.
00010000 10 [Sub]director (intrarea se referă nu la un fişier de
date, ci la un [sub]director).
00100000 20 Fişierul este de tip arhivă (archive). Bitul este
poziţionat pe valoare 1 ori de câte ori are loc o
scriere în fişier. Bitul redevine zero în urma unei
procesări cu comenzile BACKUP, RESTORE,
XCOPY din DOS.

147
Programarea calculatoarelor– Tehnica programării în limbajul Pascal

♦ Adresa de început în FAT indică adresa primului cuvânt din tabela de


alocare aferentă fişierului. Această adresă este mai mare ca unu. Drumul logic, de
principiu, parcurs până la accesul la datele unui fişier cu identificatorul C:\D1\FIS este
prezentat în figura 8.4.

8.3 Tratarea informaţiilor din etichetele fişierelor

ZONA DIR
D1

AFAT

FAT ... ... FF


Subdirectorul D1

Zona de
Da te

AFAT

Fig. 8.4 Accesul la datele fişierului C:\D1\FIS

Eticheta de fişier (intrarea în [sub]director) conţine câmpuri referitoare la


nume, extensie, atribute, data şi ora ultimei scrieri în fişier, adresa de început în FAT,
dimensiunea fişierului. Etichetele sunt create la deschiderea cu Rewrite a fişierelor. De
regulă, valorile câmpurilor nume, extensie, atribute şi adresa de început în FAT sunt
înscrise la crearea fişierului. Data şi ora ultimei scrieri se modifică la fiecare nouă
scriere în fişier. Dimensiunea fişierului se modifică la scrierea unui nou articol peste
sfârşitul iniţial de fişier (pentru fişierele TEXT la scrierea după o deschidere Append,
iar pentru fişierele binare la scrierea unui articol cu pointer mai mare ca FileSize(f)
iniţial). Unele câmpuri pot fi modificate explicit, prin funcţii şi proceduri speciale.
Astfel, SetFAttr poziţionează atributele, SetFTime poziţionează data şi ora, Rename
modifică numele [şi extensia], Truncate modifică lungimea. Eticheta de fişier poate fi
ştearsă cu procedura Erase (se şterge intrarea în [sub]director).

148
Prelucrarea etichetelor de fişier şi a altor informaţii externe memorate în discuri

8.3.1 Prelucrarea atributelor unui fişier

În unit-ul Dos sunt definite proceduri pentru poziţionarea sau citirea atributelor
unui fişier. Corespunzător valorilor pe care le poate lua octetul 0B din intrarea unui
fişier în [sub]director, în unit-ul Dos sunt definite următoarele constante:
ReadOnly = $01 ¾ fişier numai pentru citire;
Hidden = $02 ¾ fişier ascuns;
SysFile = $04 ¾ fişier de sistem;
Volumid = $08 ¾ identificator de volum;
Directory = $10 ¾ [sub]director;
Archive = $20 ¾ fişier arhivă;
AnyFile = $3F ¾ orice fişier
Fişierelor de date li se pot asocia atributele $01, $02, $20. În Turbo Pascal, un
fişier deschis are, în mod implicit, atributul $20 (arhivă) deoarece în el s-a scris sau
urmează să se scrie un articol. Fişierele binare cu atributul $01 pot fi prelucrate numai
dacă sunt deschise cu Reset şi dacă variabilei FileMode din unit-ul System i se
atribuie, în program, valoarea zero. Fişierele TEXT cu atributul $01 pot fi doar citite
(suportă numai deschiderea Reset).
Setarea pe o anumită valoare înseamnă, uneori, cumularea de atribute. De
exemplu, dacă unui fişier cu atributul $10 (subdirector) i se setează valoarea
2 (hidden), se obţine un subdirector ascuns (atributul $18).
♦ Procedura GetFAttr returnează atributele unui fişier. Ea este definită astfel:
GetFAttr(VAR f; VAR attr:WORD)
F este variabila care indică fişierul, iar attr este variabila în care procedura
depune valoarea atributelor.

Exemplu:
8.3. În secvenţa care urmează se citesc atributele fişierului extern 'TEST.DAT'
şi se afişează valorile acestora:

USES Dos;
VAR
f:TEXT;
i:BYTE;
BEGIN
.............................
Assign(f,'TEST.DAT');
GetFAttr(f,i);
Writeln(i);
Reset(f);
.............................
♦ Procedura SetFAttr poziţionează (setează) atributele unui fişier. Ea este

149
Programarea calculatoarelor– Tehnica programării în limbajul Pascal

definită astfel:
SetFAttr(VAR f; VAR attr:WORD)

F este variabila care indică fişierul, iar argumentul real corespunzător lui attr
este o expresie a cărei valoare reprezintă atributele şi care va fi înscrisă extern în
intrarea ataşată fişierului în [sub]director.

Exemple:
8.4. În secvenţa care urmează, fişierului TEST1.DAT i se asociază atributul
Readonly, iar fişierului TEST2.DAT atributul Hidden:

USES Dos;
VAR
f1:TEXT;
f2:FILE OF READ;
BEGIN
........................
Assign(f1,'TEST1.DAT');
SetFAttr(f1,1); Reset(f1);
........................
Assign(f2,'TEST2.DAT');
SetFAttr(f2,Hidden); Reset(f2);
.......................

8.5. În programul care urmează, un subdirector capătă atributul ascuns. După


setare, valoarea atributelor subdirectorului este 18 (Directory+hidden).

USES Dos;
VAR
f:FILE;
nume_dir:STRING[79];
BEGIN
Write('Director [n:\cale\director] : ');
Readln(nume_dir);
Assign(f,nume_dir);
SetFAttr(f,2);
END.

Procedurile GetFAttr şi SetFAttr au funcţii echivalente cu comanda ATTRIB


din DOS.

8.3.2 Prelucrarea datei şi orei din eticheta unui fişier

În unit-ul Dos sunt definite proceduri pentru poziţionarea sau citirea datei şi
orei ultimei scrieri în fişier. Procedurile prelucreză câmpurile $16-$17 şi $18-$19 din
eticheta fişierului (intrarea lui în [sub]director), ca o singură valoare de tip LONGINT.
"Împachetarea", respectiv "despachetarea" în/din LONGINT se pot realiza cu
procedurile PackTime, respectiv UnpackTime, definite în unit-ul Dos. Aceste
proceduri utilizează tipul de date DateTime predefinit în unit-ul Dos, astfel:

150
Prelucrarea etichetelor de fişier şi a altor informaţii externe memorate în discuri

DateTime = RECORD
Year : WORD;
Month : WORD;
Day : WORD;
Hour : WORD;
Min : WORD;
Sec : WORD;
END;
♦ Procedura PackTime "împachetează" un articol de tip DateTime într-o dată
de tip LONGINT. Ea este definită astfel:
PackTime(VAR Dc:DateTime; VAR l:LONGINT)
Dc este data şi ora, exprimate pe componente, care urmează să fie
"împachetate" sub formă LONGINT în zona Dl.
♦ Procedura UnpackTime "despachetează" o valoare de tip DateTime. Ea
este definită astfel:
UnpackTime(Dl:LONGINT, VAR Dc:DateTime)
Dl este expresie de tip LONGINT care va fi "despachetată" pe componentele
datei şi orei în zona Dc.
♦ Procedura GetFTime returnează data şi ora ultimei scrieri într-un fişier. Ea
este definită astfel:
GetFTime(VAR f; VAR Dl:LONGINT)
F este variabila care indică fişierul, iar Dl este variabila în care se recepţio-
nează data şi ora, sub formă "împachetată". Despachetarea lui Dl se face cu procedura
UnpackTime.

Exemplu:
8.6.
USES Dos;
VAR
f1: FILE OF REAL
z: DateTime;
data_ora: LONGINT;
BEGIN
.....................
Assign(f1,'A:TEST.DAT');
GetFTime(f1, Data_ora);
UnpackTime(data_ora,z);
IF Z.Year < 1994
THEN Writeln('>> Varianta veche de fisier')
ELSE Writeln('>> Varianta OK');

♦ Procedura SetFTime înscrie data şi ora în eticheta fişierului. Ea este defi-


nită astfel:
SetFTime(VAR f; Dl:LONGINT)
F este variabila care indică fişierul, iar Dl este variabila care conţine data şi ora

151
Programarea calculatoarelor– Tehnica programării în limbajul Pascal

sub formă împachetată, care se înscriu în intrarea fişierului în [sub]director. Forma


împachetată poate fi obţinută cu procedura PackTime.

Exemplu:
8.7.
USES Dos;
VAR
f1:FILE OF REAL;
z:DateTime; data_ora:LONGINT;
BEGIN
........................
ASSIGN (f1,'A:TEST.DAT');
z.Year:=1993; z.Month:=10; z.Day:=28;
z.Hour:=11; z.Min:=20; z.Sec:=0;
PackTime(z,Data_ora); SetFTime(f1,Data_ora);
..........................

8.3.3 Ştergerea fişierelor

În unit-ul System este definită procedura Erase care realizează ştergerea unui
fişier existent (şterge eticheta din director). Declaraţia ei este:
Erase(VAR f)
Dacă fişierul extern, asignat lui f, nu există, execuţia procedurii generează
eroare de I/E (IOResult are valoare diferită de zero). Procedura Erase nu se poate
executa asupra unui fişier deschis.

Exemplu:
8.8. Se citesc 100 de numere x. Elementele pozitive sunt memorate în fişierul
cu tip VECTOR.DAT. Dacă toate numerele sunt negative, fişierul VECTOR.DAT nu
trebuie să existe.

VAR
Fis:FILE OF REAL;
x:REAL; i,j:1..100; s:BOOLEAN;
BEGIN
Assign(Fis,'VECTOR.DAT'); Rewrite(Fis);
j:=FALSE;
FOR i:=1 TO 100 DO
BEGIN
Readln(x)
IF x[i] >= 0 THEN
BEGIN
Write(Fis,x);
j:=TRUE;
END;
END;
Close(Fis);
IF NOT j THEN Erase(Fis);
END.

152
Prelucrarea etichetelor de fişier şi a altor informaţii externe memorate în discuri

8.3.4 Redenumirea fişierelor

Redenumirea unui fişier existent (modificarea numelui fişierului din etichetă)


se realizează cu procedura Rename, declarată în unit-ul System, astfel:
Rename(VAR f; nume_nou:STRING)
Numele intern, f, trebuie asignat anterior unui fişier (vechi) prin procedura
Assign. Procedura Rename va redenumi acest fişier extern cu nume_nou (care este o
expresie de tip STRING, cu lungimea maximă de 80 caractere). Procedura nu copiază
fişiere, ci doar le redenumeşte. De aceea, unitatea şi calea fişierului vechi nu pot fi
modificate. Procedura nu se aplică fişierelor deschise. Dacă fişierul extern, asociat lui f
prin Assign (fişierul vechi), nu există, execuţia procedurii generează eroare de I/E
(IOResult are valoare diferită de zero). Dacă pe unitatea şi în [sub]directorul unde este
memorat fişierul vechi există deja un fişier cu numele nume_nou, procedura Rename
produce eroare de I/E.

8.3.5 Trunchierea fişierelor

În unit-ul System este definită procedura Truncate, care trunchiază un fişier


binar de la poziţia curentă a pointerului. Ea este definită astfel:
Truncate(VAR f)
Procedura şterge din fişier articolele începând din poziţia curentă a pointerului,
până la sfârşit, modificând lungimea fişierului din etichetă. După execuţia ei, funcţia
Eof(f) va returna valoarea TRUE.

8.4 Crearea şi manipularea [sub]directoarelor

În unit-ul System există o serie de proceduri pentru crearea şi ştergerea


[sub]directoarelor, pentru definirea [sub]directorului curent şi pentru obţinerea valorii
căii curente. Toate aceste proceduri au echivalent în comenzi DOS şi funcţionează
identic cu acestea. În plus, având în vedere că subdirectoarele sunt tratate ca fişiere cu
articole speciale, asupra lor pot fi aplicate funcţiile referitoare la citirea şi setarea
atributelor, datei şi orei creării subdirectorului.
♦ Procedura GetDir returnează calea completă a subdirectorului curent (path-
ul), dintr-o unitate de disc precizată. Declaraţia ei este:
GetDir(Drive:BYTE; VAR s:STRING)
Drive specifică unitatea de disc asupra căruia se aplică procedura şi poate avea
valorile: 0 - unitatea de disc curentă; 1 - unitatea de disc A; 2 -unitatea de disc B;
3 - unitatea de disc C etc.

153
Programarea calculatoarelor– Tehnica programării în limbajul Pascal

S este variabila în care se recepţionează calea completă a subdirectorului


curent, începând cu rădăcina. Forma căii returnate este n:cale, unde n este numele
unităţii de disc. Procedura este echivalentă cu comanda CHDIR (fără parametri) din
DOS.

Exemple:
8.9. Dacă unitatea C: ar avea structura de directori din figura 1.4 şi dacă
directorul curent este D4, atunci procedura GetDir(3,Cale) ar returna, în variabila Cale,
şirul: C:\D2\D4;

8.10. Procedura GetDir (9,Cale) returnează, în variabila Cale, şirul I:\, chiar
dacă unitatea I:\ nu a fost definită pe discul fix.

♦ Procedura ChDir schimbă directorul curent. Ea are următoarea declaraţie:


ChDir(s:STRING)
S conţine un şir de forma [n:][cale] care defineşte noul subdirector curent.
Calea poate fi precizată complet, pornindu-se de la rădăcină sau într-un mod relativ,
folosindu-se formele construite cu următoarele caractere: \ (directorul rădăcină); .
(directorul curent); .. (directorul părinte al celui curent).

Exemplu :
8.11. Dacă în structura de directoare din figura 1.4, directorul curent este D4 şi
se doreşte ca D3 să devină curent, se poate proceda astfel:
ChDir('C:\D3'); sau
ChDir('\D3'); sau
ChDir('..'); ChDir('..'); ChDir('D3');

Când calea este specificată greşit se generează eroare (path not found).
Procedura are funcţie echivalentă cu comanda CHDIR din DOS.
♦ Procedura MkDir creează un director în disc. Ea are următoarea declaraţie:
MkDir(s:STRING)
S conţine un şir de forma [n:]cale care defineşte subdirectorul care se creează.
Lungimea maximă a căii, de la rădăcină până la nivelul dorit, nu trebuie să depăşească
63 caractere, inclusiv caracterele \. Calea poate fi precizată complet, pornindu-se de la
rădăcină sau relativ, folosindu-se formele prescurtate construite cu caracterele \, .,
..(vezi ChDir). Procedura are funcţie echivalentă cu comanda MKDIR din DOS.

Exemplu:
8.12. Dacă în structura de director din figura 1.4 se doreşte crearea unui
subdirector D6, subordonat lui D3, se poate proceda astfel:

MkDir('C:\D3\D6') ¾ de oriunde;
MkDir('\D3\D6') ¾ de oriunde din unitatea C;
MkDir('D6') ¾ din D3;

154
Prelucrarea etichetelor de fişier şi a altor informaţii externe memorate în discuri

♦ Procedura RmDir şterge un subdirector gol. Ea are următoarea definiţie:


RmDir(s:STRING)
S conţine un şir de forma [n:]cale ce desemnează subdirectorul care se şterge.
Calea poate fi specificată complet, pornindu-se de la rădăcină sau relativ, folosindu-se
formele prescurtate construite cu caracterele \, ., .. (vezi ChDir).
Subdirectorul care se şterge nu trebuie să conţină fişiere sau subdirectoare
ataşate. În cazul în care subdirectorul nu există sau nu este gol se produce eroare de I/E.
Directorul rădăcină şi subdirectorul curent nu pot fi şterse. Procedura are funcţie
echivalentă cu comanda RMDIR din DOS.

Exemplu:
8.13. Se propune o procedură care asigură realizarea următoarelor operaţii, în
funcţie de valoarea unui parametru (op): determinarea directorului curent, schimbarea
directorului curent, crearea unui director. Procedura se construieşte în cadrul unui unit
(GestDir), care poate fi extins şi cu alte proceduri de interes general în lucrul cu
directoare şi/sau fişiere.

UNIT GestDir;
INTERFACE
PROCEDURE OpDir(op:CHAR; VAR d:STRING; VAR rez:INTEGER);
IMPLEMENTATION
PROCEDURE OpDir;
BEGIN
CASE UpCase(op) OF
'D': GetDir(0,d);
'S': BEGIN
{$I-} ChDir(d); {$I+}
rez:=IOResult;
END;
'C': BEGIN
{$I-} MkDir(d); {$I+}
rez:=IOResult;
END;
END;
END;
END.

În continuare se prezintă un apelator al subprogramului, prin care se urmăreşte


schimbarea directorului curent, cu salvarea directorului în uz de la momentul lansării,
respectiv restaurarea lui la încheierea execuţiei programului. Dacă noul director nu
există, se asigură posibilitatea creării lui, urmată de transformarea în director curent, în
aceleaşi condiţii. Specificatorul noului director este furnizat ca parametru în linia de
comandă cu care este lansat în execuţie programul. Numărul parametrilor de pe linia de
comandă este determinat prin funcţia ParamCount:WORD. Accesul la un anumit
parametru se face prin funcţia ParamStr(n:WORD):STRING, unde n este numărul
de ordine al parametrului în lista din linia de comandă.
PROGRAM ExDir;
USES GestDir;

155
Programarea calculatoarelor– Tehnica programării în limbajul Pascal

VAR r : INTEGER; c : CHAR; dinc,dnou: STRING;


PROCEDURE DetDir;
VAR dir: STRING;
BEGIN
OpDir('d',dir,r);
WriteLn('Director curent: ',dir);
END;
BEGIN
WriteLn('Program in executie curenta: ',ParamStr(0));
DetDir;
IF ParamCount <> 0 THEN
BEGIN
dnou:=ParamStr(1);
OpDir('d',dinc,r);
OpDir('s',dnou,r);
IF r <> 0 THEN
BEGIN
WriteLn('Director inexistent: ',ParamStr(1));
Write('Se creeaza ca director nou?(Y/N): ');
ReadLn(c);
IF UpCase(c) = 'Y' THEN
BEGIN
OpDir('c',dnou,r);
IF r = 0
THEN OpDir('s',dnou,r)
ELSE
BEGIN
WriteLn('>> Eroare la creare director.
Executie intrerupta!' );
RunError(r);
END;
END;
END;
END;
DetDir;
END;
{restul programului}
IF ParamCount <> 0 THEN OpDir('s',dinc,r);
DetDir;
END.

8.5 Căutarea fişierelor în [sub]directoare

În unit-ul Dos sunt definite proceduri care caută un fişier într-un [sub]director
sau într-o listă de [sub]directore. Procedurile utilizează tipurile de data PathStr şi
SearchRec, definite în unit-ul Dos, astfel:
PathStr=STRING[79];
SearchRec = RECORD
Fill : ARRAY[1..21] OF BYTE;
Attr : BYTE;
Time: LONGINT;
Size : LONGINT;

156
Prelucrarea etichetelor de fişier şi a altor informaţii externe memorate în discuri

Name : STRING[12];
END;
Semnificaţia câmpurilor tipului SearchRec este următoarea:
Fill: rezervat pentru sistemul de operare. Câmpul nu trebuie modificat în
program.
Attr: atributele fişierului (valoarea octetului $0B din intrarea fişierului în
[sub]director).
Time: data şi ora ultimei scrieri în fişier, sub formă împachetată (valoarea, ca
LONGINT, a octeţilor $16-$19 din intrarea fişierului în [sub]director).
Size: lungimea, în octeţi, a fişierului (valoarea câmpului $1C-$20 din intrarea
fişierului în [sub]director).
Name: numele fişierului urmat de punct şi extensia lui (valoarea câmpurilor
$0-$7 şi $8-$A din intrarea fişierului în [sub]director).
♦ Procedura FindFirst caută într-un [sub]director prima intrare a unui fişier
care are specificatorul şi atributul precizate în lista de parametri. Ea are declararea:
FindFirst(F_extern:PathStr; Attr:Word; VAR zona:SearchRec)
F_extern este specificatorul extern al fişierului care este căutat. Specificatorul
este format din cale, nume fişier şi, eventual, extensie. Când calea lipseşte se presupune
[sub]directorul curent. Numele şi extensia fişierului pot fi globale (formate cu
caracterele * sau ?). Attr reprezintă valoarea atributelor fişierului care se caută. Zona
este o variabilă de tipul SearchRec, definit în unit-ul Dos, care va conţine informaţii
despre fişierul f_extern, în cazul în care a fost găsit. Dacă f_extern nu este găsit,
variabila zona rămâne nemodificată. Când f_extern este găsit, variabila DosError
(definită în unit-ul Dos) va avea valoarea zero. În caz contrar, DosError are valoare
diferită de zero (vezi §8.3).
♦ Procedura FindNext caută într-un [sub]director următoarea intrare a unui
fişier care are specificatorul şi atributul precizate la apelul anterior al procedurii
FindFirst. De aici rezultă faptul că procedura FindNext poate fi utilizată numai dacă,
anterior, a fost apelată procedura FindFirst.
Procedura este definită astfel:
FindNext(VAR zona:SearchRec)
Zona are aceeaşi semnificaţie ca la procedura FindFirst. Dacă următoarea
intrare este găsită, variabila DosError (definită în unit-ul Dos) va avea valoarea zero.
În caz contrar, DosError are valoare diferită de zero.

Exemplu:
8.14. Următorul program afişează toate fişierele cu extensia .DAT dintr-o cale
precizată de la tastatură. Pentru fiecare fişier se afişează: numele, extensia, atributul,
lungimea, data şi ora ultimei scrieri în fişier.
PROGRAM EX14;
USES Dos;
VAR
Cale:String[79];
Zona:SearchRec; {Tip definit in Dos}

157
Programarea calculatoarelor– Tehnica programării în limbajul Pascal

Dc:DateTime; {Tip definit in Dos}


BEGIN
Write('Calea de cautat ([u:]\d1\d2\):');
Readln(Cale); {Se citeste calea de la tastatura; pentru
directorul curent se poate tasta ENTER}
FindFirst(Cale+'*.DAT',$3F,Zona); {Cautarea primului fisier
cu extensia .DAT}
IF DosError <> 0
THEN Writeln('>> Nu exista fisier de tip .DAT')
ELSE
REPEAT
UnpackTime(Zona.Time,Dc);
Writeln(Zona.Name:12,'',Zona.Attr:2,'',Zona.Size:10,'',Dc.Year
:4,':',Dc.Month:2,':',Dc.Day:2,'',Dc.Hour:2,'H',Dc.Min:2,'M',Dc.
Sec:2,'S');
FindNext(Zona);
Until DosError=18;
END.

♦ Funcţia FSearch caută un fişier într-o listă de [sub]directori. Ea este


asemănătoare comenzii PATH din DOS. Funcţia este definită astfel:
FUNCTION FSearch(Nume:PathStr; Lista:STRING):PathStr
Nume este variabilă de tip PathStr (definită în unit-ul Dos), care conţine
numele fişierului de căutat. Lista conţine lista directoarelor în care se continuă
căutarea, dacă fişierul nu a fost găsit în directorul curent. Căile specificate în listă
trebuie separate prin caracterul ;.
Funcţia returnează specificatorul extern al fişierului, în cazul în care îl găseşte.
Când fişierul este în directorul curent, specificatorul extern furnizat este format din
nume şi, eventual, extensie, iar, când fişierul este în alt director, specificatorul este
complet (cale + nume [+ extensie]).
Când fişierul nu este găsit, funcţia returnează şirul vid. Funcţia nu verifică
existenţa căilor. Când căile nu există, se returnează tot şirul vid.

Exemplu :
8.15. Programul care urmează caută un fişier într-o listă de directori. Când
fişierul este găsit se afişează specificatorul extern. În caz contrar, se afişează mesajul
>>Fişier negăsit.

PROGRAM EX15;
USES Dos;
VAR
Nume:PathStr;
x:STRING;
BEGIN
Write('Numele extern al fisierului cautat (xxxxxxxx.eee):');
Readln(Nume);
x:=FSearch(Nume,'C:\wp51\;\wp51\rosca\;\tp\');
IF x[0] = #0
THEN Writeln('>>Fisier negasit')
ELSE Writeln('>>Fisierul are specificatorul extern : ',x)
END.

158
Prelucrarea etichetelor de fişier şi a altor informaţii externe memorate în discuri

8.6 Tratarea şirului referitor la specificatorul


extern de fişier

În unit-ul Dos sunt definite o funcţie şi o procedură care prelucrează şirul de


caractere referitor la specificatorul extern al unui fişier. Funcţia (FExpand) şi
procedura (FSplit) nu fac nici o verificare sau căutare în suportul extern. Ele fac parte,
mai degrabă, din categoria prelucrării unor şiruri de caractere, decât din cea de
prelucrare a fişierelor.
În definirea funcţiei şi procedurii se folosesc următoarele tipuri de date
declarate în unit-ul Dos:
PathStr = STRING[79]; ¾ Specificator complet;
DirStr = STRING[67]; ¾ Unitate şi cale;
NameStr = STRING[8]; ¾ Numele propriu-zis;
ExtStr = STRING[4]; ¾ Extensie.
♦ Funcţia FExpand expandează (completează) numele cu calea. Ea este
definită astfel:
FUNCTION FExpand(Nume:PathStr):PathStr
Nume este variabilă de tip PathStr care conţine numele ce urmează să fie
expandat cu calea. În procesul prelucrării fişierelor, funcţia are sens când este utilizată
pentru extinderea cu componente (unitate, cale) furnizate implicit.

Exemple: Se consideră directorul curent C:\TP


8.16. FExpand('Test1.DAT') ¾ returnează C:\TP\TEST1.DAT

8.17. FExpand('\TP\Test1.DAT') ¾ returnează C:\TP\TEST1.DAT

8.18. FExpand('\Wp51\Test1.DAT') ¾ returnează C:\WP51\TEST1.DAT

8.19. FExpand('A:TEST1.DAT') ¾ returnează A:TEST1.DAT

8.20. Programul care urmează caută un fişier într-o listă de directori. Când
fişierul este găsit se afişează specificatorul extern. În caz contrar se afişează mesajul
>>Fişier negăsit.
Deosebirea faţă de exerciţiul 15 constă în aceea că, în cazul în care fişierul este
găsit în directorul curent, se scrie specificatorul complet (n:\cale\nume_fişier.extensie).
PROGRAM EX20;
USES Dos;
VAR
Nume:PathStr;
x:STRING;
BEGIN
Write('Numele extern al fisierului cautat (xxxxxxxx.eee):');
Readln(Nume);
x:=FSearch(Nume,'C:\wp51\;\wp51\rosca\;\tp\');

159
Programarea calculatoarelor– Tehnica programării în limbajul Pascal

IF x[0] = #0
THEN Writeln('>>Fisier negasit')
ELSE Writeln('>>Fisierul are specificatorul extern :
',FExpand(x))
END.

♦ Procedura FSplit descompune specificatorul extern (format din unitate,


cale, nume, extensie) în trei componente: unitate+cale, nume, .extensie. Procedura este
definită astfel:
FSplit(specificator:PathStr;VAR unit_dir:DirStr;VAR name:NumeStr;VAR
ext:ExtStr)
Specificator este şirul care se analizează şi se descompune în componente.
Unit_dir, nume şi ext sunt variabile în care se depun cele trei componente extrase din
specificator. Când unitatea şi calea nu sunt prezente în specificator, se returnează
valorile implicite. Când numele şi extensia nu sunt prezente în specificator, se
returnează şiruri vide.

Exemplu:
8.21. Fie un program care lucrează cu un fişier al cărui nume este introdus de la
tastatură. În cazul în care de la tastatură se introduce <ENTER>, se lucrează cu fişierul
implicit EX.DAT.
PROGRAM EX21;
USES Dos;
VAR
f:FILE OF REAL;
spec:PathStr; u:DirStr;
n:NameStr; e:ExtStr;
BEGIN
Write('Nume fi•ier (EX.DAT):');
Readln(Spec);
FSplit(Spec, u, n, e);
IF n = '' THEN n:='Ex';
IF e = '' THEN e:='.DAT';
Spec:=u+n+e;
Assign(f, spec);
Reset(f);
{Prelucrare}
Close (f);
END.

8.7 Funcţii pentru determinarea capacităţii


şi a ocupării discului

În unit-ul Dos există definite două funcţii care returnează capacitatea unui disc
şi spaţiul neocupat din el. Dacă funcţiile se aplică unităţilor de disc flexibil, în cazul în
care nu este montat discul, se generează un mesaj de sistem care solicită acest lucru
(afirmaţia este valabilă pentru toate funcţiile şi procedurile Pascal care referă unităţi de
discuri flexibile).

160
Prelucrarea etichetelor de fişier şi a altor informaţii externe memorate în discuri

♦ Funcţia DiskSize returnează capacitatea totală, în octeţi, a discului montat


în unitatea specificată. Ea are declaraţia:
Function DiskSize(Drive:BYTE):LONGINT
Drive specifică unitatea de disc, astfel: 0 - unitatea de disc curentă; 1 - unitatea
de disc A; 2 - unitatea de disc B; 3 - unitatea de disc C etc.
Funcţia returnează valoarea -1 dacă unitatea de disc nu există, dacă nu este
montat disc în unitate sau dacă unitatea este defectă.
♦ Funcţia DiskFree returnează numărul de octeţi liberi de pe un disc montat
într-o unitate. Ea are declaraţia:
Function DiskFree(Drive:BYTE):LONGINT
Drive are aceeaşi semnificaţie ca la funcţia DiskSize. Funcţia returnează
valoarea -1 dacă unitatea de disc nu există, dacă nu este montat disc în unitate sau dacă
unitatea este defectă.

Exemplu:
8.22. Se presupune că urmează să fie creat un fişier, MAT.DAT, care are
aproximativ 10000 octeţi. Se poate testa, cu o oarecare aproximare, dacă fişierul încape
pe un anumit disc (fie el C:). Trebuie reţinut faptul că octeţii liberi sunt grupaţi în
clustere libere.

USES Crt;
.........
BEGIN
.........
IF DiskFree(3) = -1 THEN
Writeln('>>Unitatea C defecta')
ELSE
IF DiskFree(3) > 10000 THEN
{Asignare, deschidere, creare}
ELSE
Writeln('>>Spatiu insuficient pentru fisier');
..........

161
UNELE ASPECTE TEHNICE
REFERITOARE LA PRELUCRAREA
FIŞIERELOR

Înţelegerea corectă a mecanismelor referitoare la prelucrarea fişierelor necesită


cunoaşterea unor detalii tehnice de realizare a operaţiilor de I/E. Câteva din ele sunt
prezentate în continuare, altele în anexa 3.

9.1 Realizarea fizică a transferului de date

Procedurile şi funcţiile de I/E asigură, prin intermediul DOS, trecerea dintre


nivelul logic, specific utilizatorului, care consideră fişierul ca o succesiune de articole
(blocuri, linii, câmpuri) şi nivelul fizic de organizare a datelor, în raport cu care fişierul
apare ca o succesiune de octeţi. În suportul extern magnetic (disc), înregistrarea datelor
se face pe număr întreg de sectoare. Sub sistemul de operare MS-DOS, sectorul are 512
octeţi şi reprezintă unitatea de transfer cu memoria principală. Fiecărui fişier îi sunt
alocate două tipuri de zone de memorie internă: zonă utilizator şi zonă tampon.
Zona utilizator este descrisă în VAR sau CONST. La ea are acces programul
utilizatorului. Zonă are semnificaţii diferite, în funcţie de tipul fişierului care se
prelucrează. Ea este fie un articol (la fişiere cu tip), fie un bloc (la fişiere fără tip), fie este
o mulţime de zone independente care sunt specificate în operaţiile de citire/scriere (la
fişierele TEXT).
Zona tampon (buffer) reprezintă zonă de memorie principală în/din care se face
transferul datelor din/în exterior şi are, sub MS-DOS, 528 octeţi. Un sistem poate lucra la
un moment dat cu mai multe buffer-e, numărul lor fiind stabilit la configurarea
sistemului (comanda BUFFERS din MS-DOS). Cu cât numărul de buffere este mai
mare cu atât creşte viteza de transfer, dar şi memoria internă ocupată. La citiri succesive,
sectoarele sunt încărcate în buffere "eliberate" şi de aici în zonele utilizator. Dacă, de
exemplu, se citeşte un articol de 120 octeţi, sistemul citeşte în buffer un sector întreg şi
mută, de aici, în zonă utilizator, 120 octeţi. La următoarea citire, sistemul va utiliza alt

162
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

buffer etc., astfel încât, la un moment dat, buffer-ele vor conţine cele mai recente date
citite. Dacă citirea se face aleator (nu secvenţial), se încarcă în buffer(e)
sectorul/sectoarele (ca întregi) care conţin(e) articolul. Dacă articolul este deja în
buffer(e), nu are loc transfer din exterior ci numai din buffer(e) în zonă utilizator.
Succesiunea de principiu a operaţiilor, în cazul unui flux general de date care
implică un singur buffer, este prezentată în figura 9.1. Ea este următoarea:
1. citirea unui sector din fişierul de intrare în zonă tampon asociată;
2. transferul datelor din buffer în zonă utilizator asociată fişierului de intrare;
3. pregătirea conţinutului zonei utilizatorului asociată fişierului de ieşire, pe baza
datelor preluate din zona fişierului de intrare sau din alte surse.

Fig.9.1 - Fluxul general de date în operaţiile de I/E

În limbajul PASCAL, aceeaşi zonă utilizator poate fi folosită atât pentru fişierul
de intrare, cât şi pentru cel de ieşire;
4. Transferul datelor din zona utilizator în buffer-ul fişierului de ieşire;
5. Scrierea în fişierul de ieşire a sectorului (când este completat), din zona
tampon.
Cu toate că procesul de trecere dintre nivelurile fizic şi logic are trăsături
principale comune, există deosebiri esenţiale de realizare pentru fişierele TEXT şi cele
binare. Pentru fişierele binare (cu tip şi fără tip) se poate considera valabilă schema de
principiu din figura 9.1. Transferul intern dintre buffer şi zonă utilizator (operaţiile 2 şi 4)
are loc fără conversii, iar operaţiile de intrare/ieşire dintr-un program pot avea loc pe
acelaşi fişier. Pentru fişierele TEXT, o prima particularitate constă în aceea că datele
sunt transferate în/din una sau mai multe zone de memorie independente şi neomogene
ca tip (figura 9.2).

163
Unele aspecte tehnice referitoare la prelucrarea fişierelor

Fig.9.2 - Principiul transferului datelor în cazul fişierelor TEXT

În plus, datele sunt "decupate" din zona buffer în zonele de date, pe baza unor
caractere cu rol de separator sau după alte reguli. Pentru datele numerice (întregi şi
reale), transferul din/în zonele buffer în/din zonele de date (operaţiile 2 şi 4 din figura
9.2) are loc cu conversie. Acelaşi lucru se întâmplă la scriere şi cu datele de tip
BOOLEAN.
Atât pentru fişierele binare cât şi pentru cele TEXT, operaţiile de transfer din
exterior în buffer (1) şi din acesta în zona utilizator (2) au loc, de regulă, ca urmare a
execuţiei procedurilor de citire. În unele situaţii, operaţia (1) are loc ca urmare a
execuţiei funcţiilor de testare a sfârşitului de fişier. Operaţiile de transfer din zona
utilizator în buffer (4) şi din acesta în exterior (5) au loc ca urmare a execuţiei
procedurilor de scriere.
Operaţiile 1 şi 2 (respectiv 4 şi 5) nu au loc, întotdeauna, simultan. Operaţia 1
are loc numai când buffer-ul de intrare este "eliberat", iar operaţia 5 are loc numai când
buffer-ul de ieşire este plin. Procesul se desfăşoară diferit la fişierele binare şi la cele
TEXT.
♦ La fişiere binare buffer-ul de intrare este "eliberat" ca urmare a execuţiei
unei operaţii de deschidere sau a citirii unui articol/bloc (Read, BlockRead) din fişierul
respectiv.

Exemplu:
9. 1. RESET(fis); ¾ buffer "eliberat"
..................
Read(fis,art); ¾ realizeaz• opera•iile 1 •i 2 din
figura 9.1 (buffer "eliberat")

164
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Funcţia Eof găseşte întotdeauna buffer-ul "eliberat" (fie că este înaintea unui
Read, fie că este după acesta), ceea ce determină ca funcţia să efectueze transferul din
exterior în zonă buffer (operaţia 1 din figura 9.1). Când procedura Read se execută după
apelul funcţiei Eof, atunci ea realizează numai operaţia (2).

Exemplu:
9.2.
WHILE NOT Eof(f) DO¾ Transfera din fi•ier în zon• buffer
(opera•ia 1)
BEGIN
Read(F,art);¾ Transfera din buffer în zon• articol
(opera•ia 2)
(* Prelucrare articol *)
END;

După testarea sfârşitului de fişier, funcţia Eof nu modifică pointerul, acesta


rămânând pe începutul buffer-ului. Tentativa de citire a unui articol, când Eof(f)=TRUE,
produce eroare de I/E (IOResult diferit de 0).
Buffer-ul de ieşire se consideră plin la fiecare scriere de articol/bloc. Procedurile
Write şi BlockWrite realizează întotdeauna operaţiile 4 şi 5 din figura 9.1.
♦ La fişierele TEXT buffer-ul de intrare se consideră "eliberat" după execuţia
unei operaţii de deschidere (implicită sau explicită) sau când pointerul avansează în
buffer după sfârşitul de linie (CR/LF) sau după marcatorul de sfârşit de fişier
(CTRL/Z).

Exemple:
9.3. Se presupun trei variabile a, b, c, de tip numeric şi secvenţa de program:

Read(a); {primul Read din program}


Read(b);
Read(c);

Înaintea primului Read, buferul de intrare este "eliberat" (datorită deschiderii


implicite). De aceea, Read(a) transferă date din exterior în buffer. Se presupune că au
fost introduse urmatoarele valori:

165
Unele aspecte tehnice referitoare la prelucrarea fişierelor

Tot procedura Read(a) realizează transferul (cu conversie) din buffer în zona a
(a=12). Pointerul se plasează în poziţia 1. Procedura Read(b) găseşte bufferul
"neeliberat", deci nu execută transfer din exterior, ci numai din buffer în zona b (b=300).
Pointerul se plasează în poziţia 2. Procedura Read(c) găseşte buffer-ul "neeliberat", deci
nu execută transfer din exterior, ci analizează caracterele din buffer. Cum la citirea
datelor numerice spaţiile şi caracterele CR/LF sunt ignorate, pointerul avansează până în
poziţia 3, când buffer-ul devine "eliberat", producându-se în acest moment o cerere de
transfer din exterior în buffer etc. În concluzie, secvenţa anterioară realizează:
Read(a) ¾ opera•iile 1 •i 2;
Read(b) ¾ opera•ia 2;
Read(c) ¾ analiza, opera•ia 1 etc..

9. 4. Se presupun trei variabile a, b, c de tip numeric şi secvenţa de program:

ReadLn(a) ; {prima citire din program}


ReadLn(b) ;
ReadLn(c) ;

Înaintea primului ReadLn buffer-ul de intrare este "eliberat" (datorită deschiderii


implicite). De aceea, ReadLn(a) transferă date din exterior în buffer. Se presupune că au
fost introduse urmatoarele valori:

Tot procedura ReadLn(a) transferă (cu conversie) din buffer în zona a (a=12) şi
plasează pointerul la sfârşitul liniei (în poziţia 1), după CR/LF.
Procedura ReadLn(b) găseşte buffer-ul "eliberat" şi provoacă cerere de transfer
din exterior etc.

Funcţia Eof poate găsi buffer-ul "eliberat" sau "neeliberat". Când îl găseşte
"eliberat" produce un transfer din exterior în buffer, plasează pointerul pe începutul său
şi testează dacă este sfârşit de fişier (CTRL/Z). Dacă funcţia Eof găseşte buffer-ul
"neeliberat", testează dacă pointerul indică sfârşitul de fişier. După testarea sfârşitului de
fişier, funcţia Eof nu modifică pointerul .
Eliberarea buffer-ului unui fişier TEXT, cu scrierea conţinutului său în suportul
extern, se poate realiza cu procedura Flush(f). F trebuie deschis pentru scriere.
În concluzie, atât pentru fişierele binare, cât şi pentru cele TEXT, trebuie
reţinute următoarele observaţii importante: • nu întotdeauna operaţiile Read, ReadLn,

166
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

BlockRead produc transfer din exterior în memoria principală. Transferul are loc numai
dacă buffer-ul este "eliberat"; • funcţia Eof realizează şi operaţia de transfer din exterior
în memoria principală. Acest lucru se întâmplă când, la execuţia funcţiei, buffer-ul de
intrare este "eliberat".

9.2 Accesul la blocul de informaţii despre fişier

Declararea variabilei asociate fişierului, de orice tip, are ca efect rezervarea şi


iniţializarea parţială de către compilator a unui bloc de informaţii (File Informaţion
Block). Unele câmpuri ale blocului primesc valori la execuţia procedurii Assign, iar
altele la deschiderea fişierului. Pentru fiecare fişier din program, compilatorul generează
câte un FIB. Numele simbolic al blocului coincide cu numele intern asociat fişierului
prin descrierea din secţiunea VAR. Informaţiile din FIB sunt utilizate, la execuţie, de
procedurile şi funcţiile care realizează operaţii de I/E.
Structura FIB este definită în unit-ul Dos prin două declaraţii de tip: FileRec,
pentru fişierele binare şi TextRec, pentru fişierele TEXT.
♦ Tipul FileRec reprezintă structura FIB pentru fişiere cu tip şi fără tip. El este
definit astfel:
FileRec = RECORD
Handle : WORD;
Mode : WORD;
RecSize : WORD;
Private : ARRAY[1...16] OF BYTE;
UserData : ARRAY[1...16] OF BYTE;
Name : ARRAY[0...79] OF CHAR;
END;
♦ Tipul TextRec reprezintă structura FIB pentru fişierele TEXT. El este
definit astfel:
TextRec = RECORD
Handle : WORD;
Mode : WORD;
BufSize : WORD;
Private : WORD;
BufPos : WORD;
BufEnd : WORD;
BufPtr : ^TextBuf;

167
Unele aspecte tehnice referitoare la prelucrarea fişierelor

OpenFunc : Pointer;
InOutFunc : Pointer;
FlushFunc : Pointer;
UserData : ARRAY[1...16] OF BYTE;
Name : ARRAY[0...79] OF CHAR;
Buffer : TextBuf;
Semnificaţia principalelor câmpuri este următoarea:
• Handle este o valoare întreagă care reprezintă identificatorul pentru fişiere
deschise. Valorile 1-7 sunt utilizate pentru dispozitivele standard de I/E (intrare, ieşire,
auxiliar, imprimantă, fişierele în curs de folosire cu comanda PRINT din DOS şi de
reţea). Fişierele deschise ale utilizatorului au valori pentru handle începând cu 8.
Fişierele nedeschise au handle=0. O valoare handle asociată unui fişier devine
disponibilă la închiderea acestuia.

Exemple:
9.5. Dacă într-un program se lucrează simultan cu trei fişiere, valorile handle
asociate sunt 8, 9, 10.

9.6. Dacă într-un program se lucrează cu trei fişiere deschise şi închise pe rând,
fiecare va avea valoarea handle opt.

Într-un program pot fi deschise simultan maxim 12 fişiere ale utilizatorului


(handle cu valori din intervalul [8, 19]).
• Mode indică starea fişierului, care poate fi exprimată şi prin următoarele
constante definite în unit-ul Dos:
FmClosed = $D7B0 ¾ fişier închis;
FmInput = $D7B1 ¾ fişier deschis pentru citire;
FmOutput = $D7B2 ¾ fişier deschis pentru scriere;
FmInOut = $D7B3 ¾ fişier deschis pentru citire/scriere;
Fişierele TEXT pot avea stările FmClosed, FmInput, FmOutput. Fişierele
binare pot avea orice stare (implicit FmClosed sau FmInOut). În unit-ul System este
definită variabila FileMode astfel:
FileMode:BYTE=2
Variabila determină modul de deschidere a fişierelor binare de către procedura
Reset. Valorile ei posibile sunt: 0 - fişier FmInput; 1 - fişier FmOutput; 2 - fişier
FmInOut. Variabila are valoarea implicită 2. Atribuirea altei valori are ca efect folosirea
acestui mod de către toate apelurile ulterioare ale procedurii Reset.
• RecSize indică lungimea articolului (blocului) rezultată din descrierea internă
programului.

168
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

• Name este identificatorul extern al fişierului, aşa cum este precizat de pro-
cedura Assign.
• BufSize reprezintă lungimea buffer-ului fişierului TEXT.
• BufPos, BufEnd, BufPtr reprezintă pointeri folosiţi în gestiunea buffer-elor
asociate fişierului TEXT.
• OpenFunc, InOutFunc, CloseFunc, FlushFunc reprezintă adresa
driver-elor pentru deschidere, citire/scriere, închidere, golire a buffer-ului.
• Buffer reprezintă zonă tampon asociată fişierului (chiar buffer-ul fişierului).
Tipul TextBuf este definit în unit-ul Dos astfel: TextBuf = ARRAY[0...127] of CHAR.
Pentru a avea acces la informaţiile din FIB, trebuie declarată o variabilă de tip
FileRec (respectiv TextRec) care să aibă aceeaşi adresă cu FIB-ul (cu variabila de tip
fişier):
a) Pentru fişiere TEXT:
VAR
f: TEXT; inf_f: TextRec ABSOLUTE f;
b) Pentru fişiere cu tip:
VAR
f: FILE OF tip; inf_f: FileRec ABSOLUTE f;
c) Pentru fişiere fără tip:
VAR
f: FILE; inf_f:FileRec ABSOLUTE f;
Câmpurile zonei inf_f se adresează cu denumirile lor din definirea articolului
FileRec, respectiv TextRec, din unit-ul Dos.

9.3 Variabile şi funcţii pentru prelucrarea erorilor de I/E

În fiecare dintre unit-urile standard sunt definite variabile şi funcţii care pot
semnala modul de desfăşurare a operaţiilor de intrare/ieşire.
♦ În unit-ul System sunt definite următoarele variabile şi funcţii:
• Variabila InOutRes conţine codurile de eroare la execuţie, generate de rutinele
de I/E, corespunzând unor situaţii cum sunt (anexa 3): eroare la citire de pe disc (codul
100), la scriere pe disc (101), fişier neasignat (102), fişier nedeschis (103), fişier
nedeschis pentru intrare (104), fişier nedeschis pentru ieşire (105), format numeric
invalid (106). Variabila InOutRes are valoarea zero dacă operaţia de I/E s-a desfăşurat
normal. Ea este definită astfel: InOutRes:Integer=0;
• Funcţia IOResult returnează programului valoarea variabilei InOutRes şi o

169
Unele aspecte tehnice referitoare la prelucrarea fişierelor

pune pe aceasta pe zero. Dacă valoarea InOutRes este diferită de zero şi directiva de
compilare $I are valoarea {$I-}, programul nu se întrerupe, dar următoarea operaţie de
I/E nu se va mai executa; dacă are valoarea {$I+} programul se opreşte cu eroare de
execuţie. De aceea, dacă se doreşte controlul desfăşurării unei operaţii de I/E, se
procedează astfel (pe exemplul procedurii READ):
{$I-} {inhibă întreruperea programului}
Read (f,zonă);
{$I+} {autorizează execuţia urmatoarei operaţii de I/E}
IF IOResult <>0
THEN {eroare de I/E} ELSE {nu există eroare de I/E};
• Variabila ErrorAddr conţine adresa unde s-a produs eroarea de execuţie. În
cazul inexistenţei erorii, conţine valoarea nil. Variabila este definită astfel:
ErrorAddr:pointer=nil. Adresa este memorată sub forma ssss:dddd, unde ssss este
adresa segmentului de cod, iar dddd este offset-ul (deplasarea în cadrul segmentului).
♦ În unit-ul Dos sunt definite următoarele proceduri şi variabile:
• Variabila DosError, de tip INTEGER, este iniţializată de funcţiile şi proce-
durile din acest unit, cu următoarele valori principale (vezi anexa 1):
0 - fără eroare;
2 - fişier negăsit;
3 - cale negăsită;
4 - prea multe fişiere deschise;
5 - acces interzis; etc.
Variabila DosError are valoarea zero când execuţia functiilor şi a procedurilor
definite în unit-ul Dos se termină normal.
• Procedura SetVerify poziţionează comutatorul verify din DOS. Comutatorul
are două valori posibile: ON (valoare logică TRUE) sau OFF (valoare logică FALSE).
Când comutatorul are valoarea ON, sistemul de operare face verificare după fiecare
operaţie de scriere în fişiere (se verifică dacă datele scrise pot fi citite fără eroare). Poziţia
On a comutatorului verify măreşte timpul de execuţie a programelor. Când comutatorul
are valoarea OFF, sistemul de operare nu face verificarea scrierii. Valoarea implicită a
comutatorului este OFF. Valoarea comutatorului rămâne activă până la o nouă setare.
Procedura este defintă astfel:
SetVerify(v:Boolean)
V este o variabilă de tip BOOLEAN. Când v are valoarea TRUE, comutatorul
verify primeşte valoarea ON. În caz contrar primeşte valoarea OFF. Procedura are efect
similar cu comanda VERIFY din DOS. Dacă comutatorul verify este ON, rezultatul
verificării scrierii este memorat în variabila DosError.
În anexa 3 sunt prezentate principalele erori de execuţie, care includ şi pe cele
de intrare/ieşire.

170
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

9.4 Anomalii în tratarea lungimii articolelor (blocurilor)


fişierelor binare

Deoarece peste un fişier fizic poate fi suprapus orice tip de fişier, rămâne în
sarcina programatorului să asigure compatibilitatea cu cerinţele de prelucrare. În cazul
fişierelor binare, lungimea şi structura articolelor (blocurilor) pot diferi între momentul
creării şi cel al exploatării.

Exemplu:
9.7. La momentul creării, o matrice este scrisă câte o linie pe articol:

TYPE
a=ARRAY[1..10] of REAL;
VAR
Fis:FILE OF a;
Linie:a;
.....................
BEGIN
Assign(Fis,'MATRICE.DAT');
Rewrite(Fis);
.....................
Write(Fis,Linie);
.....................

La momentul exploatării, matricea poate fi citită câte un element pe articol:

VAR
Fis:FILE OF REAL;
Element:REAL;
.....................
BEGIN
Assign(Fis,'MATRICE.DAT');
Reset(Fis);
.....................
Read(Fis,Element);
.....................

Fie lart şi lbloc lungimea articolelor, respectiv blocurilor unui fişier binar care
are lungimea lfis, exprimată în octeţi. O prelucrare corectă a fişierului presupune să fie
îndeplinite următoarele condiţii: lfis MOD lart=0, respectiv lfis MOD lbloc=0.
În caz contrar se produc anomalii în prelucrare, ultimii lfis MOD lart, respectiv
lfis MOD lbloc octeţi, neputând fi prelucraţi.

Exemplu:
9.8. În programul demonstrativ care urmează se creează fişierul TEST.DAT care
are lungimea 8 octeţi (se scriu patru articole de câte doi octeţi). Fişierul este exploatat cu

171
Unele aspecte tehnice referitoare la prelucrarea fişierelor

articole de 6 octeţi (REAL), FileSize(f2) returnând valoarea 1 (8 DIV 6). Prima citire din
fişier transferă primii 6 octeţi. A doua citire produce eroare de I/E
(8 MOD 6 = 2).
PROGRAM Ex8A;
VAR
f1:FILE OF WORD;
x:WORD;
f2:FILE OF REAL;
y:REAL;
BEGIN
Assign(f1,'TEST.DAT'); Rewrite(f1);
x:=0;
Write(f1,x,x,x,x);
Close(f1);
Assign(f2,'TEST.DAT'); Reset(f2);
Writeln(FileSize(f2));
Read(f2,y);
Read(f2,y); {Error 100: Disk read error}
Close(f2);
END.

Este interesant de analizat şi modul în care lucrează funcţia Eof(f), în situaţia


fişierului anterior. Dacă, după prima citire (6 octeţi), se apelează funcţia Eof(f2), aceasta
returnează valoarea FALSE (programul de mai jos afişează textul FALSE), cu toate că
pointerul este pe articolul cu numărul relativ FileSize(f2) (FileSize(f2)=
=lfis DIV lart = 1).
PROGRAM EX8B;
VAR
f1:FILE OF WORD;
x:WORD;
f2:FILE OF REAL;
y:REAl;
BEGIN
Assign(f1,'TEST.DAT'); Rewrite(f1);
x:=0;
Write(f1,x,x,x,x);
Close(f1);
Assign(f2,'TEST.DAT'); Reset(f2);
Read(f2,y);
IF Eof(f2) THEN BEGIN Writeln('TRUE'); Readln END
ELSE BEGIN Writeln('FALSE'); Readln END;
Close(f2);
END.

Din exemplul anterior se desprinde concluzia că funcţia Eof(f) returnează


valoarea TRUE dacă pointerul este plasat după lfis octeţi faţă de începutul fişierului.
Când nu se produce anomalie de lungime, afirmaţia anterioară coincide cu faptul că
Eof(f) returnează valoarea TRUE când pointerul este pe articolul cu numărul relativ
FileSize(f) (FileSize(f)*lart = lfis). De fapt, în cazul anomaliei de lungime, funcţia

172
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Eof(f) nu poate returna niciodată valoarea TRUE, deoarece "articolul scurt" nu poate
fi niciodată citit. Din cele prezentate anterior, rezultă că, în cazul prelucrării unui
fişier cu structură necunoscută, este recomandabil să se lucreze cu articole de tip
CHAR sau cu blocuri de lungime unu.

173
OBIECTE ÎN PASCAL

Programarea orientată obiect (Object Oriented Programming - OOP)


reprezintă o tehnică ce s-a impus în ultimii ani, dovedindu-se benefică pentru
realizarea sistemelor software de mare complexitate. Noţiunea de obiect datează
din anii ’60, o dată cu apariţia limbajului Simula. Există limbaje - ca Smalltalk şi
Eiffel - care corespund natural cerinţelor programării orientate obiect, fiind
concepute în acest spirit, alături de care o serie de alte limbaje procedurale, printre
care C++ şi Turbo Pascal, au fost dezvoltate astfel încât să ofere suport pentru
această tehnică. În prezent există în funcţiune sisteme software de mare anvergură
realizate în tehnica programării orientată obiect, principiile ei fiind suficient de
bine clarificate, astfel încât să se treacă din domeniul cercetării în cel al producţiei
curente de programe.

10.1 Modelul de date orientat pe obiecte

OOP reprezintă o abordare cu totul diferită faţă de programarea


funcţională, devenită deja “clasică”. Dacă în programarea clasică programatorul era
preocupat să răspundă la întrebarea “ce trebuie făcut cu datele?”, adică să
definească proceduri care să transforme datele în rezultate, în OOP accentul cade
asupra datelor şi legăturilor care există între acestea, ca elemente prin care se
modelează obiectele lumii reale. Se poate afirma, într-o primă analiză, că OOP
organizează un program ca o colecţie de obiecte, modelate prin date şi legături
specifice, care interacţionează dinamic, adică manifestă un anumit
“comportament”, producând rezultatul scontat. În general, pentru modelul de date
orientat pe obiect, se consideră definitorii următoarele concepte: obiect,
încapsulare, moştenire şi polimorfism.
1. Obiectul este modelul informaţional al unei entităţi reale, care posedă,
la un anumit nivel, o mulţime de proprietăţi şi care are, în timp, un anumit
comportament, adică manifestă o reacţie specifică în relaţiile sale cu alte obiecte
din mediul său de existenţă. Ca model, un obiect este o unitate individualizabilă
prin nume, care conţine o mulţime de date, proceduri şi funcţii. Datele descriu

174
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

proprietăţile şi nivelul acestora, iar funcţiile şi procedurile definesc


comportamentul.
Având în vedere proprietăţile comune şi comportamentul similar ale
entităţilor pe care le modelează, obiectele pot fi clasificate, adică împărţite în
mulţimi. O mulţime de obiecte de acelaşi fel constituie o clasă, care poate fi
descrisă prin modelul comun al obiectelor sale.
De exemplu, în figura 10.1, numerele raţionale, ca perechi de numere
întregi de forma (Numarator, Numitor) pot fi descrise printr-un model comun,
denumit ClasaRational. Modelul arată că orice obiect de acest fel se caracterizează
printr-o pereche de numere întregi şi că pe această mulţime sunt definite operaţii
unare şi binare, care arată cum “interacţionează” obiectele în interiorul mulţimii: un
număr raţional poate da naştere opusului şi inversului său, două numere raţionale
pot produce un alt număr raţional ca sumă, diferenţă etc.

ClasaRational

Numarator: Integer
Numitor: Integer Obiecte
AddRational (b,c) a: (5;7)
SubRational (b,c) b: (3;1)
MulRational (b,c) c: (2;5)
DivRational (b,c) d: (7;5)
OpusRational (aopus) x: (0;1)
InversRational (ainvers)

Fig. 10.1 Clasă şi obiecte – mulţimea numerelor raţionale

Numerele complexe (figura 10.2), descrise ca perechi de numere reale de


forma (p_reală, p_imaginară) pot fi grupate într-un model comun, denumit
Ccomplexe:

CComplexe Exemplu de obiect


p_reala:real;
p_imaginara:real; a:(2, 7.5)
Conjugat(b) a reprezintă numărul
Suma(b,c) complex 2+7.5i
Inmultire(b,c)
Modul:real;
Diferenta(b,c)
Impartire(b,c)

Fig. 10.2 Clasă şi obiecte – mulţimea numerelor complexe

175
Obiecte în Pascal

Generalizând, se poate afirma că o clasă de obiecte se manifestă ca un tip


obiect, iar modelul comun al obiectelor este modelul de definire a tipului obiect.
Astfel, obiectele individuale apar ca manifestări, realizări sau instanţieri ale clasei,
adică exemplare particulare generate după modelul dat de tipul obiect. Altfel spus,
o clasă poate fi considerată ca un tip special de dată, iar obiectele sale ca date de
acest tip.
Acceptarea acestei semnificaţii pentru clase de obiecte este de natură să
simplifice descrierea obiectelor şi să asigure un tratament al acestora similar
tipurilor structurate de date din limbajele de programare: este suficientă o descriere
a tipului obiect şi apoi se pot declara constante şi variabile de acest tip. Datele care
reprezintă proprietăţile obiectelor se numesc atribute şi sunt de un anumit tip, de
exemplu, întregi, real, boolean etc. Procedurile şi funcţiile care definesc
comportamentul obiectelor sunt cunoscute ca metode ale clasei. Împreună,
atributele şi metodele sunt membrii clasei, identificabili prin nume. Pentru a pune
în evidenţă faptul că un membru aparţine unui obiect se utilizează calificarea
(notaţia cu punct): nume_obiect.nume_membru. În figura 10.1, a.Numarator
referă valoarea 5, iar a.AddRational(b,x) referă metoda AddRational a obiectului
a pentru a produce obiectul rezultat x= a+b.
Aşa cum sugerează figura 10.1, obiectele trebuie să conţină valorile lor
pentru atribute, deoarece definesc starea obiectului respectiv. Metodele, fiind
comune, se specifică o singură dată. Despre o metodă, desemnată sau apelată cu un
anumit obiect, se spune că se execută în context obiect, iar obiectul respectiv este
numit curent. Apelul propriu-zis este considerat ca trimitere de mesaj la obiectul
curent, iar execuţia metodei reprezintă răspunsul obiectului curent la mesaj.
Faptul că o metodă se execută în contextul obiectului curent înseamnă că
are, în mod implicit, acces la atributele şi metodele obiectului, altele decât metoda
respectivă. Pentru alte obiecte, din aceeaşi clasă sau din clase diferite, metoda
trebuie să posede parametrii corespunzători. De asemenea, pentru a simplifica
scrierea, în interiorul unei metode referirea la membrii obiectului curent se face
fără calificare.
Pe baza acestor convenţii, în procedurile AddRational şi OpusRational,
scrise în pseudocod, s-a specificat cu un parametru mai puţin decât numărul de
operanzi pe care îi presupune operaţia respectivă, deoarece un operand este
obiectul curent. Referirea la obiectul curent se distinge de celelalte prin lipsa
calificării.
Procedure AddRational (b,c);
c.Numărător:= Numarator * b.Numitor + Numitor * b. Numarator;
c.Numitor: = Numitor * b.Numitor;
End;
Procedure OpusRational (aopus);
aopus.Numarator: = -Numarator;
aopus.Numitor: = Numitor;
End;

176
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Descrierea în pseudocod a metodelor Conjugat, Suma şi Modul din clasa


Ccomplexe (figura 10.2) poate fi făcută astfel:
procedure Conjugat(b);
begin
b.p_reala:=p_reala;
b.p_imaginara:=-p_imaginara;
end;
procedure Suma(b,c);
begin
c.p_reala:=p_reala+b.p_reala;
c.p_imaginara:=-p_imaginara+b.p_imaginara;
end;
function Modul:real;
begin
Modul:=sqrt(sqr(p_reala)+sqr(p_imaginra));
end;

Deoarece o clasă este un tip de dată, în definirea unei clase B se pot declara
atribute de tip A, unde A este la rândul ei o clasă. Mai mult, o clasă A poate defini
atribute de tip A. De exemplu, clasa Carte din figura 10.3 are atributul Autor de
tipul Persoana care este, de asemenea, o clasă. Mai mult, Persoana are atributul
Sef, care este de acelaşi tip (Persoana).

Persoana 1
100 Persoana Carte
Ionescu Marca: Integer Cota: String
Scriitor Nume: String Titlu: String
Persoana 2 Profesia: String Autor:Persoana
Sef: Persoana Pret: Real

Persoana 2
70
Popescu
Reporter
----------

Fig. 10.3 Atribute de tip

Definirea atributelor unei clase ca tipuri ale altei clase pune în evidenţă o
relaţie între clase şi, deci, între obiectele acestora.
Din punct de vedere funcţional, metodele unei clase au destinaţii diverse.
În multe cazuri şi depinzând de limbaj, unei clase i se poate defini o metodă
constructor şi o metodă destructor. Un constructor este o metodă care creează un

177
Obiecte în Pascal

obiect, în sensul că îi alocă spaţiu şi/sau iniţializează atributele acestuia.


Destructorul este o metodă care încheie ciclul de viaţă al unui obiect, eliberând
spaţiul pe care acesta l-a ocupat.
2. Încapsularea exprimă proprietatea de opacitate a obiectelor cu privire la
structura lor internă şi la modul de implementare a metodelor. Ea este legată de
securitatea programării, furnizând un mecanism care asigură accesul controlat la
starea şi funcţionalitatea obiectelor. Se evită astfel modificări ale atributelor
obiectelor şi transformări ale acestora care pot să le “deterioreze”. Potrivit acestui
mecanism, o clasă trebuie să aibă membrii împărţiţi în două secţiuni: partea publică
şi partea privată.
Partea publică este constituită din membri (atribute şi metode) pe care
obiectele le oferă spre utilizare altor obiecte. Ea este interfaţa obiectelor clasei
respective cu “lumea exterioară” şi depinde de proiectantul clasei. Modalitatea
extremă de constituire a interfeţei este aceea a unei interfeţe compusă numai din
metode. Dacă se doreşte ca utilizatorii obiectelor clasei să poată prelua şi/sau stabili
valorile unor atribute ale acestora, interfaţa trebuie să prevadă metode speciale,
numite accesorii.
Partea privată cuprinde membri (atribute şi/sau metode) care servesc
exclusiv obiectelor clasei respective. De regulă, în această parte se includ atribute
şi metode care facilitează implementarea interfeţei.
De exemplu, o stivă, ca tip de dată, poate fi descrisă de o clasă stack în
care interfaţa este constituită din metodele Push, Pop, Top, Empty, în timp ce
pointerul la capul stivei, Cap şi numărătorul de noduri, Contor, ca atribute, sunt
“ascunse” în partea privată. Ea se serveşte de obiectele altei clase, denumită Nod,
ale cărei obiecte le înlănţuieşte în stivă (figura 10.4).
Trebuie remarcat că încapsularea înseamnă şi faptul că utilizatorul
metodelor nu trebuie să cunoască codul metodelor şi nici nu trebuie să fie
dependent de eventuala schimbare a acestuia, interfaţa fiind aceea care îi oferă
funcţionalitate obiectelor în condiţii neschimbate de apelare.

Stack
Cap: Nod Partea privată
Contor: Integer
Push ( )
Pop ( ) Interfaţa(partea publică)
Top ( )
Empty ( )

Fig. 10.4 Interfaţa obiectelor

3. Moştenirea reprezintă o relaţie între clase şi este, probabil, elementul


definitoriu al OOP. Relaţia permite constituirea unei noi clase, numită derivată,
pornind de la clase existente, denumite de bază. Dacă în procesul de construire
participă o singură clasă de bază, moştenirea este simplă, altfel este multiplă. În

178
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

continuare se vor aborda câteva aspecte legate de moştenirea simplă, singura


implementată în Pascal.
Se spune că o clasă D moşteneşte o clasă A, dacă obiectele din clasa D
conţin toate atributele clasei A şi au acces la toate metodele acestei clase. Din
această definiţie, dacă D moşteneşte A, atunci obiectele din D vor avea toate
atributele şi acces la toate metodele lui A, dar în plus:
- D poate defini noi atribute şi metode;
- D poate redefini metode ale clasei de bază;
- metodele noi şi cele redefinite au acces la toate atributele dobândite sau
definite.
În figura 10.5, clasa Cerc moşteneşte clasa Point, deci un obiect de tipul
Cerc va avea ca membri coordonatele x,y moştenite şi ca atribut propriu Raza.
Funcţia Distanţa, definită pentru calculul distanţei dintre punctul curent şi punctul
p, dat ca parametru, este accesibilă şi pentru obiectele Cerc şi va calcula distanţa
dintre două cercuri (distanţa dintre centrele lor). Funcţia Arie şi procedura
Desenează sunt redeclarate de clasa Cerc, ceea ce înseamnă redefinirea lor impusă
de codul diferit pe care trebuie să-l aibă în funcţie de tipul figurilor geometrice
(cerc sau altă figură).

Point
x: Integer x: 30
y: Integer y: 150
Desenează ( )
Distanţa (p:Point): Real

Cerc
Raza: Integer x: 200
Arie ( ): Real y: 180
Desenează ( ) Raza: 50

Fig. 10.5 Moştenirea simplă

Dacă se au în vedere mulţimi de clase, atunci se observă că relaţia de moştenire


simplă induce un arbore ierarhic de moştenire pe această mulţime. Există o singură
clasă iniţială, şi anume rădăcina arborelui, fiecare clasă are un singur ascendent
(părinte) şi orice clasă care nu este frunză poate avea unul sau mai mulţi
descendenţi (fii). În fine, cu privire la moştenirea simplă se pot face următoarele
observaţii:
• dacă se aduc modificări în clasa de bază, prin adăugarea de atribute şi/sau
metode, nu este necesar să se modifice şi clasa derivată; • moştenirea permite
specializarea şi îmbogăţirea claselor, ceea ce înseamnă că, prin redefinire şi
adăugare de noi membri, clasa derivată are, în parte, funcţionalitatea clasei de bază,

179
Obiecte în Pascal

la care se adaugă elemente funcţionale noi; • moştenirea este mecanismul prin care
se asigură reutilizarea codului, sporind productivitatea muncii de programare.

4. Polimorfismul este un concept mai vechi al programării, cu diferite


implementări în limbajele de programare care se bazează pe tipuri de date (limbaje
cu tip). Ea şi-a găsit extensia naturală şi în modelul orientat pe date, implementat
prin limbaje cu tip, în care clasa reprezintă tipul de date obiect.
• Polimorfismul în limbajele de programare cu tip. Noţiunea de
polimorfism exprimă capacitatea unui limbaj de programare cu tip de a exprima
comportamentul unei proceduri independent de natura (tipul) parametrilor săi. De
exemplu, o procedură care determină cea mai mare valoare dintr-un şir de valori
este polimorfică dacă poate fi scrisă independent de tipul acestor valori. În funcţie
de modul de implementare, se disting mai multe tipuri de polimorfism.
Polimorfismul ad-hoc se materializează sub forma unor proceduri care au
toate acelaşi nume, dar se disting prin numărul şi/sau tipul parametrilor.
Polimorfismul este denumit şi supraîncărcare, având în vedere semantica specifică
fiecărei proceduri în parte.
Polimorfismul de incluziune se bazează pe o relaţie de ordine parţială între
tipurile de date, denumită relaţie de incluziune sau inferioritate. Dacă un tip A este
inclus (inferior) într-un tip B, atunci se poate pasa un parametru de tip A la o
procedură care aşteaptă un parametru de tip B. Astfel, o singură procedură
defineşte funcţional o familie de proceduri pentru toate tipurile inferioare celor
declarate ca parametri. Un exemplu clasic este cazul tipului întreg, inferior tipului
real în toate operaţiile de calcul.
Polimorfismul parametric constă în definirea unui model de procedură
pentru care înseşi tipurile sunt parametri. Polimorfismul, denumit şi genericitate,
presupune că procedura se generează pentru fiecare tip transmis la apel ca
parametru.
Cele trei tipuri de polimorfism există (toate sau numai o parte din ele) în
limbajele clasice de programare, dar unele pot să nu fie accesibile programatorului.
Aşa este cazul limbajului Pascal în care polimorfismul ad-hoc este implicit numai
pentru operatorii limbajului (+, -, /, * etc), polimorfismul parametric nu este
implementat, iar polimorfismul de incluziune este aplicabil numai funcţiilor şi
procedurilor de sistem.
• Polimorfismul în limbajele orientate obiect. Limbajele orientate obiect
sau extensiile obiect ale unor limbaje cu tip oferă, în mod natural, polimorfismul
ad-hoc şi de incluziune. Polimorfismul ad-hoc intrinsec reprezintă posibilitatea de a
defini în două clase independente metode cu acelaşi nume, cu parametri identici
sau diferiţi. Acest polimorfism nu necesită mecanisme speciale şi decurge simplu,
din faptul că fiecare obiect este responsabil de tratarea mesajelor pe care le
primeşte. Polimorfismul este de aceeaşi natură şi în cazul în care între clase există o
relaţie de moştenire, cu precizarea că, în cazul în care o metodă din clasa derivată
are parametrii identici cu ai metodei cu acelaşi nume din clasa de bază, nu mai este

180
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

supraîncărcare, ci redefinire, după cum s-a precizat în paragraful anterior.


Polimorfismul de incluziune este legat de relaţia de moştenire şi de aceea se
numeşte polimorfism de moştenire. Într-adevăr, relaţia de moştenire este de ordine
parţială. Când clasa D moşteneşte direct sau indirect clasa A, atunci D este inferior
lui A. În aceste condiţii, orice metodă a lui A este aplicabilă la obiectele de clasă D
şi orice metodă, indiferent de context, care are definit un parametru de tip A
(părinte) poate primi ca argument corespunzător (parametru actual) un obiect de
clasă D (fiu).
Observaţie: un obiect de clasă A nu poate lua locul unui obiect de clasă D,
deoarece A “acoperă” numai parţial pe D, care este o extensie şi o specializare a lui
A.
Limbajul Turbo Pascal, ca suport pentru programarea obiect, oferă ambele
forme de polimorfism pentru clase.
• Legarea statică şi dinamică a metodelor. Legarea statică a metodelor se
regăseşte atât în limbajele orientate obiect cât şi în cele clasice. Compilatorul poate
determina care metodă şi din care clasă este efectiv apelată într-un anumit context
şi poate genera codul de apel corespunzător.
În plus, datorită polimorfismului şi lucrului cu pointeri, în limbajele
orientate obiect, unui obiect din clasa părinte, desemnat indirect prin referinţă
(pointer) şi nu prin nume, i se poate atribui un obiect fiu. În general, nu este posibil
de determinat dacă, în contextul dat, metoda polimorfică trebuie apelată în varianta
clasei de bază sau a celei derivate. De aceea, compilatorul generează un cod care, la
momentul execuţiei, va testa tipul efectiv al obiectului şi va realiza legarea metodei
adecvate. În acest caz legarea este dinamică (sau la momentul execuţiei). Legarea
dinamică este mai costisitoare decât cea statică, dar reprezintă o necesitate pentru a
asigura elasticitatea necesară în realizarea programelor OOP, obiectele putând avea
caracter de variabile dinamice.

10.2 Clase şi obiecte în Pascal

Turbo Pascal implementează tehnica OOP pe fondul caracteristicilor sale


de limbaj procedural, ceea ce înseamnă că lucrul cu clase şi obiecte se realizează
asemănător cu cel cu tipuri structurate de date, mecanismul de încapsulare fiind
asigurat prin construirea unităţilor.

10.2.1 Specificarea claselor

În Pascal, clasele sunt tratate asemănător datelor de tip articol. Ţinând


seama de faptul că o clasă conţine atât date cât şi metode, specificarea unei clase
presupune declararea structurii şi definirea metodelor.

181
Obiecte în Pascal

• Declararea structurii. Declararea unei clase, ca tip, se face în secţiunea


TYPE sub următoarea formă generală:
referinta_la_clasa = ^nume_clasa {optional}
nume_clasa=OBJECT
atribut_1;
...............
atribut_n;

metoda_1;
...............
metoda_m;
{ PRIVATE
atribut_1;
...............
atribut_p;

metoda_1;
...............
metoda_q; }
END;

Fiecare declaraţie de atribut are forma: nume_atribut:tip, unde tip poate


fi predefinit sau definit anterior de utilizator, inclusiv numele unui alt tip de clasă.
La fel ca şi la tipul RECORD, unele atribute pot fi referinţe, chiar la tipul clasei
care se defineşte, caz în care declararea tipului referinţă aferent trebuie să preceadă
declararea clasei. O declaraţie de metodă cuprinde numai antetul unei funcţii sau
proceduri (signatura), adică numele, parametrii formali (dacă există) şi tipul
rezultatului, dacă este cazul. Apariţia opţională a secţiunii PRIVATE în declaraţia
de clasă anunţă partea privată a clasei. Toate atributele şi metodele care preced
această secţiune sunt considerate publice. În fine, se remarcă prezenţa cuvântului
OBJECT ca declarator de tip obiect. În Turbo Pascal nu există un declarator
CLASS, pentru a introduce o clasă, aşa cum există, de exemplu, în C++. Deci
declaratorul, OBJECT, nu declară un obiect, ci o clasă de obiecte.
• Definirea. Definirea metodelor constă în construirea corpurilor
subprogramelor a căror signatură a fost precizată. Deoarece într-un program pot să
existe mai multe clase, definirea trebuie să asigure recunoaşterea metodelor
diferitelor clase. Acest lucru se realizează prin prefixarea numelui metodei cu
numele clasei. În rest, corpul metodelor se scrie în conformitate cu regulile
generale ale limbajului şi având în vedere că metoda respectivă se execută în
contextul obiectului curent.

182
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Exemplu:
10.1. Declararea şi definirea unei clase

{ Declararea clasei Point}


Point = OBJECT
Function Getx:Integer;
Function Gety:Integer;
Procedure Init (a, b: Integer);
Function Distance (p: Point): Real;
Private
x: Integer;
y: Integer;
END;
{ Definirea metodelor}
Function Point.Getx: Integer;
Begin
Getx: = x;
End;
Function Point.Gety: Integer;
Begin
Gety: = y;
End:
Function Point.Distance (p:Point): Real;
Var
dx, dy: Integer;
Begin
dx: = x-p.x;
dy: = y-p.y;
Distance: = sqrt(dx*dx-dy*dy);
End;
Procedure Point.Init(a, b: Integer);
Begin
x: = a;
y: = b;
End;

Din exemplul, 10.1 se observă modul de declarare a unei clase, în care,


pentru asigurarea încapsulării depline, comunicarea cu exteriorul se face numai
prin interfaţă. Funcţiile Getx şi Gety sunt necesare ca accesorii pentru a returna
valorile atributelor x şi y, iar procedura Init este un constructor care iniţializează
(schimbă) valorile celor două atribute. Funcţia Distance ilustrează modul în care
trebuie înţeleasă afirmaţia că o metodă se execută în context obiect. Se observă că,
deşi calculul distanţei presupune două obiecte de tip Point, ca parametru figurează
numai un punct, p. Unde este celălalt (primul)? În mod implicit, se consideră că
primul punct este un obiect ascuns, obiectul de apel al funcţiei, numit curent,
coordonatele sale fiind x şi y. În relaţiile de calcul, referirea coordonatelor celuilalt
punct, transmis ca argument, se va face prin notaţia cu calificări: p.x, p.y. Rezultă
de aici că funcţiile Getx, Gety şi procedura Init se referă la obiectul curent.

183
Obiecte în Pascal

• Unit de clasă. Deoarece clasele trebuie să fie prin definiţie entităţi


reutilizabile, se încapsulează într-o unitate. Declararea claselor apare în secţiunea
INTERFACE, iar definirea metodelor în secţiunea IMPLEMENTATION. Se
recomandă ca fiecare clasă să fie o unitate.

10.2.2 Utilizarea obiectelor

Obiectele unei clase se comportă asemănător variabilelor de tip articol.


Sintetizând aceasta, înseamnă că:
• Obiectele se declară, ca şi variabilele, într-o secţiune VAR dintr-o
entitate de program (program principal, funcţie, procedură sau unitate). De aici
rezultă că obiectele pot avea caracter global sau local, ceea ce determină, pe de o
parte, spaţiul de vizibilitate, iar, pe de altă parte, durata de viaţă şi segmentul de
alocare.
• Un obiect poate fi declarat static sau dinamic. Atributul de static,
respectiv dinamic, se referă la momentul în care are loc alocarea spaţiului pentru
obiect (la compilare sau la execuţie).
Deoarece declararea obiectelor dinamice va fi tratată într-un alt paragraf, în
continuare se prezintă numai aspectele privind obiectele statice. Pentru obiectele
statice declaraţia are forma: nume_obiect:nume_clasa. De exemplu: p1, p2 :
Point. Ca şi în cazul variabilelor, obiectele declarate trebuie să fie iniţializate
înainte de a fi utilizate. Se apelează, în acest sens, o metodă de tip constructor. De
exemplu, p2.Init (30,100) iniţializează punctul p2 cu x = 30 şi y = 100.
Două obiecte de acelaşi tip pot participa într-o operaţie de atribuire. De
exemplu, p1: = p2. Atribuirea între obiecte nu este întotdeauna corectă. Dacă
obiectele au spaţiu extins, atunci copierea nu este corectă. De aceea, atribuirile
trebuie utilizate cu multă atenţie, ele realizându-se prin copiere bit cu bit.
Un obiect poate participa ca argument, prin valoare sau referinţă, la apelul
de procedură sau funcţie. Deoarece transferul prin valoare corespunde unei copieri,
pot apărea aceleaşi neajunsuri ca şi la operaţia de atribuire. În plus, copierea poate
fi şi costisitoare ca spaţiu şi timp. De aceea se preferă transferul prin referinţă. O
funcţie nu poate returna ca rezultat un obiect, dar poate returna o referinţă la un
obiect.
Orice metodă a unei clase, inclusiv constructorii, se apelează cu un anumit
obiect care devine acti sau curent . Apelul are forma:
obiect.nume_metoda(alte_argumente).
De exemplu:
pr. Init( 70,80);
d:= p1.Distance (p2).

Obiectul de apel este un parametru ascuns care se asociază automat cu un


parametru formal denumit Self. Acest parametru formal prefixează, în mod

184
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

implicit, atributele şi metodele utilizate de metoda apelată. Se poate spune că Self


este un pseudonim al obiectului de apel şi, de aceea, în metoda apelată se poate
scrie: Self.atribut sau Self.metoda. Uneori Self se utilizează explicit pentru a evita
ambiguitatea referirii sau atunci când este nevoie de a obţine adresa unui obiect
curent. În ultimul caz, adresa se desemnează prin @Self.

Exemplu:
10.2. Se declară şi se defineşte o clasă de numere raţionale ca perechi
(Numarator, Numitor) de numere întregi. O astfel de clasă poate fi utilă în calcule
cu fracţii ordinale, atunci când transformarea acestora în fracţii zecimale (numere
reale) nu este de dorit din cauza erorilor de trunchiere.
UNIT ClasaRat;
INTERFACE
Type
Rational = OBJECT
Procedure InitRat (x, y:Integer);
Procedure AddRat (b: Rational; VAR c: Rational);
Procedure SubRat (b: Rational; VAR c: Rational);
Procedure MulRat (b: Rational; VAR c: Rational);
Procedure DivRat (b: Rational; VAR c: Rational);
Procedure OpusRat (VAR c: Rational);
Procedure InversRat (VAR c: Rational);
Function GetNumarator: Integer;
Function GetNumitor: Integer;
Procedure ReadRat;
Procedure WriteRat;
Function CompRat (b: Rational): Integer;
PRIVATE
Numarator, Numitor: Integer;
Procedure SignRat;
END;
IMPLEMENTATION
Function Cmmdc (x,y: Integer ):integer:FORWARD;
Procedure Rational. InitRat (x, y: Integer);
{Initializeaza in forma inductiva functia (numarator,
numitor)}
Var d: Integer;
Begin
If (x=0) or (y=1)
then y=1
else begin
If (abs(x)<>1) and (abs(y)<>1) then
begin
d:= Cmmdc (x,y)
x:= x/d;
y:= y/d;
end;
SignRat;
End;
Numarator:= x;
Numitor:= y;
End;
Procedure Rational.AddRat (b: Rational; VAR c: Rational);
Var x, y: Integer;
Begin
x: = Numarator*b.Numarator+Numitor*b.Numarator;

185
Obiecte în Pascal

y: = Numitor*b.Numitor;
c.InitRat (x, y);
End;
Procedure Rational.SubRat (b:Rational; VAR c:Rational);
Var
r: Rational;
Begin
b. OpusRat ( r );
AddRat (r, c);
End;
Procedure Rational.MulRat (b: Rational; VAR c: Rational);
Var x, y: Integer;
Begin
x: = Numarator*b.Numarator;
y: = Numitor*b.Numitor;
c.InitRat (x, y);
End;
Procedure Rational.DivRat (b: Rational; VAR c: Rational);
Var r: Rational;
Begin
b.InversRat ( r );
MulRat (r, c);
End;
Procedure Rational.InversRat (VAR c: Rational);
Var d: Integer;
Begin
d:= Numarator;
if d=0 then d:=1;
c.Numarator: = c.Numitor;
c.Numitor: = d;
c.SignRat;
End;
Procedure Rational.OpusRat (VAR c: Rational);
Begin
c.Numarator: = - c.Numarator;
End;

Function Rational.GetNumarator: Integer;


Begin
GetNumarator: = Numarator;
End;
Function Rational.GetNumitor: Integer;
Begin
GetNumitor: = Numitor;
End;
Procedure Rational.SignRat;
Begin
if (Numarator>0) and (Numitor<0) or
(Numarator<0) and (Numitor<0)
then Begin
x: = -x;
y: = -y;
End:
End;
Function Cmmdc (x, y: Integer) : Integer;

186
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Begin
x: = abs (x); y: = abs (y);
While x< >y Do
if x>y then x: = x-y else y: = y-x;
Cmmdc: = x;
End;
Procedure Rational.ReadRat;
Var
txt: String [ 25];
i, x, y: Integer;
Begin
Read (txt);
i: Pos (txt, ‘/’);
If (i=0) then Begin {Numarator întreg}
Val (txt, Numarator);
Numitor: =1;
End
else
Begin
Val(Copy (txt, 1, C-1), x);
Val (Copy (txt, i, 25), y);
InitRat (x, y);
End;
End;
Procedure Rational.WriteRat;
Begin
Write (Numarator, ‘/’, Numitor);
End;

Function Rational.CompRat (b: Rational): Integer;


Var d, n1, n2: Integer;
Begin
d: = cmmdc (Numitor, b.Numitor);
n1: = Numarator*(dDIVNumitor);
n2: = b.Numarator*(dDIV b.Numitor);
if (n1<n2)
then CompRat:=-1
else if (n1 = n2)
then CompRat: = 0
else CompRat: = 1;
End;
End.
Clasa construită poate fi testată printr-un program multifuncţional, TestE2.
Operaţiile care se testează se codifică prin litere (A, S, M etc) la care se adaugă o
operaţie, T, pentru terminare. Pe baza unei variabile Op (pentru operaţia curentă)
se realizează o structură repetitivă, în corpul căreia se alege modul de tratare printr-
o structură CASE OF.

Program TestE3;
Uses ClasRat;
Var
a,b,c: Rational;
Op: Char; r: Integer;
Procedure Meniu;
Begin
WriteLn (‘ Meniu’);
WriteLn (‘A/a - Adunare’);
WriteLn (‘S/s - Scadere’);

187
Obiecte în Pascal

WriteLn (‘M/m - Inmulţire’);


WriteLn (‘D/d - Impartire’);
WriteLn (‘C/c - Comparare’);
WriteLn (‘G/g - Elemente’);
WriteLn (‘T/t - Terminare’);
WriteLn (‘ Alege operatia:’);
ReadLn ( Op );
Op: = UpCase ( Op );
End;
BEGIN
Meniu;
While Op <>’T’ Do
Begin
If (Op = ‘G’) then
Begin
a.ReadRat;
WriteLn (‘Numarator=’, a.GetNumarator);
WriteLn (‘Numitor=’, a.GetNumitor);
End
else If (Op = ‘C’) then
Case a.CompRat (b) of
-1: WriteLn (‘ a<b’);
0: WriteLn (‘ a=b’);
1: WriteLn (‘a>b’);
End
else
Begin
CaseOp of
‘A’: a.AddRat (b,c);
‘S’: a.SubRat (b,c);
‘M’: a.MulRat (b,c);
‘D’: a.DivRat (b,c);
End;
C.WriteRat;
End;
Meniu;
End;
END.

La fel ca în exemplul 10.1, şi în acest caz atributele sunt private, iar accesul
la valorile lor se asigură prin accesoriile GetNumarator, GetNumitor. În partea
privată apare o metodă care este utilizată numai intern (în clasă), în scopul asocierii
semnului numărului raţional la numărător. De asemenea, se remarcă prezenţa
funcţiei Cmmdc (cel mai mare divizor comun) care nu ţine de clasă, dar este
necesară implementării unor metode ale clasei.
La stabilirea numărului de parametri, în conceperea metodelor s-a avut în
vedere că un operand, eventual unicul pentru operaţii unare, este obiectul curent.
Atunci când, în contextul obiectului curent, s-a apelat o metodă care se referă la un
alt obiect, acesta este precizat explicit, (altfel s-ar considera acelaşi context). De
exemplu, în operaţiile de scădere şi împărţire se aplică o reducere la operaţiile de
adunare cu opusul, respectiv înmulţire cu inversul. Acest rezultat intermediar se
obţine prin b.OpusRat(r), respectiv b.InversRat(r), după care, în contextul definit la
apelul lui SubRat, respectiv DivRat se invocă AddRat(r,c) şi MulRat(r,c), ceea ce

188
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

înseamnă că ele vor fi de fapt apelate ca Self.AddRat(r,c) şi Self.MulRat(r,c), adică


a va fi prim operand.
O menţiune aparte trebuie făcută cu privire la constructorul InitRat care
iniţializează în formă ireductibilă fracţia (Numarator, Numitor) a unui obiect. De
aceea, ea este apelată ori de câte ori se creează o astfel de fracţie, fie în programul
principal, fie într-o altă metodă.
Metodele ReadRat şi WriteRat realizează legătura obiectelor cu tastatura şi
monitorul. Procedura ReadRat oferă o alternativă de iniţializare a obiectelor, prin
citirea fracţiilor de la tastatură sub forma x/y sau x, dacă numitorul este unu (număr
întreg). Procedura WriteRat afişează pe monitor un număr raţional sub formă de
fracţie x/y, în poziţia curentă de scriere.
În cazul claselor numerice (dar nu numai), pe mulţimea obiectelor unei
clase se poate defini o relaţie de ordine. Deoarece limbajul nu permite
supraîncărcarea operatorilor, este necesar să se introducă o metodă, de regulă o
funcţie, care să definească relaţia. Este cazul funcţiei CompRat care compară două
numere raţionale a, b şi returnează -1, 0, 1, după cum între cele două numere există
una din relaţiile: a<b; a=b; a>b.
Legat de precizările referitoare la supraîncărcarea operatorilor, se observă o
lipsă de naturaleţe în scrierea operaţiilor definite pe mulţimea obiectelor. De
exemplu, se scrie a.AddRat(b, c) în locul formei c:=a+b sau a.CompRat (b) în
loc de a<b etc., aşa cum ar fi posibil dacă operatorii +, < etc. ar putea fi
supraîncărcaţi (cum este posibil în C++).
În fine, se face observaţia că limbajul nu prevede facilităţi de tratare a
cazurilor de eroare care pot apărea în legătură cu obiectele unei clase. Se presupune
că proiectantul clasei introduce eventuale măsuri de tratare a erorilor sau că
utilizatorul clasei evită apelul unor metode în condiţiile în care acestea pot produce
erori. În exemplul considerat, procedurile InitRat şi InversRat tratează şi cazurile
de eroare, prin transformarea corespunzătoare a obiectelor. În acest fel, programele
nu riscă apariţia erorii de împărţire la zero, dar pot furniza rezultate neaşteptate.
Această modalitate poate semnala apariţia unei erori, dar nu este forma cea mai
elaborată de semnalare şi de prelucrare a erorii.

10.2.3 Constructori şi destructori

De cele mai multe ori, spaţiul unui obiect este compact, constituit din
spaţiile necesare atributelor sale. În cazul în care unele atribute sunt de tip referinţă,
spaţiul obiectelor clasei respective are o componentă dinamică denumită spaţiu
extins (figura 10.6).

189
Obiecte în Pascal

Spaţiu compact Spaţiu extins


(static) (dinamic)
atribut _1
referinta _1
atribut _2
atribut _3
referinta _2

Fig. 10.6 Spaţiul extins al unui obiect

Atunci când se declară o variabilă obiect dintr-o astfel de clasă,


compilatorul poate aloca spaţiul compact necesar. Spaţiul extins, la acest moment,
este necunoscut deoarece el trebuie să fie alocat în zona memoriei dinamice, la
momentul execuţiei. Rezultă de aici două aspecte importante pe care trebuie să le
aibă în vedere proiectantul clasei.
• În primul rând, trebuie să se prevadă un constructor capabil să trateze nu
numai iniţializarea atributelor statice, dar şi a celor care ocupă un spaţiu dinamic.
Constructorul trebuie să utilizeze procedura New sau GetMem pentru a obţine un
bloc de mărimea necesară, a cărui adresă trebuie încărcată în câmpul referinţă al
obiectului, iar blocul respectiv trebuie apoi iniţializat.
Într-o clasă pot fi prevăzuţi mai mulţi constructori specializaţi, dacă există
mai multe câmpuri de tip referinţă la un obiect, dacă un singur constructor ar fi
incomod de utilizat sau ar avea o listă prea mare de parametri. Mai mult, limbajul
prevede o posibilitate specială de a declara un constructor, utilizând declaratorul
Constructor în loc de Procedure. Printr-o astfel de declarare, compilatorul are
posibilitatea să distingă din mulţimea metodelor pe cele care au acest rol special,
lucru necesar în cazul moştenirii, dar util şi programatorului pentru a putea verifica
mai uşor dacă a rezolvat corect problema iniţializării obiectelor.
• În alt doilea rând, atunci când un obiect trebuie să-şi încheie existenţa (de
exemplu, la ieşirea dintr-o funcţie/procedură în care a fost creat), este necesar să se
prevadă o metodă destructor care să elibereze spaţiul extins. Destructorul trebuie să
apeleze procedura Dispose sau FreeMem pentru a returna acest spaţiu zonei heap.
În lipsa unui destructor rămân blocuri de memorie ocupate, fără ca ele să mai poată
fi referite ulterior. Declaraţia destructorului se poate face prin declaratorul
Destructor în loc de Procedure.
Constructorii şi destructorii pot avea orice utilitate pe care o consideră
programatorul, atunci când se creează sau se distrug obiecte.
Atribuirea între obiectele care au spaţiu extins are unele particularităţi.
Astfel, dacă se specifică atribuirea a:=b, atunci se realizează o copiere bit cu bit a
obiectului b în a. După operaţie, a şi b au acelaşi conţinut în spaţiul compact şi,
deci, referă acelaşi spaţiu extins (figura 10.7).

190
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

a:=b
x x
y y
z z

. .
. .
. .
Referinţă Referinţă
Spaţiu extins

Fig. 10.7 Atribuirea în cazul obiectelor cu spaţiu extins

Ulterior, modificarea realizată la unul din obiecte în spaţiul extins


afectează starea şi pentru celălalt obiect. Dacă un obiect dispare, atunci celălalt
obiect va rămâne fără spaţiu extins, iar când şi acesta va dispărea, destructorul său
va încerca să elibereze un spaţiu care nu mai există, fiind deja eliberat. În legătură
cu astfel de cazuri, deoarece operatorul de atribuire nu poate fi supraîncărcat, este
necesar să se evite operaţia şi să se prevadă o metodă proprie de copiere prin care
să se iniţializeze a cu valorile lui b, dar în spaţiu complet separat. De asemenea, se
recomandă ca obiectele de acest fel, utilizate ca argumente, să fie transmise, prin
referinţă şi nu prin valoare, având în vedere că ultima metodă este echivalentă cu o
atribuire.

Exemplu:
10.3. Se ilustrează modul de utilizare a referinţelor în declararea claselor,
de realizare a constructorilor multipli şi a destructorului de spaţiu extins. Deşi
simplificat, exemplul sugerează modul în care se poate construi o clasă matrice cu
elemente de tip Rational, prin utilizarea clasei din exemplul 10.2.
UNIT ClasaMRat;
INTERFACE
uses ClasaRat;
Const
MaxLin = 10; MaxCol = MaxLin;
ZeroRat : Rational = (Numarator:0; Numitor:1);
Type
DimLin = 1..MaxLin;
DimCol = 1..MaxCol;
Matrice = array [DimLin, DimCol] of Rational;
MatriceRat = OBJECT
Constructor SetNulMat (m, n:Integer);
Constructor ReadMat;
Procedure AddMat (b:MatriceRat; VAR c:MatriceRat);
Procedure WriteMat;
Destructor FreeExtMat;
PRIVATE

191
Obiecte în Pascal

Nlin, Ncol:Integer;
PMat:^Matrice;
END;
Var
ErrMat:Integer;
IMPLEMENTATION
Constructor MatriceRat.SetNulMat (m, n:Integer);
Var i, j:Integer;
Begin
m:=Max (m, MaxLin);
n:=Max (n, MaxCol);
GetMem (Pmat, m*n*SizeOf (Rational));
For i:=1 to m Do
For j:=1 to n Do
P.Mat^[i, j]:=ZeroRat;
Nlin:= m; Ncol:=n;
End;

Constructor MatriceRat.ReadMat;
Var m, n, i, j:Integer;
Begin
Repeat
Write (‘Numărul de linii:’); ReadLn (m);
Until m<=MaxLin;
Repeat
Write (‘Numărul de coloane:’);
ReadLn (n);
Until n<=MaxCol;
WriteLn(‘Elementele rationale ale matricei, pe
linii!’);
GetMem (Pmat, m,*n*SizeOf (Rational));
For i:=1 To m Do
For j:=1 To n Do
Begin
Write (‘x(‘, i:2,’,’,j:2,’)=’);
PMat^[i,j].ReadRat;
WriteLn;
End;
Nlin:=m; Ncol:=n;
End;
Destructor MatriceRat.FreeExtMat;
Begin
FreeMem (PMat, Nlin*Ncol*SizeOf (Rational));
End;
Procedure MatriceRat.AddMat (b:MatriceRat; VAR c:MatriceRat);
Var i, j:Integer;
Begin
ErrMat:=0;
If (Nlin=b.Nlin) and (Ncol=b.Ncol)
then
For i:=1 To Nlin Do
For j:=1 To Ncol Do
PMat^[i,j].AddRat(b.PMat^[i,j],c.PMat^[i,j]);
else ErrMat:=1;
End;

Procedure MatriceRat.WriteMat;
Var i, j:Integer;
Begin
For i:=1 To Nlin Do

192
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Begin
WriteLn (‘Linia:’, i);
For j:=1 To Ncol Do
Begin
PMat^[i, j].WriteRat;
If (i<>Ncol) Write (‘;’);
End;
End;
End;

Program Test;
Uses ClasMat;
Var
A,B,C:MatriceRat;
Begin
A.ReadMat;
B.ReadMat;
A.AddMat (B,C);
If(ErrMat)then WriteLn (‘** Eroare:Dimensiuni
diferite’)
else C.WriteMat;
A.FreeExtMat;
B.FreeExtMat;
C.FreeExtMat;
ReadLn;
END.

Analiza exemplului pune în evidenţă posibilităţile tehnicii OOP în limbajul


Pascal.
• Iniţializarea unor variabile de tip obiect, în secţiunea CONST. Este cazul
variabilei ZeroRat, pentru care se observă tratarea iniţializării ca în cazul
articolelor, definindu-se variabile pentru fiecare atribut, potrivit tipului său.
• Definirea structurii de tip array, la care tipul de bază este un tip obiect.
Tipul Rational permite definirea unui tip nou, Matrice, ca un masiv bidimensional.
• Alocarea dinamică a spaţiului pentru matrice drept spaţiu extins, potrivit
numărului de linii şi coloane, dar fără a se depăşi dimensiunile declarate ale tipului
Matrice. Se elimină, astfel, risipa de spaţiu prin alocarea statică, la dimensiuni
constante, pentru structura de tip masiv. De observat modul de referire a
elementelor matricei şi utilizarea metodelor clasei Rational pentru a trata aceste
elemente. Se spune că între clasa MatriceRat şi clasa Rational există o relaţie de tip
client-server.
• Prezenţa mai multor constructori şi a unui destructor pentru iniţializarea,
inclusiv a spaţiului extins şi, respectiv, eliberarea acestuia. Destructorul este apelat
la sfârşitul programului, pentru fiecare obiect. Spaţiul compact al obiectelor face
obiectul eliberării numai în cazul obiectelor locale, la ieşirea din procedura sau
funcţia în care obiectele au fost create, când este automat returnat stivei.
• Tratarea erorilor clasei prin intermediul unei variabile publice (din
interfaţă). Aceasta este utilizată în cazul operaţiei de adunare şi returnează valoarea
unu dacă matricele nu au aceeaşi dimensiune, respectiv zero, în caz contrar. Este
sarcina programului care apelează metodele clasei să verifice, la reîntoarcerea

193
Obiecte în Pascal

controlului, ce valoare are variabila publică. Variabila nu este legată de clasă, ci de


unitatea în care este încorporată clasa.

10.2.4 Utilizarea obiectelor dinamice

Obiectele statice nu sunt, în general, reprezentative pentru tehnica OOP.


Cel mai frecvent, obiectele au un comportament dinamic, adică în timp se nasc,
trăiesc şi dispar. În mod corespunzător, trebuie să existe posibilitatea gestionării
dinamice a spaţiului lor, astfel încât, cu aceleaşi rezerve de memorie, să poată fi
utilizate obiecte multiple, eventual de tipuri diferite. În limbajele orientate obiect,
obiectele sunt, prin definiţie, dinamice şi sistemul preia sarcina de a aloca şi elibera
spaţiul de memorie pentru acestea.Tehnica OOP implementată în Pascal face uz de
facilităţile de definire a variabilelor dinamice, dar gestiunea obiectelor este lăsată
exclusiv în sarcina programatorului.
În aceste condiţii, declararea obiectelor dinamice se poate face în una din
formele:
nume_referinta_clasa : ^nume_clasa;
nume_referinta_clasa : nume_tip_referinta;
Prima formă corespunde declarării cu tip anonim a variabilelor şi este mai
puţin indicată. A doua formă utilizează explicit un tip referinţă spre clasă şi este
posibilă numai dacă un astfel de tip a fost declarat. De regulă, pentru a da
posibilitatea definirii obiectelor dinamice, orice clasă se declară în forma:
nume_tip-referinta : ^nume_clasa;
nume_clasa =OBJECT.......END;
Aici se face uz de excepţia introdusă de limbaj, de a declara o referinţă
înainte de declararea tipului de date pe care îl referă. Această permisiune asigură
referinţe în interiorul obiectelor, la obiecte din propria clasă şi posibilitatea practică
de a construi liste înlănţuite de variabile şi obiecte.
De exemplu, dacă se presupun declaraţii de tip de forma:
PPoint=^TPoint;
TPoint=OBJECT.....END;
PRational=^TRational;
TRational=OBJECT....END;
atunci este posibil să se declare variabile obiect de forma:
PtrP1,PtrP2 : Ppoint;
PtrRat1: PRational;
După o astfel de declarare, compilatorul alocă spaţiu (static) pentru
variabilele referinţă. Este sarcina programatorului să aloce spaţiul dinamic şi să
apeleze constructorii/ destructorii de iniţializare/eliberare. În acest scop se
utilizează procedurile New şi Dispose, care au fost extinse astfel încât să accepte
drept parametru şi apelul la un constructor sau destructor. Mai mult, procedurile
posedă o replică sub formă de funcţie. Noua sintaxă a acestora se prezintă astfel:

194
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

New(variabila_referinta);
New(variabila_referinta, apel_constructor);
Variabila_referinta := New(tip_referinta, apel_constructor);
Dispose(variabila_referinta)
Dispose(varaibila_referinta, apel_constructor);
De exemplu, dacă Init(x,y:Integer) este un constructor pentru cazul clasei
Tpoint, se poate scrie:
New (PtrP1); PtrP1^.Init (30,20);
sau
New (PtrP1, Init(30, 20));
sau
PtrP1:=New (Ppoint, Init (30, 20));
În acest mod se asigură o posibilitate în plus pentru evitarea omisiunii
apelului constructorului.
Similar stau lucrurile şi cu eliberarea spaţiului. Dacă obiectele dinamice nu
au spaţiu extins, atunci eliberarea spaţiului compact dinamic se va face prin apelul
procedurii Dispose, în prima formă; de exemplu, Dispose (PtrP1). Dacă obiectul
dinamic are spaţiul extins, atunci clasa prevede şi un destructor pentru acesta
(destructor de spaţiu extins). În acest caz se poate utiliza procedura Dispose în a
doua formă. De exemplu, dacă se presupune clasa MatriceRat (exemplul_3),
declarată în forma: MatriceRat=^TMatriceRat; TMatriceRat= OBJECT…End; şi
declaraţii de obiecte de forma: PtrA:TMatriceRat, se poate apela destructorul
FreeExtMat în forma: Dispose (PtrA, FreeExtMat). Prin acest apel se execută mai
întâi destructorul, deci are loc eliberarea spaţiului extins şi apoi se eliberează
spaţiul compact dinamic.
Referirea unui obiect dinamic are forma referinta^ şi accesul la membrii
obiectelor va avea formele:
referinta^.nume_metoda(argumente)
referinta^.atribut
Aşa cum se pot construi masive de obiecte statice, tot aşa se pot construi
astfel de structuri având ca tip de bază referinţe la obiecte dinamice. Accesul la
membrii obiectului k se va face sub forma:
numearray[k]^.nume_metoda(argumente);
numearray[k]^.atribut.

10.3 Moştenirea în Pascal

Moştenirea este o relaţie de descendenţă ierarhică între clase, care oferă


mecanismele necesare pentru ca o clasă derivată să dobândească atributele

195
Obiecte în Pascal

părinţilor şi să primească dreptul de acces la metodele acestora. Deoarece permite


reutilizarea de cod, moştenirea este aspectul definitoriu al metodei OOP.

10.3.1 Aspecte de bază

În limbajul Pascal, moştenirea este implementată sub forma simplă, ceea ce


înseamnă că o clasă nouă poate fi derivată dintr-o singură clasă părinte (de bază).
Pentru ca o clasă să fie recunoscută ca bază a unei clase noi, declaraţia acesteia
trebuie să aibă forma:
nume_clasa_derivata = OBJECT(nume_clasa_baza)
declaratii membri;
End;
În Pascal, clasa derivată poate să definească membri noi sau să utilizeze
supraîncărcarea unor metode ale părintelui. Dacă se are în vedere arborele de
moştenire, clasa derivată poate supraîncărca metode ale oricărui ascendent. Totuşi,
supraîncărcarea în Pascal are unele limite faţă de alte implementări. Operatorii
limbajului nu pot fi supraîncărcaţi, iar o metodă nu poate fi supraîncărcată de mai
multe ori în cadrul aceleiaşi clase. Din punct de vedere sintactic şi semantic, aceste
limitări constituie neajunsuri, fiind necesară introducerea unor metode distincte
pentru operaţii formal identice, dar care acţionează asupra unor obiecte de tipuri
diferite. Dacă este necesar ca într-o clasă derivată, care supraîncarcă o metodă a
unui strămoş, să fie referită explicit această metodă, atunci, sintactic, numele clasei
strămoş trebuie să prefixeze numele metodei:
nume_clasa_stramos.nume_metoda_supraincarcata( )
Dacă într-un arbore de moştenire o clasă nu posedă o anumită metodă, dar
o referă fără a preciza clasa strămoş căreia îi aparţine, compilatorul caută ascendent
până la prima apariţie a ei. Limbajul prevede, de asemenea, o regulă adecvată
pentru asigurarea iniţializării corecte a obiectelor aparţinând unui arbore de
derivare. Se cere ca fiecare clasă să aibă un constructor care să apeleze
constructorul clasei părinte pentru partea moştenită şi apoi să iniţializeze atributele
proprii (figura 10.9).
O situaţie similară, dar reciprocă, este cea a destructorilor. Limbajul
prevede că, în cazul obiectelor unei ierarhii, apelul ascendent al destructorilor este
automat. Deci, dacă o clasă (finală) posedă un destructor pentru spaţiul extins,
atunci execuţia acestuia declanşează procesul de eliberare, din aproape în aproape,
a spaţiilor extinse moştenite de la toţi strămoşii, prin execuţia destructorilor
acestora. Pentru ca mecanismul să funcţioneze corect şi în cazul în care există
clase strămoş care nu posedă spaţiu extins, limbajul permite destructori cu corp vid,
pe care compilatorul îi generează automat la clasele respective. Pentru mai multă
claritate, este recomandabil ca programatorul să definească destructori cu corp vid
pe ramurile care nu conţin cel puţin un destructor propriu-zis.

196
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

O menţiune aparte trebuie făcută pentru cazul obiectelor dinamice ale unei
ierarhii de clase. Datorită polimorfismului, orice obiect este compatibil, la atribuire,
cu oricare obiect al unui strămoş. În aceste condiţii, o referinţă la un obiect strămoş
poate conţine adresa unui obiect descendent şi apelul unei metode, de forma:
PtrStramos^.Numemetoda ( ), introducând un element de nedeterminare în privinţa
obiectului de apel. Pentru astfel de cazuri, în limbajul Pascal s-a stabilit urmatoarea
regulă care se aplică în lipsa altor informaţii adiţionale: se apelează metoda clasei
corespunzăroare tipului referinţei, dacă o astfel de metodă există, sau metoda
corespunzătoare primului ascendent care o posedă.

Moştenire

Clasa A Clasa A Clasa A


a a a
b b
c c
d

Init Init Init


Fig. 10.9 Moştenirea şi apelul

Exemplu:
10.4. În ramura TX←TY←TZ←TW, definită în continuare, apare o
procedură supraîncărcată, p.

PX=^TX;
TX=OBJECT
Procedure p
END;
PY=^TY;
TY=OBJECT (TX)
Procedure p
END;
PZ=^TZ;
TZ=OBJECT (TY)
Procedure q
END;
PW=^TW;
TW=OBJECT (TZ)
Procedure r
END;

Dacă se declară obiectele dinamice PtrX:PX; PtrW:PW şi se face atribuirea şi


apelul:

197
Obiecte în Pascal

..................
PtrX:=PtrW;
PtrX^.p;
se va apela procedura p a clasei TX, ghidându-se după referinţa PtrX care este
asociată acestei clase. Dacă se declară PtrZ:PZ; PtrW:PW şi se fac atribuirea şi
apelul:
..................
PtrZ:=PtrW;
PtrZ^.p;

atunci se va apela metoda p a clasei TY, primul ascendent al clasei TZ,


deţinătoarea referinţei PtrZ, care posedă această metodă.
Ipostazele incluse în compilator, după regula de mai sus, permit legarea
statică a metodelor, care este simplă şi eficientă, dar care poate să nu fie
convenabilă în multe cazuri.

10.3.2 Legarea dinamică. Metode virtuale

Legarea dinamică se referă la metode supraîncărcate, identice ca prototip,


care sunt declarate într-o ramură a unei ierarhii de clase. În acest caz particular de
supraîncărcare, se spune că metoda este redefinită, având în vedere funcţionalitatea
nouă (corpul nou) care i se asociază. Este de dorit ca metoda să fie apelată cu
obiectul a cărui adresă o conţine referinţa de apel, fapt ce nu poate fi stabilit decât
la momentul execuţiei. Devine evidentă necesitatea înştiinţării compilatorului
asupra acestei intenţii, pentru a nu aplica regula legării statice şi, în consecinţă,
pentru a genera codul necesar determinării obiectului real şi metodei de apelat la
momentul execuţiei. O astfel de tehnică de legare a unei metode cu obiectul
dinamic este denumită dinamică (late binding).
Declararea unei metode din această categorie se realizează prin
declaratorul VIRTUAL, inclus în linia de definire a prototipului:
procedure nume (parametri); VIRTUAL;
function nume (parametri): tip_rezultat; VIRTUAL;
Metodele redefinite sunt denumite virtuale. Dacă o metodă a fost declarată
virtuală într-o clasă, atunci trebuie să fie declarată la fel în toate clasele derivate
care au această clasă la rădăcină şi redefinesc metoda în cauză. Totuşi, un
descendent nu este obligat să redefinească o metodă virtuală a unui ascendent,
subînţelegându-se, în acest caz, că se aplică regula căutării ascendente, dacă un
obiect al său o referă.
O menţiune aparte trebuie făcută cu privire la constructori şi destructori. În
primul rând, trebuie remarcat faptul că obiectele care posedă metode virtuale
trebuie să fie iniţializate prin constructor, dar constructorii nu pot fi declaraţi ca
metode virtuale. Regula este de natură să asigure o corectă iniţializare, prin evitarea
situaţiilor necontrolabile de iniţializare parţială, posibile în cazul legării dinamice.
În al doilea rând, se menţionează, ca regulă, posibilitatea de a virtualiza
destructorii. Spre deosebire de constructori, virtualizarea destructorilor este, în

198
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

multe cazuri, absolut necesară, deoarece se recomandă ca procedeu general. Ce se


poate întâmpla în cazul în care destructorii nu sunt virtualizaţi rezultă şi din
exemplul care urmează.

Exemplu:
10.5. Se presupune derivarea TA←TB←TC şi destructorul virtual D. Se
declară un masiv de referinţe la obiecte de clasă TA care se iniţializează cu adresele
unor obiecte. Se execută apoi ştergerea obiectelor dinamice, cu apelul
destructorului D, pentru a elibera spaţiul extins al acestor obiecte.
PA = ^ TA;
TA = OBJECT
........................................
Destructor D ; VIRTUAL;
End;
PB = ^ TB;
TB = OBJECT (TA)
........................................
Destructor D ; VIRTUAL;
End;
PC = ^TC;
TC = OBJECT (TB)
........................................
Destructor D; VIRTUAL;
End;
.............................................................
p:array [0..2] of PA;
.............................................................
..
p[0]: = new (PB,…); {contine obiect TB}
p[1]: = new (PC,…); {contine obiect TC}
p[2]: = new (PA,…); {contine obiect TA}
.............................................................
dispose (p[0], D); {apel destructor TB, TC}
dispose (p[1], D); {apel destructor TC, TB, TA}
dispose (p[2], D); {apel destructor TA}

Se observă că p conţine referinţe la diferite obiecte, în virtutea


polimorfismului. Ştergerea obiectelor ale căror adrese sunt în componentele lui p
implică o legare dinamică a destructorului potrivit. Astfel, pe baza regulii de apel
automat al destructorilor în sens ascendent, se eliberează corect, în fiecare caz,
spaţiul extins propriu şi cel moştenit. Dacă D nu ar fi fost declarat virtual, deoarece
p are tipul de bază corespunzător referinţei la clasa TA, în fiecare caz s-ar fi apelat
numai destructorul acestei clase, ceea ce ar fi însemnat neeliberarea întregului
spaţiu extins al obiectelor respective.
Mecanismul metodelor virtuale este implementat prin intermediul tabelei
de metode virtuale VMT (Virtual Methods Table) a clasei care se ataşează părţii de
date a obiectelor din acea clasă (figura 10.10).

199
Obiecte în Pascal

Obiect 1 Obiect 2
Atribut 1 Atribut 1

Atribut 2 VMT Atribut 2


. .
Date de control
. .
. @Metodă virtuală 1
.
@VMT @Metodă virtuală 2 @VMT
.
.
.

Fig. 10.10 Tabela de metode virtuale

Tabela de metode virtuale este ataşată la obiect de unul din constructori,


care este completat corespunzător de către compilator şi este apelat înainte de
apelul oricărei metode virtuale a obiectului respectiv.

10.3.3 Moştenirea şi instanţele


Un tip obiect descendent dintr-un altul moşteneşte, în particular, atributele
ascendentului. El posedă, deci, cel puţin câmpurile ascendentului, plus, eventual,
altele noi. O instanţă a unui tip ascendent poate fi, deci, atribuită cu o instanţă a
descendentului (obiect_parinte:=obiect_fiu). O eventuală atribuire inversă este
incorectă, pentru că ar avea ca efect neiniţializarea atributelor noi ale
descendentului. De asemenea, în cazul unui transfer prin parametri valoare, o
instanţă părinte poate fi substituită cu una fiu.
Dacă, în schimb, obiectele conţin spaţiu extins şi unuia dintre obiecte îi
este disponibilizat spaţiul extins, atunci şi celălalt îşi va pierde spaţiul extins şi,
dacă el îşi va apela destructorul, acesta va încerca eliberarea unui spaţiu de
memorie inexistent. De asemenea, dacă tipurile de obiecte conţin metode virtuale, o
atribuire nu este în general suficientă în iniţializarea corectă a unei instanţe.
O instrucţiune de atribuire utilizează dimensiunea unei instanţe pentru
transferul de date. În cazul claselor ce conţin metode virtuale, dimensiunea este
conţinută într-un câmp al VMT, deci poate fi cunoscută numai după apelul
constructorului respectiv. Efectul unei instrucţiuni de atribuire este, deci, consistent
doar în funcţie de dimensiuni, acestea putând fi determinate numai după apelul
constructorului.

200
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Exemplu:
10.6. Lucrul cu metode virtuale

program virt;
uses crt;
type
parinte=object
a,b:word;
constructor init_p(a1,b1:word);
function suma:word;virtual;
end;

fiu=object(parinte)
c:word;
constructor init_f(a1,b1,c1:word);
function suma:word;virtual;
end;
var
p:parinte;
f:fiu;
constructor parinte.init_p;
begin
a:=a1; b:=b1;
end;
function parinte.suma:word;
begin
suma:=a+b;
end;
constructor fiu.init_f;
begin
a:=a1; b:=b1; c:=c1;
end;
function fiu.suma:word;
begin
suma:=a+b+c;
end;
procedure afis;
begin
writeln('Instanta parinte a=',p.a,' b=',p.b, ' dimensiunea
',sizeof(p));
writeln('Instanta fiu a=',f.a,' b=',f.b,' c=',f.c,'
dimensiunea ', sizeof(f));
readln;
end;
begin
clrscr;
fillchar(p,sizeof(parinte)+sizeof(fiu),#0);
afis; {1}
f.init_f(1,2,3);
afis;{2}
p:=f;
afis;{3}
p.init_p(4,5);
afis;{4}
p:=f;
afis;{5}
end.

201
Obiecte în Pascal

Rezultatul execuţiei programului

{1} parinte a=0 b=0 dimensiune 0


fiu a=0 b=0 c=0 dimensiune 0
{2} parinte a=0 b=0 dimensiune 0
fiu a=1 b=2 c=3 dimensiune 8
{3} parinte a=0 b=0 dimensiune 0
fiu a=1 b=2 c=3 dimensiune 8
{4} parinte a=4 b=5 dimensiune 6
fiu a=1 b=2 c=3 dimensiune 8
{5} parinte a=1 b=2 dimensiune 6
fiu a=1 b=2 c=3 dimensiune 8

10.4 Proiectarea ierarhiilor de clase

Tehnica OOP se bazează pe date şi pe clasificarea corespunzătoare a


acestora. De aceea, pentru a asigura reutilizarea de cod, obiectivul principal al
proiectării este construirea unui arbore de moştenire cât mai judicios. În continuare
se vor face câteva recomandări de proiectare, ilustrate pe baza unui exemplu
didactic de obiecte grafice, redat în detaliu la sfârşitul capitolului.
10.4.1 Clase abstracte
Dacă în procesul de analiză se privilegieză datele, atunci primul lucru care
trebuie pus în evidenţă sunt mulţimile de obiecte care trebuie tratate. Pentru cazul
obiectelor vizuale, acestea sunt: puncte, linii, dreptunghiuri, cercuri etc. În
continuare trebuie căutate acele date (caracteristici) care sunt definitorii pentru
diferitele tipuri de obiecte şi trebuie puse în evidenţă acele elemente care sunt
comune. Datele comune se constituie în clase şi reprezintă acele date care pot fi
moştenite. În cazul figurilor geometrice, se poate constata că orice obiect se
identifică printr-un punct de cordonate (x,y) reprezentând începutul liniei, colţul
stânga sus al dreptunghiului, centrul cercului etc, că orice figură presupune o curbă
de contur trasată cu o anumită culoare, că la figurile închise se pune în evidenţă o
suprafaţă care poate fi umplută (haşurată şi colorată) într-un anumit mod etc.
Se poate uşor constata că, aplicând principiul grupării datelor comune în
clase, se ajunge în situaţia de a obţine clase pentru care nu are sens să se declare
obiecte. Astfel, de exemplu, ar fi clasa care descrie curba de contur a obiectelor
(Contur) sau cea care conţine elementele definitorii pentru suprafaţa interioară a
obiectelor (Suprafaţa). Astfel de clase se numesc abstracte (figura 10.11). Ele nu
pot fi instanţiate, adică nu pot genera obiecte de acest tip. Rolul lor în ierarhii este

202
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

acela de clase de bază care grupează date şi metode comune. Din ele, prin
specializare, se pot construi clase noi, care vor poseda caracteristicile clasei pe care
o moştenesc şi, în plus, vor defini elemente (membri) specifice. Aşa de exemplu,
clasa Contur poate da naştere unei clase Linie, de obiecte de tip linie dreaptă, iar
clasa Suprafaţa conduce la clasele Dreptunghi şi Cerc, ca obiecte cu suprafaţă
specifică.
Clasele abstracte pot fi definite la diferite niveluri în arbore, fiind posibil
ca o clasă abstractă să fie părinte pentru o altă clasă abstractă mai specializată. De
regulă, o clasă abstractă este rădăcină în orice ierarhie. Deşi clasele abstracte nu se
instanţiază, este permis să se declare referinţe către acestea. Acest lucru asigură
polimorfismul şi, la limită, o referinţă de clasă rădăcină poate recepţiona adresa
oricărui obiect din ierarhie şi, în consecinţă, poate asigura tratarea dinamică a
obiectelor polimorfice.
Uneori, clasele abstracte sunt utilizate pentru a grupa clase care nu au date
comune, dar participă împreună la realizarea programelor. Aceste clase sunt atât de
generale încât, de regulă, se reduc la un constructor vid şi, eventual, la un
destructor vid şi virtual. Ele se justifică prin aceea că, în virtutea polimorfismului,
permit aplicarea unui cod comun şi general la obiectele acestor clase, cum ar fi
constituirea de liste eterogene.
Definirea unei clase abstracte drept clasă de bază a unei ierarhii conduce la
posibilitatea de a utiliza aceeaşi clasă pentru toate ierarhiile. Principiul este aplicat,
în general, la construirea bibliotecilor de clase pe care limbajele orientate obiect le
oferă. De exemplu, în Turbo Pascal, biblioteca denumită Turbo Vision are drept
clasă primordială pentru toate ierarhiile clasa TObject. Astfel se “leagă” între ele
diferitele ierarhii şi se poate utiliza facilitatea de polimorfism.
10.4.2 Definirea metodelor

După conturarea claselor, din punctul de vedere al membrilor de tip dată,


următoarea etapă a proiectării este definirea metodelor, ţinând cont de
comportamentul dorit al diferitelor tipuri de obiecte. În cazul considerat, obiectele
se creează, se afişează, se ascund, îşi schimbă culoarea de contur ori modul de
umplere etc. (figura 10.11).
În legătură cu definirea metodelor se fac următoarele precizări:
• Fiecare clasă trebuie să posede un constructor. După modelul Turbo
Vision, se recomandă a fi denumit INIT. Constructorul unei clase trebuie să
apeleze constructorul clasei părinte, pentru a se asigura iniţializarea părţii
moştenite.
• Fiecare clasă care utilizează alocarea dinamică pentru spaţiu extins
trebuie să posede un destructor, denumit DONE, declarat virtual.
• Vor fi declarate virtuale, la nivelul claselor strămoş, toate metodele care
urmează să fie redefinite în clasele derivate. Aceste metode trebuie să fie
redeclarate ca virtuale în toţi descendenţii.
• Dacă o clasă instanţiabilă conduce la obiecte vizuale (grafice, texte),
atunci clasa trebuie să posede o metodă proprie de afişare (se spune că obiectele se

203
Obiecte în Pascal

autoafişează). Metoda de afişare se recomandă să fie definită ca virtuală, pentru a


se asigura o legare dinamică adecvată.
• Pentru a asigura satisfacerea deplină a principiului abstractizării şi
încapsulării datelor, se recomandă utilizarea secţiunii PRIVATE pentru datele
specifice clasei şi care nu intră direct în interfaţă. În acest sens trebuie prevăzute,
pentru fiecare câmp, o metodă de consultare şi o metodă de modificare a valorii.
• Deoarece programele realizate prin tehnica OOP sunt de talie mare, se
recomandă utilizarea memoriei dinamice, care trebuie eliberată sistematic, eventual
prin destructori adecvaţi.

10.4.3 Tratarea erorilor

În realizarea metodelor unei clase apar, de regulă, două tipuri de erori: de


domeniu şi de alocare.
• Erorile de domeniu sunt generate de nerespectarea domeniului de
definire a metodei (de exemplu, pentru o clasă stivă, operaţiile de citire şi ştergere
nu sunt definite în cazul în care lista este vidă). Erorile nu sunt tratate de limbaj,
fiind sarcina programatorului să prevadă mecanisme adecvate de comunicare între
metodele claselor şi utilizatorul acestora. După “agentul” care provoacă situaţiile
de eroare, se pot aplica, în general, două strategii:
a) Dacă eroarea este determinată de starea obiectului curent, adică de
obiectul cu care urmează să se apeleze o anumită metodă, atunci este detectabilă
înainte de apel. Clasa prevede metode de detecţie, ca funcţii booleene, pe fiecare
tip de eroare sau o funcţie unică pentru a sesiza prezenţa erorii. Obiectul curent va
utiliza o funcţie de detecţie adecvată şi va proceda în consecinţă, adică va apela sau
nu metoda respectivă. De exemplu, pentru un obiect de tip listă, o funcţie Empty
poate testa dacă pointerul pentru capul listei are valoarea NIL, întorcând valoarea
True, dacă lista este vidă sau False, în caz contrar.

204
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Figura
x, y
EsteVizibil
Culoare
INIT
AFISEAZA
ASCUNDE
ARIE
SCHIMBACULOARE
Punct Contur
AFISEAZA TipLinie
ASCUNDE GrosimeLinie
INITDEFAULT
SCHIMBACONTUR

Linie Suprafata
XSfarsit TipHasura
YSfarsit CuloareUmplere
Init INITDEFAULT
AFISEAZA SCHIMBAUMPLERE
ASCUNDE

Dreptunghi Cerc
Latime Raza
Inaltime INIT
INIT AFISEAZA
AFISEAZA ASCUNDE
ASCUNDE ARIE
ARIE

Fig. 10.11 Ierarhie de obiecte grafice

b) Dacă situaţiile de eroare sunt datorate stării altor obiecte, care se


primesc ca parametri în metoda aplicată, atunci strategia anterioară nu este
aplicabilă, deoarece numai metoda în cauză este în măsură să sesizeze astfel de
cazuri prin testarea parametrilor săi. În consecinţă, modul în care s-a terminat
execuţia metodei se transmite ca o informaţie apelatorului. Ea trebuie tratată ca o
informaţie globală metodei sau chiar clasei, deoarece este independentă de obiectul
cu care s-a apelat metoda. Având în vedere încapsularea claselor în unităţi de
program, astfel de informaţii pot fi valori ale unor variabile globale, declarate în
secţiunea INTERFACE. Variabila globală poate fi de tip enumerativ, tip în care
constantele definesc situaţiile de eroare. O declarare pentru aceste elemente poate
avea, de exemplu, forma:

205
Obiecte în Pascal

INTERFACE
Type
TipError=(OK, ZeroNum, TooBigNum);
Var
classErr:TipError;

La începutul metodelor implicate, variabilele de eroare se pun pe constanta


care semnifică succes şi, după testările parametrilor, se modifică adecvat. Este
sarcina apelatorului metodei ca, la reîntoarcerea controlului din metodă, să testeze
dacă execuţia s-a realizat cu succes.
• Erori de alocare. Alocarea spaţiului dinamic pentru obiecte sau a
spaţiului extins al acestora poate eşua. O astfel de situaţie conduce la abandonarea
programului. Programatorul trebuie să construiască un mecanism propriu de tratare
a situaţiei. El se bazează pe utilizarea variabilei HeapError, procedura Fail şi
posibilitatea de a iniţializa o unitate. Se procedează astfel:

◊ În fiecare constructor sau metodă care utilizează alocarea dinamică


trebuie să se testeze valoarea referinţei returnată de funcţia sau procedura de
alocare. Dacă a apărut o situaţie de eroare de alocare, procedura poate prevedea cod
pentru a recupera eroarea, dacă acest lucru este posibil sau poate să încheie în
ordine execuţia apelând procedura Fail. De exemplu:

Constructor TA.Init;
Begin

....................................................
new (Ptr1); {alocare de spatiu dinamic}
new(Ptr2);

....................................................
if (Ptr1=Nil) or (Ptr2=Nil)…….
Then
begin
{cod pentru recuperare}
sau
{cod de terminare + TA.DONE + Fail;}
end;
{continuare constructor}
End;

◊ În partea de implementare a unităţii de program a clasei se defineşte o


funcţie proprie, având un argument de tip WORD, care este utilizată în locul
funcţiei standard ce se apelează în caz de lipsă de spaţiu:

{$F+} {adresare far}


Function fallocerr (a:Word): Integer;
Begin
fallocerr:=1
End;
◊ În partea de iniţializare a unităţii de program se introduce secvenţa:

206
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Begin
HeapError:=@fallocerr;
End.

În acest mod se înlocuieşte funcţia spre care punctează variabila


HeapError, care întoarce valoarea zero în caz de lipsă de spaţiu, cu o funcţie
proprie care returnează unu. Aceasta face ca sistemul să continue execuţia
programului şi în cazul de eroare de spaţiu, făcând posibilă tratarea proprie a
situaţiei.

207
ANEXA 1
UNITATEA DOS

1. Tipuri de date
Tipul Registers
Registers =
Record
CASE INTEGER OF
0: (AX,BX,CX,DX,BP,SI,DI,DS,ES,Flags:WORD);
1: (AL,AH,BL,BH,CL,CH,DL,DH:BYTE);
End;

Registrele AX, BX, CX şi DX sunt de uz general. Pe lângă referirea globală,


ca registre de 16 biţi, pot fi referite şi semiregistre de opt biţi (un octet), la nivelul
octetului inferior, prin AL, BL, CL, DL, respectiv al celui superior, prin AH, BH, CH,
DH. Registrul BP (base pointer) conţine valoarea deplasării curente în segmentul de
stivă. Registrele SI şi DI corespund unor deplasări ale operandului sursă şi destinaţie în
cadrul segmentului de date, iar DS şi ES reprezintă adresele de segment ale acestor
operanzi. Registrul Flags conţine indicatorii de control şi de stare (flags) ai
procesorului, asociaţi unora dintre cei 16 biţi ai săi, poziţionaţi în funcţie de rezultatele
executării fiecărei operaţii. Din perspectiva referirii prin programe Turbo Pascal,
prezintă interes următorii indicatori de control (sau de condiţie): • CF (Carry Flag):
este setat la transportul în exterior al celui mai semnificativ bit în operaţiile de calcul cu
numerele reprezentate pe 16 biţi; • PF (Parity Flag): este setat dacă octetul inferior al
rezultatului unei operaţii conţine un număr par de biţi cu valoarea unu. Este folosit cu
prioritate de programele de comunicaţie; • AF (Auxiliary carry Flag): este setat dacă
cei patru biţi de ordin inferior ai unei operaţii generează un transport în exterior (folosit
pentru aritmetica BCD (Binary Coded Decimal); • ZF (Zero Flag): este setat dacă
rezultatul unei operaţii este zero; • SF (Sign Flag): este egal cu bitul de ordin superior
al rezultatului unei operaţii de calcul ce foloseşte operanzi cu semn (0 - pozitiv, 1 -
negativ); • OF (Overflow Flag): este setat dacă o operaţie de calcul cu operanzi cu
semn produce un rezultat prea mare (sau prea mic, operaţie numită subdepăşire -
underflow) pentru a putea să fie reprezentat în zona rezultatului. Unitatea Dos conţine
mai multe constante simbolice predefinite asociate acestor indicatori.

Data şi ora
DateTime =
Record
Year : WORD; {Anul}

208
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Month : WORD; {Luna}


Day : WORD; {Ziua}
Hour : WORD; {Ora}
Min : WORD; {Minutul}
Sec : WORD; {Secunda}
End;

Articol pentru căutare


SearchRec =
Record
Fill : ARRAY[1..21] OF BYTE;
Attr : BYTE;
Time : LONGINT;
Size : LONGINT;
Name : STRING[12];
End;
Semnificaţia câmpurilor este următoarea: Fill - rezervat pentru sistemul de
operare; câmpul nu trebuie modificat în program. Attr - atributele fişierului (valoarea
octetului $0B din intrarea fişierului în [sub]director). Time - data şi ora ultimei scrieri
în fişier, sub formă împachetată (valoarea, ca LONGINT, a octeţilor $16-$19 din
intrarea fişierului în [sub]director). Size - lungimea în octeţi a fişierului (valoarea
câmpului $1C-$20 din intrarea fişierului în [sub]director). Name - numele fişierului
urmat de punct şi extensia lui (valoarea câmpurilor $0-$7 şi $8-$A din intrarea
fişierului în [sub]director).

Tipuri pentru caracteristicile fişierelor


PathStr = STRING[79];⇒ Specificatorul complet;
DirStr = STRING[67];⇒ Unitatea şi calea;
NameStr = STRING[8]; ⇒ Numele propriu-zis;
ExtStr = STRING[4]; ⇒ Extensia.

Structura FIB pentru fişiere cu tip şi fără tip


FileRec =
Record
Handle : WORD;
Mode : WORD;
RecSize : WORD;
Private : ARRAY[1...16] OF BYTE;
UserData : ARRAY[1...16] OF BYTE;
Name : ARRAY[0...79] OF CHAR;
End;

209
Unitatea DOS

Structura FIB pentru fişierele TEXT

TextRec =
RECORD
Handle : WORD;
Mode : WORD;
BufSize : WORD;
Private : WORD;
BufPos : WORD;
BufEnd : WORD;
BufPtr : ^TextBuf;
OpenFunc : Pointer;
InOutFunc : Pointer;
FlushFunc : Pointer;
UserData : ARRAY[1...16] OF BYTE;
Name : ARRAY[0...79] OF CHAR;
Buffer : TextBuf;
END;

Semnificaţia principalelor câmpuri este următoarea: • Handle este o valoare


întreagă care reprezintă identificatorul pentru fişiere deschise. Valorile 1-7 sunt
utilizate pentru dispozitivele standard de I/E (intrare, ieşire, auxiliar, imprimanta,
fişierele în curs de folosire cu comanda PRINT din DOS şi de reţea). Fişierele deschise
ale utilizatorului au valori pentru handle începând cu 8. Fişierele nedeschise au
Handle=0. O valoare handle asociată unui fişier devine disponibilă la închiderea
acestuia. Într-un program pot fi deschise simultan maxim 12 fişiere ale utilizatorului
(handle cu valori din intervalul [8, 19]). • Mode indică starea fişierului, care poate fi
exprimată şi prin constantele publice specifice. Fişierele TEXT pot avea stările
FmClosed, FmInput, FmOutput. Fişierele binare pot avea orice stare (implicit
FmClosed sau FmInOut). • RecSize indică lungimea articolului (blocului) rezultată din
descrierea internă programului. • Name este identificatorul extern al fişierului, aşa cum
este precizat de procedura Assign. • BufSize reprezintă lungimea buffer-ului fişierului
TEXT. • BufPos, BufEnd, BufPtr reprezintă pointeri folosiţi în gestiunea buffer-elor
asociate fişierului TEXT. • OpenFunc, InOutFunc, CloseFunc, FlushFunc reprezintă
adresa driver-elor pentru deschidere, citire/scriere, închidere a fişierului, golire a
buffer-ului. • Buffer reprezintă zona tampon asociată fişierului (buffer-ul fişierului).
Tipul TextBuf este definit în unit-ul Dos astfel: TextBuf = ARRAY[0...127] of CHAR.
Pentru a avea acces la informaţiile din FIB, trebuie declarată o variabilă de tip
FileRec (respectiv TextRec) care să aibă aceeaşi adresă cu FIB-ul (cu variabila de tip
fişier). De exemplu: f: TEXT; inf_f: TextRec ABSOLUTE f.

210
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

2. Constante

Atributele fişierelor (octetul $0B din intrarea unui fişier în [sub]director)


ReadOnly = $01 ⇒ fişier numai pentru citire;
Hidden = $02 ⇒ fişier ascuns;
SysFile = $04 ⇒ fişier de sistem;
Volumid = $08 ⇒ identificator de volum;
Directory = $10 ⇒ [sub]director;
Archive = $20 ⇒ fişier arhivă;
AnyFile = $3F ⇒ orice fişier

Constante asociate indicatorilor de stare


Fcarry = $0001;
Fparity = $0004;
Fauxiliary = $0010;
Fzero = $0040;
FSign = $0080;
Foverflow = $0800;
Constantele corespund unor măşti cu care se poate testa dacă un anumit
indicator este setat, valoarea lor fiind dată de poziţia indicatorului în cadrul registrului
Flags. Astfel, indicatorul CF este plasat în bitul zero, deci masca asociată lui
corespunde lui 20=$0001, indicatorul PF este plasat în bitul doi, cu masca 22=$0004
etc. Dacă se doreşte, de exemplu, să se verifice dacă ZF este setat, folosind o variabilă
Reg de tipul Registers se va putea scrie: IF (Reg.Flags AND FZero) = 1 THEN ...

Constante pentru starea fişierului


FmClosed = $D7B0 ⇒ fişier închis;
FmInput = $D7B1 ⇒ fişier deschis pentru citire;
FmOutput = $D7B2 ⇒ fişier deschis pentru scriere;
FmInOut = $D7B3 ⇒ fişier deschis pentru citire/scriere;

3. Variabile

DosError: INTEGER;
Este iniţializată de funcţiile şi procedurile din acest unit, cu următoarele valori
principale:0 - fără eroare; 2 - fişier negăsit; 3 - cale negăsită; 4 - prea multe
fişiere deschise; 5 - acces interzis etc. Variabila DosError are valoarea zero
când execuţia funcţiilor şi a procedurilor definite în unit-ul Dos se termină
normal.

211
Unitatea DOS

4. Proceduri şi funcţii
Prelucrarea fişierelor
Procedure GetFAttr(VAR f; VAR attr:WORD);
Returnează atributele unui fişier.
Procedure SetFAttr(VAR f; VAR attr:WORD);
Poziţionează (setează) atributele unui fişier.
Procedure GetFTime(VAR f; VAR Dl:LONGINT);
Returnează data şi ora ultimei scrieri într-un fişier. Dl este variabila în care se
recepţionează data şi ora, sub formă "împachetată".
Procedure SetFTime(VAR f; Dl:LONGINT);
Înscrie data şi ora în eticheta fişierului. Dl este variabila ce conţine data şi ora
sub formă împachetată, care se înscriu în intrarea fişierului în [sub]director.
Procedure FindFirst(F_extern:PathStr; Attr:WORD; VAR zona:SearchRec);
Caută într-un [sub]director prima intrare a unui fişier care are specificatorul şi
atributul precizate în lista de parametri. F_extern este specificatorul extern al
fişierului căutat. Specificatorul este format din cale, nume fişier şi, eventual,
extensie. Când calea lipseşte se presupune [sub]directorul curent. Numele şi
extensia fişierului pot fi generice (formate cu caracterele * sau ?). Attr
reprezintă valoarea atributelor fişierului care se caută. Zona este o variabilă de
tipul SearchRec, care va conţine informaţii despre fişierul f_extern în cazul
când a fost găsit. Dacă f_extern nu este găsit, variabila zona rămâne
nemodificată. Când f_extern este găsit, variabila DosError va avea valoarea
zero. În caz contrar, DosError are valoare diferită de zero.
Procedure FindNext(VAR zona:SearchRec);
Caută într-un [sub]director următoarea intrare a unui fişier care are
specificatorul şi atributul precizate la apelul anterior al procedurii FindFirst.
Rezultă că procedura FindNext poate fi utilizată numai dacă, anterior, a fost
apelată procedura FindFirst. Zona are aceeaşi semnificaţie ca la procedura
FindFirst. Dacă următoarea intrare este găsită, variabila DosError va avea
valoarea zero. În caz contrar, DosError are valoare diferită de zero.
Function FSearch(Nume:PathStr; Lista:STRING):PathStr;
Caută un fişier într-o listă de [sub]directori. Ea este asemănătoare comenzii
PATH din MSDOS. Nume este variabilă de tip PathStr, care conţine numele
fişierului de căutat. Lista conţine lista directoarelor în care se continuă
căutarea, dacă fişierul nu a fost găsit în directorul curent. Căile specificate în
listă trebuie separate prin caracterul ;. Funcţia returnează specificatorul extern
al fişierului, în cazul în care îl găseşte. Când fişierul este în directorul curent,
specificatorul extern furnizat este format din nume şi, eventual, extensie, iar
când fişierul este în alt director, specificatorul este complet (cale+nume [+
extensie]). Când fişierul nu este găsit, funcţia returnează şirul vid. Funcţia nu
verifică existenţa căilor. Când căile nu există, se returnează şirul vid.

212
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

Function FExpand(Nume:PathStr):PathStr;
Expandează (completează) numele cu calea. Nume este variabilă de tip
PathStr ce conţine numele care urmează să fie expandat cu calea. În procesul
prelucrării fişierelor, funcţia are sens când este utilizată pentru extinderea cu
componente (unitate, cale) furnizate implicit.
Procedure FSplit(specificator:PathStr;VAR unit_dir:DirStr;VAR name:NumeStr;VAR
ext:ExtStr);
Descompune specificatorul extern (unitate, cale, nume, extensie) în trei
componente: unitate+cale, nume, .extensie. Specificator este şirul care se
analizează şi se descompune în componente. Unit_dir, nume şi ext sunt
variabile în care se depun cele trei componente extrase din specificator. Când
unitatea şi calea nu sunt prezente în specificator, se returnează valorile
implicite. Când numele şi extensia nu sunt prezente în specificator, se
returnează şiruri vide.
Procedure SetVerify(v:BOOLEAN);
Poziţionează comutatorul verify din DOS. Comutatorul are două valori: ON
(valoare TRUE) sau OFF (valoare FALSE). Când comutatorul are valoarea
ON, sistemul de operare face verificare după fiecare operaţie de scriere în
fişiere (se verifică dacă datele scrise pot fi citite fără eroare). Poziţia ON a
comutatorului verify măreşte timpul de execuţie a programelor. Când
comutatorul are valoarea OFF, sistemul de operare nu face verificarea scrierii.
Valoarea implicită a comutatorului este OFF. Valoarea comutatorului rămâne
activă până la o nouă setare. V este o variabilă de tip BOOLEAN. Când v are
valoarea TRUE, comutatorul verify primeşte valoarea ON; în caz contrar pri-
meşte valoarea OFF. Procedura are efect similar cu comanda VERIFY din
DOS. Dacă comutatorul verify este ON, rezultatul verificării scrierii este
memorat în variabila DosError.

Ocuparea discului
Function DiskSize(Drive:BYTE):LONGINT;
Returnează capacitatea, în octeţi, a discului montat în unitatea specificată.
Drive specifică unitatea de disc, astfel: 0 - unitatea de disc curentă; 1 - unitatea
de disc A; 2 - unitatea de disc B; 3 - unitatea de disc C etc. Funcţia returnează
valoarea -1 dacă unitatea de disc nu există, dacă nu este montat disc în unitate
sau dacă unitatea este defectă.
Function DiskFree(Drive:BYTE):LONGINT;
Returnează numărul de octeţi liberi de pe un disc montat într-o unitate. Drive
are aceeaşi semnificaţie ca la funcţia DiskSize. Funcţia returnează valoarea -1
dacă unitatea de disc nu există, dacă nu este montat disc în unitate sau dacă
unitatea este defectă.

213
Unitatea DOS

Execuţia programelor
Procedure Exec(specif,parametru:STRING);
Încărcarea în memorie şi lansarea în execuţie a programului aflat în fişierul
specif. Parametrii de pe linia de comandă sunt specificaţi în parametru.
Function DosExitCode:WORD;
Codul de retur al unui proces (program).

Întreruperi
Procedure Intr(ni:BYTE; VAR reg:Registers);
Lansarea întreruperii ni. În variabila reg se pregătesc argumentele.
Procedure MsDos(VAR reg:Registers);
Lansarea întreruperii $21. În variabila reg se pregătesc argumentele.
Procedure GetIntVec(int:BYTE; VAR adrint:POINTER);
Obţinerea, în adrint, a adresei întreruperii cu numărul int.
Procedure SwapVectors;
Salvarea vectorilor de întrerupere.

Data şi timpul
Procedure GetDate(VAR an, luna, ziua_luna, ziua_sapt:WORD);
Obţinerea datei din sistem.
Procedure SetDate(VAR an, luna, ziua:WORD);
Poziţionarea datei în sistem.
Procedure GetTime(VAR ora, minut, sec, sec100:WORD);
Obţinerea timpului din sistem.
Procedure SetTime(VAR ora, minut, sec, sec100:WORD);
Poziţionarea timpului în sistem.
Procedure PackTime(VAR Dc:DateTime; VAR Dl:LONGINT);
"Împachetarea" unui articol de tip DateTime într-o dată de tip LONGINT. Dc
este data şi ora, exprimate pe componente, care urmează să fie "împachetate"
sub formă LONGINT în zona Dl.
Procedure UnpackTime(Dl:LONGINT, VAR Dc:DateTime);
"Despachetarea" unei valori de tip DateTime. Dl este expresie de tip
LONGINT care va fi "despachetată" pe componentele datei şi orei în zona Dc.

Alte funcţii
Function DosVersion:WORD;
Returnează un cuvânt ai cărui octeţi conţin numărul principal al versiunii
curente a sistemului de operare (octetul inferior), respectiv numărul secundar
(octetul superior).

214
ANEXA 2
UNITATEA CRT

1. Constante
Constante pentru culorile de fond şi de text

Culori de fond şi text Culori de text


Constantă simbolică Constantă Constantă simbolică Constantă
(cod) (cod)
Black (negru) 0 DarkGray (gri închis) 8
Blue (albastru) 1 LightBlue (albastru deschis) 9
Green (verde) 2 LightGreen (verde deschis) 10
Cyan (turcoaz) 3 LightCyan (turcoaz deschis) 11
Red (roşu) 4 LightRed (roşu deschis) 12
Magenta (violet) 5 LightMagenta (violet deschis) 13
Brown (maro) 6 Yellow (galben) 14
LightGray (gri deschis) 7 White (alb) 15
Blink=128; {Adunată la codul culorii se obţine afişare intermitentă}

Constante care definesc modurile text

Constantă simbolică Constantă (cod) Explicaţie


BW40 0 Adaptor color negru, rezoluţie 40 x 25
BW80 2 Adaptor color negru, rezoluţie 80 x 25
Mono 7 Adaptor monocrom negru/alb, rezoluţie 80 x 25
CO40 1 Adaptor color, rezoluţie 40 x 25
CO80 3 Adaptor color,rezoluţie 80 x 25
Font8x8 256 Adaptor EGA/VGA, 43 şi 50 linii

2. Proceduri şi funcţii
Pregătirea scrierii
Procedure AssignCrt (VAR f:TEXT);
Asignează fişierul f la dispozitivul CRT.
Procedure TextMode (Mode:WORD);
Selectează modul text.
Procedure Window (X1, Y1, X2, Y2:BYTE);
Defineşte fereastra text.
Procedure TextBackground (Color:BYTE);
Defineşte culoarea fondului.
Procedure TextColor (Color:BYTE);

215
Unitatea CRT

Defineşte culoarea de scriere.


Procedure HighVideo;
Defineşte atributul de intensitate mare.
Procedure LowVideo ;
Defineşte atributul de intensitate mică.
Procedure NormVideo;
Restabileşte atributele implicite.
Procedure ClrScr;
Şterge fereastra curentă.
Procedure ClrEol;
Şterge caracterele liniei curente, de la poziţia cursorului până la sfârşit.
Procedure DelLine;
Şterge linia curentă; textul este defilat în sus cu o linie.
Procedure InsLine;
Inserează o linie goală în poziţia cursorului; textul este defilat în jos cu o
linie.
Procedure GotoXY (X, Y:BYTE);
Mută cursorul pe linia Y, coloana X.
Function WhereX: BYTE;
Returnează numărul coloanei în care se găseşte cursorul.
Function WhereY: BYTE;
Returnează numărul liniei pe care se găseşte cursorul.

Programarea difuzorul intern


Procedure Sound (Hz:WORD);
Porneşte difuzorul intern, care emite un sunet continuu de frecvenţă Hz.
Procedure Delay (Ms:WORD);
Introduce o întârziere în execuţia următoarei instrucţiuni.
Procedure NoSound;
Opreşte difuzorul intern.
Lucrul cu tastatura
Function KeyPressed: BOOLEAN;
Întoarce TRUE dacă a fost apăsată o tastă.
Function ReadKey: CHAR;
Returnează codul caracterului rezultat prin apăsarea unei taste.

216
ANEXA 3
ERORI DE EXECUŢIE

Apariţia unei erori de execuţie determină întreruperea programului şi afişarea


unui mesaj de eroare, de forma: Run-time error nnn at xxxx:yyyy, unde nnn este
codul erorii de execuţie, iar xxxx:yyyy este adresa ei (segment şi offset). Erorile de
execuţie se împart în: erori DOS (coduri 1-99); erori de intrare/ieşire (coduri 100-
149), erori critice (coduri 150-199) şi erori fatale (coduri 200-255).

1. Erori DOS
1. Funcţie inexistentă. Generată de un apel al unei funcţii DOS inexistente.
2. Fişier inexistent. Generată de execuţia uneia din procedurile Reset, Append,
Rename sau Erase, dacă identificatorul asignat variabilei de tip fişier nu corespunde
unui fişier existent.
3. Cale inexistentă. Generată de execuţia uneia din procedurile:
• Reset, Append, Rewrite, Rename sau Erase, dacă identificatorul asignat
variabilei de tip fişier este invalid sau include un sub[director] inexistent;
• ChDir, MkDir sau RmDir, dacă sub[directorul] este invalid sau inexistent.
4. Prea multe fişiere deschise. Generată de execuţia uneia din procedurile Reset sau
Append dacă, la un moment dat, sunt deschise simultan mai mult de 12 fişiere ale
utilizatorului. Dacă se doreşte raportarea erorii pentru un număr mai mic de fişiere
deschise simultan, trebuie ca fişierul CONFIG.SYS să nu conţină clauza FILES=xx
sau să specifice numărul de fişiere dorit.
5. Acces interzis la fişier. Generată de execuţia uneia din procedurile:
• Reset sau Append, dacă FileMode permite scrierea, dar identificatorul asig-
nat variabilei fişier specifică un [sub]director/fişier read-only;
• Rewrite, dacă sub[directorul] este plin sau identificatorul asignat variabilei
fişier specifică un [sub]director/fişier existent read-only;
• Rename, dacă identificatorul asignat variabilei fişier specifică un fişier
existent;
• Erase, dacă identificatorul asignat variabilei fişier specifică un sub[direc-
tor]/fişier read-only;
• MkDir, dacă: există un fişier cu aceleaşi nume în sub[directoru] părinte; nu
există spaţiu în sub[directorul] părinte; este specificat în cale un dispozitiv;
• RmDir, dacă: sub[directorul] nu este vid; nu se specifică un sub[director] în
cale; directorul specificat include rădăcina;
• Read/BlockRead pentru un fişier cu tip/fără tip, dacă acesta nu a fost des-
chis pentru citire;

217
Erori de execuţie

• Write/BlockWrite pentru un fişier cu tip/fără tip, dacă acesta nu a fost des-


chis pentru scriere.
6. Handle de fişier invalid. Generată la transmiterea unui handle (vezi §8.2) invalid
de fişier, la un apel al sistemului DOS.
12. Cod invalid de acces la fişier. Generată de execuţia uneia din procedurile Reset
sau Append pentru fişiere cu tip/fără tip, dacă valoarea variabilei FileMode este
invalidă.
15. Număr dispozitiv invalid. Generată de execuţia uneia din procedurile GetDir
sau ChDir, dacă numărul dispozitivului periferic este invalid.
16. Sub[directorul] curent nu poate fi suprimat. Generată de execuţia procedurii
RmDir, dacă în calea specificată este inclus directorul curent.
17. Redenumire fişiere pe dispozitive diferite. Generată de execuţia procedurii
Rename, dacă specificatorii de fişiere nu sunt pe acelaşi dispozitiv.

2. Erori de intrare/ieşire
Erorile de intrare/ieşire determină întreruperea execuţiei programului, numai
dacă instrucţiunea respectivă a fost compilată cu directiva {$I+} (valoare impicită).
În cazul în care se specifică directiva de compilare {$I-}, execuţia programului conti-
nuă, iar apariţia erorii este depistată cu ajutorul funcţiei IOResult.

100 Eroare la citirea de pe disc. Generată de execuţia procedurii Read pentru


fişiere cu tip, dacă se încearcă citirea sfârşitului de fişier.
101 Eroare la scrierea pe disc. Generată de execuţia uneia din procedurile Close,
Write, WriteLn, Flush sau Page, dacă s-a umplut discul (nu mai este spaţiu pe disc).
102 Fişier neasignat. Generată de execuţia uneia din procedurile Reset, Rewrite,
Append, Rename sau Erase, dacă variabila fişier nu a fost asignată unui nume fizic,
prin procedura Assign.
103 Fişier nedeschis. Generată de execuţia uneia din procedurile/funcţiile Close,
Read, Write, Seek, Eof, FilePos, FileSize, Flush, BlockRead sau BlockWrite, dacă
fişierul nu este deschis.
104 Fişier nedeschis pentru intrare. Generată de execuţia uneia din proceduri-
le/funcţiile Read, ReadLn, Eof, EoLn, SeeKEof sau SeeKEoln, dacă fişierul TEXT
respectiv nu este deschis pentru consultare.
105 Fişier nedeschis pentru ieşire. Generată de execuţia uneia din procedurile
Write sau WriteLn, dacă fişierul TEXT respectiv nu este deschis pentru creare/ex-
tindere.
106 Format numeric invalid. Generată de execuţia uneia din procedurile Read sau
ReadLn, dacă o valoare numerică citită dintr-un fişier TEXT nu concordă cu formatul
numeric declarat.

218
Programarea calculatoarelor – Tehnica programării în limbajul Pascal

3. Erori critice
150 Disc protejat la scriere
151 Unit necunoscut
152 Dispozitivul nu este pregătit
153 Comandă necunoscută
154 Eroare CRC în dată
155 Cerere pe un dispozitiv greşit
156 Eroare de poziţionare pe disc
157 Tip dispozitiv necunoscut
158 Sector negăsit
159 Imprimantă în aşteptarea hârtiei
160 Incident la scrierea pe dispozitiv
161 Incident la citirea de pe dispozitiv
162 Întrerupere hardware

4. Erori fatale
200 Împărţire la zero. Generată de împărţirea la 0 a unui număr, cu operatorii / ,
MOD sau DIV.
201 Nonapartenenţă la un interval. Generată de instrucţiunile compilate cu
directiva {$R+}, în următoarele condiţii:
• expresia de indice pentru referirea unui element de masiv este în afara
intervalului;
• atribuirea unei valori în afara intervalului stabilit pentru variabila
respectivă;
• atribuirea unei valori în afara intervalului stabilit pentru un parametru de
procedură/funcţie.
202 Depăşire stivă. Generată la apelul unei proceduri/funcţii, compilate cu directiva
{$S+}, când nu este spaţiu suficient în stivă pentru memorarea variabilelor locale.
Stiva se poate mări cu directiva de compilare {$M}. Eroarea apare şi în cazul unui
apel recursiv infinit.
203 Depăşire heap. Generată de execuţia uneia din procedurile New sau GetMem,
când nu este suficient spaţiu în heap, pentru alocarea unui bloc sau a unei zone de
mărime specificată.
204 Operaţie cu pointer invalid. Generată de execuţia uneia din procedurile
Dispose sau FreeMem dacă: pointerul are valoarea nil sau indică o locaţie în afara
zonei heap; lista blocurilor libere nu poate fi extinsă, deoarece este plină; HeapPtr
are o valoare prea apropiată de limita inferioară a listei libere.
205 Depăşire virgulă mobilă. Generată în urma unei operaţii al cărei rezultat este
un număr prea mare pentru a fi reprezentat într-un tip real de dată Pascal.
206 Depăşire inferioară virgulă mobilă. Generată în urma unei operaţii al cărei
rezultat este un număr prea mic pentru a fi reprezentat într-un tip real de dată Pascal.

219
Erori de execuţie

Apare numai dacă se utilizează coprocesorul matematic 8087. Se transmite, implicit,


valoarea zero.
207 Operaţie virgulă mobilă invalidă. Generată dacă:
• Argumentul funcţiilor Trunc sau Round este în afara intervalului
[-2147483648, 2147483647];
• Argumentul funcţiei Sqrt este negativ;
• Argumentul funcţiei Ln este negativ sau zero;
• A apărut o depăşire a stivei 8087.
208 Managerul de reacoperire nu este instalat. Generată în urma apelului unei
funcţii/proceduri de reacoperire, în cazul în care componenta de gestiune a
structurilor de reacoperire (Overlay Manager) nu a fost instalată (cel mai adesea nu
s-a apelat procedura OvrInit sau apelul ei a eşuat).
209 Eroare la citirea unui fişier de reacoperire. Generată în cazul în care se
produce o eroare când managerul de reacoperire încearcă să citească un unit dintr-un
fişier de reacoperire.

220
BIBLIOGRAFIE

1. Apostol, C., Introducere în programare. Teorie şi practică Pascal,


Roşca, I., Gh., Casa de editură şi presă Viaţa Românească, Bucureşti,
Ghilic-Micu, B., 1993
Roşca, V.

2. Apostol, C., Prelucrarea fişierelor în Pascal, Editura Tehnică,


Roşca, I., Gh., Bucureşti, 1994
Ghilic-Micu, B.,
Roşca, V.

3. Ionescu, A., Fundamentele programării. Probleme rezolvate şi


Cocianu, C. propuse, Editura ASE, Bucureşti, 2000

4. Findlay, W., Pascal. An Introduction to Methodical Programming,


Watt, D. Pitman Publishing, 1987

5. Knuth, D. The Art of Computer Programming, Vol. 2,


Seminumerical Algorithms, Addison-Wesley, 1981

6. Knuth, D. The Art of Computer Programming, Vol. 2,


Seminumerical Algorithms, Addison-Wesley, 1981

7. Roşca, I., Gh., Programare sistematică în Pascal, Editura Didactică şi


Apostol, C., Pedagogică, Bucureşti, 1998
Ghilic-Micu, B.,
Roşca, V.

8. Roşca, I., Gh., Bazele elaborării programelor. Exerciţii rezolvate şi


Apostol, C., propuse, Editura ASE, Bucureşti, 1999
Ghilic-Micu, B.,
Stoica, M.,
Cocianu, C.,
Uscatu, C.

9. Stoer, I., Introduction to Numerical Analysis, Springer-Verlag,


Bulirsh, R. 1980

221

S-ar putea să vă placă și