Sunteți pe pagina 1din 119

Universitatea "Lucian Blaga" din Sibiu Facultatea de tiine

Prof.univ.dr. Valer Roca

CAPITOLE DE PROGRAMARE PROCEDURAL

Sibiu 2008

Prof.dr.Valer Roca ULBS

Prefa
Prezenta lucrare, intitulat Capitole de programare procedural, se adreseaz studenilor de la specializarea Informatic, de la Facultatea de tiine, Universitatea Lucian Blaga din Sibiu. Lucrarea a fost conceput ca manual n sprijinul cursului de Programare procedural, care se pred n semestrul I al anului I, studiii de licen, pentru a acoperi cteva capitole fundamentale de programare. In elaborarea lucrrii s-a avut n vedere c exist o bogat literatura de specialitate, inclusiv cri on-line, n limba romn i n limba englez, care pot s fie utilizate de studeni n studiul programrii C/C++, dar se simte nevoia, cel puin pentru o tematic de baz, s se adopte o tratare orientat, mai cu seam, spre tehnica programrii. Cele cinci capitole acoper aspectele fundamentale cu privire la programarea structurilor dinamice de date (cu referire la liste i arbori), la lucrul cu fiiere, inclusiv tratarea algoritmicii pentru probleme reprezentative, la modul n care se poate realiza prezentarea grafic a datelor (cu unele elememte de animaie), la controlul monitorului n modul consol, cu cteva elemente de programare a sunetelor, pentru a realiza o interfa atractiv a programelor i, lucrarea se ncheie, cu un capitol de faciliti pe care le ofer, n plus, limbajul C++ pentru realizarea programrii procedurale. Desigur c lucrarea presupune cunotinele de baz pentru programare n limbalul C/C++ i, prin studiul ei, studenii pot aprofunda, ntr-un numr relativ redus de pagini, prile corespunztoare din programa disciplinei menionate. Lucrarea poate fi parcurs secvenial sau prin selecie, avnd la dispoziie un cuprins sub form de linkuri interne.

Autor: Prof.univ.dr. Valer Roca Prof.dr.Valer Roca ULBS 2

CUPRINS 1. Structuri dinamice de date


1.1 Pointeri
1.1.1 Declararea i utilizarea pointerilor 1.1.2 Operaii asupra pointerilor 1.1.3 Echivalena indexrii i expresiei cu pointeri. Scderea pointerilor 1.1.4 Pointerii i parametrii funciilor 1.1.5 Declaraii cu pointeri i interpretarea lor

1.2 Variabile dinamice 1.3 Array-uri dinamice 1.4 Liste nlnuite i arbori
1.4.1 Liste nlnuite 1.4.2 Arbori binari

1.5 Realizarea operaiilor de baz pe liste nlnuite


1.5.1 Traversarea unei liste 1.5.2 Inserarea ntr-o list 1.5.3 Stergere ntr-o list 1.5.4 Distrugerea unei liste

1.6 Realizarea operaiilor de baz pe arbori binari de cutare


1.6.1 Traversarea arborilor binari 1.6.2 Cutarea n arbori binari 1.6.3 Inserare n arbori binari de cutare

1.7 Realizarea programelor care utilizeaz structuri dinamice 1.8 Un exemplu de utilizare a structurilor dinamice

2. Utilizarea fiierelor
2.1 Tipuri de fiiere admise 2.2 Implementarea lucrului cu fiiere
2.2.1 Streamuri 2.2.2 Controlul unui stream 2.2.3 Modul de lucru cu streamul

2.3 Citire din streamuri text


2.3.1 Citire la nivel de caracter 2.3.2 Citire la nivel de cmp 2.3.3 Citire la nivel de linie

2.4 Scriere n streamuri text


2.4.1 Scriere la nivel de caracter 2.4.2 Scriere la nivel de cmp

Prof.dr.Valer Roca ULBS

2.4.3 Scriere la nivel de linie

2.5 Citire i scriere n streamuri binare 2.6 Controlul sfritului de fiier i poziionarea n fiier 2.7 Algoritmica lucrului cu fiiere
2.7.1 Prelucrarea secvenial 2.7.2 Controlul paginilor n realizarea rapoartelor 2.7.3 Construirea fiierelor binare pentru aplicaii de eviden 2.7.4 Indexarea fiierelor 2.7.5 Prelucrare pe grupe de articole

2.8 Un exemplu de aplicaie de lucru cu fiier binar

3. Prezentarea grafic a datelor


3.1 Regimul grafic al monitorului 3.2 Utilizarea modului grafic 3.3 Grafic n culori 3.4 Vizor pentru desen 3.5 Desen prin puncte i vectori 3.6 Raportul de aspect 3.7 Scrierea textelor n modul grafic 3.8 Primitive pentru figuri 3.9 Transformarea coordonatelor 3.10 Elemente de animaie
3.5.1 Aspecte generale 3.5.2 Facilitii pentru animaie date de sistemul grafic

4. Controlul monitorului i al difuzorului


4.1 Regimul text al monitorului 4.2 Faciliti de lucru cu texte n modul pagin
4.2.1 Stabilirea unui mod text al consolei 4.2.2 Utilizarea ferestrelor de scriere

4.3 Faciliti de sunet

5. Faciliti n C++ pentru programare procedural


5.1 Declararea de articole 5.2 Referine 5.3 Operatorii de alocare i de eliberare 5.4 Funcii cu argumente implicite 5.5 Suprancrcarea funciilor 5.6 Tipuri abstracte de date

Prof.dr.Valer Roca ULBS

1. Structuri dinamice de date


Limbajul C, prin mecanismul lucrului cu pointeri, ofer programatorilor faciliti deosebite pentru construirea i utilizarea variabilelor dinamice, a listelor i a arborilor.

1.1 Pointeri
Aa dup cum s-a artat ntr-un capitol anterior, pointerii sunt variabile de adres legate de un anumit tip de dat standard sau declarat de programator. Mecanismul utilizrii pointerilor ofer o mai mare fexibilitate n folosirea memoriei, dect referirea direct prin variabile statice. Figura 1.1 sugereaz modul n care, prin utilizarea adresei dat de un pointer, se acceseaz valoarea dintr-o alt locaie de memorie. Referirea locaiei se face indirect i tehnica aceasta este denumit referire prin indirectare.

Adres

Valoare

numepointer

Fig.1.1 Referirea locaiilor prin pointeri

1.1.1 Declararea i utilizarea pointerilor Din punctul de vedere al limbajului, un pointer trebuie, mai nti, s fie declarat i apoi s fie ncrcat cu adresa locaiei, nainte de a putea fi utilizat. In secvenele care urmeaz, se utilizeaz pointeri la tipuri standard i la o structur de date. double x = 3.65, *px, z; int k = 5; struct {int *a; char t; double y;} s, *ps; px = &x; ps = &s;

Prof.dr.Valer Roca ULBS

(*ps).y = 1.2; ps->a = &k; z = *px + ps->y + *(ps->a); Se remarc modul simplu de utilizare a pointerului px, sub forma *px, pentru a referi valoarea 3.65, din locaia variabilei x. In cazul utilizrii structurilor de date, limbajul ofer forme alternative de scriere care sunt echivalente. In aceast secven, referirea la variabila y, de forma (*ps).y este echivalent cu forma ps->y. Programatorii prefer forma ultim care este mai uor de neles, mai ales atunci cnd apar indirectri n lan, aa cum este cazul cu referirea valorii la care trimite pointerul a: forma *(ps->a) este mai clar dect forma *((*ps).a). In aceste scrieri * i -> sunt considerai operatori de indirectare i, n expresii, ei se bucur de proprietatea de asociativitate la dreapta. De aceea, referirile de forma p1->(p2>->(pn-1->(pn->a))) pot fi scrise, fr paranteze sub forma p1->p2->->pn->a i devin mai clare. 1.1.2 Operaii asupra pointerilor Limbajul stabilete operaiile acceptate aupra variabilelor pointeri, avnd n vedere structura adresei de memorie ca un cuplu de forma (adres segment, deplasare n segment), aa cum este cazul modelelor de memorie cu segmente. De exemplu, o operaie de adunare ntre doi pointeri nu este corect deoarece nu produce o adres valid, adunarea prilor de adres de segment ne avnd sens. Redm, n continuare, operaiile acceptate, cu modul de scriere i semificaia acestuia. Adunarea/scderea unei constante ntregi pozitive se red prin expresia pointer k i este interpretat n raport de tipul pointerului. Adresa rezultat se determin sub forma pointer k*sizeof(tip), unde tip este tipul de care este legat pointerul. In particular, adunarea sau scderea lui k=1 nu nseamn incrementare/decrementare, ci agugarea lungimii tipului. Atenie, deci, la forma de scriere i la semificaia expresiei respective ! In secvena: double x, *px, *pz; double *py; py = NULL; px = &x; px = px + 4; pz = px; se atribuie lui px adresa x, & fiind operatorul de adres. Apoi, noua valoare a lui px este adresa lui x mrit cu 4*8 = 32 i nu cu 4, cum s-ar putea crede din forma instruciunii de atribuire. Atribuirea de valoare are semificaie pentru oricare pointer, dac aceasta este valoarea conveniona NULL, avnd semificaia de adres nul. In secvena de mai sus, py primete o astfel de valoare. Altminteri, unui pointer poate s i se atribuie numai adresa unei variabile de acelai tip sau valoarea unui pointer de acelai tip. In secvena de mai sus, x i px se refer la acelai tip double, de aceea atribuirea px = &x este corect. Din aceleai motiv, este corect i atribuirea pz = px. Compararea se poate face cu valoarea NULL pentru oricare pointer, utiliznd operatorii = i !=. Altminteri, compararea are sens numai pentru pointeri de acelai tip i n expresiile respective pot fi utilizai oricare dintre operatorii relaionali. Pentru secvena de mai sus, se pot scrie expresii relaionale de forma px != py , pz > px etc. 1.1.3 Echivalena indexrii i expresiei cu pointeri. Scderea pointerilor

Prof.dr.Valer Roca ULBS

Aa dup cum se tie, un array n C are o structur aparte, numele de variabil dat acestuia fiind considerat un pointer constant la primul element. In aceste condiii, limbajul a introdus, urmtoarea echivalen: tab[k] *(tab+k) unde tab este numele variabilei array, iar k este indice. Aplicnd regula de semificaie a expresiei cu pointeri din dreapta, rezult modul de calcul al adresei i faptul c, n ambele cazuri, este referit acelai element, dar prin forme diferite de scriere. Echivalena este valabil i pentru array-uri n-domensionale, ns expresia, fiind funcie de n indici i de dimensiunile declarate, aa cum rezult din modul de liniarizare, prezentat n capitolul referitor la acest tip de date, este mai dificil de utilizat Echivalena pune n eviden i posibilitatea de a scdea doi pointeri de acelai tip, dac, n plus, ei au i aceeai adres de segment, aa cum este cazul a doi pointeri ncrcai cu adresele unor elemente din acelai array. Dac se presupune c q=&tab[k] i p=&tab[k+n], atunci se deduce succesiv: p-q = *(tab+k+n)- *(tab+k) = *(tab+k+n-tab-k) = n. Se observ cum diferena, n acest caz, produce ca rezultat un numr natural, reprezentnd numrul de componente care se gsesc n array ntre cele dou ranguri. Trebuie semnalat aici c pointerii de tip array fiind pointeri constani, nu pot fi modificai prin operaii de adunare cu constante i nici prin atribuire, dar pot s fie supui la acele operaii care nu le afecteaz valoarea. 1.1.4 Pointerii i parametrii funciilor In multe situaii, transferul parametrilor la funcii se realizeaz prin adres, adic prin intermediul pointerilor. Dup cum se tie, o astfel de metod de transfer este obligatorie n cazurile n care funcia trebuie s modifice valoarea parametrului actual corespunztor, dar ea se utilizeaz uneori i n cazul stucturilor de date, pentru a evita multiplicarea datelor prin copiere i ncrcarea suplimentar a timpului de execuie. In aceste condiii, protecia datelor i a pointerilor mpotriva modificrilor accidentale este esenial. In scrierea funciilor care se gsesc n astfel de situaii, programatorul poate s utilizeze facilitile pe care le ofer limbajul pentru a declara date constane i pointeri constani, prin utilizarea corespunztoare a modificatoruli const. Modificatorul const poate fi plasat, ntr-o declaraie cu pointeri, n patru poziii, cu semificaia care este prezentat cu ajutorul declaraiilor care urmeaz: int* pv = 100; // pointer non-const la data non-const int* const pv = 100; // pointer const la data non-const const int* pv = 100; // pointer non-const la data const const int* const pv = 100; // pointer const la data const Dac transferul prin adres este fcut pentru structuri voluminoase, n vederea eliminrii copierilor costisitoare, parametrul actual se recomand a fi protejat declarnd parametrul formal corespunztor ca o dat constant. Declaraia trebuie s fie precedat de modificatorul const, aa cum se observ n secvena care urmeaz, unde, n funcia sum(), arrayul x este declarat constant i orice ncercare de modificare a componentelor sale va fi semnalat ca eroare de ctre compilator. double sum(const double* x, int n) { int k; double s; for(s=1, k=0; k<n; s+=x[k]; k++); x[0] = s; /* Eroare */ return s;

Prof.dr.Valer Roca ULBS

} Pentru pointeri, la fel ca i pentru alte variabile, se utilizeaz modificatorul const, pentru a declara c valoarea pointerului respectiv este nemodificabil. Caracterul de pointer constant, nu afecteaz posibilitatea de a modifica valoarea din locaia referit de pointer. In schia de funcie care urmeaz, pointerii pp i chp sunt declarai constani, n vreme ce pointerul pm nu. Astfel, atribuirea pm=p este corect, n vreme ce atribuirea pp=pp+4 va determina compilatorul s semnaleze o eroare de sintax. Atribuirile constantelor 5.25 i 'a' sunt corecte, ele referindu-se la valoarea pe care o va conine locaia respectiv, referit prin pointer constant. void func(double const* pp, char const* chp, double* pm) { *pp = 5.25; *chp = 'a'; pp = pp +4; /* Atribuire incorecta */ pm = pp; /* Atribuire corecta */ } Se atrage atenia asupra poziiei modificatorului const n declararea de pointer constant ! Dac s-ar face o declaraie de forma const double* pp, atunci valoarea din locaie este constant i nu poate fi modificat i compilatorul ar semnala eroare la atribuirea *pp = 5.25. Pentru cazul parametrilor array, problema proteciei pointerului respectiv este automat rezolvat, dac declaraia parametrului este de forma tip numearray[]. In baza echivalenei indexrii cu o expresie cu pointer, aa cum s-a discutat mai sus, exist i posibilitatea de a declara ca parametru formal un pointer i acesta s primeasc, la apelul funciei, adresa dat de un pointer la un array, cu elemente de acelai tip. In acest caz, se recomand s se declare parametrul formal ca un pointer constant, pentru a proteja adresa array-ului parametru actual. In secvena care urmeaz, n funcia prodscal() pointerul pp este automat protejat la modificare, n timp ce pm nu este, dei ambii au corespondent, la apel, adrese de array. ...... long prodscal(int pp[], int n, int* pm) {long s; int k; s = 0; for(k = 0; k < n; k++) s = s + pp[k] * pm[k]; return s; } ..... void main() { int a[5] = {3, 4, 2, 1, 7}; int b[5] = {-4, 3, 2, 5, 6}; long psc; psc = prodscal(a, 5, b); ....... } Pentru protecie, este recomandabil ca prototipul funciei s se declare sub forma: long prodscal(int pp[], int n, int const* pm).

Prof.dr.Valer Roca ULBS

O atenie deosebit trebuie acordat situaiei n care o variabil pointer din apelator trebuie s poat fi modificat printr-o funcie. La fel ca n cazul variabileleor obinuite, este necesar un parametu formal la care s se aplice transferul prin adres. Un astfel de parametru trebuie declarat ca un pointer, adic el este un pointer la pointer. In secvena care urmeaz, este schiat o funcie la care se transfer un astfel de pointer, presupunnd cazul unei stive nlnuite la care se terge un nod i, prin aceast operaie pointerul pentru topul stivei se modific. Deoarece parametrii de tipul pointer la pointer, sunt utilizai, mai ales, n legtur cu structurile dinamice de date, n aceast secven, s-a ales un astfel de caz, funcia free() facnd parte din sistemul de alocare a memoriei dinamice. Detalii despre memoria dinamic, sistemul de alocare i structurile dinamice sunt date n alte paragrafe ale acestui capitol. /* Declararea functiei */ int stergenod(NOD** top) {NOD* p; if(!*top) return 1; p = (*top)->link; free(*top); *top = p; return 0; } /* Apelul functiei */ . . . . . NOD *cap; . . . . . int r = stergenod(&cap); . . . . . In aceast secven, variabila pointer cap trebuie s se actualizeze ca urmare a tergerii nodului din vrful stivei. In acest scop, parametrul formal top este declarat pointer la pointer i trebuie s se supun regulii de indirectare discutate mai sus, pentru a accesa valoarea, aa cum se observ n instruciunile funciei. La apel, pentru variabila pointer cap, se transmite subprogramului adresa, la fel ca pentru oricare variabil obinuit. Tipul NOD, construit de programator, este un struct ce descrie structura unui element al stivei. De regul, un element are cel puin un cmp pentru informaie i o adres care puncteaz spre elementul urmtor (link). In multe cazuri, este convenabil ca o funcie s ntoarc un pointer la o variabil n care funcia a construit rezultatul, n locul rezultatului nsui. Trebuie, ns, acordat atenie validitii pointerului, dup revenirea din funcie n apelator, avnd n vedere existena variabilei pe care o desemneaz. Dup cum se tie, variabilele declarate ntr-o funcie sunt variabile locale, de clas automatic, crora li se aloc memorie pe stiva sistem n momentul intrrii n funcia respectiv i memoria ocupat de acestea este automat eliberat nainte de rentoarcerea la apelator. In aceste condiii, un pointer la o variabil local nu are sens n apelator, deoarece zona de memorie pe care o adreseaz nu mai exist. O prim modalitate de rezolvare a acesteri situaii, este aceea a schimbrii clasei de memorie pentru variabila n cauz, prin declararea ei de clas static. In acest mod, variabilei respective i se aloc spaiu n segmentul de date al programului i aceasta rmne alocat pe toat durata execuiei programului. In secvena care urmeaz, se schieaz un astfel de caz i se sugereaz modul de utilizare a rezultatului. double* func(...) {static double x; double* px = &x; . . . . . . . . return px;

Prof.dr.Valer Roca ULBS

} . . . . . . . . double* d, y, z=5.25; d = func(...); y = *d + z; . . . . . . . . O alt modalitate este aceea a construirii unei variabile dinamice n funcia respectiv, deoarece zona de memorie alocat ei este persistent, adic aceasta este accesibil, pe baz de adres i dup ntoarcerea la apelator. In secvena care urmeaz se sugereaz acest mod de rezolvare, considernd cazul de mai sus. double* func(...) {double* px; px = (double*)malloc(sizeof(double)); . . . . . . . . return px; } . . . . . . . . double* d, y, z=5.25; d = func(...); y = *d + z; . . . . . . . . In aceast secven, pointerului px i se atribuie adresa unei zone de memorie dinamic, solicitat sistemului de alocare prin funcia malloc(). Rezultatul se construiete n aceast zon i adresa ei este cea care se transmite apelatorului. Pe baza discuiei de mai sus, se pot face urmtoarele recomandri pentru transferul parametrilor i a rezultatului la funcii: pentru variabile scalare nemodificabile, transfer prin valore; pentru structuri voluminoase, transfer prin adres. Arrayul se transfer implicit prin adres; pentru variabile modificabile, indiferent de mrime, transfer prin adres; pentru rezultat ca variabil local retunare prin valoare: pentru rezultat n heap, returnare prin pointer; pentru structuri voluminoase nemodificabile, ca parametru sau ca rezultat, se declar pointer la dat constant i/sau rezultat constant aa cum se sugereaz n declaraia: const tip* func(const tip* pv); 1.1.5 Declaraii cu pointeri i interpretarea lor Utilizarea pointerilor, benefic altminteri, poate s aduc i neajunsuri, printre care semnalm dificultatea de a descifra programe surs. Un caz tipic, l constiuie interpretarea declaraiilor complexe, pentru care, n cele ce urmeaz se d, sub form de algoritm, un ndrumar. In continuare, se dau cteva exemple, succesiunea entitilor care se analizeaz fiind marcate prin numere de ordine 1. Se ncepe cu identificatorul din declaraie. 2. Se merge spre dreapta cutnd o entitate sub forma unei perechi de paranteze rotunde ( ) sau drepte [ ]. O succesiune de dou sau mai multe perechi de paranteze drepte se considerat ca o entitate unic. 3. Dac s-a selectat o entitate, se interpreteaz ca funcie, respectiv array. 4. Se schimb sensul de deplasare nspre stnga cutnd o entitate *, dat de cel mai apropiat caracter de acest fel care nu a fost nc analizat. Dac * este precedat de modificatorul const, atunci aceast sucsesiune formeaz o entitate. Se interprezeaz entitatea ca indicaie de pointer sau pointer constant i se continu cu deplasare spre stnga.

Prof.dr.Valer Roca ULBS

10

5. Dac n cutarea spre stnga, se ntlnete o parantez rotund deschis (, atunci entitatea care este cuprins ntre aceast parantez i perechea ei dreapta este considerat tratat i algoritmul se reia de la pasul 2. 6. Dac n cutarea spre dreapta, se ajunge la o parantez rotund nchis ), atunci algoritmul se reia de la pasul 4. 2. Dac n cutarea spre dreapta, se ajunge la sfritul declaraiei, atunci se ncheie cutarea i se interpreteaz declaraia de tip.

Exemplul 1: char* (* ( * var )(int))[10]


4 2

1. Identificatorul var este declarat ca 2. un pointer la 3. o funcie care are un parametru ntreg i care returneaz 4. un pointer la 5. un array de 10 elemente care sunt 6. pointeri la 2. tipul char

Exemplul 2: double ( * var (double (*)[3])) [5]

1. Identificatorul var desemneaz o funcie care are un parametru i returneaz 2. un pointer la 3. un array cu 3 componente 4. de tip double Parametrul funciei esteun pointer la un array cu 5 componente de tipul double

Prof.dr.Valer Roca ULBS

11

Exemplul 3:

struct s * (* var[5])[5]
6 5 1 4

1. Idetificatorul var este 2. un array cu 5 componente care sunt 3. pointeri la 4. un array cu 5 componente care sunt 5. pointeri la 6. tipul struct s

Exemplul 4: unsigned int * ( * const* var [5][10])(void)


7 6 4 3 1 5

1. Identificatorul var este 2. o matrice cu 5 linii i 10 coloane 3. pointeri constani la 4. un pointer la 5. o funcie care nu are parametrii i returneaz 6. un pointer la 2. un ntreg fr semn Declaraiile de funcii, incluse, se analizeaz n acelai mod, depinznd de poziia lor. In exemplul care urmeaz sunt trei funcii, analiza ncepe cu funcia intermediar. De remarcat prezena parantezelor care o delimiteaz, fr care scrierea ar fi fost eronat sintactic.

Prof.dr.Valer Roca ULBS

12

Exemplul 5:

void ( * var (int,double(*)(int) ) )(int)


5 3 1

1. Identificatorul var este 2. o funcie 1 care are doi parametri i returneaz 3. un pointer la 4. o funcie 2 cu un parametru ntreg care returneaz 5. un void Funcia 1 are un parametru ntreg i un parametru care este pointer la o funcie -3 care are un parametru intreg i returneaz un double

1.2 Variabile dinamice


La fel ca i alte limbaje, limbajul C implementeaz posibilitatea utilizrii spaiului de memorie disponibil, denumit spaiu de memorie dinamic sau spaiu heap, potrivit unui model prestabilit pentru memoria intern. Utilizarea acestui spaiu se bazeaz pe pointeri, ca variabile prin care se pot accesa locaii de memorie, denumite blocuri. In plus fa de cazul static, blocurile de memorie pot s fie controlate - alocate i eliberate - la momentul execuiei programului, prin sistemul pe care limbajul l ofer, denumit sistem de gestiune a memoriei heap. Datorit posibilitiilor de utilizare i a posibilitilor de control, un cuplu (pointer, bloc heap) este denumit variabil dinamic. Variabilele dinamice pot fi utilizate independent sau pot fi asamblate n structuri de variabile, prin care se pot implementa structuri complexe de date, cunoscute ca structuri dinamice de date. In fig. 1.2, este redat modelul logic al memoriei interne n sistemele de operare DOS (UNIX, MS-DOS), de referin pentru implementarea sistemului de gestiune heap.

Prof.dr.Valer Roca ULBS

13

Spaiul heap Spaiul pentru stiva sistem Spaiul de date statice ale programului Spaiul pentru codul programului Spaiul pentru antetul programului

Fig. 1.2 Modelul logic al memoriei interne pentru un program C Sistemului de gestiune pentru heap pune la dispoziia programatorului o mulime de funcii, prin care programul poate aloca i elibera, la momentul execuiei programului, blocuri de memorie, de lungimi variabile, la care s se refere prin pointeri. Mulimea de funcii, conform standardului ANSI, conine un nucleu obligatoriu, la care o anumit implementare a limbajului poate aduga i alte funcii. In acest capitol, se face referire numai la sistemul de gestiune heap recomandat de standard, pentru a asigura construirea de programe portabile. Prototipurile funciilor sistemului de gestiune a memorie heap, la fel ca n cazul altor biblioteci, sunt declarate n fiierul header alloc.h care trebuie s fie inclus n programul surs, atunci cnd se face uz de acest spaiu. Alocarea de blocuri de memorie presupune apelul uneia din funciile de alocare/realocare urmtoare: void * malloc(size_t blocksize); void * calloc(size_t nitem, size_t blockitemsize); void * realloc(void* pblock, size_t blocksize); In aceste prototipuri, size_t este tipul intreg, ntlnit i la alte funcii, definit n mai multe fiiere header, printre care i fiierul alloc.h. Acest tip permite declararea unor ntregi foarte mari, ntregi care ar putea fi necesari n exprimarea unor cereri de blocuri, avnd n vedere c lungimile acestora se exprim n bytes. Lungimea blocului cerut este dat prin parametrul blocksize, n cazul funciei malloc() sau este produsul dintre numrul de elemente nitem i lungimea unui element, n cazul funciei calloc(). Ultima funcie este utilizat, mai ales, n legtur cu alocarea spaiului pentru array-uri cu numr variabil de componente (array dinamic). Ambele funcii de alocare ntorc adresa blocului, ca pointer nelegat de un tip de dat, pointer void, care poate fi NULL, dac spaiul heap liber nu are disponibil un bloc de mrimea cerut. In acest mod, programatorul poate controla, testnd valoarea pointerului, dac alocarea s-a realizat sau nu i poate decide n consecin. Trebuie, de asemenea, remarcat faptul c programatorul trebuie s rein adresa returnat de alocator, ntr-o variabil pointer, legat de tipul de date pe care urmeaz s-l conin blocul, pentru utilizare ulterioar. Rezult, de aici, necesitatea de a converti, prin cast, pointerul void la tipul dorit, ceea ce, poate nsemna utilizarea unei alocri de forma:

Prof.dr.Valer Roca ULBS

14

tip * pblock; if(pblock = (tip*)functiealocare(parametri)) {/* Spatiu alocat. Utilizare pointer pblock */} else { /* Rezolvare situatie de lipsa de spatiu */ } In textul care urmeaz, se aloc spaiu pentru un real dublu, pentru un articol, care urmeaz a fi utilizat ca nod al unei liste, i pentru un array intreg cu n componente, utiliznd o secven fr testare de reuit: #include <alloc.h> #include <string.h> void main() {......................... /* Definiri de tipuri si de variabile */ double* predab; typedef struct art {char info[50]; art* next; } NOD; NOD* cap; int* arrayint; .......................... /* Alocare de blocuri */ predab = (double*)malloc(sizeof(double)); cap = (NOD*)malloc(sizeof(NOD)); n = 80; arrayint = (int*)calloc(n, sizeof(int)); .......................... /* Utilizarea blocurilor alocate */ *predab = -5.75L; strcpy(cap->info, " "); cap->next = NULL; for(int k = 0; k < n; k++) arrayint[k] = 0; } Aa cum rezult din text, se recomand ca lungimea blocului sau a elementului de bloc, n funciile de alocare, s se indice prin operatorul sizeof(), pentru a evita specificarea eronat a lungimii. Se remarc, de asemenea, modul de conversie i utilizare, avnd n vedere tipurile blocurilor i a cmpurilor pe care acestea le conin. Funcia realloc() este utilizat n cazul n care un bloc alocat anterior trebuie s fie reajustat ca mrime, potrivit parametrului blocksize specificat. Funcia ntoarce pointer nul, dac nu este posibil realocarea. Dac se utilizeaz valoarea NULL, ca parametru pentru pointerul la bloc, atunci aceast funcie este echivalent cu malloc(). Noul bloc poate fi obinut prin prelungirea vechiului bloc sau poate fi alocat pe un spaiu nou. In ambele cazuri, coninutul blocului este conservat pn la minimum ditre cele dou lungimi. Eliberarea blocurilor alocate se realizeaz prin funcia free() care are un prototip simplu: void free(void* pblock); Pentru exemplele de mai sus, eliberarea se face sub forma: free(predab); free(cap); free(arrayint);

Prof.dr.Valer Roca ULBS

15

In ncheierea acesui paragraf, se face precizarea c implementrile limbajului C utilizeaz, n general o metod de gestiune a spaiului heap, denumit garbage collection (colectarea rezidurilor). Potrivit acestei metode, evidena spaiului liber i ocupat se ine pe mai multe liste, cu blocuri de mrimi diferite. Alocarea unui bloc, nseamn scoaterea lui din lista de spaiu disponibil i trecerea lui ntr-o list de spaiu ocupat. Atunci cnd programul cere o eliberare de bloc, blocul respectiv este, de regul marcat ca disponibil, fr s fie returnat unei liste de spaiu liber. La anumite intervale de timp, sistemul de gestiune declaneaz procesul de eliberare proriu-zis, comasnd blocuri adiacente, marcate ca disponibile, pentru a construi blocuri libere de lungime ct mai mare, pe care le evideniaz n liste corespunztoare. Acest proces se aplic i atunci cnd, la o cerere de alocare, nici una din listele de spaiu liber nu poate oferi un astfel de bloc. Alocarea eueaz, dac, i dup colectare (de reziduri), cererea nu poate fi satisfcut. Prin acest sistem, se asigur o funcionare, ct mai ndelungat posibil, a programelor care utilizeaz memoria heap fr a a ncrca exagerat timpul de execuie al programului cu timp necesar procesului de gestiune a acestui spaiu. In fine, menionm c n C++, au fost introdui operatorii (new, dispose), pentru a facilita utilizarea spaiului heap, legat, mai ales, de tehnica programrii orientate pe obiecte, care sunt prezentai n lucrare, n capitolul de faciliti ale acestui limbaj.

1.3 Array-uri dinamice


Aa dup cum se cunoate, n limbaj exist posibilitatea declarrii i referirii, prin indexare, a array-urilor uni i multidimensionale. Acestea sunt variabile de clas automatic al cror spaiu de memorie le este atribuit n segmentul de date al programului sau pe stiva sistem, la dimensiunile maxime declarate i acest spaiu, n timpul execuiei programului, nu mai poate fi ajustat (mrit sau micorat) i nici nu poate fi eliberat, atunci cnd rolul acelui array a ncetat. In programele mari, n condiiile n care determinarea spaiului maxim necesar pentru astfel de structuri este dificil, utilizarea eficient a memoriei trebuie s fie o preocupare a programatorului. O soluie posibil este aceea a construirii de structuri array n memoria heap care s poat fi dinamic ajustate dimensional i s poat fi eliberate n timpul execuiei programului. Structurile array construite n heap poart denumirea de array dinamic. Acestea pstreaz proprietatea de spaiu compact, dar pierd, cu excepia celor unidimensionale, posibilitatea referirii directe prin indexare. Pentru array-urile unidimensionale, pe baza echivalenei ntre expresia de indexare, cu un indice, i expresia de indirectare asociat, aa cum s-a artat mai nainte n acest capitol, exist posibilitatea utilizarii alternative a celor dou modaliti. O prim abordare este aceea a construirii unui array cu spaiu la dimensiuni efective, care, n timpul execuiei programului nu mai trebuie ajustat dimensional, denumit array dinamic neajustabil. Formal, un asfel de array este un tuplu de forma (n, psp, bloc), unde n este numrul efectiv (maxim) de elemente, iar psp este pointerul la blocul de spaiu din heap. Dup cum se observ, un astfel de array este o variabil dinamic i utilizarea lui revine la realizarea urmtoarei secvene de aciuni: - declararea unui pointer la tipul de dat pe care urmeaz s l conin elementele: tip* psp; - preluarea spaiului din heap: psp = (tip*)malloc(n*sizeof(tip)); - utilizarea elementelor, fie prin indexare psp[k] , fie prin indirectare *(psp +k), unde k este poziia( rangul) elementului, n convenia de referire cu indice ntreg, cu valori ncepnd zero. Controlul ncadrrii n blocul de spaiu se realizeaz prin raportarea la valoarea n-1 sau la adresa (psp + n-1), ca adres a ultimului element ; - eliberarea spaiului, atunci cnd array-ul nu mai este necesar: free(psp);

Prof.dr.Valer Roca ULBS

16

In listingul 1.1, utiliznd un array dinamic neajustabil, funcia main() introduce un ir de n date ntregi, utiliznd adresarea cu indexare i le nsumeaz, folosind referirea prin indirectare. Listingul 1.1 Utilizarea unui array dinamic neajustabil #include <alloc.h> #include <stdio.h> void main() {int n, k, s; int *psp, *p; printf(" Numarul de componente: "); scanf("%d", &n); psp = (int*)malloc(n*sizeof(int)); printf("Componentele: "); for(k=0; k < n; k++) scanf("%d", &psp[k]); s = 0; for(p = psp; p < (psp + (n-1)*sizeof(int)); p++) s = s + (*p); printf("Suma este %d ", s); } Cazul general este cel al unui array dinamic ajustabil. Un astfel de array este un tuplu de forma (nelem, pozelem, psp, bloc), unde nelem este numrul maxim de elemente care pot fi nserate n array la dimensiunea curent a blocului de memorie alocat, iar pozelem d rangul ultimului element efectiv ncrcat. Iniial, este necesar ca aceste variabile s aib valorile: nelem = 0; pozelem = -1; psp = NULL; Lucrul cu un astfel de array este ceva mai complicat, deoarece operaiile de inserare a unui elemente n array pot cere o cretere a spaiului pe care acesta l posed la un anumit moment, inclusiv momentul iniial, cnd un asfel de spaiu nu exist. De aceea, pentru un asfel de array este necesar s se construiasc funcii care s poat realiza nserri, cu realocare de spaiu, precum i alte operaii, cum ar fi preluarea elementului de un anumit rang, cu controlul ncadrrii n limita numrului de elemente efectiv prezente n array. In cele ce urmeaz, se sugereaz o list de astfel de funcii care ar putea asigura o funcionalitate ct mai complet: - funie pentru nserarea unui element la sfrit, dup ultimul element efectiv prezent; - funcie pentru nserarea unui element pe o poziie interioar k, cu deplasarea spre dreapta a elementelor pentru a face loc; - funcie de nlocuire a valorii unui element de rang k; - funcie de preluare a valorii ultimului element; - funcie de preluare a valorii elementuli de rang k; - funcie de ncrcare de array prin citire de la consol; - funci de afiare de array pe monitor, - funcie de sortare de array etc. In continuare, cu titlu de exemplu, n listingul 1.2 este redat o funcie de nserare a unui element pe poziia k i una de preluare a valorii elementului de rang k. Aici s-a fcut ipoteza, care va fi discutat mai n detaliu ntr-un paragraf urmtor, c tuplu care definete un

Prof.dr.Valer Roca ULBS

17

array dinamic ajustabil se definete ca un tip de dat struct, pentru date de tip double, de forma: typedef struct {int nelem; int pozelem; double *psp; } DINARRAY; In aceste condiii, programul apelator declar variabile de tip DINARRAY, atunci cnd dorete o astfel de structuri, le iniializeaz cum s-a artat mai sus i transfer funciilor de lucru cu array pointeri de acest tip. De exemplu, aceste aciuni, cu apelul funciei de nserare, sunt sugerate de secvena de instruciuni care urmeaz: DINARRAY vectdin, *pvect; vectdin.nelem = 0; vectdin.pozelem = -1; vectdin.psp = NULL; pvect = &vectdin; . . . . . . . . . . . . . . . . . . . . int err = setitemat(pvect, 5, -200.75); Listingul 1.2 Funcia de nserare i funcia de preluare pentru un array dinamic ajustabil /* Functia de inserare a unui element pe pozitia k */ int setitemat(DINARRAY* pa, int k, double x) {double *r; if(k<0 || k>pa->pozelem) return 2; if(pa->pozelem +1 > pa->nelem) {r = (double*)realloc(pa->psp, (pa->nelem +50)*sizeof(double)); if(!r) return 1; pa->psp = r; pa->nelem += 50; } for(int i= pa->pozelem + 1; i > k; i--) p[i]=p[i-1]; pa->psp[k] = x; pa->pozelem++; return 0; } /* Functia de preluare a valorii elementului de rang k */ int getitemat(DINARRAY* pa, int k, double *x) {if(k<0 || k>pa->pozelem) return 2; *x = pa->psp[k]; return 0; } Se observ c funcia de nserare verific, mai nti, corectitudinea indicelui k i dac acesta este n afara limitelor, returneaz codul 2. Cnd nserarea este corect definit, se verific dac mai este spaiu pentru a putea deplasa spre dreapta elementele. Dac blocul nu are cel puin un element neocupat, se face o realocare, cu suplimentare a blocului cu 50 de elemente. Dac realocarea eueaz, funcia returneaz codul 1, pentru a arta c inserarea nu s-a putut realiza, din lips de spaiu. Dac blocul a fost realocat, se rencarc corespunztor pointerul la spaiu, deoarece blocul ar fi putut s fie alocat pe alt amplasament i se modific numrul maxim de elemente posibile, n noile condiii. Apoi, se face deplasarea, din poziia k, spre dreapta cu o poziie, se nscrie, n poziia k, valoarea x, se actualizeaz

Prof.dr.Valer Roca ULBS

18

informaia cu privire la poziia ultimului element prezent i funcia returneaz codul 0, pentru a marca reuita. Funcia de preluare a elementului de rang k, verific indicile k. Dac acesta ete incorect, operaia de preluare eueaz, funcia ntoarce codul 2 i valoarea lui x este nedeterminat. Dac preluarea reuete, funcia returneaz codul 0 i x conine valoare corect.

1.4 Liste nlnuite i arbori


Frecvent, n programele scrise n C, sunt necesare liste nlnuite i arbori binari, ca structuri dinamice de date care s faciliteze rezolvarea diferitelor clase de probleme. Cu scopul de a fixa elementele necesare nelegerii mecanismelor de implementare n limbaj, n acest paragraf, se face o scurt trecere n revist a acestor tipuri de structuri. 1.4.1 Liste nlnuite Listele de date sunt compuse din articole, denumite noduri, dispuse ntr-o anumit ordine. In fiecare nod, rezid informaia propriu-zis, la care se adaug o informaie special, care s materializeze legtura nodului respectiv cu vecinii si. Aceasta nseamn c lista de date trebuie s se construiasc ntr-un spaiu adresabil, de exemplu heap, informaia de legtur fiind adresa (adresele) blocului (blocurilor) vecinului (vecinilor). Dac se noteaz cu P = {p1, p2, , pn} mulimea adreselor din spaiul de construcie, la care se adaug valoarea NULL, pentru a desemna adresa vid i se noteaz cu D = {d1, d2,, dn} mulimea informaiilor care vor fi coninute de de nodurile listei, atunci se pot defini formal listele simplu i dublu nlnuite, dup cum urmeaz. O list simplu nlnuit sau list asimetric este cuplu (cap, La) unde La este mulimea { (di, pi) | di D, pi P }, pe care s-a definit o relaie de ordine, cu cap = p0, ca adres a blocului care conine perechea (d1, p1), cu pi, 1 i n-1 ca adres a blocului care conine perechea (di+1, pi+1) i cu pn = NULL, pentru perechea (dn, pn). O list dublu nlnuit sau list simetric este tripletul (prim, Ls, ultim), unde Ls este mulimea { (pi, di, si) | di D, pi, si P }, pe care s-a definit o relaie de ordine direct, cu Prim = p0, ca adres a blocului care conine tuplul (p1, d1, s1), cu pi, 1 i n-1 ca adres a blocului care conine tuplul (pi+1, di+1, si+1) i cu pn = NULL, pentru (pn, dn, sn) i o relaie de ordine invers, cu ultim = pn+1, ca adres a blocului care conine tuplul (pn, dn, sn), cu si, unde n i 2, ca adres ablocului (pi-1, di-1, si-1) i cu s1 = NULL. Convenind s se figureze nodurile prin dreptunghiuri i legturile prin sgei, cu valoarea NULL ca legare la pmnt, cele dou tipuri de liste se pot reprezenta grafic aa cum se arat n fig.1.3.

Prof.dr.Valer Roca ULBS

19

d1 cap

d2

dn-1

dn

d1

d1

d1

d1

prim Fig.1.3 Liste nlnuite

ultim

Figura sugereaz c nodurile unei liste simplu nlnuite pot fi "vizitate" numai ntr-un singur sens, de la nodul de pointer cap spre nodul cu legtur la pmnt (ultimul nod), deoarece, pentru fiecare nod se cunoate numai succesorul su imediat. O adtfel de list, prin convenie, este vid, adic nu are nici un nod, atunci cnd pointerul cap = NULL. Similar, pentru o list dublu nlnuit, vizitarea se poate face de la nodul dat de pointerul prim spre nodul ultim, dar i invers, de la nodul dat de pointerul ultim spre primul nod, deoarece fiecare nod interior are legtur spre succesorul imediat, n ordine direct i spre predecesorul imediat, n ordine invers. Dac lista dublu nlnuit este vid, atunci prim = ultim =NULL. Listele cresc dinamic, plecnd de la situaia de list vid, adugnd mereu cte un nod i se micoreasz dinamic, tergnd cte un nod. Cele dou operaii tipice, denumite adugare/tergere sau nserare/tergere, fac ca, n timpul execuiei programului respectiv, structura i numrul de noduri ale listei s sufere o permanent schimbare. Astfel, o list poate trece de mai multe ori ntre situaia de list vid i nevid, n general, pe alt amplasament, datorit faptului c, la nserare se solicit un nou bloc, iar la tergere se elibereaz cte un bloc, care, pn la o nou nserare, poate fi ocupat de alt structur. Dup locul unde se fac cele dou operaii tipice, se disting stivele i cozile, ca liste particulare, deosebit de utile n programare. Stiva nlniut (stack) este o list asimetric la care operaia de nserare i operaia de tergere se fac numai la primul nod (nodul de cap), care, n acest caz este denumit vrful stivei sau top. Datorit acestui comportament sau disciplin de lucru, bine sugerat de modul n care se gareaz i se scot vagoanele ntr-o linie nchis la un capt, stivele se mai numesc liste LIFO (Last-In-First-Out = ultimulintratprimul-ieit). La o stiv, de regul se viziteaz numai nodul top, dar nu este exclus vizitarea i a altor noduri, caz n care vizitarea presupune o operaie de traversare a stivei, total sau parial. Datorit acestui comportament, stiva este o structur frecvent utilizat n diferite probleme care presupun o atare disciplin n lucrul cu datele. Coada nlnuit (queue) este o list asimetric la care operaia de nserare se face la captul dat de pointerul ultim, iar operaia de tergere se face la captul dat de pointerul prim. Coada este denumit list FIFO (First-In-First-Out=primul-intratprimul-ieit), deoarece materializeaz comportamentul obinuit pentru tratarea cozilor cotidiene: cozi de persoane, cozi de automobile etc. Prin analogie cu aceste cozi, captul la care se face tergerea este denumit faa cozii, operaia de tergere este denumit servire, iar pointerul spre acest nod este notat fata. Similar, nodul dup care se poate face o nserare

Prof.dr.Valer Roca ULBS

20

este este denumit spatele cozii i pointerul se noteaz spate. Tratarea corect a unei cozi presupune totdeauna doi pointeri, pentru oricare din operaiile care modific coada i este suficent cunoaterea unui singur pointer, pointerul de fa, n cazul unei operaii de traversare. 1.4.2 Arbori binari Pstrnd notaiile introduse n paragraful anterior, se poate defini formal un arbore binar sau arbore de ordinul doi, utiliznd o schem recursiv. Un arbore binar este cuplul (cap, A2), unde A2 este o mulime de forma { (pi, di, si) | di D, pi, si P } care ndeplinete urmtoarele condiii: 1) exist unic nodul de adres p0 care conine tripletul (p1, d1, s1), numit nod rdcina al arborelui i cap = p0; 2) diferena A2 {( p1, d1, s1)} se descompune n mulimile A21 i A22, cu A21 A22 = ; 3) dac A21 = , atunci p1=NULL, iar dac A21 , atunci exist unic (pk, dk, sk) n A21 astfel nct p1 s fie adresa blocului acestui element, numit succesor stnga al lui ( p1, d1, s1) ; 4) dac A22 = , atunci s1=NULL, iar dac A22 , atunci exist unic (pr, dr, sr) n A22 astfel nct s1 s fie adresa blocului acestui element, numit succesor dreapta al lui ( p1, d1, s1) ; 5) A21 i A22 sunt arbori binari. Dac se reprezint nodurile arborelui binar la fel ca la liste, se poate obine o imagine de forma celei din fig.1.4, situaia de arbore vid fiind marcat convenional prin valoarea NULL a pointerului cap. Se observ c, ntr-un arbore binar, fiecare nod are cel mult doi succesori direci, iar structura de noduri care deriv din acesta poate fi considerat un arbore, numit subarbore, a crui rdcin este nodul considerat. Nodurile arborelui binar care nu au succesori direci sunt denumite frunze. De asemenea, trebuie remarcat faptul c n tehnica lucrului cu abori binari se utilizeaz adesea o terminologie mprumutat din structurile de tip arbore genealogic. Astfel, se vorbete de nod tat i copii si, pentru a desemna un nod i succesorii si direci, noduri frai i noduri veri, descendeni i ascendeni etc. La fel ca la liste, operaiile la arbori se refer la nserarea i tergerea de noduri care fac ca un arbore s creasc sau s descreasc dinamic, dar, din punctul de vedere al realizrii, acestea sunt mai complicate. Ambele operaii solicit o operaie de traversare parial, pentru a depista nodul dup care trebuie fcut nserarea sau nodul care trebuie ters, operaie care, de regul se bazeaz, pentru identificare, pe informaia coninut n noduri. Aceast categorie de arbori binari, pentru care structura este dat prin relaia care exist ntre informaiile distincte pe care nodurile le conin sunt denumii arbori binari de cutare. In continuare, pentru exemplificarea modului de implementare, se consider, n detaliu, numai aceast categorie.

Prof.dr.Valer Roca ULBS

21

cap d1

d2

d3

d4

d5

d6

d7 Fig.1.4 Arbore binar

Dac se noteaz cu info partea din structura unui nod care conine informaia, cu llink cmpul de legtur spre succesorul stnga, iar cu rlink cmpul de legtur spre succesorul dreapta, atunci un arbore binar este arbore de cutare dac, pentru oricare nod de adres p, diferit de o frunz, sunt adevrate urmtoarele relaii de dominare: p->info > p->llink->info; p->info < p->rlink->info. Relaiile de mai sus trebuie interpretate astfel: oricare nod i domin subarborele stnga i este dominat de subarborele su dreapta. In fig.1.5 este reprezentat un exemplu de astfel de arbore, n care, pentru simplitate, informaia, de tip caracter, este nscris n cerculee, iar legturile sunt redate prin sgei.

Prof.dr.Valer Roca ULBS

22

Fig.1.5 Arbore binar de cutare

Dac un astfel de arbore este traversat n ntregime, adic se viziteaz, ntr-o anumit ordine, fiecare nod, se poate obine o list liniarizat a informaiilor coninute n arbore. Traversarea complet a unui arbore binar, inclusiv a arborilor binari de cutare, se poate face n mai multe moduri, dup ordinea n care se selecteaz sucesorii direci, n raport cu momentul selectrii nodului respectiv ca rddcin de subarbore. Teoretic, este comod s se defineasc traversarea n mod recursiv, pe baza subarborelui stnga S, a subarborelui dreapta D i a rdcinii R a acestuia, astfel: traversare n preordine (RSD) pentru fiecare subarbore se viziteaz rdcina R, se traverseaz n preordine subarborele stnga S i apoi se traverseaz n preordine subarborele dreapta D; traversare n ordine simetric (SRD) pentru fiecare subarbore se traverseaz n ordine simetric subarborele stnga S, se viziteaz rdcina R, i apoi se traverseaz n ordine simetric subarborele dreapta D; traversare n postordine (SDR) pentru fiecare subarbore se traverseaz n postordine subarborele stnga S, se traverseaz n postordine subarborele dreapta D i apoi se viziteaz rdcina R. Pentru exemplul din fig.1.5 se obin urmtoarele liste liniare: f, d, b, a, c, e, h, g, i pentru preordine (RSD); a, b, c, d, e, f, g, h, i pentru ordine simetric (SRD); a, c, b, e, d, g, i, h, f pentru postordine (SDR). Se obsrv c lista care se obine printr-o traversare n ordine simetric este o list sortat, de aceea un arbore de cutare traversat complet, n aceast ordine, poate reprezenta o metod de sortare, denumit sortare pe arbore binar.

1.5 Realizarea operaiilor de baz pe liste nlnuite


Asupra listelor se pot realiza diferite operaii, dar, n cele ce urmeaz, prezentarea se limiteaz la operaiile de traversare, nserare i tergere, pentru care este recomandabil i comod s se construiasc funcii cu parametri.

Prof.dr.Valer Roca ULBS

23

Oricare program care trebuie s implementeze astfel de operaii trebuie s defineasc un tip de dat pentru nodul listei. In modul cel mai simplu, tipul NOD este un articol n care, pentru simplitate, informaia este considerat ca string. Pentru o list simplu nlnuit, tipul poate fi definit astfel: #define n 50 typedef char informatie[n]; typedef struct art {informatie info; struct art* succesor; }NOD; Depinznd de situaie, definiia se poate adapta pentru liste dublu nlnuite i diferite tipuri de informaie, inclusiv pentru cazul n care informaia este un alt articol sau un array dinamic. In ultimul caz, blocul de informaie fiind variabil, nodul trebuie s conin un pointer spre spaiul heap al blocului. 1.5.1 Traversarea unei liste Operaia de taversare este o operaie de vizitare a nodurilor listei, n ordinea specific acesteia. Aceasta se poate realiza, printr-o funcie, numit funcie de traversare, ca o traversare total sau poate fi o traversare parial de cutare. Traversarea total presupune vizitarea tuturor nodurilor, de la captul ales, pn la sfritul listei, fr ieire din funcia de traversare. Cu fiecare nod localizat, se pot executa prelucrri general valabile, pentru toate nodurile, i/sau prelucrri specifice, condiionate, de informaia coninut de noduri. Prelucrrile care se fac asupra unui nod constituie operaia de tratare de nod. Dac este necesar, n raport de informaia coninut n nod, nodurile care sunt supuse operaiei de tratare pot fi alese, operaia de traversare, n acest caz, fiind denumit traversare total cu selecie. In listingul 1.3 este redat, pentru exemplificare, structura unei funcii de traversare total cu selecie, pentru o list sumplu nlnuit, n care operaia de tratare s-a presupus c este realizat de o alt funcie, denumit tratare(). Listingul 1.3 Structura general a funciei de traversare a unei liste sumplu nlnuit cu selecie int traversare(NOD* inceput, char * infsel) {NOD* p; /* Rezolvarea situatiei de lista vida */ if(!inceput) return 1; /* Traversarea listei nevide */ p = inceput; do {if(conditie-de-selectie) tratare(p); p = p->succesor; } while (p); return 0; } In structura funcie de traversare, trebuie remarcat, n primul rnd, lista de parametri. Parametrul, denumit inceput este un pointer spre nodul cu care se ncepe traversarea, de regul, nodul de nceput - dat de pointerul cap al listei. Inceputul traversrii poate fi i un alt nod, atunci cnd este necesar o traversare total a unei subliste terminal a acesteia. Parametrul pointer primit aici este un parametru prin valoare i este conservat de funcia de

Prof.dr.Valer Roca ULBS

24

traversare din motive de semantic, mai ales dac acesta este pointerul de cap i, n consecin, este introdus un pointer de lucru p, numit pointer de traversare. Parametrul infsel este destinat primirii informaiei de selecie, adic a informaiei cu care se stabilete, prin expresia denumit conditie-de-selectie, dac nodul curent va fi tratat sau nu. Condiia de selectie este specific, dar aici s-a meinut presupunerea c informaia din noduri este un text i, prin urmare, parametrul trebuie s fie un pointer la char. In al doilea rnd, trebuie observat modul n care este conceput codul funciei de traversare, din punctul de vedere al asigurrii caracterului general al acestei funcii i anume tratarea strii listei. Aici, funcia distinge cele dou cazuri, prin rezultatul ntreg pe care l returneaz apelatorului: valoarea 1, pentru situaia de list vid i valoarea 0, dac lista este nevid, avnd cel puin un nod i a fost traversat. In al treile rnd, se remarc bucla de ciclare do, pentru controlul traversrii, cnd lista este nevid. Nodul curent, accesat prin pointerul de traversare p, este livrat sau nu funciei tratare(), n raport de valoarea de adevr a condiiei de selectie. Apoi, indiferent de caz, se trece la un alt nod, prin linia de avans p = p->succesor care obine adresa nodului succesor sau valoarea NULL, dac lista s-a terminat. Funcia de tratare, prin pointerul p primit, i acceseaz informaiile din nod i realizeaz tratamentul adecvat. Dac, n listingul 1.3, se renun la selecie, se obine o funcie de traversare total normal, eventual de la ultimul nod spre nceput, dac lista este dublu nlnuit i se nlocuiete legtura succesor cu legtura predecesor. Traversarea parial este, de regul, conceput ca o traversare de cutare i presupune vizitarea n ordine a nodurilor, de la captul ales, spre sfritul listei, pentru a localiza un nod care ndeplinete o condiie dat, n raport cu informaia coninut. Traversarea listei se termin cu succes, atunci cnd s-a localizat un astfel de nod (primul, dac pot exista mai multe noduri care ndeplinesc codiia impus) sau fr succes (insucces), dac un astfel de nod nu exist i a fost atins sfritul listei. In listingul 1.4, este redat, pentru exemplificare, structura unei funcii de cutare, pe o list simplu nlnuit, n ipoteza c informaia din noduri este de tip text. Funcia returneaz apelatorului o adres sau pointer, prin care l informeaz asupra situaiei ntlnite, pe baza urmtoarei convenii: - rezultat = NULL lista vid sau insucces; - rezultat NULL succes i rezutatul este adresa nodului gsit. Listingul 1.4 Structura general a funciei de traversare pentru cutare pe o list simplu nlnuit NOD* cutare(NOD* inceput, char * infsel) {NOD *p, *s; s = NULL; p = inceput; /* Rezolvarea situatiei de lista vida */ if(!inceput) return s; /* Traversarea listei nevide */ do {if(conditie-de-cautare) {s = p; break; } else p = p->succesor; } while (p); return s; }

Prof.dr.Valer Roca ULBS

25

Parametrii au aceeasi semificaie ca la procedura anterioar, iar conditie-decautare este expresia pe care trebuie s o ndeplineasc informaia din nodul curent, n raport cu informaia dat de parametrul infsel. Rezultatul cutrii se pregtete prin pointerul ajuttor s care, atunci cnd lista este nevid, n bucla do, rmne pe valoarea NULL, dac nu exist un nod care s satisfac cerina impus sau primete valoarea pointerului p, numit pointer de traversare, dac nodul curent este cel corespunztor. Traversarea parial cu pointer de urmrire este o variant a traversrii de cutare, necesar, de multe ori, n lucrul cu listele simplu nlnuite cnd intereseaz nu numai adresa nodului care ndeplinete criteriul de cutare ci i adresa predecesorului imediat al acestuia. Aa, de exemplu este cazul tergerii unui nod, cnd refacerea legturilor nu este posibil, dac nu se posed i adresa predecesoruuli nodului care se terge. In acest tip de traversare, fa cazul anterior, funcia de cutare trebuie s prevad nc un parametru, de tip pointer la pointer la nod, prin care s se returneze adresa predecesorului. In listingul 1.5, este redat structura funciei de traversare cu urmrire, unde acest pointer, denumit pointer de urmrire, este parametrul r, declarat sub forma NOD** r. In listingul model, s-au utilizat urmtoarele convenii, cu privire la valorile pointerilor: - rezultat NULL, r = NULL lista vid; - rezultat NULL, r NULL insucces, r conine adresa ultimului nod a listei; - rezultat NULL, r = NULL succes, rezultatul este adresa primului nod al listei i acesta ndeplinete criteriul de cutare, nu exist predecesor; - rezultat NULL, r NULL succes, rezultatul este adresa nodului care ndeplinete criteriul de cutare, iar r conine adresa predecesorului su. Listingul 1.5 Structura general a funciei de traversare pentru cutare, cu pointer de urmrire, pe o list simplu nlnuit NOD* traversarecuurmarire(NOD* inceput, char * infsel, NOD** r) {NOD *p, *s, *pr; s = pr = NULL; p = inceput; /* Rezolvarea situatiei de lista vida */ if(!p) return s; /* Verificarea primului nod */ if(conditie-de-cautare) {s = p; *r =pr; return s; } /* Traversarea listei care are cel putin doua noduri */ pr = p; p = p -> succesor; do {if(conditie-de-cautare) {s = p; break; } else {pr = p; p = p->succesor; } } while (p); *r = pr; return s;

Prof.dr.Valer Roca ULBS

26

} In funciile de cutare, conditie-de-cautare este o expresie care trebuie scris pentru nodul curent, adic n raport de pointerul de traversare p. In ipotezele fcute aici, trebuie utilizat funcia de comparare de iruri de caractere i aceast condiie se scrie sub forma: strcmp(p->info, infsel) 0; unde operatorul relaional se nlociuete cu unul din operatorii ==, <, >, depinznd de cerina pe care trebuie s o indeplineasc informaia din nod fa de informaia de selecie: - informaia din nod == informaia de selecie; - informaia din nod < informaia de selecie; - informaia din nod > informaia de selecie; Trebuie acordat atenie apelului unei astfel de funcii care, pe poziia parametrului r, trebuie s transfere o adres unei variabile pointer, aa cu sugereaz secvena urmtoare: NOD *inceput, *p, *r; ....... p = traversarecuurmarire(inceput, "textde informatie", &r); 1.5.2 Inserarea ntr-o list Inserarea unui nod nou se poate realiza la unul din capetele listei sau n interiorul listei, dup sau naintea unui nod dat. Pentru ndeplinirea unei astfel de sarcini, pentru fiecare caz, se recomand construirea unei funcii, numit funcie de nserare. O astfel de funcie trebuie s cunoasc pointerul sau pointerii care definesc lista, informaia care trebuie s fie pus n nod i, adresa nodului referin, dac nserarea nu se face la un capt. De regul, apelatorul are sarcina s posede informaiile cerute de funcia de nserare, iar, dac este necesar apelatorul va realiza traversarea prealabil a listei, cu cutare, pentru a construi unele din aceste informaii. Funcia de nserare trebuie s actualizeze pointerii caracteristici ai listei, dac nserarea i afecteaz, i s realizeze corect nserarea i n situai n care lista este vid. La nserare, n general, trebuie avut n vedere situaia n care se modific pointerii caracteristici. De aceea, toi pointerii care vor suferii modificri n funcia de nserare, modificri care trebuie s se regseasc ca atare n apelator, trebuie s fie declarai de tipul pointer la pointer la nod. Corespunztor, la apel, argumentele trebuie s fie adrese de variabile pointeri. Inserarea n capul listei este specific stivelor nlnuite, dar poate fi aplicat i la o list oarecare. Din punct de vedere algoritmic, nu ridic probleme deosebite, aa cum se poate deduce din listingul 1.6, n care se construiete un model de funcie pentru o list simplu nlnuit, cu informaie de tip text. Grafic, nserarea n fa se ilustreaz ca n fig.1.6 a. Listingul 1.6 Structura general a funciei de nserare n capul unei liste simplu nlnuit int inserareinceput(NOD** inceput, char * infnod) {NOD *p; /* Alocare si pregatire nod */ p = (NOD*)malloc(sizeof(NOD)); if(!p) return 1; strcpy(p->info, infnod); p->succesor = NULL; /* Legarea nodului nou, cu actualizarea pointerului de inceput*/ if(!*inceput) *inceput = p; /* Lista a fost vida */ else { p->succesor = *inceput;

Prof.dr.Valer Roca ULBS

27

*inceput = p; }; return 0; }

/* Lista a avut cel putin un nod */

In acest listing, parametrul infnod aduce informaia de pus n nod care, dup alocarea de spaiu - adresa n p - se copiaz n nod, legtura acestuia spre succesor fiind iniializat NULL. Dac lista este vid, atunci acest nod devine cap al listei i pointerul inceput devine egal cu p. Dac lista are cel puin un nod, atunci nodul nou p, trebuie s puncteze spre nodul dat de pointerul inceput i acest pointer se actualizeaz cu adresa lui p, pentru a arta noul cap al listei. De remarcat faptul c funcia testeaz situaia de lips de spaiu i ntoarce, ca rezultat, valoarea 1 (adevrat), pentru a marca lipsa de spaiu i valoarea 0, dac alocarea a reuit. Dac lista este dublu nlnuit, atunci funcia de nserarea are doi pointeri caracteristici (inceput i sfarsit) care trebuie s figureze n lista de parametri. Funcia trebuie s iniializeze la NULL ambele legturi ale nodului nou i s fac legarea acestuia sub forma: /* Lista a fost vida */ *inceput = p; *sfarsit = p; /* Lista a avut cel putin un nod */ p->succesor = *inceput; (*inceput)->predecesor = p; *inceput = p; Dac lista dublu nlnuit a fost vid, funcia actualizeaz ambii pointeri caracteristici, pentru a puncta spre nodul nou, ca unic nod al listei. Atunci cnd lista are cel puin un nod, trebuie ca fostul nod de inceput s puncteze, prin legtura sa predecesor spre nodul nou p, dar numai pointerul inceput trebuie actualizat, pointerul sfarsit trebuind s rmn cu vechea sa valoare. Inserarea la sfritul listei este specific cozilor, dar poate fi aplicat i la o list oarecare. Din punct de vedere algoritmic, nu ridic probleme deosebite, aa cum se poate deduce din listingul 1.7, n care s-a construit un model de funcie pentru o list simplu nlnuit, cu informaie de tip text. Grafic, nserarea n fa se ilustreaz ca n fig.1.6 - b. De remarcat aici faptul c funcia trebuie s primeasc pointerii caracteristici ai listei, de inceput i sfrit, pe care trebuie s i actualizeze corespunztor. Dac lista a fost vid, ambii pointeri puncteaz spre noul nod. Dac lista a avut cel puin un nod, ultimul su nod trebuie s trimit spre nodul nou, iar pointerul de sfrsit trebuie s arate spre nodul nserat.

Prof.dr.Valer Roca ULBS

28

a) Inserare n capul listei p 1

inceput

inceput

b) Inserare la sfarsitul listei 1

inceput

sfarsit

sfarsit

c) Inserare dup un nod dat dupanod

inceput

Fig.1.6 Inserare n list asimetric

Listingul 1.7 Structura general a funciei de nserare la sfritul unei liste simplu nlnuit int inseraresfarsit(NOD** inceput, NOD** sfarsit, char * infnod) {NOD *p; /* Alocare si pregatire nod */ p = (NOD*)malloc(sizeof(NOD)); if(!p) return 1; strcpy(p->info, infnod); p->succesor = NULL; /* Legarea nodului nou, cu actualizarea pointerului de inceput*/ if(!*inceput) /* Lista a fost vida */ {*inceput = p; *sfarsit = p; } else /* Lista a avut cel putin un nod */ { (*sfarsit)->succesor = p; *sfarsit = p; }; return 0; }

Prof.dr.Valer Roca ULBS

29

Pentru liste dublu nlnuite, este necesar s se modififice corespunztor secvenele de instruciuni date n cazul nserrii la nceput, modificnd captul de referin, adic nlocuind pointerul spre predecesor cu pointerul spre succesor, astfel: /* Lista a fost vida */ *inceput = p; *sfarsit = p; /* Lista a avut cel putin un nod */ (*sfarsit)->succecesor = p; p->predecesor = *sfarsit; *sfarsit = p; Inserarea dup un anumit nod al listei se aplic la o list oarecare. In acest caz, funcia de nserare trebuie s cunoasc, n plus fa de pointerii caracteristici, adresa nodului dup care se face nserarea. In cele ce urmeaz, cu titlu de exemplu, se presupune c funcia primete un pointer dupanod, cu adresa nodului dup care se face nserarea i pe care apelatorul o poate obine, de exemplu, prin traversare cu pointer de urmrire. Este recomandabil ca funcia de nserare s considere, de asemenea, situaia n care se solicit nserarea dup un nod dar lista este vid, adic o situaie pentru care parametrii inceput =NULL i dupanod NULL. In listingul 1.8, n ipoteza c pointerul dupanod este adresa unui nod al listei, este construit funcia model de nserare, iar situaia este ilustrat grafic n fig.1.6 c. Listingul 1.8 Structura general a funciei de nserare dup un nod dat la o list simplu nlnuit int inseraredupa(NOD** inceput, NOD* dupanod, char * infnod) {NOD *p; /* Alocare si pregatire nod */ p = (NOD*)malloc(sizeof(NOD)); if(!p) return 1; strcpy(p->info, infnod); p->succesor = NULL; /* Legarea nodului nou */ if(!*inceput) *inceput = p; /* Lista a fost vida */

else /* Lista a avut cel putin un nod */ {p->succesor = dupanod->succesor; dupanod->succesor = p; }; return 0; } Dac nu exist sigurana c pointerul dupanod conine o adres corect a unui nod al listei cnd aceasta este nevid, se poate introduce o secven de verificare prin traversare. Dac, n traversare, adresa nodului dupanod nu este egal cu adresa nici unui nod al listei, se refuz nserarea i funcia rentoarce valoare 2, ca mesaj de eroare. Dac lista este dublu nlnuit, atunci funcia trebuie s primeasc ambii pointeri specifici. In cazul de list nevid, de exemplu, cnd nserarea nu se face dup ultimul nod, legturile trebuie modificate astfel:

Prof.dr.Valer Roca ULBS

30

p->succesor = dupanod->succesor; p->predecesor = dupanod; dupanod->succesor->predecesor = p; dupanod->sucecesor = p; 1.5.3 Stergere ntr-o list La fel ca la nserare, tergerea poate s se refere la nodul din capul listei, la nodul ultim al listei sau la un nod oarecare din interiorul listei. Se pot construi funcii de tergere, pentru fiecare caz, eventual cu obligaia funciei de a returna informaia pe care o conine nodul ce este ters. In continuare, se presupune aceast situaie i se consider c informaia este de tipul text. La fel ca la nserare, tergerea poate modifica valorile pointerilor caracteristici. De aceea, toi pointerii care vor suferii modificri n funcia de tergere, modificri care trebuie s se regseasc ca atare n apelator, trebuie s fie declarai de tipul pointer la pointer la nod. Corespunztor, la apel, argumentele trebuie s fie adrese de variabile pointeri. Stergerea nodului din capul listei este tipic pentru stive i cozi, dar se poate utiliza i n cazul listelor oarecare. Funcia de stergere trebuie s primeasc, ca parametri, pointerul sau pointerii caracteristici ai listei i un pointer la char pentru un spaiu al al apelatorului n care s primeasc informaia din nodul care se terge. Pentru a distinge dac informaia returnat este semificativ, adic lista nu a fost vid, funcia trebuie s ntoarc un rezultat 1, dac lista a fost vid i un rezultat 0, n caz contrar. In listingul 1.9 este redat o astfel de funcie, pentru o list simplu nlnuit. Listingul 1.9 Structura general a funciei de tergere n capul unei liste simplu nlnuit int stergecap(NOD** inceput, char* infodinnod) {NOD *p; if(!*inceput) return 1; /* Lista a fost vida */ /* Stergere nod de cap, lista nu este vida */ p = (*inceput)->succesor; strcpy(infodinnod, (*inceput)->info); free(*inceput); *inceput = p; return 0; } Soluia ca apelatorul s i aloce spaiu pentru a primi informaia din nodul care se terge este recomandabil, pentru ca blocul de memorie s nu rmn ne eliberat dup utilizare, aa cum, de regul, se ntmpl dac funcia de tergere aloc n heap spaiul i returneaz la apelator numai adresa acestuia. In acest caz, apelul s-ar putea realiza ca n secvena care urmeaz: char t[10]; int r; . . . . . r = stergecap(&cap, t); Stergerea nodului ultim al listei este tipic pentru cozi, cnd funcia de tergere trebuie s primeasc ambii pointerii caracteristici, dar se poate utiliza i n cazul listelor oarecare. Dac lista este simplu nlnuit, funcia de tergere trebuie s cunoasc i adresa nodului predecesor al ultimului nod. Pentru a simplifica sarcinile apelatorului, funcia de

Prof.dr.Valer Roca ULBS

31

tergere nsi poate s execute o traversare care s depisteze predecesorul nodului ultim. Dac lista este dublu nlnuit, aceast adres este coninut n structura nodului ultim, n legtura denumit predecesor. Deoarece cozile solicit operaii frecvente de tergere, ele se pot proiecta ca liste dublu nlnuite (decozi - dequeue), pentru ca operaia de tergere s se realizeze uor, fr traversare. In continuare, pentru a arta n ce const, din punct de vedere algoritmic, o traversare de stabilire a acestei adrese, se presupune o list simplu nlnuit. Listingul 1.10 red structura funciei de tergere, n acest caz. Listingul 1.10 Structura general a funciei de tergere a nodului ultim al unei liste simplu nlnuit int stergesfarsit(NOD** inceput, char * infdinnod) {NOD *p, *r; infodinnod = NULL; /* Lista a fost vida */ if(!*inceput) return 1; /* Lista are un singur nod si devine vida */ strcpy(infdinnod, (*inceput)->info); free(*inceput); *inceput = NULL; return 0; /* Lista are cel putin doua noduri si se traverseaza */ r = *inceput; p = (*inceput)->succesor; while (p != NULL) /* Traversare */ {r =p; p = p->succesor; } /* Se sterge nodul sfarsit si lista ramane nevida */ strcpy(infdinnod, p->info); free(p); r->succesor = NULL; return 0; } In aceast funcie, r este pointerul de urmrire care rmne n urma pointerului de traversare p i, n final, d adresa predecesorului nodului de sfrit. Pe baza lui, se actualizeaz la NULL legtura, nodul r devenind nod ultim. Stergerea unui nod oarecare, de regul, presupune c nodul de ters se d prin informaia pe care o conine. In acest caz, funcia de tergere trebuie s realizeze o traversare cu pointer de urmrire, pentru a obine adresa nodului de ters i a predecesorului su. In listingul 1.11, se apeleaz funcia traversarecuurmarire(), n care se presupune o condiie de selecie pe egal. Se observ modul de apel al acestei funcii care primete pointerul de nceput (*inceput), informaia pentru selecie i adresa variabilei pointer r (&r). Listingul 1.11 Structura general a funciei de tergere a unui nod oarecare al unei liste simplu nlnuit int stergeoarecare(NOD** inceput, char* infsel, char * infdinnod) {NOD *p, *r; p = traversarecuurmarire(*inceput, infsel, &r); if((p==NULL) && (r==NULL))return 1; /* Lista este vida */ if((p==NULL) && (r!=NULL))return 2; /* Nu exista nodul de sters */ if((p!=NULL) && (r==NULL)) /* Se sterge primul nod */ {strcpy(infdinnod, p->info);

Prof.dr.Valer Roca ULBS

32

*inceput = p->succesor; free(p); return 0; } if((p!=NULL) && (r!=NULL)) {strcpy(infdinnod, p->info); r->succesor = p->succesor; free(p); return 0; }

/* Se sterge nodul p */

In aceast funcie, s-a extins codificarea rezultatului ntors, pentru a sesiza i situaia cnd nodul care se cere a fi ters nu exist n list, situaie ce poate apare, mai ales, datorit informaiei greite ce o conine parametrul infsel. De remarcat corecia care se aplic asupra pointerului inceput, n cazul n care se terge primul nod. De asemenea, trebuie acordat atenie modului n care se apeleaz funcia, potrivit unei secvene de forma: char t[10]; int n; . . . . . . n = stergeoarecare(&cap,"text", t); Dac, pentru o astfel de list, se ine i un pointer de sfarit, denumit sfarsit, atunci funcia trebuie s l primeasc n lista de parametri i trebuie s disting cazul cnd nodul care se terge este ultimul. In mod corespunztor, secvena situaiei, marcat prin comentariul /* Se sterge nodul p */, trebuie modificat astfel: if(p!=NULL && r!=NULL) /* Se sterge nodul p */ {strcpy(infdinnod, p->info); r->succesor = p->succesor; if(p->succesor==NULL)*sfarsit = r; free(p); return 0; Pentru cazul listelor dublu nlnuite, funciile de tergere se realizeaz uor, lista coninnd informaii de legtur care faciliteaz accesarea nodurilor n ambele direcii. Lsm cititorului, ca exerciiu, realizarea funciilor corespunztoare, avnd ca model funciile scrise la liste simplu nlnuite. 1.5.4 Distrugerea unei liste Dup cum se poate uor constata, la terminarea lucrului cu o list, aceasta poate s fie nevid i, n consecin, programul trebuie s elibereze spaiul ocupat. Aceast operaie este denumit distrugere de list i ea trebuie s elibereze spaiul nod cu nod. Operaia poate fi programat printr-o funcie adecvat, aa cum rezult din listingul 1.12. Listingul 1.12 Distrugerea unei liste void ditrugelista(NOD* inceput) {NOD* p; while(!inceput) {p = inceput; inceput = inceput->succesor; free(p); } }

Prof.dr.Valer Roca ULBS

33

1.6 Realizarea operaiilor de baz pe arbori binari de cutare


La fel ca i la liste, un program care trebuie s implementeze operaiile pe arbori binari trebuie s defineasc un tip de dat pentru nodul arborelui. In modul cel mai simplu, tipul NOD este un articol n care, pentru simplitate, informaia este considerat ca string i definete cele dou legturi spre succesorii direci (stnga = llink, dreapta = rlink): #define n 50 typedef char informatie[n]; typedef struct art {informatie info; art *llink, *rlink; }NOD; Depinznd de situaie, definiia se poate adapta pentru diferite tipuri de informaie, inclusiv pentru cazul n care informaia este un array dinamic. In ultimul caz, nodul trebuie s conin un pointer spre spaiul heap care se aloc array-ului. In cele ce urmeaz, avnd n vedere numai tratarea modului n care se pot implementa operaiile, se insist asupra operaiilor de traversare, cutare i nserare, considernd arborii binari de cutare. Problema distrugerii unui astfel de arbore se abordeaz n legtur cu exemplul de la sfritul capitolului. 1.6.1 Traversarea arborilor binari Traversarea arborilor binari se realizeaz ca o traversare total, n una din ordinile definite anterior: preordine, ordine simetric i postordine. Traversarea parcurge nodurile, potrivit metodei respective, indiferent de informaia pe care acestea o conin. In acest proces, fiecare nod este diponibil, pe rnd, pentru a i se aplica operaiile de tratare dorite. La fel ca n cazul listelor, se presupune c aceste operaii sunt realizate de funcia tratare(p), unde p este pointerul la nodul curent disponibil. Traversarea poate fi programat ntr-o funcie traversare() care poate fi realizat iterativ sau recursiv. In cele ce urmeaz se dicut, n detaliu, numai traversarea n ordine simetric. Pentru celelalte tipuri de traversri, se pot implementa funcii, ntr-o manier analog, modificnd corespunztor funcia pentru traversare simetric Traversarea iterativ n ordine simetric presupune o funcie care primete un pointer spre rdcina arborelului i, la fiecare nod vizitat, apeleaz funcia de tratare pe care o ofer programul. Deoarece legturile n arbore sunt totdeauna de la un nod printe spre succesorii si direci, o traversare n ordine simetric (SRD) presupune ca dup ce s-a vizitat, n ordine simetric, subarborele stnga, s se revin la rdcina acestuia, adic s se fac o revenire n sens invers direciei legturii lleft. Acest lucru nu este posibil, dect dac rdcinile subarborilor, n ordinea n care se trece prin ele nainte, sunt memorate. Dac memorarea acestora se realizeaz ntr-o stiv nlnuit, atunci drumul napoi se poate reconstitui extrgnd adresele de noduri n ordine invers memorrii, adic exact aa cum lucreaz o astfel de list. In listingul 1.13, n care se prezint codul funciei de traversare, stiva de lucru are partea de informaie de tip adres, denumit adrnodarbore, partea de legtur este denumit urmator, iat vrful stivei este gestionat prin pointerul top. Pentru a distinge cele dou tipuri de noduri, tipul pentru nodul arborelui este denumit NODABORE, iar cel al stivei de lucru este NODSTIVA. In fine, se face ipotez simplificatoare, c exist spaiu heap suficient pentru a putea construi stiva i pentru cea mai lung ramur a arborelui. Dealtfel, ipoteza este plauzibil, dac se are n vedere c la o ramur de 10000 de noduri, sunt necesari 80000 de bytes sau cca. 80 KB, ceea ce, pentru memoriile actuale este un spaiu mic.

Prof.dr.Valer Roca ULBS

34

Listingul 1.13 Structura funcie de traversare iterativ n ordine simetric a unui arbore binar void traversareSRD(NODARBORE* cap) {NODSTIVA *top, *r; NODARBORE *p; p=cap; top=NULL; do { while(p!=NULL) /* Pune adresa nodului arborelui in stiva */ {r = (NODSTIVA*)malloc(sizeof(NODSTIVA)); r->adrnodarbore = p; r->urmator = top; top = r; p = p->llink; /* Deplasare spre stanga */ } /* Extrage o adresa de nod al arborelui din stiva */ if(!top) {p = top->adrnodarbore; top = top->urmator; r = top; free(r); tratare(p); /* Tratare nod p */ p = p->rlink; /* Deplasare spre dreapta */ } } while(p != NULL || top != NULL); } Dac se urmrete codul funciei, se constat c stiva crete, iniial de la stiv vid, pe msur ce se face o deplasare spre stnga n arbore (p = p->llink), pn la un nod care are succesor stnga vid. In momentul cnd un astfel de nod a fost introdus n stiv, deplasarea stnga se oprete, se extrage nodul din vrful stivei, acesta se trateaz, prin funcia tratare(p) i apoi se face o schimbare a direciei de deplasare (p = p->rlink), spre subarborele dreapta al nodului tratat. Ciclul este reluat cu punerea n stiv a nodului rdcin al acestui subarbore i acesta este traversat spre stnga, pn la un nod cu succesor stnga vid etc, atta timp ct cel puin unul dintre pointerul p i pointerul top sunt nenuli, adic mai exist noduri ne vizitate. Traversarea recursiv n ordine simetric poate fi o tratare mai simpl, din punct de vedere algoritmic, aa cum rezult din listingul 1.14. O astfel de abordare, dei extrem de simpl, poate s nu dea satisfacie, pentru arbori voluminoi, avnd n vedere spaiul de memoriei al stivei sistem, mult mai redus dect spaiul heap i necesarul de memorie mai mare pentru a memora n stiv strile de ntrerupere, potrivit tehnicii de execuie a funciilor recursive. Listingul 1.14 - Structura funcie de traversare recursiv n ordine simetric a unui arbore binar void traversareSRD(NODARBORE* k) {if(k->llink != NULL) traversareSRD(k->llink); tratare(k); if((k = k->rlink)!= NULL) traversareSRD(k); }

Prof.dr.Valer Roca ULBS

35

1.6.2 Cutarea n arbori binari Aa dup cum s-a artat ntr-un paragraf anterior, la arborii binari de cutare, structura este definit de o relaie de dominare definit pe informaia coninut n noduri. Acest tip de arbore i gsete utilizarea n programele care stocheaz iniial informaii care posed o astfel de structur i apoi, la diferite momemte de timp, verific dac o anumit informaie este deja achiziionat. Datorit modului de memorare, pentru mulimi mari de informaii, un arbore de cutare poate fi mai eficient, ca timp mediu de cutare, dect o stocare a acestor informaii ca un array, la care trebuie adugat i facilitatea ca arborele s creasc dinamic, prin inserare, atunci cnd o informaie nu se gsete, dar se vrea adugarea ei. In acest paragraf, se consider cutarea ca un proces pur, ceea ce presupune c arborele este deja creat i o funcie, denumit funcie de cutare, stabilete dac o informaie, dat ca parametru, este sau nu n arborele de cutare. In listingul 1.15 este redat o astfel de funcie, n ipoteza cunoscut cu privire la structura de nod. Funcia primete pointerul cap la rdcina arborelui i intoarce ca rezultat un pointer valid p la ultimul nod vizitat. Din punct de vedere algoritmic, cutarea se realizeaz prin deplasare alternativ, cu pointerul p, fie n subarborele stnga fie n subarborele dreapta, n raport de relaia informaiei dat de parametrul infodat cu informaia info, coninut de nodul curent. Se constat cu uurin c informaia nu exist n arbore, dac n procesul deplasrii prin subarbori s-a ajuns prima dat la un nod frunz (terminal). Listingul 1.15 - Structura funcie de cutare pe un arbore binar de cutare int cautarearb(NODARBORE* cap, char* infodat, NODARBORE** pp) {NODARBORE *p; int terminat, succes, rel; terminat = succes = 0; p = cap; do {rel = strcmp(infodat, p->info); if(rel == 0) /* Informatia s-a gasit */ {succes = 1; terminat = 1; } else /* Informatia inca nu s-a gasit */ {if(rel < 0) /* Deplasare stanga sau terminat*/ {if(p->llink != NULL) p = p->llink; else terminat = 1; /* Informatia nu exista */ } else /* Deplasare dreapta sau terminat */ {if(p->rlink != NULL) p = p->rlink; else terminat = 1; /* Informatia nu exista */ } } } while(!terminat); *pp = p; return (succes); } In codul funciei, s-a utilizat o ciclare pe baza variabilei boolene terminat care pleac pe 0 (fals) i primete valoare 1 (adevrat), fie atunci cnd s-a gsit nodul ce conine informaia, fie atunci cnd se ajunge la un nod terminal. Variabila succes, de acelai tip, evideniaz eecul cutrii (valoarea 0) sau succesul acestui proces (valoarea 1). In final, funcia ntoarce valoarea variabilei succes i pointerul p al ultimului nod vizitat. In acest fel,

Prof.dr.Valer Roca ULBS

36

procedura de cutare poate fi utilizat n operaii de nserare (vezi paragraful urmtor), nodul ultim vizitat va fi cel de care se va lega un nod nou, atunci cnd informaia nu s-a gsit. Se atrage atenia asupra modului de declarare a parametrului pp, ca pointer la pointer la nod, adic sub forma NODARBORE** pp, pentru ca adresa nodului ultim vizitat s poat ajunge n apelator. In consecin, pentru apel, parametrul actual corespunztor trebuie decarat sub forma NODARBORE *p i transferat prin adres, sub forma &p. 1.6.3 Inserare n arbori binari de cutare Operaia de nserare este mecanismul general prin care se construiete, din aproape n aproape, un arbore binar de cutare. Operaia de nserare se poate programa ca o funcie de nserare care, la fiecare apel, adaug la arborele existent un nod nou. Inserarea unui nod nou trebuie s implementeze relaia de dominare specific. In listingul 1.16 este prezentat o astfel de funcie care primete, n lista de parametri, pointerul la rdcina arborelui, la primul apel pointer NULL, i un pointer la informaia de pus n nod. La fel ca alte funcii de nserare, funcia ia n considerare situaia spaiului heap i elimin situaia cnd aceeai informaie se repetat. Situaiile acestea sunt codificate ctre apelator, prin rezultatul ntreg pe care funcia l ntoarce: valoarea 1, dac spaiul s-a epuizat, valoarea 0, dac a existat spaiu pentru nod i nserarea s-a realizat i valoarea 2, dac informaia respectiv exist deja ntr-un nod. Listingul 1.16 - Structura funcie de nserare n arbore binar de cutare int inserarearb(NODARBORE** cap, char* infodat) {NODARBORE *p, *q; int s; q = (NODARBORE*)malloc(sizeof(NODARBORE)); if(!q) return 1; /* Lipsa spatiu */ /* Pregatire nod nou */ strcpy(q->info, infodat); q->llink = q->llink = NULL; /* Inserarea nodului q */ if(!*cap) *cap = q; /* Arbore vid, prima inserare */ else /* Inserare dupa o cautare */ {s = cautarearb(*cap, infodat, &p) ; if(s == 1) /* Informatia exista deja */ {free(q); return 2; } /* Legarea nodului nou */ if(strcmp(infodat, p->info)p->llink = q; else p->rlink = q; return 0; } } Trebuie remarcat faptul c, n funcia de nserare s-a apelat funcia de cutare cautarearb(). Variabila s recepioneaz rezultatul cutrii i, n cazul n care aceasta are valoarea 1, informaia exist deja n nod, de aceea, nodul q pregtit deja se elibereaz, deoarece inserarea nu se face. Dac informaia nu exist, nodul p este cel la care trebuie legat nodul q, fie ca succesor stnga, fie ca succesor dreapta, pe baza relaiei de comparare a informaiei infodat cu informaia info din p.

Prof.dr.Valer Roca ULBS

37

1.7 Realizarea programelor care utilizeaz structuri dinamice


Pentru a putea manipula, cu mai mult uurin, diferitele structuri de date, inclusiv n situaiile n care se utilizeaz mai multe structuri de aceali fel, n programul respectiv, este recomandabil s se construiasc tipuri de date: list arbore, array etc. Un astfel de tip de dat, n tehnica programrii procedurale, poate fi conceput ca un articol, descris prin struct, care s defineasc elemntele caracteristice drept cmpuri: pointeri, numr de noduri (elemente), unitatea de alocare i realocare etc. In aceste condiii, utiliznd aceleai funcii, se pot manevra structuri de acelai fel, prin declararea de variabile de acest tip. Fiecare astfel de structur dinamic este perfect identificat de o variabil i, prin notaia prefixat cu punct, cu numele acelei variabile, se disting elementele, fr confuzie. Tipurile struct definite pot s fie extinse ulterior, n tehnica programrii cu tipuri abstracte sau n tehnica programrii orientat pe obiecte, astfel nct s ncorporeze i funciile de manipulare, prin utilizarera limbajului C++. Mai mult, se poate depi specificitatea impus de tipul de dat al informaiei din noduri, dac se utilizeaz proprietatea de genericitate evitnduse constucia sistemului de funcii pentru fiecare caz. In concluzie, pentru a construi un program bun pentru lucrul cu o structur dinamic de date, cu informaii de un anumit fel, este necesar s se in seama de urmtoarele recomandri: s se defineasc un tip de dat pentru nodul (elementul) structurii care s introduc tipul de dat al elementelor i, dac este cazul, legturile; s se defineasc un tip de dat pentru structura respectiv care s cuprind elementele caracteristice ale acelui fel de structur; s se declare i s se defineasc funciile necesare, pentru utilizarea structurii respective. In paragrafele anterioare, s-a artat cum se definete tipul de dat pentru nod i cum trebuie abordat construcia funciilor necesare manevrrii structurii dinamice respective. In acest punct, exemplificm definirea tipului prin dou astfel de structuri dinamice. Pentru un array dinamic se poate declara un tip de forma: typedef struct {informatie* pblock; int nelem; int pozelem} DINARRAY; unde pblock este pointerul la spaiul heap compact alocat, la un anumit moment, acelei structurii, nelem reprezint numrul de elemente care pot fi coninute n spaiul alocat, iar pozelem arat poziia ultimului element prezent n array, potrivit conveniei de numerotare dat de limbaj. Pe baza tipului declarat, se poate defini o variabil vectdin, sub forma: DINARRAY vectdin; i pot fi referite aceste elemente ca vectdin.pblock, vectdin.nelem, vectdin.pozelem. La momentul de nceput, stuctura trebuie s fie vid, adic este necesar s se iniializeze structura declarat, sub forma: vectdin.pblock = NULL; vectdin.nelem = 0; vectdin.pozele = -1; In aceste condiii, funciile asociate structurii dinamice trebuie s primeasc valorile sau adresele elementelor caracteristice, dup cum acestea nu vor fi sau vor fi modificate n funciile respective. Ele pot s fie transferate individual, aa cum s-a artat ntr-un paragraf

Prof.dr.Valer Roca ULBS

38

anterior sau, mai comod, global, ca un unic parametru, sub forma unui pointer la aceea structur. In acest ultim caz, se recomand o declaraie de forma: DINARRAY vectdin, * pvectdin; ceea ce impune o iniializare a pointerului sub forma pvectdin = &vectdin. In funciile respective, dac se face ipoteza denumirii la fel a parametrului formal corespunztor, elementele necesare trebuie referite n notaia prefixat cu sgeat, pe baza acestui parametru, sub forma: pvectdin->numeelement. Pentru o coad, de exemplu, tipul este definit de mai puine elemente caracteristice i se poate declara sub forma: typedef struct {NODCODA* fata; NODCODA* spate;} COADA; unde fata i spate sunt pointeri la nceputul i respectiv ultimil nod. Corespunztor, se poate declara o variabil, vcoda iniial vid: COADA vcoda; vcoada.fata = NULL; vcoada.spate = NULL; La fel ca mai sus, i n acest caz, se poate alege una din variante, pentru transferul parametrilor la funciile asociate. Fiind numai dou elemete, se poate utiliza, fr complicaii de scriere, transferul individual, de pointeri. Se recomand ca tipurile care trebuie declarate, mpreun cu prototipurile funciilor de lucru cu structura dinamic respectiv, s se constituie sub forma unui fiier header *.h, iar definirele de funcii s se construiasc ntr-un alt fiier *.c. In felul acesta, se obin structuri cu un grad mai ridicat de reutilizare care s poat fi folosite i n alte programe, cu modificri minime. In fine, pentru a avea o viziune mai complet asupra acestui mod de lucru, se poate urmrii exemplul din paragraful care urmeaz. Se atrage atenia c tehnica de mai sus poate fi aplicat cu succes n cazul n care funciile care utilizeaz tipul respectiv nu sunt recursive. In cazul funciilor recursive, este nevoie s se construiasc replici ale structurii dinamice, pentru fiecare apel, ceea ce poate conduce foarte repede la epuizarea stivei sistem.

1.8 Un exemplu de utilizare a structurilor dinamice


Pentru a ilustra modul n care se pot utiliza structurile dinamice n rezolvarea de probleme, se consider urmtorul caz: S se realizeze un program care s furnizeze o list alfabetic, pentru o mulime de termeni (cuvinte) care se dau de la tastatur. Rezolvare: Deoarece este vorba de o mulime de cuvinte, a construi lista cerut revine la a realiza o sortare a acestora, pe baza ordinii lexicografice. Cum mulimea are un numr nedefinit de cuvinte, de lungimi diferite, este preferabil s se utilizeze un arbore de sortare, aa cum s-a artat n paragraful referitor la arbori binari. Arborele binar de sortare ofer posibilitatea de a stoca cuvintele n heap, pe un spaiu strict necesar, ceea ce poate asigura sortarea unui mare numr de termeni. Aceasta nseamn c n structura nodului arborelui nu se nscrie informaia, ci un pointer la blocul unde se memoreaz cuvntul respectiv. Se tie c, dup construcia arborelui, prin traversare n ordine simetric, se obine o list sortat a cuvintelor. Trebuie inut seama c aceast list poate fi mare i, n plus, trebuie

Prof.dr.Valer Roca ULBS

39

s fie redat pe hrtie. De aceea, lista sortat trebuie s fie nscris pe un fiier care, ulterior s poat fi listat, ntr-un anumit format. Inlisingul 1.17 este redat coninutul fiierului header al aplicaiei, denumit arbsort.h. Acesta se presupune c se gsete pe directorul curent i cuprinde declararea tipurilor de date i prototipurile funciilor necesare. Aici trebuie remarcat faptul c nodul arborelui nu conine cuvntul, ci un pointer la spaiul din heap, strict necesar, unde se memoreaz cuvntul respectiv. De asemenea, se observ c tipul pentru arborele de sortare posed, pe lng pointerul cap i un pointer la fiierul pe care se va gsi lista de cuvinte. Listingul 1.17 Fiierul antet arbsort.h /* Definirea nodului arborelui */ typedef struct nod {char* pinf; struct nod* llink; struct nod* rlink; } NODARBORE; /* Definirea arborelui de sortare */ typedef struct {NODARBORE* cap; FILE* fc;} ARBORESORTARE; /* Definirea prototipurilor functiilor pentru arbore */ int cautarearb(ARBORESORTARE* pa, char* infodat, NODARBORE** p); int inserarearb(ARBORESORTARE* pa, char* infodat); void traversareSRD(NODARBORE* k, FILE *f); void traversareRSD(NODARBORE* p); In listingul 1.18 este redat fiierul funciei principale. Funcia main() declar variabilele necesare, iniializeaz variabilele de tip structur i construiete arborele de sortare, prin conversaie la consol, apelnd funcia de nserare. Dup ce arborele a fost construit, se apeleaz funcia de traversare, pentru a crea, pe un fiier deja deschis n directorul curent, lista sortat a cuvintelor, fiecare cuvnt fiind scris pe un rnd distinct. In final, este apelat funcia care distruge arborele, ptin traversare n postordine. Listingul 1.18 Fiierul programului principal, pentru sortarea de cuvinte pe arbore #include <stdio.h> #include <string.h> #include "arbsort.h" /* Functia principala main() */ void main() {ARBORESORTARE as; char cuv[80]; int sp; /* Introducerea cuvintelor. Constructia arborelui */ as.cap = NULL; as.fc = NULL; printf("\nDati cate un cuvant.\n" "Terminati prin cuvant vid (ENTER):\n"); scanf("%s", cuv); while(strlen(cuv)!= 0) {sp = inserare(&as, cuv); if(!sp) {printf("\nNu mai pot fi introduse cuvinte; lipsa spatiu !\n"); break;

Prof.dr.Valer Roca ULBS

40

} /* Traversarea arborelui pentru sortarea cuvintelor */ as.fc = fopen("Listacuv.txt", "w"); traversareSRD(as.cap, as.fc); fclose(as.fc); printf("\nLista sortata a cuvintelor se gaseste in fisierul\n " "Listacuv.txt, pe directorul curent.\n"); /* Distrugerea arborelui, pentru eliberarea spatiului */ traversareRSD(as.cap); } Funciile sunt implemetate n listingul 1.19, considernd fiierul surs arbsort.c, pe directorul curent. Funciile care se utilizeaz aici sunt cele discutate anterior, la care s-au adus unele modificri i la care s-a adugat funcia de distrugere de arbore, prin treaversare n postordine. Funcile de cutare i de nserare sunt legate ntre ele, funcia de nserare apelnd, pentru fiecare cuvnt, funcia de cutare. Dac se urmrete codul surs al acestor funcii, se constat c exist o modificare, impus de structura nodului arborelui care conine pointer la informaia din nod. Atunci, la nserare este necesar s se aloce spaiu att pentru nod ct i pentru informaie i spaiile respective s se iniializeze corespunztor, aa cum rezult din secvena: q = (NODARBORE*)malloc(sizeof(NODARBORE)); t = (char*)malloc(sizeof(infodat)+1); . . . . . . . . . . . . . /* Pregatire nod nou */ q->pinf = t strcpy(t, infodat); q->llink = q->llink = NULL; Atunci cnd cutarea este fr succes, pointerul p aduce adresa nodului dup care trebuie s aib loc nserarea. Subarborele n care se leag nodul nou este dat de comparaia ntre informaia din nod i cuvntul dat. Relaia de comparare a informaiei dat cu cea din nod este de forma strcmp(infodat, p->pinf), avnd n vedere pointerul pinf la informaie. La funcia de cutare, s-a modificat numai modul de comparare a informaiilor, avnd n vedere structura nodului, aa cum arat instruciunea: rel = strcmp(infodat, p->pinf). Listingul 1.19 Funciile programului de sortare pe arbore- fiierul arbsort.c #include <stdio.h> #include <alloc.h> #include <string.h> #include "arbsort.h" /* Functia de cautare pe un arbore binar de cautare */ int cautarearb(ARBORESORTARE* pa, char* infodat, NODARBORE** pp) {NODARBORE *p; int terminat, succes, rel; terminat = succes = 0; p = pa->cap;

} printf("\nDati cate un cuvant.\n" "Terminati prin cuvant vid (ENTER):\n"); scanf("%s", cuv);

Prof.dr.Valer Roca ULBS

41

if(!p) return succes; /* Arbore vid. Informatia nu exista */ do {rel = strcmp(infodat, p->pinf); if(rel == 0) /* Informatia s-a gasit */ {succes = 1; terminat = 1; } else /* Informatia inca nu s-a gasit */ {if(rel < 0) /* Deplasare stanga sau terminat*/ {if(p->llink != NULL) p = p->llink; else terminat = 1; /* Informatia nu exista */ } else /* Deplasare dreapta sau terminat */ {if(p->rlink != NULL) p = p->rlink; else terminat = 1; /* Informatia nu exista */ } } } while(!terminat); *pp = p; return (succes); } /* Functia de inserare in arbore binar de cautare */ int inserarearb(ARBORESORTARE* pa, char* infodat) {NODARBORE *p, *q; char *t; int r; q = (NODARBORE*)malloc(sizeof(NODARBORE)); if(!q) return 1; /* Lipsa spatiu pentru nod */ t = (char*)malloc(sizeof(infodat)+1); if(!t) return 1; /* Lipsa spatiu pentru informatie */ /* Pregatire nod nou */ q->pinf = t; strcpy(t, infodat); q->llink = q->rlink = NULL; /* Inserarea nodului q */ if(!pa->cap) {pa->cap = q; /* Arbore vid, prima inserare */ return 0; } /* Inserare dupa o cautare */ p = pa->cap; r = cautarearb(pa, infodat, &p) ; if(r == 1) /* Informatia exista deja */ {free(q); free(t); return 2; } /* Legarea nodului nou */ r = strcmp(infodat, p->pinf); if(r < 0) p->llink = q; else p->rlink = q; return 0; }

Prof.dr.Valer Roca ULBS

42

/* Functia de traversare recursiv in ordine simetric */ void traversareSRD(NODARBORE* k, FILE *f) {if(k->llink != NULL) traversareSRD(k->llink, f); fprintf(f, " %s \n", k->pinf); if((k->rlink)!=NULL) traversareSRD(k->rlink, f); } /* Functia de traversare recursiva n postordine pentru distrugere arbore*/ void traversareRSD(NODARBORE* p) {NODARBORE *s, *d; s = p->llink; d = p->rlink; free(p->pinf); free(p); if(!s) traversareRSD(s); if(!d) traversareRSD(d); }

Funcia de traversare n postordine (RSD), este implementat recursiv i are sarcina s elibereze spaiul ocupat de nodurile arborelui. Se observ c, funcia trateaz rdcina p a unui subarbore, n sensul tergerii spaiului pentru informaie i apoi a spaiului nodului nsui. Dup tratarea rdcinii, dac exist subarborele stnga i/sau dreapta, adresai respectiv prin pointerii s i d, acetia sunt traversai n postordine, opernd similar pe rdcinile acestora. Traversarea se termin atunci cnd ambii subarbori sunt vizi. In fine, se remarc faptul c funciile de traversare primesc numai parametri implicai i nu se utilizeaz parametrii de tipul ARBORESORTARE. In cazul n care s-ar utiliza un astfel de parametru, de exemplu la traversarea simetric, la fiecare apel, trebuie s se construiasc un subarbore de acest tip, aa cum rezult din secvena care urmeaz i spaiul stivei sistem poate fi repede epuizat, fr ca traversarea s fie complet. void traversareSRD(ARBORESORTARE* pa) {ARBORESORTARE ps, pd; NODARBORE *k; k = pa->cap; if(k->llink != NULL) {ps.cap = k->llink; ps.fc = pa->fc; traversareSRD(&ps); } fprintf(pa->fc, " %s \n", k->pinf); if((k->rlink)!=NULL) {pd.cap = k->rlink; pd.fc = pa->fc; traversareSRD(&pd); } }

Prof.dr.Valer Roca ULBS

43

2. Utilizarea fiierelor
Un program comunic cu mediul sistemului, n principal, prin intermediul fiierelor. Fiierele reprezint principalul mijloc de organizare a datelor de mare volum care au un caracter persistent. Un fiier odat creat poate fi reutilizat, cu o mulime de programe i la diferite momente de timp, eventual, dup ce a fost adus la zi, din punctul de vedere al coninutului, dac este cazul. Un fiier este o colecie de date memorate pe suport magnetic, de regul disc magnetic, organizate ntru-un anumit mod, pentru a putea fi regsite ulterior. Modul n care sunt organizate datele pe suport magnetic reprezint nivelul fizic al fiieruli, iar modul cum aceste date sunt vzute n procesul de prelucrare este nivelul logic de organizare. Nivelul fizic presupune utilizarea pistelor i sectoarelor discului pentru memorarea datelor ca secvene binare, n vreme ce nivelul logic asigur fiierului o structur orientat spre utilizator n care fiierul este vzut ca o colecie de uniti de date: linii de text sau blocuri de bytes (articole). Sistemul de operare este responsabil cu implementarea lucrului cu fiierele i ofer asistena necesar prin intermediul unei biblioteci de rutine, denumit Sistem de gestiune a fiierelor SGF. Limbajele de programare utilizeaz SGF i i creaz o bibliotec specific de subrutine prin care programatorii, utilizatori ai acelui limbaj, pot realiza prelucrri la nivelul logic. Principiile i regulile dup care se memoreaz datele pe mediul extern, constituie o metod de organizare de fiier. Un sistem de operare poate implementa mai multe metode de organizare, dar metoda secvenial este o metod general standardizat. Modul n care se regsesec unitile de date n fiierul logic constituie tipul de acces. Dac, n ordinea logic specific a unitilor de date, o unitate nu poate fi localizat dect prin parcurgerea integral a unitilor care o preced, accesul este secvenial. Dac o unitate poate fi selectat independent de celelalte, accesul este direct. Toate metodele de organizare admit accesul secvenial, iar unele metode permit i accesul direct. Lucrul cu un fiier presupune operaii care se refer la fiier ca o entitate evideniat n sistemul de operare (operaii de gestiune) i operaii de prelucrare propriu-zis a datelor pe care fiierul le conine. Operaiile de gestiune se refer la nscrierea fiierului, sub un anumit nume, n sistemul de eviden practicat de sistemul de operare (sistem de directori, foldere), redenumirea fiierului, mutarea fiierului n alt director de eviden sau pe alt dispozitiv, realizarea de copii, tergerea fiierului din eviden i a datelor sale atunci cnd este perimat etc. Operaiile propriu-zise se refer la utilizarea datelor n programe pentru calcule, situaii, rapoarte etc (operaia de consultare), i la actualizarea periodic a acestora, dac acest lucru este necesar, prin modificarea valorilor unor articole existente (modificare), tergerea articolelor perimate (tergere) i adugare de articole noi (adugare). Operaiile de modificare, tergere i adugare sunt denumite operaii de actualizare, o metod putnd s le suporte pe toate sau numai o parte dintre ele. In practic, fiierele pot s fie utilizate n programe ca structuri ajuttoare de date externe, fie ca o prelungire temporar a memoriei interne, datorit volumuli mare de rezultate intermediare, fie ca mijloc de comunicarea ntre programele unei aplicaii. Astfel de fiiere, de regul, nu se actualizeaz i, n final sunt terse. Cele mai frecvente aplicaii de prelucrare de date utilizeaz fiiere persistente care se actualizeaz periodic, denumite fiiere de eviden. Un fiier de eviden poate fi utilizat n comun de mai multe aplicaii, eventual concomitent i el are o perioad lung de via . In acest capitol, vor fi discutate principalele aspecte de implementare i utilizare a fiierelor n limbajul C. In acest sens, sunt prezente i cteva paragrafe care se refer la algoritmica prelucrrii, constituindu-se n recomandri care pot uura scrierea de programe de aplicaie, n condiiile n care implementarea dat de limbaj nu asigur suficiente faciliti.

Prof.dr.Valer Roca ULBS

44

2.1 Tipuri de fiiere admise


Limbajul C implementeaz o singur metod de organizare, n dou variante i anume metoda secvenial (fig.2.1). In metoda secvenial, fiierul este considerat ca o secven de entiti de date, terminat printr-o secven special de caractere, denumit marca de sfri de fiier EOF (End Of File).

linie

linie

linie

linie

linie

EOF

Separator (marc) de linie (CR/LF)

Marc de sfit fiier (EOF)

a) Fiier text
b bloc bloc bloc bloc bloc bloc EOF

0.b

1.b

2.b

5.b

b) Fiier binar

Fig.2.1 Organizarea fiierelor secveniale pe disc

Un fiier de tip text este constituit din linii de caractere de lungimi diferite, ca numr de caractere, de aceea este necesar delimitarea lor printr-o secven special de caractere fr grafie, caracterele CR/LF, denumit marc de linie (rnd sau articol). Liniile avnd un coninut semificativ (numere ntregi, numere reale, caractere), prelucrarea acestor fiiere presupune conversia intre forma intern i cea extern de reprezentare a valorilor, la memorare (scriere) i, respectiv, extragere (citire) a acestora din fiier. Fiierele text asigur compatibiblitatea ntre sisteme de calcul i ntre limbaje de programare, fiind conforme cu reguli de organizare general acceptate. De aceea, ele sunt utilizate pentru schimbul de date ntre aplicaii. Un fiier de tip text, vzut ca spaiu de uniti logice de date, este, prin definiie, un spaiu ne adresabil. Aceasta nseamn c fiierul se creaz, scriind liniile de date una dup alata, de la nceputul fiierului spre sfritul su, adic operaia de creare a fiierului se face n access secvenial. Odat creat, operaia de consultare a datelor se realizeaz, de asemenea, citind unitile de date, n acces secvenial, fie de la nceput spre sfrit, fie de la sfrsit spre nceputul su. In acest tip de fiiere, nu pot fi realizate operaii care presupun poziionarea undeva n interiorul acestui spaiu, cum ar fi operaiile de tergere de linii, nserare de noi linii sau cele de modificare de coninut la anumite linii, numite, mpreun, operaii de actualizare de fiier. Deoarece sfritul de fiier poate fi detectat, fiierele de tip text pot fi extinse prin adugare la sfrit, operaie ce presupune eliminarea mrcii EOF, scrierea secvenial a noilor linii i apoi refacerea mrcii de sfrit de fiier. Avnd n vedere

Prof.dr.Valer Roca ULBS

45

modul de selectare a liniilor de text n realizarea operaiilor fundamentale, despre fiierele de tip text se spune c sunt fiiere cu acces secvenial. Un fiier de tip binar este o secven de blocuri de bytes care pot avea sau nu aceeai lungime i al cror coninut nu are semificaie pentru sistemul de gestiune al fiierelor. In lucrul cu aceste fiiere blocurile sunt citite i scrise ca secvene de bii, fr interpretare, de aceea, despre aceste blocuri se spune c sunt imagini de memorie intern. Fiierele binare, n forma lor general, sunt utilizate de aplicaii, mai ales, ca o prelungire a memoriei interne, atunci cnd sunt implicate rezultate intermediare de mare volum. Fiierele binare, fiind fiiere secveniale, pot avea, de asemenea, comportamentul descris mai nainte, din punctul de vedere al operaiilor posibile i al realizrii acestora la nivel de bloc. In plus, datorit faptului c, prin definiie, blocurile nu au o semantic, este posibil ca n fiier s se scrie i s se citeasc blocuri de mrimi diferite, n acelai program sau n programe distincte. Altfel spus, ntr-un fiier binar, programele pot vedea dup dorin mprirea n blocuri, adic structura de bloc a acestor fiiere este virtual. Datorit acestui fapt, are sens poziionarea oriunde n fiier i realizarea unei prelucrti specifice, ncepnd cu acea poziie. Sistemul de gestiune al fiierelor ofer, n acest sens, operaia de poziionare pe un anumit byte, considernd numerotarea poziiilor acestora ncepnd cu zero (poziie relativ). Se spune c un fiier binar admite i acces direct, prin poziie relativ. Dac fiierul binar este creat cu blocuri de aceeai lungime b, aa cum se sugereaz n figura 2.1, atunci mecanismul adresrii directe poate fi utilizat sistematic, adresa de nceput a unui bloc, ca poziie relativ a primului su byte, putnd fi calculat, pe baza lungimii comune a blocurilor i a numrului n al blocului respectiv. Dac, prin program, blocului i se d o anumit semificaie informaional ca o structur de tipul struct i fiecare bloc poate fi identificat unic n fiier prin valorile unui cmp al acestei structuri, denumit cheie primar, atunci numrul n 1 l poate genera programul nsui. Programul definete o transformare de poziie de bloc, pe baza cheii primare, ca o funcie i, pe baza ei, calculeaz pozia relativ pe a acestuia, aa cum se arat n relaiile care urmeaz: n = n(CheiePrimar) pe = (n - 1) * b. Sub aceast form, fiierele, binare pot fi utilizate n realizarea unor sisteme de programe pentru aplicaii practice cum ar fi: aplicaie de gestiune a studenilor unei faculti (universiti), de gestiune a materialelor, a salariailor unei companii, a crilor dintr-o bibliotec etc, deoarece ofer att acces secvenial ct i acces direct dirijat prin coninutul blocurilor. In toate aceste cazuri, un bloc cuprinde informaii despre un obiect: student, material, salariat, carte etc, iar cheia primar este matricolul studentului, codul materialului, marca salariatului sau cota crii.

2.2 Implementarea lucrului cu fiiere


La fel ca i alte limbaje, limbajul C implementeaz lucrul cu fiiere prin intermediul unei biblioteci de funcii, definite n fiierul header stdio.h i bazate pe conceptul de stream. 2.2.1 Streamuri Pentru a uniformiza modul de lucru cu fiierele disc i cu alte dipozitive de intrare/ieire, limbajul introduce noiunea de stream. Un stream este un concept logic care definete o secven de bytes, cu sau fr o anumit structur, care este surs de date de intrare sau purttor de date de ieire. Pentru a putea lucra cu un fiier, n aceast tehnic, programul trebuie s defineasc un stream i s l asocieze cu fiierul fizic, ca suport de memorare extern de date. Operaia de asociere ntre un stream i un fiier este denumit deschidere de fiier. Dup ce streamul nu mai este necesar, programul trebuie s realizeze o operaie de nchidere de fiier care

Prof.dr.Valer Roca ULBS

46

desface asocierea acestuia cu fiierul. Operaiile de deschidere i nchidere, permit ca, ntr-un program, acelai stream s poat fi asociat succesiv cu mai multe fiiere. Observaie: Aa dup cum se tie deja, exist streamuri asociate implicit cu tastatura i monitorul, dispozitive indispensabile oricrui program. Pentru acestea, fiierul stdio.h definete streamurile standard: stdin , pentru intarea de la tastatur i stdout, pentru ieirea pe monitor. Pentru aceste dispozitive, denumite fiiere interactive, compilatorul generaz automat deschiderea, la nceputul programului i nchiderea, la sfritul programului.

Program

Fiier intrare

Tampon de intrare
1 2

Citire:
1 - Umplere 2 - Extracie

Variabile de intrare
Prelucrare

Scriere:
3 - I nserie 4 - Golire

Variabile de ieire

3 4

Tampon de ieire

Fiier ieire

Fig.2.2 Prelucrarea fiierelor prin stream

Prin acest mecanism, programul lucreaz n mod direct cu streamul, din care citete sau n care scrie cmpuri de bytes, ajutat de funciile din biblioteca standard de intrare/ieire, fr s fie preocupat de comunicarea acestuia cu fiierul asociat. Deoarece , modul de lucru cu streamurile standard, pentru tastatur i monitor, a fost tratat ntr-un capitol anterior, n acest capitol se detaliaz aspectele de lucrul cu fiiere pe disc. Dup cum sugereaz i fig.2.2, un stream posed un spaiu de memorie, ca un array de bytes, de o anumit mrime, numit tampon (buffer), prin intermediul cruia se vehiculeaz bytes fiierului i asupra streamului se efectueaz operaii de citire/scriere. O operaie de citire nseamn o extracie din tampon a unui cmp de bytes, tratarea acestuia ntr-un anumit mod i atribuirea valorii rezultate prin prelucrare, unei variabile a programului. O operaie de citire poate gsi un tampon nevid sau poate ntlni situaia de tampon gol i, n consecin, ea trebuie s execute o umplere cu noi date din fiier. Dac operaia de citire curent, aflat ntr-o astfel de situaie, preia date pn la reumplerea tamponului sau pn la ternimarea fiierului, dac acesta are mai puini bytes dect capacitatea acestuia, atunci nu ori care operaie subsecvent de citire trebuie s realizeze preluare de date din fiier. Astfel, prin definirea unui tampon suficient de mare, operaiile de

Prof.dr.Valer Roca ULBS

47

citire devin mai eficiente, deoarece se reduce numrul de accese la dispozitiv care sunt mari consumatoare de timp. O operaie de scriere n stream nseamn inserie a unui cmp de bytes n tampon, ncepnd cu o anumit poziie. Operaii succesive scriu, n continuare n tampon, atta timp ct acesta are un spaiu rmas suficient pentru lungimea cmpului pe care l trateaz operaia respectiv (operaia curent). In caz contrar, operaia curent realizeaz o golire a coninutului tamponului pe fiier i apoi i nsereaz n tampon cmpul pe care trebuie s-l scrie. Similar cazului operaiei de citire, un tampon suficient de mare evit accesele repetate la dispozitiv i mbunete timpul operaiilor de scriere a datelor n fiier. Limbajul implementeaz dou tipuri de streamuri: stream de tip text i stream de tip binar. Un stream de tipul text (text stream) este un stream cu o anumit structur. Un astfel de stream const din una sau mai multe linii de text, ca secvene de caractere terminate prin caracterul newline (\n). Acest tip de stream este destinat realizrii i prelucrrii de fiiere secveniale de tip text care, afiate pe dispozitive ce recunosc secvena de caractere CR/LF ca o comand de trecere pe rnd nou, cum este monitorul i imprimanta, produc pagini de text. Datorit difernei de structur, operaiile de prelucrare trebuie s aplice un tratament suplimentar, pentru a asigura trecerea dela convenia pe care o respect streamul la cea care este utilizat de fiier. Atunci cnd se preiau date din fiier, funcia care face reumplerea streamului trebuie s nlocuiasc, n tamponul curent, secvena de caractere CR/LF cu un singur caracter newline, iar atunci cnd se depun date n fiier, din tampon, funcia care realizeaz operaia trebuie s nlocuiasc, n prealabil, caracterul newline cu secvena de caracter CR/LF . Operaiile de citire i scriere ntr-un stream de tip text presupun conversii ntre forma caractere i forma intern de reprezentate a valorilor pe care cmpurile de bytes le reprezint. Un stream de tip binar (binary stream) este un stream fr structur care conine cmpuri de bytes sau blocuri crora, n operaiile de citire/scriere nu lise d nici o semificaie. Se spune c aceste operaii se fac la nivel de imagine de memorie, adic o operaie de citire extrage din stream secvena de bii corespunztoare unui bloc i i stocheaz n memoria intern , iar o operaie de scriere depune n stream secvena de bii coninu de un bloc de memorie intern. Un stream binar se poate asocia cu un fiier care a fost creat printr-un stream binar sau se poate asocia cu un fiier de tip text, dac programul nu dorete s ia n seam semificaia de caractere a blocurilor. De asemenea, trebuie observat c programele pot trata un fiier cu stream binar, citind i scriind blocuri de aceeai mrime sau de mrimi diferite. Atunci cnd fiierul reprezint un coninut cu semificaie pentru program (dar nu i pentru operaiile de intrare/ieire), fiierul se creaz scriind blocuri egale, iar la citire, de regul, mrimea blocului este aceeai cu cea de la momentul crerii. 2.2.2 Controlul unui stream Alturi de funcii, fiierul header stdio.h conine tipul structurat de date, denumit FILE care asigur declararea unui stream ca o structur dinamic de date. O structur de tipul FILE memoreaz starea unui stream sub form de cmpuri de date ntregi, de bii (flags) sau de caractere i cuprinde informaii cum sunt: indicator de eroare (flag) este pus pe o valoare 1 de o funcie care ntlnete o eroare de citire sa scriere n fiier; indicator de sfrit de fiier (flag) este pus pe valoarea 1 de o funcie de citire, dac ntlnete sfritul de fiier; indicator de poziie n stream (pointer) specific poziia urmtorului byte n stream care poate fi accesat (citit sau scris); indicatori pentru modul de prelucrare (flags) exist un indicator pentru a preciza utilizarea streamului pentru citire, pentru scriere i pentru citire/scriere. Indicatorii sunt mutual exclusivi, valoarea 1 arat permisiunea, iar 0 interdicia; indicator de stream binar (flag) valoarea 1 arat tipul binar, iar valoarea 0 tip text. Implicit streamul se consider text;

Prof.dr.Valer Roca ULBS

48

tamponul streamului specific adresa unui array de tipul char n heap (pointer) i mrimea acestuia (numr ntreg), de regul, mrimea prestabilit n fiierul stdio.h; nivelul de umplere/golire al tamponului numr ntreg care specific numrul de bytes existeni n tampon dup o operaie de umplere, respectiv de nserare etc. Pentru a uura lucrul cu streamuri, fiierul stdio.h declar i o serie de constante simbolice, cum este constanta EOF necesar pentru testarea faptului c o funcie de citire a ntlnit sfritul de fiier, sau BUFSIZ care definete mrimea tamponului streamului (implicit 512 bytes) etc. Nu este recomandabil ca programul s modifice, prin referiri pe baza pointerului, nici unul din cmpurile structurii aferente, dar programul trebuie s utilizeze pointerul, ca parametru, n funciile de lucru cu streamul. NumaI funciile de I/O pot controla, pe baz de pointer, structura pentru streamul respectiv. 2.2.3 Modul de lucru cu streamul Programul care dorete s prelucreze un fiier, trebuie s realizeze paii care urmeaz. 1). Declararea streamului. Se declar cte o variabil pointer de tipul FILE pentru fiecare stream. De exemplu: FILE *strmIntr, *strmIes; // Stream intarare si stream iesire 2). Deschiderea streamului. Pentru construcia structurii de stare, se utilizeaz funcia fopen(), ntr-o expresie de atribuire de forma: pointer_la_stream=fopen(). Urmare a unei astfel de atribuiri, n heap se construiete o structur de stare pentru stream care este iniializat corespunztor i pointerul la stream este ncrcat cu adresa blocului de memorie alocat acestei structuri. Funcia fopen() are prototipul urmtor: FILE *fopen( const char *filename, const char *mode ); Parametrul filename este un pointer care d specificatorul fiierului. Specificatorul urmeaz regulile de construcie ale sistemului de operare, cu meniunea c separatorul ntre prile acestuia trenuie s fie \\, avnd n vedere semificaia caracterului \ n scrierea caracterelor fr grafie. In cazul n care specificatorul este incomplet, se aplic regulile sistemului de operare cu privire la discul curent, directorul curent i extensia implicit. Parametrul mode este un pointer care specific, sub form de string, modul de prelucrare al streamuli i tipul de stream. Se pot utiliza stringurile care sunt redate n tabelul 2.1. Tabelul 2.1 Valorile posibile pentru parametrul mode la deschidere Stringul Semificaie Deschide un fiier existent pentru citire in modul text. Dac fiierul nu r exist sau nu poate fi gsit, operaia eueaz. Deschide un fiier vid pentru scriere n modul text. Dac fiierul exist, w coninutul su este distrus. Deschide un fiier, in modul text, pentru adugare la sfrit. Dac fiierul a nu exist sau nu poate fi gsit, operaia creaz un fiier vid. Deschidere, n modul text, pentru citire/scriere. Fiierul trebuie s existe, r+ altfel operaia eueaz. Deschidere, n modul text, un fiier vid pentru scriere/citire. Dac fiierul w+ exist, coninutul su este distrus. Deschide un fiier, in modul text, pentru adugare la sfrit i citire. Dac a+ fiierul nu exist sau nu poate fi gsit, operaia creaz un fiier vid.

Prof.dr.Valer Roca ULBS

49

rb wb rb+ wb+

Deschide un fiier existent pentru citire in modul binar. Dac fiierul nu exist sau nu poate fi gsit, operaia eueaz. Deschide un fiier vid pentru scriere n modul binar. Dac fiierul exist, coninutul su este distrus. Deschidere, n modul binar, pentru citire/scriere. Fiierul trebuie s existe, altfel operaia eueaz. Deschidere, n modul binar,a unui fiier vid pentru scriere/citire. Dac fiierul exist, coninutul su este distrus.

Trebuie remarcat faptul c, atunci cnd un stream se deschide pentru adugare la sfrit, toate operaiile de scriere se fac numai la sfritul fiierului, iar eventualele operaii de citire sunt posibile numai n partea deja scris. Eticheta de sfrit de fiier este tears, dac este cazul, numai n momentul n care apare prima operaie ce presupune adugarea de date noi la fiier. Dac operaia de deschidere eueaz, funcia fopen() ntoarce un pointer NULL. Altminteri valoarea returnat este un pointer valid chiar spre structura de stare a streamului. Aceast valoare reprezint modul prin care se poate controla dac deschiderea a reuit. In exemplul care urmeaz, se deschid dou fiiere: unul pentru intrare i altul pentru ieire, considernd c deschiderea fiierului de ieire trebuie s aib loc numai dac a putut fi deschis fiierul de intrare i se schieaz o structur corespunztoare a funciei main(). #include <stdio.h> void main() { FILE *strmIntr, strmIes; char fnameIntr []=C:\\DirDate\\DirPers\\Personal.txt; // Deschiderea fisierului de intrare strmIntr=fopen(fnameIntr, r); if(!strmIntr) {puts(Fisierul de intrare nu poate fi deschis !); exit(1); } // Deschiderea fisierului de iesire strmIes=fopen(A:\\Salarii.txt, w); if(!strmIntr) {puts(Fisierul de iesire nu poate fi deschis !); fclose(strmIntr); exit(1); } // Prelucrarea fisierului deschis ca intrare si // scriere pe fisierul deiesire ................................. // Inchiderea fisierelor fclose(strmIntr); fclose(strmIes); } 3). Prelucrarea streamului. Se utilizeaz funciile de I/O pentru a citi i/sau scrie n stream. Pentru modul n care se realizeaz aceast etap, a se vedea paragrafele urmtoare. Aici, trebuie fcut observaia c exist diferene de implementare a modului de prelucrare, pentru diferite platforme C, de aceea, pentru o tratare uniform i pentru a asigura o portabilitate ct mai mare a programelor, vor fi tratate numai funciile standardului ANSI. De asemenea, se face meniunea c tratarea nu este exhaustiv, fiind comentate i exemplificate cele mai uzuale dintre funciile legate de prelucrare a fiierelor.

Prof.dr.Valer Roca ULBS

50

4). Inchiderea streamului. Se apeleaz funcia fclose(), aa cum rezult din exemplul de mai sus. Aceast funcie are rolul de a ncheia prelucrarea, prin golirea tamponului, dac acesta nu s-a umplut la ultima operaie de scriere (bloc scurt) i apoi de a elibera resursele ocupate de stream. Operaia de nchidere este recomandabil s se fac imediat ce prelucrarea cu un stream s-a ncheiat, mai ales atunci cnd se utilizeaz multe fiiere n program, astfel nct s se asigure , mai bine, resursele necesare celorlalte fiiere care se prelucreaz n continuare.

2.3 Citire din streamuri text


Operaiile de citire a streamurilor de tipul text sunt asistate de o serie de funcii specifice care sunt capabile s extrag cmpuri de bytes din stream, s le analizeze din punctul de vedere al scrierii corecte, eventual, s converteasc forma extern caractere n form intern codificat i s atribuie valoarea astfel obinut unei variabile de tip corespunztor. In streamurile de tipul text, pot apare o serie de caractere care separ cmpurile, denumite spaii albe (whitespaces sau simplu ws). Sunt considerate caractere ws caracterul blank, caracterul tab i newline. Toate celelalte caractere, exceptnd %, sunt considerate caractere non-whitespace. Exist mai multe posibiliti de lucru din punctul de vedere al mrimii irului de caractere care se extrage: la nivel de caracter, la nivel de cmp de caractere cu semificaie sau la nivel de linie de text, pe care le prezentm succint, n continuare. 2.3.1 Citire la nivel de caracter Citirea la nivel de caracter este asigurat de funcia fgetc() care are urmtorul prototip: int fgetc( FILE *stream ); Aceast funcie ntoarce caracterul din poziia curent din stremul de intrare sau valoarea EOF, dac s-a detectat sfritul de fiier. Funcia nu distinge ntre caracterele cu i fr grafie, de aceea cracterul citit poate fi oarecare, inclusiv newline. De exemplu, pentru streamul de intrare de mai sus se poate scrie: char c; c = fgetc(strmIntr); 2.3.2 Citire la nivel de cmp Citirea la nivel de cmp, denumit i citire formatat, reprezint parte central a prelucrrii streamurilor text i se refer la iruri de caractere care au o anumit semantic: numere ntregi, numere reale, texte etc. Funcia care asigur acest mod de citire este fscanf() care are urmtorul prototip: int fscanf( FILE *stream, const char *format [, argument ]... ); Dup cum se observ din acest prototip, lista de parametrii a acestei funcii are trei pri: pointerul stream la streamul de intrare, formatul format de intrare i o list de variabile de intrare, desemnat prin construcia de meta limbaj [, argument ].... Deoarece, prin citire se urmrete s se asigneze valori, provenite din fiier, unor variabile ale programului, de diferite tipuri de date, lista de argumente, denumit list de intrare, care reprezint aceste variabile, este elementul central. Lista de intrare este o list de adrese ale variabilelor respective care pot s fie variabile simple, cmpuri ale unor structuri de date de tipul struct sau elemente de array. In consecin, n aceast list, variabile trebuie s fie precedate de operatorul de adres &, sub forma &var. Sistemul ofer posibilitatea de a citi formatat global iruri de caractere i de a le memora n structuri array de tipul char. In acest caz, deoarece, o variabil array este, prin definiie, variabil de adres (pointer), n lista de intrare, numele acesteia nu trebuie precedat cu operatorul de adres.

Prof.dr.Valer Roca ULBS

51

Parametrul format este un string, constitui din subiruri de caractere predefinite, denumite specificatori de format care descriu cum trebuie tratate cmpurile din streamul de intrare. Un specificator de format poate conine i alte caractere, diferite de caracterele specifice. Pentru fiecare variabil din lista de intrare, trebuie s existe n format un specificator de format. Specificatorii de format se pun ncoresponden, de la stnga la dreapta, cu variabilele din lista de intrare. Un specificator de format este totdeauna introdus prin caracterul % i are urmtoarea form general: %[*][width] [{h | l | L}]type In aceast construcie, prezentat n metalimbaj, parantezele drepte arat opionalitate elementuli pe care l ncadreaz, perechea de acolade arat alegerea unui elemet dintre elementele pe care acestea le conin, separate prin bare verticale, iar elementele subliniate trebuie s fie nlocuite potrivit semificaiei dat de textul respectiv. Singurul element obligatoriu este cel denumit type care este un caracter, dintr-o mulime prestabilit i care arat tipul de dat pentru interpretarea cmpului corespunztor din stream: numr, string sau caracter. Tabelul 2.2 prezint caracterele posibile i semificaia lor, avnd n vedere tipurile de date care pot fi citite n C. In acest context, un specificator de format poate avea forma simpl %type, ca de exemplu: %d - specificator pentru un cmp ce reprezint un numr ntreg, %f - specificator pentru un cmp ce reprezint un numr real, %s - specificator pentru un cmp ce reprezint un string etc. Caracterele h, l, L, denumite modificatori de tip, pot precede caracterul pentru indicarea tipului unui cmp, numai n cazul cmpurilor numerice i au semificaia: h short int, l long int sau double, dac irul de caractere reprezint un numr real, L long double. Modificatorii reprezint o posibilitate de a desemna unele tipuri de date, pentru care nu exist definit un caracter specific de tip. Elementul width este un numr natural care specific numrul maxim de caractere care compun cmpul. Tabelul 2.2 Caraterele pentru tip de dat n specificatorii de format Caracter de tip Semificaie Intreg (int) redat n baza 10 d Intreg (int) redat n baza 8 o Intreg (int) redat n baza 10, 8 sau 16 i Intreg fr semnn(unsigned int) redat n baza 10 u Intreg (int) redat n baza 16 x Intreg (long int) redat n baza 16 X Real (float) redat n baza 10 scriere matematic sau tiinific e, E, f, g, G String (char [ ] ) s Caracter (char) c Pointer p Caracterul * n specificatorul de format este denumit caracter de suprimare a atribuirii i el spune funciei de citire s extrag cmpul curent din stream dar s nu realizeze tratarea lui. Limbajul stabilete reguli cu privire la modul n care decurge scanarea streamului de intrare i extragerea cmpurilor. Procesul de scanare pornete de la o anumit poziie n stream, denumit caracter urmtor, evideniat dinamic de indicatorul de poziie al streamului, care, la nceputul unui nou tampon, este primul caracter. Cu excepia cazului cmpurilor descrise cu specificatorul %c, n procesul de scanare sunt eliminate caracterele ws i un cmp ncepe cu primul caracter diferit de ws. Dup ce un cmp a fost extras, caracter urmtor devine caracterul care succede ultimului caracter ce compune cmpul extras.

Prof.dr.Valer Roca ULBS

52

Limbajul definete urmtoarele reguli cu privire la caracterele succesive se compun un cmp n stream: toate caracterele pn la, dar fr a include, urmtorul ws; toate caracterele pn la primul caracter care nu poate fi convertit sub specificatorul respectiv de format; cel mult n caractere, unde n este numrul care definete, n specificatorul de format , numrul maxim de caractere n cmp (width). Dac n formatul de intrare apar secvene de caractere care nu sunt caractere de format, atunci acestea trebuie s corespund exact cu secvena curent de caractere din cmpul de intrare. Aceste caractere sunt scanate, dar nu sunt reinute pentru cmpul ce va fi convertit prin specificatorul de format. Dac apare un conflict, aceste caractere rmn n cmpul de intrare ca i cum nu ar fi fost scanate. Scanarea pentru extragerea unui cmp se poate ncheia nainte de apariia unui ws sau poate fi abandonat i se trece la scanarea urmtorului cmp, n una din urmtoarele situaii: specificatorul de format conine caracterul de suprimare a atribuirii; s-a citit numrul maxim n caractere, prevzut de specificatorul de format; urmtorul caracter nu poate fi convertit sub specificatorul de format respectiv. Atunci cnd este stopat scanarea unui cmp, pentru una din aceste cauze, se presupune c primul caracter pentru scanarea urmtorului cmp va fi caracterul care a determinat oprirea. Operaia de citire se termin n urmtoarele circumstane: urmtorul caracter pentru cmpul de intrare curent este n conflict cu caracterul nonwhitespace corespunztor din format; urmtorul caracter n cmpul curent este un caracter ce marcheaz sfritul de fiier; formatul de intrare a fost n ntregime utilizat. Limbajul prevede cteva convenii care se aplic la unii specificatori de format, astfel: Specificatorul %c determin extragerea urmtorului caracter, ori care ar fi acesta, inclusiv un ws. Pentru a sri un ws i a extrage urmtorul caracter non-whitespace trebuie utilizat specificatorul %1s; Pentru specificatorul de forma %nc, unde n este lungimea cmpului, adresa corespunztoare din lista de intrare trebuie s fie adresa unui array cu n elemente char; Pentru specificatorul de format %s, adresa corespunztoare din lista de intrare, trebuie s fie un array char cu cel puin n+1 caractere, dac trebuie citit un cmp string de lungime n. In array, dup ultimul caracter este memorat caracterul null ca terminator de string. Caracterele ws care ncheie un cmp string de intrare sunt blanc i newline; Pentru specificatorii de format ai numerelor reale, cmpul de intrare poate fi un numr scris n convenia matematic sau tiinific, n sistem octal, zecimal sau hexazecimal. NOT: In diferite implementri, dar n afar de standardul ANSI, exist un specificator de format util pentru a defini stringuri n care sunt acceptate numai anumite caractere i care are forma %[set-de-caractere]. Intre parantezele drepte se enumer caracterele ce vor fi acceptate, eventual sub forma de domenii de caractere i constituie setul-de-cutare. Dac primul caracter n setul de caractere este caracterul caret ^ , atunci se definete un set de cutare complementar celui specificat de caracterele care urmeaz acestui caracter. De exemplu, %[a-cdfg] definete un string care poate conine ori cte i n ori ce ordine dintre caracterele a, b, c, d, f, g, iar %[^abc] definete pentru string oricare caractere cu excepia lui a, b, c. Specificarea setului de cutare este sensitiv la litere mari i mici. De exemplu, %[AFa-f] specific toate literele mari i mici din intervalul respectiv. Dac se uilizeaz scrierea sub form de interval, atunci acestea trebuie s fie nchise i disjuncte. In condiiile unui astfel de

Prof.dr.Valer Roca ULBS

53

specificator de format, cmpul de intrare nu mai este delimitat de ws, ci de apariia unui caracter care nu aparine setului de cutare definit de specificator. Dup execuie, funcia fscanf() ntoarce o valoare ntreag semificnd numrul de variabile crora li s-a atribuit efectiv valoare prin citire. Acest numr nu include un cmp scanat, cu care nu s-a puptut realiza atribuirea, adic o valoare zero arat c nu s-a realizat nici o atribuire, primul cmp a fost scanat, dar n-a putut fi convertit. Dac survine o altfel de eroare n timpul primei scanrii sau apare sfritul de fiier n cursul acesteia, funcia returneaz valoarea EOF. Rezult c valoarea returnat este un instrument pentru a face o verificare cu privire la modul de ncheiere a operaiei de citire, dar o valoare egal cu numrul de variabile din lisata de intrare nu este o certitudine de reuit, ci o indicaie de posibil corectitudine. 2.3.3 Citire la nivel de linie Dintr-un stream de tipul text , pot fi citite linii de text, prin utilizarea funciei fgets(): char *fgets( char *string, int n, FILE *stream ); Linia este compus din toate caracterele, ncepnd cu poziia curent, fr nici o eliminare de ws, pn se ntlnete caracterul newline, inclusiv acest caracter sau pn fost citite n 1 caractere, care din aceste dou evenimente apare primul. Linia de text este memorat n array-ul de tipul char, desemnat prin parametrul string i i se adaug caracterul null, terminator de string. Dac citirea detecteaz o eroare sau sfritul de fiier, funcia ntoarce pointerul NULL, altminteri returneaz chiar adresa zonei de memorare. Trebuie acordat atenie dimensionrii zonei de memorare, pentru ca s poat fi stocate toate caracterele, inclusiv caracterul null, altminteri funcia va semnala o situaie de eroare.

2.4 Scriere n streamuri text


Operaiile de scriere n streamurile de tip text, la fel ca i cele de citire, se pot realiza la nivel de caracter, de cmp i la nivel de linie. Poziia de la care ncepe memorarea n stream a irului de caractere este evideniat dinamic n structura de stare, prin indicatorul de poziie. Operaiile de scriere n streamuri de tip text sunt asistate de o serie de funcii specifice, inclusiv de funcii capabile s construiasc irul de caractere de scris prin conversia valorilor unor expresii. 2.4.1 Scriere la nivel de caracter Scrierea la nivel de caracter este asigurat de funcia fputc() care are urmtorul prototip: int fputc(int c, FILE *stream ); Aceast funcie scrie caracterul c n poziia curent din stremul de ieire i ntoarce caracterul scris sau valoarea EOF, dac s-a detectat un eveniment de eroare. Funcia nu distinge ntre caracterele cu i fr grafie, de aceea cracterul scris poate fi oarecare, inclusiv newline. De exemplu, pentru streamul de ieire de mai sus se poate scrie: char c = '*'; fputc(c, strmIntr); 2.4.2 Scriere la nivel de cmp Scrierea la nivel de cmp, denumit i scriere formatat, reprezint o form important a prelucrrii streamurilor text. Funcia care asigur acest mod de scriere este fprintf() care are urmtorul prototip: int fprintf( FILE *stream, const char *format [, argument ]... );

Prof.dr.Valer Roca ULBS

54

Dup cum se observ din acest prototip, lista de parametrii a acestei funcii, la fel ca i corespondenta ei la citire, are trei pri: pointerul stream la streamul de ieire, formatul format de ieire i o list de expresii de ieire, desemnat prin construcia de meta limbaj [, argument ]....Dup execuie reuit, funcia ntoarce numrul de caractere care au fost nserate n stream. Dac la execuie survine o eroare, funcia returneaz o valoare EOF. Deoarece, prin scriere se urmrete s se memoreze n fiier valori ale unor expresii de calcul din program, lista de argumente, denumit list de ieire, este determinant. Lista de ieire poate cuprinde ori care expresie corect n limbaj care produce o valoare ce poate fi convertit n form caractere. In mod uzual, din motive de simplitate, lista de ieire se exprim prin variabile simple i constante. Trebuie evitat confuzia cu funcia de citire, reinnd c aici se scriu variabilele (identificatorii) i nu adresele variabilelor, deoarece acestea desemnear valorile. In consecin, structurile de date de tipul struct i array, cu elemete de alte tipuri dect char, nu pot fi scrise dect prin componentele lor, fiecare din acestea reprezentnd o valoare. Exist o excepie important care se refer la posibilitatea de a scrie formatat global iruri de caractere memora n structuri array de tipul char i terminate prin caraterul null. In acest caz, n lista de ieire se scrie identificatorul de array. Parametrul format este un string, constituit din specificatori de format care descriu cum trebuie tratate cmpurile pentru streamul de ieire, caratere non-format i secvene de control (secvene escape). Pentru fiecare variabil din lista de intrare, trebuie s existe n format un specificator de format. Specificatorii de format se pun ncoresponden, de la stnga la dreapta, cu variabilele din lista de intrare. Caracterele non-format i secvenele escape sunt nserate n streamul de ieire n poziiile care rezult din locurile ocupate n format. Un specificator de format de ieire este totdeauna introdus prin caracterul % i are urmtoarea form general: %[flags] [width][.precision] [{h | l | L}]type In aceast construcie, prezentat n metalimbaj, parantezele drepte arat opionalitate elementului pe care l ncadreaz, perechea de acolade arat alegerea unui elemet, dintre elementele pe care acestea le conin, separate prin bare verticale, iar elementele subliniate trebuie s fie nlocuite potrivit semificaiei dat de textul respectiv. Singurul element obligatoriu este cel denumit type care este un caracter, dintr-o mulime prestabilit i care arat tipul de dat: numr, string sau caracter i conversia la care trebuie s fie supus pentru producerea cmpului corespunztor din stream. Caracterele prezentate n tabelul 2.2 se utilizeaz i n specificatorii de format de ieire, cu unele precizri care vor fi fcute n continuare. Astfel, un specificator de format de ieire poate avea, de asemenea, forma simpl %type, ca de exemplu: %d - specificator pentru un cmp ce reprezint un numr ntreg, %f - specificator pentru un cmp ce reprezint un numr real, %s - specificator pentru un cmp ce reprezint un string etc. Pentru numere reale, formatul %f nseamn scriere n form matematic, n timp ce %e i %E se utilizeaz pentru scrierea n form tiinific, cu litera e sau E separator al mantisei de exponent. Formatele %g i %G construiesc cmpul de ieire n scriere matematic sau tiinific, depinznd de magnitudinea numrului i precizia cerut pentru acesta. Se alege forma tiinific numai dac exponentul numrului este -4 sau este mai mare sau egal cu numrul care, n specificatorul de format, exprim precizia. Caracterele h, l, L, sunt modificatori de tip care pot precede caracterul pentru indicarea tipului unui cmp, numai n cazul cmpurilor numerice i au semificaia: h short int, l long int sau double, dac irul de caractere reprezint un numr real, L long double. Modificatorii reprezint posibilitatea de a desemna unele tipuri de date, pentru care nu exist caracter specific de tip. Elementul width este un numr natural n care specific numrul maxim de caractere care compun cmpul de ieire (mrimea zonei de scriere). In mod implicit, dac lungimea

Prof.dr.Valer Roca ULBS

55

irului de ieire, eventual rezultat prin conversie, este mai mic dect n, acesta este aliniat la dreapta i este completat cu spaii la stnga. Caracterul blank este, implicit, caracterul de umplere. Dac width este redat printr-un caracter *, atunci se consider c argumentul din lista de ieire care succede argumentul curent ce se prelucreaz cu specificatorul respectiv, ca numr ntreg, nu este valoarea ce trebuie scris, ci este specificaia de mrime de zon de scriere. Acest mod de specificare, permite construirea de cmpuri dinamice ca mrime, variabilele de dimensionare trebuind s fie iniializate nainte de apelul funciei de scriere. Dac width este mai mic dect cel mai mic numr de poziii caracter necesare sau specificaia de mrime de zon lipsete, funcia de ieire utilizeaz o zon de mrime suficient pentru scrierea complet a valorii respective. Elementul precision se refer, n general, la numrul de zecimale care se vor reine n scrierea numerelor reale. Dac, n specificatorul de format, precizia este numrul ntreg m, atunci vor fi reinute primele m zecimale, dup ce s-a aplicat operaia de rotunjire, iar dac m=0, atunci se reine numai partea ntreag a numrului (fr marca zecimal). Dac elementul precision se red prin caractertul * , atunci se presupune c precizia este specificat printr-o variabil din lista de ieire care poate s fie prima variabil sau poate s fie a doua variabil spre dreapta, dac i partea de width s-a exprimat prin *, dup argumentul care se trateaz curent. Dac precizia se utilizeaz i pentru valori ntregi, atunci ea este luat n considerare numai atunci cnd m excede numrul d de cifre pe care l are numrul i are drept efect umplerea la dreapta cu m d zerouri. Astfel, se pot realiza programe care lucreaz intern cu numere ntregi micorate cu o putere a lui 10 i apoi, la scriere, acestea s fie redate n mrimea lor natural. O meniune aparte trebuie fcut pentru specificatorii de scriere de stringuri care utilizeaz precizia m, deoarece, n cazul n care m este mai mic dect lungimea stringului, se scriu numai primele m caractere. Partea flags se red printr-un caracter care definete modul de aliniere n zona de scriere, scrierea semnului numerelor, a caracterelor blank, a punctuli zecimal (marca zecimal) i a prefixului de numr octal i hexazecimal. Se utilizeaz urmtoarele caractere: - pentru a specifica aliniere la stnga i umplere cu spaii la dreapta; + pentru a specifica redarea obligatorie a semnului sub forma + sau -; 0 pentru a defini caracterul zero drept caracter de umplere la numere; # pentru a specifica o prelucrare suplimentar pentru numere care se stabilete n raport de carcterul type astfel: x, X se pune n prefixul de scriere pentru numere hexa; e, E, f la numere reale se utilizeaz totdeauna marca zecimal; g, G la fel ca mai sus, dar la partea fracionar se completeaz numrul de zecimale cu zerouri, pn la specificaia de precizie. 2.4.3 Scriere la nivel de linie Intr-un stream de tipul text , pot fi scrise, ca un tot, linii de text, prin utilizarea funciei fputs(): int fputs( char *string, FILE *stream ); In acest caz, linia este compus din toate caracterele din zona dat de parametrul string, pn se ntlnete caracterul null, exclusiv acest caracter. Linia de text este memorat n streamul de ieire, ncepnd poziia curent din tampon i, la sfritul ei nu se adaug caracterul newline, aa cum procedeaz funcia puts(), pentru streamul stdio. Funcia returneaz numrul de caractere scrise sau valoarea EOF, dac scrierea detecteaz o eroare.

Prof.dr.Valer Roca ULBS

56

2.5 Citire i scriere n streamuri binare


Operaiile de citire i scriere n streamurile binare se realizeaz la nivel de bloc de bytes, prin funciile fread() i fwrite() care au prototipurile: size_t fread(void *buff, size_t bsz, size_t count, FILE *stream ); size_t fwrite(const void *buff, size_t bsz, size_t count, FILE *stream ); De la nceput, trebuie observat prezena, n prototipuri, a tipului de date sizet care este un tip ntreg, definit n fiierul stdio.h care definete numere ntregi foarte mari, ceea ce asigur posibilitarea de a lucra cu fiiere i blocuri mari de date. Parametrii au urmtoarea semificaie: parametrul buff este zona de memorie intern care memoreaz blocurile de date manipulate prin aceste funcii. O astfel de zon poate fi definit ca un array sau ca o dat structurat struct ; parametrul bsz este un numr ntreg, mai mare sau egal cu unu, care definete mrimea maxim a blocului care se manipuleaz; parametrul count este un numr ntreg, mai mare sau egal cu unu care stabilete numrul maxim blocuri de lungime bsz care se dorete a fi manipulate la execuia funciei respective. Funcia fread() citete cel mult count blocuri de lungime bsz bytes, din streamul de intrare i le memoreaz n zona de memorie buff. Indicatorul de poziie n stream este avansat cu numrul de bytes citii n total, astfel nct, o operaie subsecvent printr-o funcie fread() poate continua preluarea din stream, dac datele deja exist sau poate realiza o nou umplere a tamponului streamului. Dac streamul deschis la intrare este asociat cu un fiier de tip text, atunci funcia fread() nlocuiete perechea de caractere CR/LF cu un singur caracter newline. Funcia fread() returneaz numrul de blocuri complete efectiv citite, numr care poate fi mai mic dect cel precizat prin parametrul count, dac a aprut o eroare sau dac sa ntlnit sfritul de fiier, fr ca ultimul bloc s fie complet. Funciile ajuttoare feof() i ferror() pot fi utilizate pentru a putea distinge situaia survenit. Funcia fwrite() nsereaz n tampon, ncepnd cu poziia curent, dat de indicatorul de poziie al acestuia, pn la count blocuri, fiecare de lungime bsz, din zona de memorie buff. Indicatorul de poziie al streamului de ieire este incrementat cu numrul de bytes efectiv nserai n tamponul streamului. Dac streamul a fost deschis ca stream text, atunci funcia fwrite() nlocuiete fiecare caracter newline cu secvena de caractere CR/LF. La n terminare, funcia fwrite() returneaz numrul de blocuri complete efectiv nserate care poate fi mai mic dect count, dac a aprut o situaie de eroare.

2.6 Controlul sfritului de fiier i poziionarea n fiier


Aa dup cum s-a artat n partea de nceput a acestui capitol, prelucrarea fiierelor este adesea o prelucrare secvenial ciclic, n care fiecare entitate citit este tratat n acelai mod i iterarea se ncheie atunci cnd se detecteaz sfritul de fiier. Sistemul de funcii de intrare/ieire ofer funcia feof() care testeaz flagul corespunztor din structura FILE i ntoarce o valoare ntreg nenul, corespunztoare situaie de sfrit de fiier sau valoarea 0, pentru situaia contrar. Funcia are urmtorul prototip: int feof( FILE *stream ); Prelucrarea fiierelor binare se poate realiza i n acces direct, dac programul definete poziia de nceput a blocului care trebuie tratat. Implementarea streamurilor binare introduce o modalitate de control a poziiei n fiier i funcii care s permit definirea i

Prof.dr.Valer Roca ULBS

57

aflarea (interogarea) acesteia. In fig. 2.3 se prezint ntuitiv modul n care este conceput poziionarea, avnd n vedere cele trei posibiliti de referin (origine): nceputul de fiier, poziia curent n fiier i sfritul de fiier.

+offset

pe

pe

- offset

- offset Inceput fiier

Poziie curent

+ offset

Sfrit fiier

SEEKSET

SEEKCUR

SEEKEND

Fig.2.3 Poziia efectiv pe n fiier n raport de origine i offset

Poziia efectiv pe se calculeaz ca sum algebric ntre un numr care desemneaz originea, exprimat prin una din constantele SEEK_SET, SEEK_CUR, SEEK_END, definite n stdio.h i o deplasare (offset). Dac se prelucreaz blocuri de o mrime bsz i blocul respectiv este al n - lea fa de origine, spre dreapta, respectiv spre stnga, poziia pe a nceputului blocului dorit (primul byte din stnga), se face astfel: pe=SEEK_SET pe=SEEK_CUR pe=SEEK_END pe=SEEK_CUR + (n-1)*bsz + (n-1)*bsz n*bsz n*bsz.

Pentru orice fiier deschis pe un dispozitiv care admite poziionare direct, sistemul de operare pstreaz un pointer la fiier, exprimat ca o variabil ntreg de tipul long. Acest pointer este actualizat corespunztor dup oricare operaie de citire/scriere. Valoarea lui poate fi modificat i aflat prin program (interogare), pe baza apelului unor funcii specifice. Dac fiierul este un fiier curat binar, adic fr caractere CR/LF, poziionarea prin program se face fr probleme, calculul de poziie, aa cum s-a indicat mai sus, este totdeauna corect, deoarece blocul vzut n stream i cel fizic sunt identice. La fiierele text care sunt asociate cu un stream binar, datorit modului n care sunt tratate secvenele CR/LF, blocul intern i cel extern difer. In acest caz, exist garania poziionrii corecte numai pe nceputul de fiier, sfritul de fiier i o repoziionare pe poziia curent, dar numai dac valoarea pointerului de poziie s-a obinut prin interogarea sistemului de operare. Aa se explic prezena mai multor funcii legate de poziionare i interogare a poziiei pe care fiierul stdio.h le prevede. Un prim grup de funcii sunt fseek() i ftell(), pentru poziionare i respectiv interogarea relativ, descris mai sus. Aceste funcii au prototipurile urmtoare: int fseek( FILE *stream, long offset, int origine ); long ftell( FILE *stream ); i se utilizeaz, de regul, atunci cnd fiierul este un fiier curat binar, creat n modul de lucru cu stream binar asociat.

Prof.dr.Valer Roca ULBS

58

Parametrii celor dou funcii au semificaia prezentat mai nainte, de aceea, aici trebuie remarcate numai valorile pe care aceste funcii le returneaz. Funcia fseek() returneaz zero n caz de poziionare reuit i o valoare negativ n caz de eroare. Funcia ftell() returneaz valoarea pointerului la fiier, n caz de reuit i o valoare negativ, n caz de eroare. Valoarea de poziie returnat de ftell() este calculat ca offset fa de nceputul de fiier ca origine. Dac este necesar o nou poziionare, atunci aceast valoare returnat de ftell() poate fi utilizat ca offset n funcia fseek(). Un al doilea grup de dou funcii complementare fgetpos() i fsetpos(), prima pentru a interoga poziia pointerului n fiier i a doua pentru a seta poziia acestuia, utilizeaz o schem de poziionare absolut, referitor la nceputul fiierulu. Acestea au prototipurile: int fgetpos( FILE *stream, fpos_t *pos ); int fsetpos( FILE *stream, const fpos_t *pos ); Tipul de date fpos_t este definit n stdio.h ca un un tip ntreg lung. Parametrul pos este cel care recepioneaz valoarea pentru pointerul fiierului n interogarea cu fgetpos() i este surs de valoare pentru fsetpos(). Funciile pot fi utilizate, ca alternativ, n cazul fiierelor curat binare, pentru a evita poziionarea relativ. Un astfel de mod de lucru este util, n msura n care poziia curent ce se obine prin interogare este apoi reutilizat, pentru repoziionarea pe acelai bloc, iar modificarea curent a pointerului este realizat prin funciile citire/scriere. O prelucrare, n acest sens, apare ca singura corect n cazul fiierelor text, avnd n vedere observaia fcut la nceputul paragrafului cu privire la prelucrarea binar a fiierelor text.

2.7 Algoritmica lucrului cu fiiere


In realizarea unor aplicaii complexe care utilizeaz fiiere, se ridic o serie de probleme cu privire la proiectarea coninutului fiierelor i a algoritmilor de lucru cu acestea. Aa sunt, de exemplu, aplicaiile n care fiierele sunt utilizate pentru eviden, care presupun operaii de actualizare, pentru care este necesar organizarea de fiiere cu acces direct care s poat face distincie ntre blocuri, prin cheie primar i s poat sesiza dac un bloc are sau nu un coninut valid. Programatorul trebuie s suplineasc lipsa n limbaj a unor faciliti n acest sens, de aceea, n cele ce urmeaz se atrage atenia aupra unora din ele i se sugereaz modul n care pot s fie depite. In toate consideraiile care se fac n continuare, se presupune c fiierele se prelucrarez la nivel delinie sau de bloc, pentru care se utilizeaz termnul de articol. 2.7.1 Prelucrarea secvenial Aplicaiile practice de eviden presupun, de multe ori, prelucrarea integral a unui fiier, n care fiecare articol face obiectul aceluiai mod de tratare (prelucrare pe articole). Din punct de vedere algoritmic, se poate da o schem general de lucru n care se construiesc proceduri ce trebuie s cuprind obligatoriu anumite elemente. In fig.2.4, aceast mod de prelucrare, denumit prelucrare secvenial, este redat sub form de schem logic. In programarea operaiilor schemei de prelucrare, se recomand construirea de funcii pentru cele trei proceduri care s fie apoi apelate de funcia main(), aa cum se sugereaz n listingul 2.2. In aceast rezolvare, funciile au drept parametru, pointerul la stream, funcia de prelucrare are, n plus i un pointer la articol i fiecare funcie poate avea i ali parametri, dup necesiti. Se poate concepe un program care s defineasc, inclusiv streamu, variabile globale acele variabile care sunt utilizate de funcii, fiecare din ele urmnd s-i defineasc propriile variabile locale. Schema general poate fi particularizat pentru diferite situaii de prelucrare, scriind corespunztor codul funciilor implicate, funcia main() rmnnd aceeai.

Prof.dr.Valer Roca ULBS

59

STAR

Deschidere fisier f Operatii care se fac la inceput de fisier

Inceput_fisie

Citire articol curent Tratare articol curent


NOT EOF(f) Nu Sfarsit_fisier Da

Prelucrare

Operatii care se fac la sfarsit de fisier Inchidere fisier f

STOP

Fig.2.4 Scema general de prelucrare secvenial a unui fiier

Listingul 2.2 Programarea schemei de prelucrare secvenial #include <stdio.h> // Definirea unui tip pentru articol typedef {} ARTICOL; // Declararea prototipurilor functiilor void Inceput_fisier(FILE *f,); void Prelucrare(FILE *f, ARTICOL *a,); void Sfarsit_fisier(FILE *f,); // Definirea functie main() void main(void) {FILE *f; ARTICOL a; Inceput_fisier(f,); while (!feof(f)) Prelucrare(f,&a,); Sfarsit_fisier(f,); } // Definirea funciilor 2.7.2 Controlul paginilor n realizarea rapoartelor Prelucrarea unor fiiere poate s conduc la realizarea de rapoarte pentru care se cere un anumit mod de punere n pagin i de numerotare a paginilor. Implementarea lucrului cu fiiere n C nu ofer nici o facilitate, n acest sens, de aceea programatorul trebuie s construiasc un mecanism prin care s poat controla punerea n pagin. Problema realizrii de rapoarte cu aspect deosebit este complex, de aceea, n discuia care urmeaz se sugereaz numai modul n care se poate concepe un algoritm simplu de control al paginrii. Se presupune cazul prelucrrii secveniale a unui fiier f i obinerii unui fiier g de tipul text

Prof.dr.Valer Roca ULBS

60

pe care se scrie raportul i care apoi este trimis spre mprimare. Acest mod de lucru este tipic, avnd n vedere c imprimarea propriu-zis, n reele de calculatoare, este un proces separat, controlat de un server de imprimare. In esen, controlul paginrii se bazeaz pe variabile ajuttoare pentru contorizarea rndurilor scrise contrand i construirea numerelor de pagin contpag. Aici, se face ipoteza c programul construiete raportul pe un anumit tip de pagin pe care este disponibil un anumit numr de rnduri maxr, pentru scrierea cu fontul implicit. Pentru realizarea algoritmic, se dezvolt corespunztor procedurilie din schema prelucrrii secveniale, aa cum rezult din fig.2.5. S-a presupus c la scrierea unui rnd se consum nr linii, inclusiv liniile goale necesare, iar procedura CapRap(), de construire a capului unei pagini, consum rc linii. Se remarc, de asemenea, modul n care se comand saltul la pagin, n aceast procedur, prin scrierea unei linii care conine secvena escape format din caracterul '\f' (formfeed).

Inceput_fisier

Prelucrare

CapRap

Deschide fisierul f de intrare Deschide fisierul g pentru raport

Cireste din f articol curent

contpag++

Scrie in g Prelucreaza articolul curent Nu contrand > maxr Da

'\f'

contpag = 0 contrand = maxr + 1

Alte operatii specifice

CapRap

Scrie in g numar pagina Scrie in g antet Scrie in g titlu Scrie in g cap tabel

IESIRE

Scrie lini in g pentru rand

contrand = rc

IESIRE Sfarsit_fisier contrand += nr

Alte operatii specifice

IESIRE

Inchide fisierul f Inchide fisierul g

IESIRE

Fig.2.5 Algoritmizarea controlului pentru punere n pagin

Prof.dr.Valer Roca ULBS

61

2.7.3 Construirea fiierelor binare pentru aplicaii de eviden Fiierele de eviden trebuie s poat suporta, n general, toate operaiile de actualizare: inserare de articole, modificare de articole i tergere de articole. Implementarea eficient a acestor operaii presupune, n primul rnd, valorificarea posibilitii de acces direct, deoarece, la un moment dat, un numr redus de articole trebuie s fie supuse unor astfel de operaii. Adresarea direct este uor implementabil, cum s-a artata anterior, dac articolele posed o cheie primar, cu valori naturale, iar articolele, n perioada de via a fiierului, acoper relativ dens (peste 75%) intervalul valorilor posibile. In acest caz, se poate construi aceea transformare n, pentru numrul blocului unui articol, ca o funcie de forma: n=n(CheiePrimar)=CheiePrimar ValoreMinimCheiePrimar Se remarc, apoi, ideea c fiierul se dimensioneaz pentru o anumit perioad de via, suficient de lung i orice operaie, inclusiv operaia de nserare de articole, se face numai n interiorul acestui spaiu. In momentul n care apar articole care trebuie nserate n afara cadrului iniial al fiierului, acesta trebuie s fie reorganizat. Acest mod de lucru presupune o posibilitate de a distinge ntre blocurile n care s-a nscris un articol i cele care sunt nc ne ocupate, pentru a evita erorile de suprascriere, datorate unor greeli de specificare a cheii primare. In al treilea rnd, se constat c operaia de tergere nu poate fi o tergere fizic, deoarece, din punct de vedere practic, ar nsemna reutilizarea unor valori de cheie primar pentru a identifica alte articole (obiecte) i acest lucru poate crea confuzii. De aceeea, operaia de tergere se face ca o tergere logic, ceea ce nseamn pstrarea articolelor n fiier, dar marcarea lor ca inactive. Din consideraiile de mai sus, rezult c programatorul trebuie s gndeasc o structur de articol (fig.2.6) care, prin valorile codificate ale indicatorului de serviciu IS, s permit distincia ntre situaiile n care se poate gsi un bloc: bloc gol, activ sau inactiv i s conceap o operaie de preformare pentru fiier. Preformarea fiierului n seamn construirea fiierului, la dimensiunile maxime necesare, cu blocuri goale, adic blocuri pentru care indicatorul de serviciu IS s fie zero.

IS

CHEIE PRIMAR

RESTUL ARTICOLULUI

0 bloc gol 1 bloc activ 2 bloc inactiv

Fig.2.6 Structura blocului pentru un fiier de eviden

Realizarea unei structuri de bloc de acest fel, permite derularea corect a accesului secvenial, cu posibilitatea de a distinge articolele efective i, n plus, este posibil un control

Prof.dr.Valer Roca ULBS

62

asupra validitii unor operaii de actualizare, evitndu-se distrugerile de articole datorit erorilor de furnizare, de ctre utilizator, a cheii primare de access (cheie de cutare). Pentru scrierea efectiv a sistemului de programe se pot adopta mai multe tehnici: programe distincte pe operaii, programe pe grupuri de operaii (program multifuncional), sau o tehnic mixt. Tehnica adoptat depinde, n general, de frecvena operailor i de necesitatea de evitare a redundanei codului. Aa de exemplu, operaiile de actualizare se pot incorpora n acelai program multifuncional, dac ele se execut, n timp, cu o frecven redus i codul necesar nu este disproporionat ntre aceste operaii. Se poate adopta o strategie de construcie a unui program de modificare, dac aceast operaie este frecvent i un program de actualizare prin nserare i tergere. In principiu, este recomandabil s existe un program separat de nserare n acces direct care poate fi utilizat n orice situaie, inclusiv n timpul primei ncrcri cu articole (populare iniial) care, de regul, fiind de volum mare, trebuie s fie executat n mai multe etape. Pentru operaiile care presupun consultarea fiierului, n general, se construiesc programe distincte. Din punct de vedere algoritmic, programele au specificitate, dar, n general, se poate face uz de o schem de prelucrare secvenial, dac se distinge corect postura fiierului de eviden n prelucrarea respectiv. Din acest punct de vedere, fiierul de eviden poate fi fiier conductor sau fiier condus. Dac articolele fiierului de eviden trebuie tratate secvenial, iar prelucrarea se ncheie cnd s-a detectat sfritul lui, atunci algoritmul tipic de prelucrare este dat de schema prelucrrii secveniale, prezentat mai nante, n care fiierul de eviden este fiier conductor. Aa este cazul programelor care consult fiierul pentru realizarea de situaii sau rapoarte. Dac fiierul de eviden este prelucrat, ca urmare a unui proces secvenial desfurat pe baza altui fiier, schema de prelucrare este, de asemenea, una secvenial, dar cu alt fiier pe post de fiier conductor. Pentru operaiile de actualizare de mare volum, de exemplu, este recomandabil ca datele de intrare implicate s fie colectate pe un fiier secvenial care se extinde, pe msur ce apar date noi i periodic, acest fiier este utilizat ca fiier conductor, pentru a realiza actualizarea fiierului de eviden. In cazul operaiilor de actualizare a fiierului de eviden, cnd volumul de date de intrare este redus i acestea se furnizeaz direct de la tastatur, tastatura poate fi considerat ca fiier conductor. Tastatura este, deasemenea, fiier conductor, n multe operaii de consultare aleatoare a fiierului de eviden, cnd utilizatorul dorete s obin informaii din anumite articole. Datorit structurii sale ceva mai deosebite, pentru exemplificare algoritmic, se consider cazul unui program multifuncional (fig.2.7). Programul, n ntregimea sa, este condus prin tastatur, pe baza unui meniu care se afieaz pe monitor ca o list de operaii codificate ntr-un anumit fel, de exemplu prin litere, din care utilizatorul poate alege una. Pentru ncheierea procesului, meniul trebuie s cuprind o operaie, codificat cu litera T n schem, care conduce la oprirea execuiei. Fiecare din operaiile posibile poate fi imaginat ca o funcie care, algoritmic, este o subschem de prelucrare secvenial, cu fiierul de eviden ca fiier conductor sau un alt fiier pe acest post. 2.7.4 Indexarea fiierelor Indexarea unui fiier, denumit fiier baz, este o operaie de construire a unui alt fiier asociat, denumit fiier index care s faciliteze regsirea articolelor n acces direct din fiierul de baz. Unui fiier baz i se pot asocia mai multe fiiere index, pentru a putea asigura regsirea n acces direct a articolelor dup mai multe criterii. Index primar. Dac indexarea se face dup cheia primar, atunci indexul este denumit index primar. Un astfel de index este necesar s se asocieze unui fiier de eviden, n cazul n care cheia primar nu este proprie pentru definirea numrului blocului asociat unui articol. Aa de exemplu, este cazul unui fiier de cri, pentru care cheia primar este este cota crii, exprimat ca un ir de caractere cu o anumit semificaie pe subiruri (cota crilor se obine prin codificare zecimal), inproprie pentru calcul. In aceeai situaie se gsesc i cheile primare obinute prin codificare secvenial prin numere naturale cu care se poate calcula numrul blocului, dar utilizarea practic a lor este ineficient ca spaiu pe disc, n cazul n

Prof.dr.Valer Roca ULBS

63

care articolele, n perioada de via a fiierului, las goluri prea multe (peste 25% din ntregul spaiu).

START

MENIU(op)

DA op!='T '

NU 'A' 'B' fA

op 'N'

fB

fN

STOP

MENIU(op)

Fig.2.7 Schema general a unui program multifuncional

Un index primar este, n ultim analiz, o funcie definit tabelar care asociaz, la fiecare valoare a cheii primare, un numr n0, reprezentnd numrul relativ al blocului n care este memorat, n fiierul baz, articolul respectiv. Din punct de vedere informatic, un fiier index este un fiier cu articole de forma (ValoareCheiePrimar, n) care poate fi construit, de exemplu, ca un tabel (fig.2.8), sortat dup valorile cheii primare, considerat cmp de indexare. In figur, se prezint fiierul baz i fiierul index asociat, presupunnd, pentru simplitate, o cheie desemnat print-o singur liter.

Prof.dr.Valer Roca ULBS

64

Fier baz

10

11

Fiier index (.NDX)

A 2 B 1 C 6 D 3 E 7 F 0 L 10 M 4 O 8 P9 Q 11

Fig.2.8 Index primar asociat unui fiier baz

Pentru exemplificare algoritmic (fig.2.9), se presupune o cheie primar de tip caracter i se consider cazul n care fiierul index poate fi stocat, n ntregime n memoria intern, ca un array T, de articole de forma (CH, B), unde CH memoreaz valorile cmpului de indexare, iar B numrul relativ de bloc n fiierul baz. In aceste condiii, a construi indexul nseamn a crea i sorta intern array-ul T i apoi a scrie T pe disc. In construirea lui T, se citiete secvenial fiierul de baz, n articolul a, cu cmpul a.CP pentru valorile cheii primare i, pentru fiecare articol citit care are a.IS = 1, se nscrie un element n T, n ordinea n care vin articolele, variabila n fiind cea care ine evidena numerelor relative de bloc din fiier, iar m pe cea a intrrilor n T. Pentru sortare, se aplic o metod oarecare de sortare, de exemplu metoda transpoziiei, innd seama c ordinea de sortare se determin prin compararea valorilor consecutive T[k-1].CH i T[k].CH, k2, dar se interschimb, dac este cazul intrarea k n ntregimea ei, cu intrarea k-1. Scrierea lui T sortat pe disc se poate realiza ca un bloc unic care acoper toate intrrile efectiv ocupate. Dac T se declar ca array static, de o mrime maxim, atunci apare implicit situaia cnd se completeaz cel mult un numr de intrri care nu totdeauna umplu pe T. Pentru utilizare ulterioar, este convenabil s se rein n fiierul de index i numrul de intrri efectiv ocupate n T i, n acest sens, se poate rezerva prima intrare si s nscrie acest numr n T[0].B. Astfel, n T, intrrile efective pentru articolele fiierului de baz ncep de la unu. Operaiile de atribuire de tip ir de caractere i comparare caracter trebuie s fie funcii scrise de programator, avnd n vedere c este nevoie de copiere si de comparare de iruri de aceeai lungime care nu au caracterul null terminator. Pentru funcia de copiere se aplic o atribuire la nivel de caracter, iar pentru funcia de comparare, necesar n sortare, trebuie aplicat o relaie lexicografic de forma string1 > string2 i funcia va ntoarce 1 dac relaia este adevrat i 0 altfel. Index regular. Indexarea poate fi aplicat i dup alte cmpuri ale articolululi, dac se dorete s se creeze un instrument pentru acces rapid la articolele care ndeplinesc o anumit condiie n raport cu valorile cmpului de indexare. Practica programrii recomand tipul de index,

Prof.dr.Valer Roca ULBS

65

denumit index regular, care cuprinde cte o intrare separat pentru fiecare articol i admite valori multiple identice pentru cmpul de indexare. Indexul se ine sortat, de regul cresctor, dup valorile cmpului de indexare. Intr-un astfel de index, se pun n eviden secvene de articole care au aceeai valoare pentru cmpul de indexare, dar sunt localizate aleator. Rezult c indexul regular este un instrument care permite o prelucrare sortat a articolelor fiierului de baz, dup valorile cmpului de indexare, fr ca fiierul de baz s fie reorganizat fizic fa de momentul crerii. Adic, indexul regular reprezint o sortare logic a fiierului de baz, de care pot beneficia toate prelucrrile secveniale care presupun sortare dup cmpul utilizat n indexare. Un astfel de exemplu va fi dat n paragraful urmtor. Trebuie remarcat, de asemenea, c un index regular poate asigura rspuns la unele ntrebri cu privire la coninutul fiierului de baz, fr ca acesta s fie prelucrat, fiierul de index fiind singura surs pentru rspuns. De exemplu, dac se presupune un fiier de eviden a salariailor unei companii i indexarea se face dup cmpul Salariu, din articolul acestui fiier, atunci fiierul de index poate rspunde singur la ntrebri cu caracter statistic de genul: numrul de salariai (ponderea) care au un anumit nivel de salaraiu; numrul de salariai pe fiecare nivel de salarizare practicat de companie; numrul salariailor care au un salariu mai mic (mai mare) dect un nivel dat etc. In fine, se constat c, din punct de vedere algoritmic, un index regular se poate crea similar cazului indexului primar.

Inceput_fisier

Prelucrare

Sfarsit_fisier

Deschide fisier baza b

Citeste articol din b

Inchide fisier baza b

m=0 n = -1

n=n+1 NU a.IS=1 DA

T[0].B = m

Sortare(T)

IESIRE m=m+1 Deschide fisier index x

Copiere(T[m].CH, a.CP) Scrie T in x T[m].B = n Inchide fisier index x

IESIRE

IESIRE

Fig.2.9 Algoritmul de indexare dup cheie primar

Prof.dr.Valer Roca ULBS

66

Utilizarea indexului primar. In ipoteza fcut la crearea indexilor, un program de prelucrare pe baz de index, trebuie s ncarce fiierul de index n array-ul T, nainte de oricare operaie de acces la fiierul de baz. Pentru o operaie de acces, cu o cheie de cutare CC, trebuie derulai urmtorii pai: 1. Se caut secvenial n T intrarea n care valoarea cmpului CH al acesteia este identic cu CC. Dac aceast valoare este gsit n intarea k se trece la pasul 2. Dac nu exist valoarea CC n nici o intrare din T se trateaz cazul de excepie, de exemplu, prin mesaj ctre utilizator i operaia de acces este terminat. Aici trebuie remarcat c, datorit indexului sortat, situaia de cutare fr succes poate fi sesizat i nainte de parcurgerea integral a lui T, imediat ce valoarea CC devine mai mare dect valoatrea curent CH. 2. Se preia n n numrul blocului T[k].B i se calculeaz poziia de nceput a acestuia, pe baza relaiei pe = (n -1) * LungimeBloc. Se comand, prin funcia fseek(), poziionarea n fiierul de baz, relativ la nceputul fiierului i cu offsetul pe. 3. Se execut operaia de citire sau scriere necesar. Consideraiile cu privire la indexare reprezint o punere n tem. In practic problematica indexrii este mult mai vast i complex, iar realizarea indexilor cere structuri de date mai eficiente, ca de exemplu structurile de tipul arbore binar echilibrat. O problem deosebit este cea a ntreinerii indexilor, avnd n vedere operaiile de nserare i tergere de articole. O modalitate acceptabil de ntreinere ar putea fi aceea a reindexrii tuturor indexilor dup execuia programelor de inserare/tergere. In condiiile exemplificate mai sus, reindexarea se poate realiza cu acelai program cu care s-a realizat indexarea, reconstruind, de fiecare dat, indexul. Dac fiierul de eviden are articole cu multe cmpuri i, pentru o bun parte din ele, se construiesc indexi, apare o problem suplimentar, de eviden a fiierelor index, deoarece, cu timpul ar putea apare uitarea, din punctul de vedere al desinaiei i/sau actualizrii lor. In practic, problema se rezolv prin construirea unui index complex, denumit index structural, care nglobeaz, prin structur de date adecvat, toi indexii unui fiier. 2.7.5 Prelucrare pe grupe de articole In lucrul cu fiierele binare, sunt cazuri n care prelucrarea trebuie realizat pe grupe omogene de articole. O astfel de prelucrare este eficient, dac fiierului de prelucrat i se asociaz un index de tip regular, construit dup cmpul ale crui valori definesc grupele de articole. De exemplu, dac fiierul de eviden este un fiier de salariai, care aparin unor uniti organizatorice ale companiei (birouri, ateliere etc) i aceast apartenen este memorat n articole prin valorile (codificate) ale unui cmp denumit compartiment, atunci se poate obine, de exemplu, o situaie care arat, pentru fiecare compartiment care este fondul de salarii necesar. Fondul de salarii, pentru un compartiment, se obine sumnd salariile din grupul de articole ale salariailor care aparin de acel compartiment. Cmpul dup care se definesc grupele este denumit caracteristic de grupare CG sau caracteristic de control i prelucrarea pe grupe este cunoscut ca o prelucrare cu grupare sau cu control dup o caracteristic. (Deoare ce, de multe ori la nivelul grupelor se fac sume pe anumite cmpuri, prelucrarea se numete i cu grad de total). In practic, este uzual ca prelucrarea cu grupare s determine la nivelul fiecrei grupe, mrimi cum sunt: numrul de articole, valorile extreme pentru un anumit cmp, suma valorilor pentru un cmp, media valorilor pentru un cmp etc. Acestea pot fi determinate separat sau mai multe din ele n aceeai prelucrare. Indiferent de de mrimile care se calculeaz, aspectul central al prelucrrii se refer la controlul asupra grupelor, avnd n vedere c acestea se constituie ad-hoc i nu au o delimitare marcat fizic. Singurul mod n care grupele pot fi sesizate este mecanismul controlului curent de apartenen, pe baza valorilor caracteristicii de grupare CG i a ipotezei de sortare introdus de indexul regular care se bazeaz pe urmtoarele reguli: primul articol dintr-o grup, prin valoarea caracteristicii de grupare, d valoarea ACG care caracterizeaz grupa;

Prof.dr.Valer Roca ULBS

67

oricare alt articol al gupei are valoarea caracteristicii de grupare CG egal cu ACG.

Citire_baza

START

k=k+1 NU km DA

Inceput_fisier

DA SF=0 n = T[k].B pe = n * LungimeBloc NU

Inceput_grupa

SF = 1

Pozitionare in b la pozitia data de pe SF=0 si ACG= a.CG Citeste articol din b Sfarsit_fisier NU Sfarsit_grupa

DA

Prelucrare

IESIRE

STOP

Procedura ========= Inceput_fisier

Operaii ======== Deschirere fisier baza b Incarcare index in T m =T[0].B ; k = 1; SF = 0 Citire_baza (citire initiala) Operatii specifice la inceput de fisier ACG = CG Operatii specifice la inceput de grupa Operatii de prelucrare a articolului citit Citire_baza (citire curenta) Operatii specifice la sfarsit de grupa Inchidere fisier baza b Operatii specifice la sfarsit de fisier

Inceput_grupa Prelucrare Sfarsit_grupa Sfarsit_fisier

Fig.2.10 Schema general de prelucrare cu grupare a unui fiier

Prof.dr.Valer Roca ULBS

68

In baza acestui mecanism, rezult c se poate aplica o schem de lucru secvenial, care marcheaz nceputul prelucrrii unei grupe prin memorarea n variabila ACG a valorii CG citit din primul articol al acesteia. Urmtorul articol citit, n ordinea dat de sortare, aparine aceleai grupe, dac valoarea citit pentru CG este egal cu ACG. Altminteri, grupa s-a terminat i ncepe o alt grup. O situaie aparte are ultima grup care se termin atunci cnd citirea curent constat c nu mai exist articole. Astfel, se pot formula reguli generale pentru terminarea unei grupe i pentru terminarea prelucrrii nsi, de forma: o grup se termin atunci cnd, n procesul citirii secveniale, se schimb valoarea caracteristicii de control CG fa de variabila ACG sau nu mai exist articole; prelucrarea se ncheie atunci cnd nu mai exist articole de citit. In figura 2.10, este redat o schem general care poate fi aplicat, prin particularizare corespunztoare a procedurilor, pentru oricare problem de prelucrare de acest tip. Prelucrarea unei gupe presupune un numr necunoscut de pai, dar cel puin unu, de aceea structura de control fundamental este o ciclare cu condiie logic. Dac se ine seama de faptul c ultima grup se ncheie la terminarea fiierului, pentru uniformitatea tratrii, prelucrarea ntregului fiier nu se poate face prin ciclare cu numrtor, pe baza valorii m=T[0].B care arat numrul articole active. De aceea s-a introdus o variabil SF, pentru realizarea ciclrii la nivel de fiier care, iniial are valoarea 0, semificnd faptul c fiierul de baz nu a fost n ntregime prelucrat i valoarea 1, atunci cnd nu mai sunt articole de prelucrat. Accesul la articolele fiieruli de baz se face prin procedura Citire_baza care se folosete de indexul asociat. Pentru claritatea algoritmului s-au notat T[k].CG i T[k].B cmpurile din intrarea k a lui T. Procedura de citire citete articolul din fiierul de baz, dac un astfel de articol mai exist sau atribuie valoarea 1 variabilei SF, dac nu mai sunt articole de prelucrat. Controlul terminrii articolelor se face dup dup numrul m=T[0].B, prin variabila de contorizare k. Procedurile de prelucrare cuprind operaii obligatorii i operaii specifice. Prin particularizarea operaiilor specifice se pot obine schemele pentru diferite cazuri de prelucrare. Atunci cnd programatorul trebuie s rezolve o astfel de problem, trebuie s disting aceste operaii i, eventual, s i realizeze algoritmul detaliat, la nivelul acestor proceduri.

2.8 Un exemplu de aplicaie de lucru cu fiier binar


Pentru a clarifica modul n care pot fi utlizate direct fiierele binare, s-a construit o aplicaie condus prin meniu. Aplicaia trebuie s creeze i s actualizeze un fiier de salariai (suboperaia de modificare salariu) care, pentru simplitate, are un articol cu urmtoarea structur: marca: ntreg 100; nume: text de cel mult 30 caractere (numai nume fr prenume !); salariu: ntreg lung. Funcia principal main() a aplicaie este monitor multifuncional care lanseaz operaiile de preformare, inserare, actualizare i afiare a fiierului, pe baza opiunii dat de utilizator, la solicitarea funciei pentru meniu Meniu(). Operaia de afiare a fost introdus pentru a putea vizualiza fiierul, dup creare i dup actualizare, pentru control, avnd n vedere c acesta, ca fiier binar, nu este direct lizibil. Pentru fiecare din cele patru operaii, sa realizat o funcie: FormatFile(), InsertFile(), UpdateFile() i ViewFile(). Pentru a reduce redundana codului, s-a introdus o funcie pentru deschidere de fiier OpenFile() care este apelat corespunztor de funciile pentru operaii. In acest program, redat n listingul 2.1, s-au preferat variabilele globale legate de fiier. In acest sens, s-a definit tipul de date SALARIAT i s-a declarat o variabil fiier fprs, alturi de o variabil articol artS, ca zon tampon, care vor fi utilizate n toate operaiile cu fiierul, din alte puncte ale aplicaiei. De asemenea, s-a presupus c fiierul are un director i un nume predefinit, specificatorul acestuia fiind variabila global pstrFile. De remarcat, de

Prof.dr.Valer Roca ULBS

69

asemenea, modul de control asupra operaiei de deschidere, cu utilizarea variabilei globale a sistemului errno i a funciei strerror(), pentru a obine mesajul de eroare. Listingul 2.1 Fisierul aplicaiei cu fiier binar #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <conio.h> #define nMaxArt 10 /* Declarare tip de data pentru articolul fisierului */ typedef struct {int IS; int marca; char nume[31]; long salariu;} SALARIAT; /* Declarare fisier si a zonei sale articol */ FILE *fprs; SALARIAT artS; char pstrFile[]="C:\\CCurs\\Personal.dat"; /* Prototipuri de functii */ char Meniu(); int OpenFile(char * mode); void FormatFile(); void InsertFile(); void UpdateFile(); void ViewFile(); /* Functia principala */ void main(void) {char op; op = Meniu(); while(op != 'T') {switch(op) {case 'P': FormatFile(); break; case 'I': InsertFile(); break; case 'M': UpdateFile(); break; case 'L': ViewFile(); break; default:puts("Operatie necunoscuta !"); } op = Meniu(); } getch(); } /* Functia de pentru meniu */ char Meniu() { char op; clrscr(); puts("\nMENIU"); puts("P - Preformare fisier"); puts("I Inserare articole in fisier");

Prof.dr.Valer Roca ULBS

70

puts("M - Modificare salariu"); puts("L - Listare fisier"); puts("T - Terminare"); printf("Operatia dorita: "); scanf("%1s", &op); op = toupper(op); return op;

/* Functia pentru deschidere de fisier */ int OpenFile(char * mode) { char* opn; fprs = fopen( pstrFile, mode); if (!fprs) { opn = strerror(errno); printf( "\nNu se poate deschide fisierul %s,\n " "Causa : %s,\n", pstrFile, opn); return 0; } else return 1; } /* Functia pentru preformare FormatFile */ void FormatFile() {int k; artS.IS = 0; artS.salariu = 0; strcpy(artS.nume, " "); if(!OpenFile("wb") exit(1); for(k=0; k<nMaxArt; k++) fwrite(&arts, sizeof(SALARIAT), 1, fprs); } /* Functia pentru populare InsertFile */ void InsertFile() { int m_marca; long m_salariu, offset; char m_nume[31]; /* Deschiderea de fisier pentru sciere. Iesire, la esec. */ if(!OpenFile("r+b")) exit(1); /* Secventa de populare a fisierului */ artS.IS = 1; puts(" \nSe introduc, pe rand, articolele.\n " " Procesul se termina prin marca zero.\n "); printf("\nMarca: "); scanf("%d", &m_marca); while ((m_marca >= 100 ) && (m_marca <= 100 + nMaxArt 1)) { /* Verificarea non prezentei articolului */ offset=(m_marca-100)*sizeof(SALARIAT); fseek(fprs, offset, SEEK_SET); fread(&artS, sizeof(SALARIAT), 1, fprs); if(artS.IS == 0) {/* Pregatire si scriere articol in fisier */ artS.marca = m_marca; printf("Nume: "); scanf("%s",m_nume);

Prof.dr.Valer Roca ULBS

71

} fclose(fprs); }

strcpy(artS.nume, m_nume); printf("Salariu: "); scanf("%ld", &m_salariu); artS.salariu = m_salariu; artS.IS = 1; /* Scrire articol in fisier in acces direct */ fseek(fprs, offset, SEEK_SET); fwrite(&artS, sizeof(SALARIAT), 1, fprs); } else printf("\n EROARE : Articol deja prezent. "); printf("\nMarca: "); scanf("%d", &m_marca);

/* Functia pentru actualizare UpdateFile */ void UpdateFile() { int m_marca, m_proc; long nSal,vSal, offset; /* Deschiderea de fisier pentru citire/sciere. Iesire, la esec. */ if(!OpenFile("r+b")) exit(1); /* Secventa de actualizare a fisierului */ puts("\n Se introduc, pe rand, marcile articolele de modificat.\n " " Procesul se termina prin marca zero.\n "); printf("\nMarca: "); scanf("%d", &m_marca); while ((m_marca >= 100 ) && (m_marca <= 100 + nMaxArt 1)) { /* Verificarea prezentei articolului */ offset=(m_marca-100)*sizeof(SALARIAT); fseek(fprs, offset, SEEK_SET); fread(&artS, sizeof(SALARIAT), 1, fprs); if(artS.IS == 1) {/* Modificarea interna a salariului */ printf("Procentul: "); scanf("%d", &m_proc); vSal=artS.salariu; nSal=(long)(vSal+m_proc*vSal/100); artS.salariu=nSal; /* Rescriere articol in acces direct */ fseek(fprs, offset, SEEK_SET); fwrite(&artS, sizeof(SALARIAT), 1, fprs); } else printf("\n EROARE : Articol inexistent. "); printf("\nMarca: "); scanf("%d", &m_marca); } fclose(fprs); } /* Functia pentru listarea fisierului ViewFile */ void ViewFile() { FILE* fperslst; int nByteR; char txt[50]; /* Deschiderea de fisier pentru citire */

Prof.dr.Valer Roca ULBS

72

if(!OpenFile("rb")) exit(1); /* Secventa de listare a fisierului */ fperslst = fopen("C:\\CCurs\\Personal.lst", "w"); /* Introducere cap tabel */ fprintf(fperslst, "Marca Nume" " Salariu\n"); fprintf(fperslst, "-----------------------" "-----------------------\n"); /* Citire fisier si introducere articole in tabel */ nByteR=fread(&artS, sizeof(SALARIAT), 1, fprs); while (nByteR) { if(artS.IS == 1) {/* Punere in lista */ sprintf(txt, "%5d %-30s %8ld\n", artS.marca, artS.nume, artS.salariu); txt[strlen(txt)] = '\0'; fputs(txt, fperslst); } nByteR=fread(&artS, sizeof(SALARIAT), 1, fprs); } fclose(fperslst); fclose(fprs);

Preformarea fiierului se realizeaz prin funcia FormatFile(). Aceasta pregtete un articol astS gol pe care l scrie secvenial n toate cele nMaxArt poziii ale fiierului. Popularea fiierului se face prin funcia InsertFile() care posed variabile pentru fiecare din cmpurile viitorului articolului. Funcia cere, pe rnd, componentele unui articol, pregtete articolul n artS i l nscriere, n acces direct, n fiier. Poziionarea corect pe nceputul zonei de nscriere a unui articol ine seama c poziia este o funcie de marc i de lungimea articolului, aa cum rezult din instruciunea de calcul pentru variabila offset. Funcia verific dac articolul nu este deja prezent. Procesul de nserare se ncheie atunci cnd, la solicitarea mrcii pentru un nou articol, se d marc 0. In acest fel, fiierul poate fi populat, n sesiuni diferite i extins ulterior, n acelai mod. Actualizarea fiierului este realizat prin intermediul funciei UpdateFile(). Actualizarea se realizeaz la unele articole, date prin marc i const n majorarea salariului, la fiecare din acestea, cu un procent specific. Funcia solicit marca salariatului la care se face mrirea, acceseaz articolul, solicit procentul de majorare, efectueaz modificarea intern i apoi rescrie articolul. Funcia lucreaz corect, avnd n vedere structura articolelor. Procesul de modificare se ncheie, atunci cnd, la solicitarea unei noi mrci , se rspunde cu 0. Afiarea fiierului pentru control se realizeaz prin funcia ViewFile(), sub form de raport, pe un fiier de tip text care poate fi ulterior afiat sau imprimat. Fiierul este localizat la fel i are acelai nume cu fiierul de eviden, dar difer prin extensie (.lst). Fiierul se citete secvenial, la nivel de articol binar i, pentru fiecare articol activ citit se scrie o linie de text n fiierul list. De remarcat modul n care se urmrete aici sfritul de fiier, pe baza numrului de bytes citii, tiind c funcia fread() ntoarce zero, atunci cnd nu mai exist articol de citit.

Prof.dr.Valer Roca ULBS

73

3. Prezentarea grafic a datelor


Prezentarea grafic este o modalitate alternativ de afiare a datelor pe monitor care poate facilita inelegerea i interpretarea acestora.

3.1 Regimul grafic al monitorului


Posibilitatea de a realiza imagini grafice este asigurat de regimul grafic al monitorului. In acest regim, ecranul monitorului este un spaiu matriceal de puncte (pixeli), cu originea n colul din stnga sus al ecranului , cu axa Ox orientat spre dreapta i axa Oy orientat n jos (fig.3.1). Un punct din acest spaiu are coordonatele (x,y) care exprim plasarea sa pe coloana x i linia y. Valorile coordonatelor sun numere pozitive ale cror valori maxime xmax, ymax sunt date de rezoluia plcii grafice.

(0, 0)

(x, y)

(xmax, ymax)

Fig.3.1 Ecranul n modul grafic

A afia date n acest regim nseamn, n ultim instan, a aprinde cu anumite culori mulimi de pexeli (desen prin pixeli) sau a trasa colorat segmente de dreapt (desen prin vectori). Desenul prin pixeli i desenul prin vectori sunt cele dou modaliti de desenare pe care le suport, prin hardware, regimul grafic al monitorului. Dac se are n vedere codificarea binar a culorilor posibile, atunci ori ce imagine se exprim numeric binar, dac fiecrui pixel i se asociaz culoarea cu care acesta este aprins. Aa dar, o imagine video se prezint ca o matrice binar, pe care partea de hardware o utilizeaz n comanda tubului catodic al monitorului pentru a ilumina corespunztor punctele suprafeei fosforescente a acestuia. In realizarea imaginii sunt implicate componente hardware i software. Partea hardware implicat n realizarea imaginilor grafice este placa grafic a monitoruli care cuprinde memoria video i interfaa video. Memoria video, de o anumit mrime, de exemplu 1MB, este o memorie RAM care are rolul de a stoca, sub form binar, imaginea care se afieaz pe ecran. Memoria video este mprit n pagini, fiecare pagin corespunznd unui ecran. Dac memoria are mai mult de o pagin, atunci se pot pregti mai multe ecrane i, la un moment dat, una din acestea este aleas ca pagin video, adic pagina care asigur imaginea pe monitor.

Prof.dr.Valer Roca ULBS

74

Interfaa video este partea de comand (circuitele logice) care asigur preluarea, cu o anumit frecven, a imaginii din memoria video i care realizeaz, pe baza ei, semnalele de comand pentru tubul monitorului de creare i meninere a imaginii pe ecran. Refacerea imaginii este realizat cu o frecven de peste 50Hz, astfel nct ochiul uman percepe imaginea ca fiind continu. Plcile grafice ale monitoarelor sunt realizate dup un anumit standard i pot s lucreze cu mai multe nivele de rezoluie i de culori (moduri grafice). Un mod grafic al unei plci nseamn o anumit rezoluie, un anumit numr de culori i un anumit numr de pagini pentru memoria video. Partea de software legat de realizarea imaginii este driverul de plac. Acesta asigur servicile necesare pentru iniializarea i comanda interfeei video. Fiecare tip de plac are instalat n sistemul de operare un driver propriu, livrat o dat cu placa respectiv.

3.2 Utilizarea modului grafic


In limbajul de programare C, programatorului i se ofer posibilitatea de a utiliza regimul grafic al monitorului, prin intermediul unui ansamblu de componente care comport trei pri (sistem grafic): biblioteca de funcii, n forma obiect, care ofer serviciile necesare construirii imaginilor. Prototipurile funciilor sunt definite n fiierul header graphics.h care conine, de asemenea, constante simbolice i structuri de date necesare n lucrul cu funciile bibliotecii. driverele de plci grafice, sub forma unor unor fiiere executabile cu extensia .bgi. fonturi pentru afiarea textelor, sub forma de fiiere cu extensia .chr. In instalarea implicit a limbajului, componentele pentru modul grafic sunt plasate ntrun director separat, denumit BGI. Pentru a pute fi utilzat, sistemul grafic trebuie s fie instalat n memorie i iniializat, iar dup folosire, trebuie eliberate resursele care i-au fost afectate. De aceea, biblioteca grafic ofer dou funcii complementare initgraph() i closegraph() prin apelul crora se realizeaz cele dou operaii. Funcia de iniializare are forma: void initgraph(int *graphdriver, int *graphmode, char *pathtodriver) Funcia de iniializare aloc spaiu de memorie pentru driverul de plac specificat de parametrul graphdriver, ncarc driverul din directorul dat de parametrul pathtodriver, trece monitorul n regimul grafic i realizeaz iniializrile necesare: stabilete modul grafic pe baza parametruli graphmode, seteaz la valorile prestabilite variabila pentru punctul curent, paleta de culori, vizorul de desen, variabila de eroare etc. Programatorul poate alege driverul i un mod grafic aferent dintr-o mulime prestabilit. Aceste elemete sunt definite n fiierul graphics.h sub forma unor constante simbolice. De exemplu, VGA este constanta pentru a defini driverul de plac grafic VGA, iar constanta VGAHI definete modul cel mai inalt acceptat de aceasta: rezoluie de 640x480, palet de 16 culori i o singur pagin de memorie video. Funcia accept o manier alternativ de stabilire a driverului i a modului, denumit autodetecie. Dac se iniializeaz parametrului graphdriver cu valoarea dat de constanta DETECT, atunci funcia interogheaz sistemul pentru a afla driverul pentru placa cu care este echipat monitorul calculatorului i alege modul cu cea mai mare rezoluie al acesteia. De regul, se prefer autodetecia, dac acest mod de alegere nu influieneaz negativ imaginea. Dac imaginea a fost proiectat pentru o anumit plac i un anumit mod, atunci alegerea trebuie s fie explicit. Aici se are n vedere c plcile actuale asigur emularea unor plci inferioare lor, realizate dup standardul IBM. Aa de exemplu, o plac VGA poate lucra ca o plac EGA. Iniializarea modului grafic poate reui sau poate fi ratat, datorit unor cauze cum ar fi insuficena memoriei pentru ncrcarea driverului, lipsa driverului solicitat, imposibilitatea de a realiza autodetecia etc. De aceea, biblioteca de funcii ofer funcia graphresult() cu care se

Prof.dr.Valer Roca ULBS

75

poate interoga o variabil pentru rezultatul iniializrii, pe care funcia de iniializare o pune pe zero, la reuit i pe o valoare negativ, n caz de eec. Funcia grapherrormsg(), care primete ca argument codul de eroare ntors de graphresult(), ntoarce mesajul de eroare aferent ca un string. Reuita iniializrii poate fi testat comparnd valoarea ntoars de graphresult() cu constanta simbolic grOk. Rezult, din cele de mai sus, c ntr-un program care folosete regimul grafic, codul poate fi structurat aa cum se sugereaz n listingul 3.1, unde s-a fcut ipoteza c desenarea are loc n funcia main(). Listingul 3.1 Structura codului unui program care utilizeaz modulul de grafic #include <graphics.h> .... void main() { int graphdriver, graphmode, err; .... graphdriver = DETECT; initgraph(graphdriver, graphmode, C:\\TC\\BGI); err = graphresult(); if(err != grOk) {printf(* Eroare de initializare: %s, grapherrormsg(err)); exit(1); } /* Desenarea imaginii */ .... closegraph(); }

3.3 Grafic n culori


Modulul grafic n C ofer posibilitatea realizrii de imagini colorate, potrivit cu facilitile pe care le asigur placa grafic. Sistemul implementeaz culorile pe principiul paletei de culori. O palet de culori este un array cu maximum MAXCOLR + 1 componente care se pot ncrca cu codurile unor culori, unde constanta MAXCOLR este definit n graphics.h. Exist o palet implicit (defaultpalette) care este iniializat la momentul ncrcrii sistemului grafic de funcia initgraph(). Programatorul poate crea propria sa palet, ntr-o structur de tipul palettetype, definit n fiierul graphics.h care, n afar de un array, denumit colors, cu componente char, are un cmp size de specificare a numrului de culori pentru driverul i modul curent. Odat stabilit o palet de culori, se presupune c referirea la culorile pe care le conine se face prin poziia lor relativ, conform cu valorile posibile pentru indexul acestuia. Astfel, valoarea 0 se refer la culoarea al crui cod este n componenta de rang 0, valoarea 1 se refer la culoarea al crui cod este n componenta de rang 1 etc. Pe baza paletei, programatorul poate stabili cele dou culori pe care le utilizeaz la un moment dat (culorile curente): culoarea de fond i culoarea de desen. Culoarea de fond (background color) se refer la spaiul pe care se face desenul, toi pixelii din aceast zon, fiind aprini cu aceeai culoare. Culoarea de desen (foreground color) este cea cu care se modific pixelii fondului, pentru a pune n eviden imaginea dorit. Dac programatorul nu stabilete aceste culori, atunci, implicit, este utilizat culoarea din prima intare a paletei, pentru fond i cea din ultima intare a paletei, pentru culoarea de desen. De exemplu, n paleta implicit pentru plac VGA, cu palet de 16 culori, intrarea de rang 0 conine culoarea negru, iar intrarea de rang 15 conine culoarea alb, adic desenul se va face cu alb pe fond negru. Programatorul are la ndemn o serie de funcii pentru a manevra culorile. Determinarea paletei. Deoare ce caracteristicile de culoare depind de placa grafic i modul grafic selectat, se poate obine un program relativ portabil, dac acesta se scrie

Prof.dr.Valer Roca ULBS

76

astfel nct s i determine singur facilitile pe care le poate utiliza. Pentru aceasta se pot utiliza funcii cum sunt: getmaxcolor() funcie, fr parametri, care ntoarce o valoare intreag reprezentnd indexul maxim din palet care poate fi utilizat pentru a referi culoarea de desen; getpalettesize() funcie, fr parametri, care ntoarce o valoare de tip ntreg reprezentnd numrul maxim de intrri care pot fi ncrcate n palet pentru modul grafic curent; getdefaultpalette() funcie, fr parametri, care returneaz un pointer la paleta implicit, iniializat la ncrcarea driverului; getpalette(&paleta) funcie care ncarc n variabila paleta, de tipul palettetype, coninutul structurii care definete paleta curent. Modificarea paletei curente. Programatorul poate modifica culorile ntr-o palet, selectiv sau global, la toate culorile, dar pentru aceasta trebuie s cunoasc codurile de culoare. Codurile culorilor sunt definite prin constante simbolice n fiierul graphics.h pentru tipurile de plci acceptate. De exemplu, pentru placa VGA, la fel ca la placa EGA, sunt definite constante pentru 16 culori, ca de exemplu, EGA_WHITE, EGA_RED, EGA_MAGENTA, EGA_GREEN, EGA_LIGHTRED etc. Se uitlizeaz funciile: setallpalette(paleta) funcie care ncarc ntreaga palet curent cu valorile de culoare pe care le d variabila paleta, de tipul palettetype, ncrcat n prealabil cu codurile de culoare; setpalette(index, codecolor) funcie care permite modificarea intrrii de rang index, n care se nscrie culoarea dat de constanta codecolor. Dac unul sau ambii parametrii sunt eronai, intrarea rmne nemofificat i se semnaleaz o eroare care poate fi aflat prin grapgresult(). In ambele situaii, modificarea paletei curente atrage dup sine modificarea culorilor desenului existent pe ecran, n concordan cu noua configuraie de culori. Acest lucru poate fi utilizat cu scopul de a produce efecte de culoare deosebite sau pentru a releva un desen ascuns, prin modificarea treptat a culorilor. Determinarea i modificarea culorilor de fond i de desen. Funciile din aceast categorie sunt cel mai frecvent utilizate pentru a afla sau defini cele dou culori, avnd n vedere convenia cu privire la indexul acestora: setbkcolor (codecolor) funcia modific poziia de index 0, pentru culoarea de fond, din paleta curent la culoarea dat de parametrul codecolor; getbkcolor() funcie, fr parametri, care returneaz codul culorii de fond, din elementul de index 0, pentru paleta curent; setcolor (codecolor) funcia modific poziia de index maxim, la culoarea dat de parametrul codecolor, pentru culoarea de desen, din paleta curent; getcolor() funcie, fr parametri, care returneaz codul culorii de desen, situat in elementul de index maxim, din paleta curent. Orice modificare a unor culori afecteaz numai desenul care urmeaz a fi realizat i aceste modificri rmn n vigoare pn la o alt setare. In secvena care urmeaz, se salveaz cele dou culori din paleta curent, se modific culoarea de fond la rou (cod 4) i cea de desen la albastru (cod 1), se terge ecranul la culoarea de fond, se deseneaz o bar albastr i apoi se restaureaz culorile iniiale: int ColorF, colorD; ColorF = getbkcolor(); ColorD = getcolor(); setbkcolor(4); setcolor(1); cleardevice(); bar(20, 10, 60, 80); setbkcolor(ColorF); setcolor(ColorD);

Prof.dr.Valer Roca ULBS

77

3.4 Vizor pentru desen


Pentru o mai mare flexibilitate n realizarea imaginilor grafice s-a introdus conceptul de vizor de desen. Un vizor (viewport) este o suprafa rectangular de pixeli, delimitat de un dreptunghi virtual care are laturile paralele cu axele de coordonate, definit n interiorul spaiului grafic al monitorului. Un astfel de dreptunghi se poate defini cu ajutorul funciei setviewport() care cere precizarea coordonatelor punctelor pentru colul stnga-sus i dreapta-jos: void setviewport(int v1, int v3, int v2, int v4, int clip)

Aici (v1, v3) i (v2, v4) sunt coordonatele n spaiul ecran pentru colul stnga-sus i respectiv dreapta-jos ale dreptunghiului, iar parametrul clip se refer la decuparea desenului la limitele vizorului. Dac nu este setat un vizor curent, se presupune vizorul de dimensiune maxim care este ntregul spaiu ecran (vizor implicit). Vizorul implicit are coordonatele colurilor (0, 0) i (getmaxx(), getmaxy()), unde cele dou funcii returneaz coordonatele maxime pentru modul grafic ales. Dac se dau valori incorecte pentru vizor, n raport cu valorile posibile, vizorul nu se seteaz i se semnaleaz eroare. De aceea, pentru a nu ajunge ntr-o astfel de situaie, este recomandabil s se stabileasc dimensiunile vizorului n raport de coordonatele maxime ale spaiului. variabila CP care pstreaz Odat cu stabilirea vizorului, se iniializeaz coordonatele ultimului punct accesat de o funcie de desenare, denumit punct grafic curent (Current Position) . Vizorul respectiv devine spaiul curent n care se face desenul, iar toate funciile care sunt folosite pentru desenarea propriu-zis lucreaz n coordonate relative la originea vizorului curent care este colul stnga-sus (v1, v3). Datorit acestei interpretri, la setarea vizorului, punctul curent CP este pus pe (0, 0), pentru ca originea vizorului s fie punctul iniial. Parametrul clip poate lua valoarea 1, dac nu se permite ca desenul s depeasc graniele vizorului sau valoarea 0, n caz contrar. Atunci cnd desenul realizat printr-o funcie de desenare depete spaiul vizorului, partea excedentar este decupat (clipping) i nu va fi vizibil. Se protejeaz astfel desenul din vizoarele vecine vizorului curent. Un program poate s aib mai multe vizoare, pentru a crea o imagine mai deosebit, pe care le seteaz i reseteaz ntr-o anumit ordine. Dac programul utilizeaz difeite opiuni, de exemplu pentru culori, stil de linie etc, dup ce a stabilit un anumit vizor curent, atunci acestea sunt valabile numai n interiorul acestuia. Astfel, vizoarele se particularizeaz i imaginile n vizor devin mai atractive. De exemplu, culoarea de fond aleas poate fi efectiv utilizat prin tergerea acelui vizor prin apelul funciei clearviewport(). In secven care urmeaz se definesc dou vizoare. In primul se deseneaz un cerc, cu grosime implicit de linie, n al doilea vizor se deseneaz un dreptunghi cu linie groas i apoi se revine la primul vizor pentru a tia cercul cu o linie groas. Vizoarele se definesc cu clipping i utilizeaz culori diferite: rou i alb pentru primul i albatru cu rou, pentru al doilea. #define CLIP_ON 1 /* Desen in primul vizor */ setviewport(10, 20, 200, 150, CLIP_ON); setbkcolor(4); clearvieport(); setcolor(15); circle(85, 45, 50); /* Desen in al doilea vizor */ setviewport(95, 200, getmaxx() 80, getmaxy 40, CLIP_ON);

Prof.dr.Valer Roca ULBS

78

setbkcolor(1); clearvieport(); setcolor(4); setlinestyle(SOLID_LINE,0, THICK_WIDTH); rectangle(5, 10, getmaxx()- 130, getmaxy()- 50); /* Redesenare in primul vizor */ setviewport(10, 20, 200, 150, CLIP_ON); setlinestyle(SOLID_LINE,0, THICK_WIDTH); line(15, 120, 160, 30);

3.5 Desen prin puncte i vectori


Desenul prin puncte este un mod natural de realizare a imaginilor neuniforme care presupun dierite submulimi de pixeli care dau suprafee specific colorate, fr o anumit regularitate. Biblioteca de funcii ofer funciile putpixel() i getpixel(), cu urmtoarele prototipuri: void putpixel (int x, int y, int color); unsigned getpixel(int x, int y); Funcia putpixel() aprinde pixelul de coordonate (x, y), din vizorul curent, cu culoarea de cod color, iar getpixel() ntoarce codul culorii cu care este aprins un pixel cu astfel de coordonate. Biblioteca de funcii implementeaz modul de desen prin vectori bazat pe mulimile regulate de pixeli pe care le presupun segmentele de dreapt trasate ntre dou puncte. Acest mod faciliteaz realizarea imaginilor oferind funcii care pot trasa segmente cu un anumit stil (tip i grosime de linie) i cu anumite culori. Funciile utilizeaz culoarea de desen curent i stilul de linie curent. Linia implicit este continu i are grosimea de un pixel (linie subire). Programatorul poate seta un alt stil de linie care rmne valabil pn la o nou setare, prin intermediul funciei setlinestyle() , cu prototipul: void setlinestyle(int linetype, unsigned pattern, int thickness). Parametrul linetype se poate exprima prin valorile 0 4 sau constantele simbolice (SOLID_LINE, DOTTED_LINE, CENTER_LINE, DASHED_LINE, corespunztoare USERBIT_LINE), iar parametrul thickness definete grosimea liniei, prin valorile 1 i 2 (NORM_WIDTH, THICK_ WIDTH ) ca linie subire de un pixel sau linie groas de trei pixeli. Parametrul pattern d un model de linie, atunci cnd tipul de linie este definit de programator (parametrul linetype = 4). Modelul se definete prin irul de 16 bii ai acestui parametru, pe baza conveniei c un bit 1 nseamn pixel aprins, iar un bit 0 un pixel stins. Prin reluare ciclic, se traseaz cu acest model linii de ori ce lungime. De exemplu, valoarea 0xF053 (n binar irul de bii 1111000001010011) definete o linie de forma . Pentru desenarea de linii exist o serie de funcii care lucreaz n vizorul curent i difer numai prin ipotezele cu privire la punctul de nceput i punctul final al segmentului: void line(int x1, int y1, int x2, int y2); void lineto(int x2, int y2); void linerel(int dx2, dy2). Pentru funcia line(), trebuie date explicit coordonatele ambelor capete, n timp ce pentru celelalte dou, punctul iniial este considerat automat punctul grafic curent CP. Aceste dou funcii sunt comode, atunci cnd se leag ntre ele segmente pentru a defini linii frnte, cnd extremitatea iniial a unui segment coincide cu extremitatea final a segmentului precedent. In funcia linerel(), coordonatele punctului final al segmentului care se traseaz nu se dau explicit, ci se specific deplasrile dx2, dy2 pe care funcia trebuie s le adauge algebric la coordonatele punctuli grafic curent pentru a obine aceste coordonate.

Prof.dr.Valer Roca ULBS

79

Dac se are n vedere c punctul grafic curent se poate obine prin funciile fr parametri getx() i gety(), se pot pune n eviden relaiile de calcul pe care le utilizeaz funciile de trasare pentru a obine coordonatele absolute (xe, ye) n spaiul ecran ale unui punct: xe = v1 + x; ye = v3 + y - pentru specificare explicit ; xe = v1 + getx() + dx; ye = v3 + gety() + dy - pentru specificare relativ. Funciile de desenare aplic automat clipingul la limitele vizorului, dar, pentru a nu se produce deformare a imaginii, punctul grafic curent este totdeauna fixat la valoarea dat de extremitatea final a segmentuli specificat prin parametri. Sistemul grafic i permite programatoruli s mute punctul grafic curent dup dorin, prin apelul funciilor: void moveto(int x2, int y2); void moverel(int dx2, dy2); n care parametrii au semificaia prezentat la funciile de trasare. In exemplul care urmeaz, se consider punctele P1(20, 45), P2(40, 25), P3(65, 70), P4(130, 70), date n coordonate absolute ecran care se deseneaz ntr-un vizor ale crui vrfuri sunt (20, 10), (130, 45). Cele trei moduri de scriere a secvenei de apel pentru desen sunt redate n tabelul 3.1, iar efectul desenrii este dat de fig.3.2. Tabelul 3.1 Secvene de apel pentru desenarea punctelor P1 P4 Cu line() Cu lineto() Cu linerel() moveto( 10, 30) moveto(10, 30) line(10, 30, 30, 10) lineto ( 30, 10) linerel (20, -20) line(30, 10, 55, 55) lineto ( 55, 55) linerel (25, 40) line(55, 55, 70, 25) lineto ( 70, 25) linerel (15, -30)

Prof.dr.Valer Roca ULBS

80

(0, 0)

(10, 15) (30, 10) (95, 20) (70, 25)

(10, 30)

(120, 55)

(65, 70)

Fig.3.2 Reprezentarea liniei frnte a punctelor P1 P4 relativ la vizor In fine, trebuie fcut observaia c desenarea punctelor i a segmentelor supune regulii de combinare a pixelilor acestora cu pixelii fondului pentru a realiza un desen opac sau transparent, aa cum se va arta n paragraful cu privire la elementele de animaie.

3.6 Raportul de aspect


In realizarea desenelor prin segmente apare un fenomen de deformare care poate fi deranjant. Acesta se datoreaz formei pixelilor care, cu excepia modurilor de mare rezoluie, nu sunt de form ptratic, ci dreptunghiular. Aceasta face s se aplice implicit uniti de msur diferite pe cele dou axe, chiar i atunci cnd intenia programatorului este aceea de a utiliza aceeai unitate. In aceste cazuri, deformarea poate fi atenuat dac, n realizarea desenului, se aplic aplic o anumit corecie asupra numrului de pixeli ai segmentelor, corecie dat de raportul dintre cele dou dimensiuni ale pixelului. Dac se noteaz cu Lpx lungimea pe axa Ox i cu Ipy nlimea pe axa Oy a unui pixel, atunci raportul Ra = Ipy / Lpx, denumit raport de aspect, poate fi uitilizat pentru a determina, n pixeli, lungimile segmentelor care se deseneaz. Pentru a obine cele dou valori, se poate utiliza funcia getaspectratio() care are forama; void getaspectratio(int * Lpx, int *Ipy) n care cele dou valori se dau normalizate la 10000, adic * Ipy = 10000 i * Lpx 10000. Dac se noteaz cu n numrul de pixeli ai unui segment AB orizontal i cu m numrul de pixeli ai unui segment vertical CD, atunci petru ca lungimile lor s se gseasc ntr-un raport k, trebuie ca m (n) s se determine n raport de n (m) prin relaiile: m = Ra * n / k; n = m * k / Ra. Pentru segmente oblice care au un anumit raport, lucrurile sunt mai complicate, avnd n vedere c acestea sunt aproximate prin pixeli, dup un algoritm intern plcii grafice, care presupune aprinderea celor mai apropiai pixeli de linia teoretic, n raport cu panta

Prof.dr.Valer Roca ULBS

81

acesteia. Dac se cere desenarea unui segment de o lungime dat n milimetri, atunci o soluie posibil ar fi aceea de a construi un dreptunghi virtual care s aib segmentul respectiv drept diagonal i pe baza lungimii laturilor s se determine punctele de col ale dreptunghiului ntre care se deseneaz segmentul oblic. In exemplul urmtor se deseneaz, pe ntrgul ecran, un ptrat cu latura orizontal de m = 80 de pixeli i vrful stnga-sus n punctul (50, 30). Deoarece k este 1, numrul de pixeli pentru latura vertical va fi n = 80 / Ra, iar secvena de desenare este: int Lpx, Ipy, n; double Ra; getaspectratio(&Lpx, &Ipy); Ra = Ipy/(double)Lpx; n = 80/Ra; moveto( 50, 30); lineto(130, 30); lineto(130, 30+n); lineto( 50, 30+n); lineto( 50, 30); Funciile pentru primitive, cum sunt cele de desenare de cercuri i elipse, utilizeaz raportul de aspect pentru ca desenul s fie real (cercul s fie cerc i nu elips, de exemplu). Dac, n condiiile de utilizare a raportului de aspect, continu s existe probleme de deformare, acestea se datoreaz reglajului de aliniere al monitorului. Pogramatorul poate modifica dinamic valorile pentru Lpx i Ipy, pe baz funciei setaspectratio(), cu aceeai parametrii ca i funcia getaspectratio(), pentru a obine o potrivire mai bun, acest mod de lucru fiind echivalentul software al reglajului hardware de aliniere al monitorului.

3.7 Scrierea textelor n modul grafic


In mod uzual, imaginile grafice conin text explicativ pentru scrierea crora sistemul grafic prevede unele faciliti. In mod implicit, funciile de scriere de text utilizeaz culorile curente de fond i de desen, textele sunt scrise cu un font prestabilit, de o anumit mrime, scrierea este pe direcie orizontal i textul este aliniat la stnga pe un punct, dat de cursorul grafic (punctul grafic curent CP). In mod dinamic, programatorul poate modifica unele atribute ale scrierii prin apelul funciilor de setare, pe care le prezentm, n continuare. Fontul. Programatorul poate alege ntre 5 fonturi predefinite, pe care le refer prin codurile 0 4 sau prin constantele simbolice corespunztoare (DEFAULT_FONT, TRIPLEX_FONT, SMALL_FONT, SANS_SERIF_FONT, GOTHIC_FONT). DEFAULT_FONT este fontul matriceal implicit, de 8x8 pixeli, celelate sunt fonturi care se deseneaz prin segmente (fonturi vectoriale). Programatorul poate instala propriile fonturi vectoriale, astfel nct numrul total de fonturi instalate curent s fie n limita capacitii tabelei interne a sistemului grafic utilizat n acest sens (maxim 20 de fonturi curent instalate). Un font este instalat temporar, adic acesta este disponibil numai pe timpul execuiei programului respectiv de grafic. Pentru instalare, se presupune c fontul este memorat pe disc, ntr-un fiier de extensie .CHR i instalarea nseamn ncracarea fiierului respectiv n memorie i asocierea fontului cu un numr intreg (handle) cu care poate fi apoi referit (vezi funcia settextstyle()). Instalarea se realizeaz prin apelul funciei installuserfont() care are forma: int installuserfont(char *pathtofont); n care parametrul reprezint specificatorul fiierului, iar valoarea ntoars este un handle.

Prof.dr.Valer Roca ULBS

82

Un font instalat poate fi redimensionat, caraterele acestuia putnd fi modificate uniform, prin cretere cu acelai factor pe ambele dimensiuni sau pentru fiecare dimensiune n parte (lime sau nlime). Direcia de scriere. Diecia de scriere poate fi orizontal , desemnat prin valoarea 0 (HORIZ_DIR) sau vertical, desemnat prin valoarea 1 (VERT_DIR). Implicit este direcia orizontal. Caracteristicile de mrime i de direcie de scriere constituie stilul textului i acesta poate fi definit, odat cu algerea fontului, apelnd funcia settextsyle() care are forma: void settextstyle(int font, int direction, int charsize); Fontul dorit se precizeaz prin handle n parametrul font, direcia se d prin parametrul direction, iar factorul de mrire a caracterelor se d prin parametrul charsize. Factorul de mrire poate fi un numr ntre 1 i 10, valoarea 1 nsemnnd mrimea de definiie. De exemplu, un font care are matricea de definiie de caracter de 8x8, la un facor de mrire de 3 va scrie caracterele pe o matrice de 24x24. Dac mrirea este neuniform, atunci parametrul charsize trebuie s fie 0 i factorii de mrire se dau prin apelul funciei setusercharsize() care are prototipul: void setusercharsize(int multx, int divx, int multy, int divy); Primii doi parametrii dau factorul multx / divx, pentru limea de caracter, iar ceilali doi se utilizeaz pentru factorul multy / divy, pentru nlimea caracterelor. De exemplu, dac fontul curent trebuie s scrie caractere de dou ori mai late i de 1.5 ori mai nalte, atunci cei patru parametrii sunt 2, 1, 3, 2. Alinierea. Alinierea se face pe direcia de scriere, n raport cu punctul grafic curent. Pentru scriere orizontal, alinierea poate fi la stnga (valoare 0 LEFT_TEXT), centrat (valoare 1 CENTER_TEXT) i la dreapta (valoare 2 RIGHT_TEXT). Pentru direcia de scriere vertical, alinierea poate fi jos (valoare 0 BOTTOM_TEXT), centrat (valoare 1 CENTER_TEXT) i sus (valoare 2 TOP_TEXT). Pentru a alege modul de aliniere, se apeleaz funcia settextjustify() care are forma: void settextjustify(int horiz, int vert); Pentru scriere efectiv, textul de scris trebuie s se prezinte sub form de string, conform cu regulile de marcare a sfritului prin caracterul null. Se poate utiliza una din funciile: void outtext(char *string); void outtextxy(x, y, char *string); Dac se utilizeaz prima funcie de scriere outtext(), atunci alinierea se face pe punctul grafic curent, iar, dac scrierea este orizontal cu aliniere la stnga, dup scriere, punctul grafic curent este actualizat, innd seama de lungimea textului scris i fontul utilizat. Dac se utilizeaz outtextxy(), atunci punctul de referin pentru alinierea textului se d prin coordonate (x, y) i, dup scriere, punctul grafic curent nu este afectat. Scrierea, cu oricare din cele dou funcii, se face n vizor, adic funciile aplic clippingul asupra textului care depete limita vizorului pe direcia de scriere. De asemenea, funciile de scriere in cont de setarea modului de scriere, la fel ca i alte primitive de desenare, cum s-a artat i pentru cazul desenrii prin vectori. In exemplul care urmeaz, se scrie de 4 ori textul Limbajul C/C++ cu fonturile predefinite. Textul se scrie centrat orizontal pe linia median vertical a ecranului, ncepnd cu linia y = 50, fiecare linie fiind scris cu o alt culoare i cu o alt mrime: int x, y, k;

Prof.dr.Valer Roca ULBS

83

char textstr[]=Limbajul C/C++; cleardevice(); settextjustify(CETNTER_TEXT, CENTER_TEXT); x=getmaxx()/2; y=50; for (k=1; k<=4; k++) { setcolor(k); settextstyle(k, HORIZ_DIR, k); outtextxy(x,y, textstr); y=y+textheight(textstr)+8; } Uneori, exist necesitatea de a cunoate limea i/sau nlimea textului care se scrie. Se pot utiliza funciile textwidth() i textheight(), aa cum rezult i din exemplul de mai nainte.

3.8 Primitive pentru figuri


Pentru a facilita realizarea imaginilor, sistemul grafic posed funcii, numite primitive grafice, pentru cteva figuri plane reprezentative: dreptunghiuri, cercuri, elipse, poligoane etc. Unele din aceste primitive umplu figura desenat cu o culoare compact sau cu un anumit model de haur. Toate funciile au fost concepute s deseneze n vizorul curent, adic n coordonate relativ la originea (v1, v3) a acestuia i s aplice clippingul. Ca i funciile pentru desenarea de vectori, primitivele utilizeaz, pentru contur, tipul de linie i culoarea curent selectate. Dac o primitiv umple interiorul figurii desenate cu o culoare compact, atunci aceasta este, implicit, culoarea de desen curent. Dac programatorul dorete haurarea interiorului figurii, atunci poate alege un anumit model de umplere cu culoare compact sau haurare dintr-o mulime de stiluri prestabilite (2 modele de umplere i 9 stiluri de hauri) sau poate s i defineasc propriul model de haurare. Pentru a alege un model predefinit se utilizeaz funcia setfillstyle() care are prototipul: void setfillstyle(int pattern, int color). Parametrul pattern d, sub forma uni numr ntreg sau constant simbolic, tipul de haur, iar parametrul color indic culoarea cu care se face haurarea. In listingul 3.2, este construit un program care utilizeaz toate modelele predefinite. Listingul 3.2 Modele de umplere i haurare implicite #include <graphics.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <conio.h> /* constante pentru numele stilurilo acceptate */ char *fname[] = { "EMPTY_FILL", "SOLID_FILL", "LINE_FILL", "LTSLASH_FILL", "SLASH_FILL", "BKSLASH_FILL", "LTBKSLASH_FILL", "HATCH_FILL", "XHATCH_FILL",

Prof.dr.Valer Roca ULBS

84

"INTERLEAVE_FILL", "WIDE_DOT_FILL", "CLOSE_DOT_FILL", "USER_FILL" }; int main(void) { /* Auto detectarea modului grafic */ int gdriver = DETECT, gmode, errcode; int style, mdx, mdy; char stylestr[40]; /* Initializarea regimului grafic */ initgraph(&gdriver, &gmode, ""); errcode = graphresult(); if (errorcode != grOk) { printf("Eroare sistem grafic: %s\n", grapherrormsg(errcode)); exit(1); } mdx = getmaxx() / 2; mdy = getmaxy() / 2; for (style = EMPTY_FILL; style < USER_FILL; style++) { /* Slecteaza stilul de umplere */ setfillstyle(style, getmaxcolor()); /* Preia denumirea stilului din array */ strcpy(stylestr, fname[style]); /* Deseneaza o bara umpluta */ bar3d(0, 0, mdx-10, mdy, 0, 0); /* Scrie sub bara textul de definire al stilului */ outtextxy(mdx, mdy, stylestr); /* Asteapta apasarea unei taste pentru a continua */ getch(); cleardevice();

} /* Inchide regimul grafic */ getch(); closegraph(); return 0;

Dac programatorul dorete un model propriu de umplere prin haurare, atunci trebuie s defineasc modelul acestuia prin apelul funciei setuserpattern() care are forma: void setuserpattern(char *upattern, int color). Parametrul upattern este un array de 8 bytes care este interpretat ca ir de 64 de bii. Un bit de valoare 1 indic aprinderea pixelului, la culoarea dat, prin cod, de parametrul color .

Prof.dr.Valer Roca ULBS

85

Modelul este reutilizat ciclic, pn cnd ntreaga suprafa interioar a figurii este umplut. De exemplu, apelul funciei sub forma: char upattern[] = {0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00}; setuserpattern(upattern, 4); produce o haur orizontal, de culoare roie, de forma .

Principalele primitive pentru figuri sunt grupate pe tipuri dup cum rezult din cele ce urmeaz. Primitive pentru dreptunghiuri i bare. Primitivele pentru aceste tipuri de figuri sunt: void rectangle(int x1, int y1, int x2, int y2); void bar(int x1, int y1, int x2, int y2); void bar3d(int x1, int y1, int x2, int y2, int depth, int topflag); unde (x1, y1) reprezint colul stnga-sus, iar (x2, y2) desemneaz colul dreapta-jos al dreptunghiului. Funcia rectangle() deseneaz conturul unui dreptunghi, cu linia de stil i culoare curente, iar funcia bar() deseneaz un dreptunghi umplut, cu stilul de umplere curent, dar fr contrur. Funcia bar3d() consider o bar (dreptunghi n spaiu) a crui fa este dreptunghiul definit de coordonatele date ca parametru, umplut cu stilul curent, bara are o grosime dat de parametrul depth, iar capacul superior este vizibil sau nu, dup cum parametrul topflag este 1 sau 0. Primitive pentru linii poligonale i poligoane. Sistemul ofer dou proceduri cu care se pot desena linii poligonale nchise, respectiv poligoane umplute: void drawpoly(int numpoints, int *polypoints); void fillpoly(int numpoints, int *polypoints). In ambele cazuri, primul parametru precizeaz numrul de vrfuri, iar al doilea este un array de tipul pointtype, definit n graphics.h, adic un array care are drept componente articole care definesc coordonatele x i y ale unui punct. Pentru ca linia poligonal s se nchid, trebuie ca numrul de componente ale acestuia s fie numpoints+1, ultima component fiind egal cu prima. Funcia drawpoly() traseaz conturul poligonului cu atributele de culoare i stil de linie curente. Funcia fillpoly() deseneaz un poligon umplut cu stilul de culoare i haur curent. De exemplu, secvena care urmeaz, deseneaz un pentagon haurat cu linii roii nclinate spre dreapta: pointtype pentagon[6]; pentagon[0].x=50; pentagon[0].y=80; pentagon[1].x=100; pentagon[1].y=60; pentagon[2].x=150; pentagon[2].y=90; pentagon[3].x=120; pentagon[3].y=130; pentagon[4].x=60; pentagon[4].y=110; pentagon[5].x=50; pentagon[5].y=80; setfillstyle(SLASH_FILL, 4); fillpoly(5, pentagon); Primitive pentru arce, cercuri i elipse. Funciile din aceast categorie utilizeaz atributele de culoare i umplere curente, dar stilul de linie este totdeauna linie continu. Unghiurile care intervin ca parametri sunt exprimate n grade, msurate n sens invers acelor de ceasornic

Prof.dr.Valer Roca ULBS

86

(sens trigonometric), adic cu 00 la ora 3 i 900 la ora 12. Funciile utilizeaz implicit raportul de aspect pentru a elimina deformarea. Funciile de baz sunt: void arc(int x, int y, int stung, int finung, int raza); void circle(int x, int y, int raza); void ellipse(int x,int y,int stung,int finung,int xraza,int yraza); void fillellipse(int x, int y, int xraza, int yraza); pieslice(int x, int y, int stung, int finug, int raza); sector(int x, int y, int stung, int finung, int xraza,int yraza); Parametrii stung i finung se refer la unghiul de start i unghiul final, iar raza, xraza i yraza desemneaz razele figurii. Figura se deseneaz cu centrul n punctul (x, y). Funcia circle() deseneaz conturul unui cerc, iar funcia arc() deseneaz un arc de cerc care, la limit, poate fi un cerc, depinznd de alegerea unghiurilor. Funcia pieslice() deseneaz un sector de cerc umplut care poate fi, la limit, un cerc umplut. Aproape similar este i cazul elipselor: funcia ellipse() deseneaz un arc sau un contur de elips, funcia sector() deseneaz un sector de elips sau o elips umplut, iar funcia fillellipse() deseneaz o elips umplut. De exemplu, secvena care urmeaz deseneaz un arc, un sector de cerc umplut i o elips umplut: arc(150, 100, 0, 270, 50); pieslice(getmaxx/2, getmaxy/2, 0, 360, 100); fillellipse(150, 150, 300, 75); Trebuie remarcat i faptul c sistemul grafic posed i alte funcii care permit realizarea de desenete complexe, cum de exemplu este funcia getarccoords() care furnizeaz informaii necesare racordrii arcelor cu segmente sau funcia floodfill() care permite umplerea unei suprafee cu o culoare etc. In listingul 3.3, este redat un program care deseneaz, prin bare n spaiu, de diferite culori, o histogram pentru producia de carne (mii tone) ntr-o serie de n ani, cu n5. Listingul 3.3 Program pentru desenarea unei histograme prin bare n spaiu #include <stdio.h> #include <graphics.h> #define h 100 #define baza 250 #define lat 30 #define dist 30 #define gros 10 #define peste 40 #define sub 5 #define pstart -5 #define font 3 #define titlx 10 #define titly 10 typedef unsigned int vector[10]; unsigned int maxp( vector prod, int n); void main(void) {int an, driver, mode, err, x1,y1,x2,y2, n, i; int charsize, pattern, color; unsigned int pmax; vector p; double k;

Prof.dr.Valer Roca ULBS

87

char pstr[8]; char anstr[4]; /* Intrarea in regimul grafic */ driver=DETECT; initgraph(driver, mode, C:\TC\BGI); err=graphresult(); if(err != grOk) {printf( Eroare regim grafic: %s\n, grapherrormsg(err)); exit(1); } /* Introducerea datelor */ printf(\n Numar de ani (<= 10): ); scanf( %d , &n); pintf(\n Primul an al seriei: ); scanf(%d, &an); printf(\n Productia pe ani: ); for(i=0; i<n; i++) scanf(%ud, &p[i]); /* Determinarea factoruli de scala */ pmax= maxp(p, n); k=h/pmax; /* Initializari generale */ y2=baza; x2=pstart; cleardevice(); /* Scrierea titlului histogramei */ charsize=3; settextstyle(font, HORIZ_DIR, charsize); settextjustify(LEFT_TEXT, TOP_TEXT); outtextxy(titlx,titly,Histograma productiei de carne(mii tone) ); /* Initializari pentru desenarea barelor */ charsize=1; settextstyle(font, HORIZ_DIR, charsize); settextjustify(CENTER_TEXT, TOP_TEXT); /* Desenarea barelor */ for(i=0; i<n; i++) {x1=x1+dist; y1=baza (int)p[i]*k; x2=x1+lat; pattern++; setfillstyle(pattern, color); color++; bar3d(x1, y1, x2, y2, gros, 1); sprintf(pstr,%8d, p[i]); outtextxy(x1+lat/2,y1-peste+1, pstr); sprintf(anstr,%4d, an); outtextxy(x1+lat/2,y2+sub+1, anstr); an++; }

Prof.dr.Valer Roca ULBS

88

/* Desenarea liniei baza */ setlinestyle(0, 0, 3); line(pstart, y2, (n+1)*(lat+dist), y2); getch(); } /* Functia de maxim */ unsigned int maxp( vector prod, int n) {unsigned int i, m; m=prod[0]; for(i=1; i<n; i++) if(m<prod[i]) m=prod[i]; return m; } In acest program, s-a presupus desenarea pe ntregul ecran. Dac h este nlimea, n pixeli, pentru producia maxim pmax, atunci coeficientul de scal k=h/pmax permite determinarea inlimii barei pentru o producie oarecare p[i] ca fiind p[i]*k. Dac barele se deseneaz pe pe linia orizontal baza, cu lime lat, grosimea gros i la distana dist unele de altele, atunci coordonatele feei exterioare a unei bare se pot determina astfel: x1=x2+dist; y1=baza- (int)(p[i]*k); x2=x1+lat; y2=baza; unde y2=pstart , pentru nceput. Barele se umplu diferit, deasupra fiecreia din ele, la distana peste, se scrie centrat mrimea produciei pe care o reprezint. Punctul n care se face centrarea este (x1+lat/2, y1peste+1). Sub fiecare bar, la distana sub de linia bazelor, se scrie centrat, n punctul (x1+lat/2, y1-peste+1), anul corespunztor. Linia bazelor se traseaz ca linie groas, ntre punctele (pstart, y2) i ((n+1)*(lat+dist), y2).

3.9 Transformarea coordonatelor


In realizarea graficii computerizate adesea desenul dintr-un vizor este o reproducere, prin micoare sau mrire a unui desen dintr-un dreptunghi al unui spaiu cartezian. Dac se consider c dreptunghiul din spaiul real are laturile paralele cu axele, atunci problema care trebuie rezolvat este aceea a determinrii coordonatelor punctelor din vizor care corespund punctelor cunoscute din dreptughiul spaiului real. Figura 3.3 sugereaz relaia dintre cele dou dreptunghiuri i introduce notaiile care vor fi utilizate n continuare.

Prof.dr.Valer Roca ULBS

89

x (v1, v3)

y Y (w1, w3)

(xe0, ye0)

(xe, ye)

(v2, v4)

(xr0, yr0) X (xr, yr) (w2, w4)

Fig.3.3 Transformarea coordonatelor

Dac imaginea din spaiul real este considerat ncadrat de dreptunghiul (w1, w3), (w2, w4), iar vizorul este definit de colurile (v1, v3), (v2, v4), atunci, din asemnarea imaginilor din cele dou spaii i innd seama de orientarea axelor, se pot scrie urmtoarele egaliti de rapoarte:

xe xe 0 v2 v1 = xr xr0 w2 w1 ye ye 0 v4 v3 = yr0 yr w3 w4

(1)

Dac se expliciteaz xe, ye, innd seama de faptul c xr0, yr0 i xe0, ye0 sunt centrele dreptunghiurilor corespunztoare, se obine un sistem de relaii care dau coordonatele absolute ale punctului (xe, ye), n spaiul ecran:

xe =

v2 - v1 v1.w2 v2.w1 xr + w2 - w1 w2 w1 (2) v4 v3 v4.w3 v3.w4 ye = yr + w3 w4 w3 w4


90

Prof.dr.Valer Roca ULBS

Dac se ine seama de originea vizorului, atunci n coordonate relative dxe, dye se pot scrie relaiile:

xe = v1 + dxe ye = v3 + dye

(3)

Tind seama de relaiile (2) i (3) se obin coordonatele relative la vizor pentru punctul (xe, ye), de forma:

v2 v1 (xr w1) w2 w1 (4) v4 v3 dye = (yr + w3) w3 w4 dxe =


In relaiile de mai sus, n final se consider valorlie ntregi corespunztoare, avnd n vedere adresabilitatea n spaiul ecran. Transformrile de coordonate ofer programatoruli posibilitatea s exprime desenul n spaiul logic i primitivele de desenare s primeasc coordonate calculate din acestea. Calculul devine facil, dac punctele din spaiul logic se pot exprima, ele nsele, prin expresii analitice, aa cum, de exemplu este cazul desenrii unei poriuni din graficul unei funcii. In exemplul care urmeaz se reproduce graficul unei funcii reale f(x) pe un interval [a, b] pe care ea este continu. Funcia se poate particulariza, de la execuie la execuie, schimbnd numai codul lui f(x), n exemplu, s-a considerat sin(x). Dreptunghiul de reprezentat este dat de intervaul [a, b] i de valorile maxim i minim pe acest interval. Vizorul de desen se stabilete prin conversaie cu utilizatorul i se corecteaz astfel nct s se poat reprezenta toate punctele calculate ale graficului. Punctele graficului se calculeaz pornind de la numrul de puncte dorit de utlizator pentru diviziunea intervalului. Pentru rapiditatea i simplitatea desenrii, punctele graficului sunt precalculate i convertite n coordonate relative la vizor. In final, se traseaz axele, dac acestea aparin dreptunghiului graficului.

#include <stdio.h> #include <math.h> #include <graphics.h> #include <conio.h> #define MaximX 400 #define CLIPON 1 /* Functia generala de reprezentat */ float f(float x); /* Functia main() */ void main(void) { int graphdriver,graphmode; float w1,w2,w3,w4; int v1,v2,v3,v4; int a,b,k1,k2; int k,n,grerr,xc,yc; float xr,ymax,ymin,h,r; float yr[MaximX];

Prof.dr.Valer Roca ULBS

91

int dxe[MaximX]; int dye[MaximX]; /* Intare in sistemul grafic */ graphdriver=DETECT; initgraph(&graphdriver,&graphmode,c:\\tc\\bgi); grerr=graphresult(); if(grerr!=grOk) {printf(\nEroare: %s\n,grapherrormsg(grerr)); getch(); exit(1); } /* Stabilirea intervalului si a diviziunii acestuia */ printf(\nIntervalul [a,b] de reprezentare: ); scanf(%d %d,&a,&b); printf(\nNumarul de puncte diviziunea intervalului [a,b]; ); scanf(%d,&n); if(n>MaximX) n=MaximX; /* Calculul valorilor functiei si determinarea valorii maxime si minime */ h=(float)(b-a)/(float)n; xr=a; yr[0]=f(xr); ymax=ymin=yr[0]; for (k=1;k<n;k++) {yr[k]=f(xr); if(ymax<yr[k]) ymax=yr[k]; else if (ymin>yr[k]) ymin=yr[k]; xr=xr+h; } /* Definire dreptunghiului graficului care va fi desenat */ w1=a; w3=ymax; w2=b; w4=ymin; /* Stabilirea vizorului prin conversatie si corectarea acestuia, daca este cazul */ printf(\nColtul stinga-sus al vizorului: ); scanf(%d %d,&v1,&v3); printf(\nColtul dreapta-jos al vizorului: ); scanf(%d %d,&v2,&v4); if((v2-v1)<n) v2=v1+n; r=(w3-w4)/(w2-w1); if(v4<(v3+n*r))v4=v3+n*r; /* Calculul punctelor graficului in vizor */ k1=(v2-v1)/(w2-w1); k2=-(v4-v3)/(w3-w4); xr=a;

Prof.dr.Valer Roca ULBS

92

for (k=0;k<n;k++) {dxe[k]=k1*(xr-w1); dye[k]=k2*(yr[k]-w3); xr=xr+h; } /* Trasarea graficului */ setviewport(v1,v3,v2,v4,CLIPON); moveto(dxe[0],dye[0]); for (k=1;k<n;k++) lineto(dxe[k],dye[k]); /* Desenarea axelor de coordonate */ moveto(0,-k2*w3); lineto(dxe[n-1],-k2*w3); moveto(-k1*w1,v3); lineto(-k1*w1,v4); getch(); closegraph(); } /* Functia de reprezentat float f(float x) { return sin(x); } */

3.10 Elemente de animaie


Animaia poate fi considerat o tehnic de realizare a micrii obiectelor grafice pe ecran. Ea presupune refacerea imaginii video, de mai mult de 30 de ori pe secund, cu obiectele n diferite poziii, astfel nct s se asigure ideea de micare. Reuita n realizarea animaiei este condiionat de resursele calculatorului, cum sunt: viteza microprocesorului, numrul de pagini grafice ale memoriei video etc. 3.5.1 Aspecte generale Animaia pune programatorului o multitudine de probleme, multe din ele dificil de realizat fr suport n biblioteca grafic. Comentm cteva din acestea, pentru a contura mai bine complexitatea problematicii animaiei cu calculatorul. In primul rnd, programul trebuie s ia n considerare comportarea obiectului de animat n mediu. In cazurile simple, imaginea pe ecran se reduce la obiectul n micare, adic nu exist mediu ambient. Uzual, ns, pe ecran exist o imagine peste care trebuie s se mite obiectul. Aceast imagine este, pentru obiectul animat, un mediu ambient pe care trebuie s l conserve. Rezult de aici, c imaginea obiectului, n diferitele sale poziii de micare, este scris peste imaginea mediului i distruge imaginea de sub cea a obiectului, iar atunci cnd acesta se deplaseaz n alt poziie, imaginea veche trebuie s fie refcut, indiferent care a fost aceasta. In al doilea rnd, programul crete n complexitate dac obiectul animat nu rmne identic cu el nsui n toate poziiile sale de micare, aa cum este, n general, cazul. Este evident mai simplu de animat un obiect pentru care imaginea formei sale se conserv, deoarece aceast imagine poate fi realizat o singur dat, poate fi memorat i apoi afiat n diferite poziii pe ecran. Dac obiectul i schimb forma de la o poziie la alta, atunci imaginea sa trebuie redesenat n continuu. In acest caz, consumul de timp poate fi important i pentru reuita animaiei sunt necesare resurse suplimentare, ca, de exemplu, mai multe pagini video.

Prof.dr.Valer Roca ULBS

93

In al treilea rnd, obiectul n micare poate s aib un anumit comportament la atingerea limitelor vizorului de afiare. Problema este simpl atunci cnd obiectul poate iei i intra n vizor, deoarece aceste aspecte sunt rezolvate prin tehnica clippingului. Dac obiectul nu poate prsi vizorul sau atingerea limitelor acestuia i imprim o anumit traiectorie viitoare, atunci programul trebuie s prevad teste de situaie i metode de determinare a traiectoriei derivate. Aa, de exemplu, se ntmpl cu o bil n micare, ca urmare a lovitii cu un tac, atunci cnd se izbete de marginile mesei de biliard. In sfrit, probleme complexe ridic situaiile n care mediul nsui poate modifica forma obiectului sau atunci cnd trebuie animate concomitent mai multe obiecte care se pot eventual s i condiioneze reciproc micarea. Dac se are n vedere aspectul de micare propriu-zis al unui obiect, atunci din punct de vedere algoritmic, se pot pune n eviden urmtorii pai: a) Se afieaz pe ecran imaginea cu obiectul n poziia a. b) Se construiete, dac este cazul, imaginea obiectului pentru o nou poziie b. c) Se terge obiectul din poziia a i se reface imaginea de sub acesta. d) Se atribuie lui a valoarea b i se reia, dac mai este necesar, de la pasul a). 3.5.2 Facilitii pentru animaie date de sistemul grafic Biblioteca grafic conine cteva funcii care pot facilita realizarea animaiei, dar nu numai pentru aceasta, ci i pentru a desena i redesena imagini statice. Moduri de scriere. Sistemul grafic a fost conceput astfel nct programatorul s poat definii modul n care imaginea pe care o deseneaz (imagine curent) va interaciona cu imaginea existent n acea zon (imagine anterioar). Maniera n care se definesc culorile pixelilor din imaginea anterioar i cea curent pentru a da imaginea nou dintr-o anumit zon se numete mod de scriere (write mode). Modul curent necesar se declar prin apelul funciei setwritemode(), n forma: void setwritemode(int mode); Din pcate, modul de scriere afecteaz numai cteva primitive i anume cele de trasare de linii, funcia pentru contur de dreptunghiuri, funcia pentru contur de poligoane i funciile de scriere de text, iar parametrul mode putnd avea numai dou valori 0 (COPYPUT) i 1 (XORPUT). In modul COPYPUT, biii de imagine anterioar sunt nlocuii cu biii de imagine curent i deci imaginea nou devine identic cu cea curent. Deoarece nlocuirea se realizeaz n memoria video, ca operaie logic pe bit, se poate spune c acest mod realizeaz o operaie AND- operatorul i - ntre biii celor doi operanzi. In modul XORPUT, cele dou imagini se combin dup un model XOR pe bit - operatorul sau-exclusiv (suma modulo-2) - care, spre deosebire de OR operatorul sau - la coincidena biilor produce un bit 0. Modul XORPUT are proprietatea de a reface imaginea anterioar, dac se aplic de dou ori, pentru a realiza formula (A XOR B) XOR B, unde prin A s-a notat imaginea anterioar, iar prin B cea curent. Pentru a vedea efectul de refacere, se poate urmri tabelul 3.2. Facilitatea aceasta poate fi utilizat pentru a terge obiectul din poziia curent, cu restabilirea vechii imagini, dac se face o redesenare pe acelai loc. Tabelul 3.2 Refacerea imaginii prin modul de scriere XOR Imaginea anterioar (A) 0011 1011 1111 0000 Imaginea curent (B) 0101 0010 1101 1111 (A XOR B) 0110 1001 0010 1111 (A XOR B) XOR B 0011 1011 1111 0000

Prof.dr.Valer Roca ULBS

94

In exemplul care urmeaz, se deseneaz, n modul normal de scriere, un dreptunghi de fundal, definit prin punctele (5,5), (50, 70). Apoi, n modul XOR_PUT, se deseneaz peste fundal un alt dreptunghi, definit prin colurile (30,30), (100,160). Se reface dreptunghiul de fundal , prin redesenare XOR_PUT, a dreptunghiului de acoperire. In final, se deseneaz normal, n alt loc, dreptunghiul de acoperire. #include <graphics.h> #include <conio.h> void main() {int gd,gm,xmax,ymax; int r,err; gd=DETECT; initgraph(&gd,&gm,"C:\\TC\\BGI"); err=graphresult(); if(err!=grOk) { printf(" Eroare : %s",grapherrormsg(err)); exit(1);} /* Desenare normala a unui dreptunghi de fundal */ rectangle(5, 5, 50, 70); getch(); /* Desenare in modul XOR_PUT a unui dreptunghi de acoperire */ setwritemode(XOR_PUT); rectangle(30, 30,100, 160); getch(); /* Restaurarea dreptunghiului de fundal, prin redesenarea XOR_PUT a dreptunghiului de acoperire */ rectangle(30, 30, 100, 160); getch(); /* Desenare normala, in alt loc, a dreptunghiului de acoperire */ setwritemode(COPY_PUT); rectangle(120, 150, 170, 260); getch(); } Salvarea i restaurarea unei imagini. O imagine binar, de form dreptunghiular, din memoria video poate fi copiat global ntr-un array i apoi poate fi mutat ntr-o alt parte. Se utilizeaz funciile pereche getimagesize() i putimage() care au prototipurile: void getimage(int x1, int y1, int x2, int y2, void *buffimg); void putimage(int x1, int y1, void *buffimg, int writemode); Punctele (x1, y1) i (x2, y2) reprezint coordonatele colurilor dreptunghiului stngasus, dreapta-jos, iar parametrul buffimg este un array care trebuie s aib cu 4 bytes mai mult dect numrul de bytes necesari pentru imaginea propriu-zis. In cei 4 bytes suplimentari, sistemul grafic nscrie, la nceputul acestui buffer, lime i nlimea imaginii. Pentru a facilita rezervarea corect a spaiului, se poate utiliza funcia imagesize() care are forma: unsigned int imagesize(int x1, int y1, int x2, int y2) care returneaz numrul de bytes necesari (nu mai mult de 64KB) sau un cod de eroare, pentru depire de dimensiune.

Prof.dr.Valer Roca ULBS

95

La restaurare, funcia putimage() are nevoie numai de colul stnga-sus a noii poziii i de modul n care imaginea aceasta va interaciona cu cea existent n zon. Parametrul writemode poate lua valorile 0 i 1, definind modurile de interaciune cu imaginea din ambient desemnate prin constantele simbolice: COPYPUT, XORPUT. Utiliznd o dubl punere a imaginii, conform formulei (A XOR B) XOR B, descris mai sus, se terge imaginea scris n dreptunghiul respectiv i se reface imaginea veche. In exemplul care urmeaz, se realizeaz animarea unui cerc, cu raz de 50 de pixeli, pe tot ecranul, cu alunecare pe direcia orizontal i/sau vertical, prin apsarea tastelor sgei. Iniial, cercul se gsete n centrul ecranului i poate fi deplasat n oricare din cele 4 direcii, eventual, cu ieire i revenire n ecran. La fiecare micare, deplasarea pe direcia respectiv este de 8 pixeli. Tehnica de animare se bazeaz pe funcia putimage(). #include <graphics.h> #include <conio.h> #include <alloc.h> void main() {int gd,gm,xmax,ymax, oprire; int r,err; char ch1, ch2; int xp, yp, xc, yc; void *p; /* Intrare in sistemul grafic */ gd=DETECT; initgraph(&gd,&gm,"C:\\TC\\BGI"); err=graphresult(); if(err!=grOk) { printf(" Eroare : %s",grapherrormsg(err)); exit(1);} /* Desenare cerc initial */ cleardevice(); xp = getmaxx()/2; yp = getmaxy()/2; circle(xp, yp, 50); /* Salvare bloc de imagine cu cercul desenat */ xp = xp 50; yp = yp 50; sz = imagesize(xp, yp,xp + 100, yp + 100); p = (void*)malloc(sz + 4); getimage(xp, yp, xp + 100, yp + 100, p); /* Animare cerc */ ch1 = getch(); oprire = 0; while (!oprire) {if(ch1 == 13) oprire = 1; else {if(ch1== 0) {ch2 = getch(); switch (ch2) {case 72: yc = yp 8; break; /* sageata case 75: xc = xp 8; break; /* sageata case 77: xc = xp + 8; break; /* sageata case 80: yc = yp + 8; /* sageata } putimage(xp, yp, p, XOR_PUT); putimage(xc, yc, p, COPY_PUT);

sus */ stanga */ dreapta */ jos */

Prof.dr.Valer Roca ULBS

96

} free(p); closegraph();

xp = xc; yp = yc; } ch1 = getch(); }

In exemplu, imaginea anterioar a cercului se terge repetat, datorit ciclrii, prin rescrierea blocului cu XOR_PUT pe acelai loc, dup care aceasta este desenat normal n alt punct. Sistemul de coordonate xp, yp (poziia anterioar) i xc, yc (poziia curent) faciliteaz aceast deplasare. De remarcat, de asemenea, modul n care se capteaz tastele sgei, pentru care tastatura emite dou caractere: caracterul ch1, de cod 0 i caracterul ch2, de tast propriu-zis. Pentru tastele normale, cum este tasta ENTER, se emite numai un cod ch1. Pagin activ i pagin video. Pentru modurile grafice care suport mai multe pagini, cum este cazul VGA, imaginea se poate constitui n pagina activ, n timp ce pe ecran este afiat pagina video. Pentru selecia paginilor se utilizeaz funciile: void setactivepage(int npage); void setactivepage(int npage); unde parametrul poate lua valorile 0, 1, etc. In exemplul care urmeaz se considera doua pagini. Pagina 1 are drept semn o bar la partea de sus, iar pagina 0 se distinge printr-un cerc plin la partea de jos. In centrul paginilor se scrie un text explicativ, iar comutarea are loc la apsarea unei taste. #include #include #include #include <graphics.h> <stdlib.h> <stdio.h> <conio.h>

int main(void) { /* Intrare in sistemul grafic */ int gd = EGA, gm = EGAHI, err; int x, y, h; initgraph(&gdriver, &gmode, "C:\\TC\\BGI"); err = graphresult(); if (err != grOk) { printf("Eroare: %s\n", grapherrormsg(err)); printf("Apasa o tasta pentru terminare !"); getch(); exit(1); } x = getmaxx() / 2; y = getmaxy() / 2; h = textheight("A"); /* Selecteaza pagina 1 ca pagina activa, deseneaza o bara si scrie un text explicativ */

Prof.dr.Valer Roca ULBS

97

setactivepage(1); bar(0, 0, 100, 80); settextjustify(CENTER_TEXT, CENTER_TEXT); outtextxy(x, y, "Aceasta este pagina 1."); outtextxy(x, y+h, "Apasa o tasta pentru terminare !"); /* Selecteaza pagina 0 ca pagina activa, scrie un text explicativ si deseneaza un cerc umplut. Implicit, aceasta este pagin video. */ setactivepage(0); outtextxy(x, y, " Aceasta este pagina 0."); outtextxy(x, y+h, "Apasa o tasta pentru a vedea pagina 1!"); pieslice(getmaxx()- 100, getmaxy()-100,0, 360, 50); getch();

/* Selecteaz pagina 1 ca pagina video */ setvisualpage(1); /* Inchide sistemul grafic */ getch(); closegraph(); return 0;

Prof.dr.Valer Roca ULBS

98

4. Controlul monitorului i al difuzorului


Programele C ctig n utilitate i atractivitate prin modul n care sunt prezentate datele de tip text pentru utilizator. Aceasta presupune incorporarea facilitilor de afiare n ferestre, de colorare i de emitere de sunete.

4.1 Regimul text al monitorului


In regimul text , ecranul monitorului este vzut ca un spaiu de caractere, dispuse pe linii i coloane (fig. 4.1). Un element (x, y) este o matrice de pixeli care, prin aprindere corespunztoare, red forma unui caracter. Un caracter special, denumit cursor, arat poziia curent de scriere, adic locul peste care se va suprapune un caracter matriceal. Cursorul avanseaz corespunztor, pe msur ce sunt afiate caracterele textului. Regimul text al unui monitor are dou moduri de lucru: modul defilare i modul pagin.
1 1 2 2 x cmax

y (x, y)

lmax

Fig. 4. 1 Spaiul de caractere al ecranului n regimul text

In modul defilare, afiarea textelor se face pe linii, de sus n jos i de la stnga la dreapta. Atunci cnd s-a umplut i ultima linie a ecranului, imaginea este ridicat cu o linie, prima linie de text se pierde i se elibereaz spaiul ultimei linii. In continuare, textul se scrie numai pe ultima linie i, de fiecare dat cnd aceast linie se umple, are loc o astefel de micare. Acest comportament, denumit defilare, similar avansului hrtiei la o main de scris, este puin controlabil prin program. In modul defilare, singurul lucru comandabil este trecerea pe un rnd nou i oprirea / reluarea afirii, la anumite momente, pentru ca textul afiat pe ecran s poat fi citit. Modul defilare este modul implicit de lucru al monitoruli n regimul text. In modul pagin, spaiul matriceal al monitorului este fix i adresabi. Cursorul poate fi poziionat dup dorin i pot fi controlate culorile de fond i de afiare a caracterelor. Pentru controlul culorilor, fiecrui caracter de afiat i se asociaz un caracter de atribute care definete culoarea de fond, culoarea de afiare i posibilitatea ca imaginea caracterului s clipeasc intermitent (fig.4.2) . Datorit numrului de bii afectai pentru fiecare, se pot definii 8 culori de fond i 16 culori de afiare, deoarece bitul 3, de intensitate, difernieaz dou grupe de cte 8 culori: culorile primare i forma luminoas a culorilor primare.

Prof.dr.Valer Roca ULBS

99

6 5

4 3

2 1

0 - BLACK 1 BLUE 2 GREEN 3 CYAN 4 RED 5 MAGENTA 6 BROWN 7 LIGHTGRAY

8 LIGHTGRAY 9 LIGHTBLUE 10 LIGHTGREEN 11 LIGHTCYAN 12 LIGHTRED 13 LIGHTMAGENTA 14 YELLOW 15 WHITE

Culoare de scriere (text color) Culoare de fond (background color) Clipire (blinking)

Fig.4.2 Atibutele de culoare i culorile de fond i de scriere

4.2 Faciliti de lucru cu texte n modul pagin


Avnd ca suport fizic modul pagin al regimului text i modul de lucru al tastaturii, limbajul C definete un dispozitiv logic de intrare / ieire, denumit consol. Facilitile consolei sunt accesibile programelor prin intermediul unei biblioteci al crui fiier header este conio.h. Fiierul header, care trebuie inclus n program, definete prototipurile funciilor, variabilele, structurile i constantele pentru programarea sistemului consol . 4.2.1 Stabilirea unui mod text al consolei Consola poate avea mai multe moduri text. Un mod text definete spaiul de afiare, ca numr de linii i coloane, i posibilitile de utilizare a culorilor. In sistemul de programare, modurile se refer prin numere ntregi sau constante simbolice aferente. Uzuale sunt modurile C80 care nseamn un ecran color cu 80 de coloane i 25 de linii i respectiv C4350 care se refer la un ecran color cu rezoluie text de 80 de coloane i 43 de linii, dac placa este EGA sau cu rezoluie text de 80 de coloane i 50 de linii, dac placa este VGA. Setarea unui mod text se face prin apelul funciei textmode() care are forma: void textmode(int mode);

n care parametrul este constanta asociat modului. La execuia acestei funcii, fereastra de afiare este considerat ntregul ecran i atributele de culoare sunt cele implicite (fond negru, scriere cu alb). Modul setat devine mod curent i despre acesta se pot obine informaii ntr-o structur de tipul text_info apelnd funcia gettextinfo(): textinfo ti; gettextinfo(&ti); Structura ti conine informaii despre coordonatele colurilor ecranului, despre atributele, de culoare, rezoluia text a ecranului i poziia cursorului. Dac nu se face o setare de mod, este considerat modul text de rezoluie maxim al plcii video. Pentru modul curent se pot defini culorile de fond i de scriere prin intermediul funciilor : void textbackground(int color); void textcolor(int color).

Prof.dr.Valer Roca ULBS

100

Parametrul color poate lua o valoare ntre 0 i 7, pentru culoarea de fond, sau o valoare ntre 0 i 15, pentru culoarea de desen. In fiierul conio.h sunt definite constante simbolice prentru aceste culori. Pentru a realiza clipirea imaginii caracterelor, trebuie adugat constanta BLINK (128) la codul culorii, dat ca parametru n funcia textcolor(). Execuia funciilor de stabilire a culorilor de fond i de scriere afecteaz numai caracterele care vor fi afiate ulterior setrii. Atributele de culoare setate rmn valabile pn la o nou setare. In programul care urmeaz, se scrie, cu alb pe fond albastru, o prim linie de text i, cu fond alb i culoare de scriere negru, o alt linie, dup care se revine la culorile iniiale, de la lansarea programului: #include <conio.h> void main () {textmode(C4350); clrscr(); textbackground(BLUE); cprintf("\r\nO linie alba pe fond albastru !\r\n"); textbackground(WHITE); textcolor(BLACK); cprintf("\r\nO linie neagra pe fond alb !\r\n"); normvideo(); getch(); } De asemenea, n modul curent, se poate stabili forma cursorului, alegnd ntre un cursor linie orizontal (NORMAL_CURSOR) i un cursor definit prin umplerea matricei ntregului cartacter cu culoarea de scriere, denumit cursor plin (SOLID_CURSOR). In acest scop, trebuie apelat funcia setcursortype(ct), unde ct este una din constantele simbolice redate mai sus. 4.2.2 Utilizarea ferestrelor de scriere Scrierea textului poate fi controlat n anumite limite, avnd n vedere facilitile pe care modul pagin le ofer. Definirea unei ferestre de scriere. Scrierea textului pe ecran se poate realiza ntr-o anumit zon a acestuia, denumit fereastr curent de scriere (text window). O fereastr se definete ca un dreptunghi n spaiul ecran, prin coordonatele colurilor stnga-sus i dreapta-jos. Ecranul ntreg este o fereastr de coordonate (1, 1), (cmax, lmax), depinznd de modul text ales. Declararea unei ferestre se face prin apelul fincieie window() care are prototipul: void window(int x1, int y1, int x2, int y2); unde (x1, y1) este colul stnga-sus i (x2, y2) colul dreapta-jos. Este recomandabil ca ferestrele s fie definite relativ la rezoluia modului text curent selectat, lucru care se poate realiza fcnd uz de structura de informaii ti. De exemplu, n secvena care urmeaz se consider modul implicit, se obine structura de informaii prin gettextinfo() i apoi se definete o fereastr al crui col dreapta-jos s fie cu 10 coloane i 5 linii mai sus dect punctul limit infrerioar al ecranului i s nceap pe coloana 3 i linia 7 a acestuia: text_info ti; gettextinfo(&ti); window(3, 7, ti.screenwidth 10, ti.screenheight 5);

Prof.dr.Valer Roca ULBS

101

O fereastr definit devine automat fereastr curent de scriere i rmne activ pn la o definire a unei alte fereastre. Ferestrele redefinite nu i pstreaz proprietile avute la ultima definire. De aceea, pentru o rescriere ntr-o fereastr, cu aceleai caracteristici (reutilizare), este necesar ca programul s utilizeze variabile care s memoreze starea fiecrei ferestre reutilizabile (culori, poziie a cursoruli). Dup redefinire, pe baza valorilor memorate, programul trebuie s seteze aceste caracteristici. Poziionarea cursorului. In fereastra curent, programatorul poate s gestioneze poziia cursorului dup dorin, impunnd astfel locul din fereastra activ n care se va ncepe scrierea unui text. Poziia curent a cursorului poate fi aflat prin funciile wherex(), wherey() i poate fi mutat prin gotoxy(). Aceste funcii au prototipurile: int wherex(); int wherey(); void gotoxy(int x, int y); i acestea lucreaz n coordonate relative, potrivit originii ferestrei. De exemplu, dac trebuie mutat cursorul n linia 3 coloana 50 a ferestrei curente atunci se scrie gotoxy(50, 3), iar dac se mut cursorul cu 5 linii mai jos i cu 7 coloane mai la stnga fa de poziia actual, atunci se scrie gotoxy(wherex()-7, wherey()+5). Stergeri i defilri. In fereastra curent, se poate realiza o pregtire pentru scriere, fie la nivelul ntregii ferestre, fie la invelul unei linii. Exist cteva funcii, fr parametrii, care asist programatorul n acest sens: void clrscr() terge ntreaga ferestr curent, mutnd cursorul n originea acestei ferestre; void clreol() terge linia pe care se gsete cursorul, de la cursor la sfritul liniei, fr a muta cursorul; void delline() - terge complet linia pe care se gsete cursorul i mut n sus cu un rnd textul care se afl sub linia tears, fr a muta cursorul; void insline() deplaseaz n jos cu un rnd textul din fereastr (linia ultim din fereastr se pierede), ncepnd cu linia pe care se afl cursorul i nsereaz aici o linie goal, fr a muta cursorul. Scriere n fereastr. Pentru scrierea n fereastra curent sunt disponibile funcii similare scrierii standard: la nivel de caracter, la nivel de string i scriere cu format. Acestea ncep scrierea n poziia curent i mut corespunztor cursorul, pe msur ce sunt scrise caracterele. Funciile disponibile sunt: int putch(int c); int cputs(const char *str); int cprintf(const char *format [, arg,...]); Funcia putch() scrie, n poziia curent, caracterul dat de c i ntoarce caracterul scris sau EOF, la nereuit. Dac c este caracterul newline, funcia nu l nlocuiete cu perechea de caractere CR/LF. Funcia cputs() scrie stringul (terminat cu null) dat de pointerul str, fr a aduga caracterul newline la sfrit i ntoarce codul ultimului caracter scris. Este sarcina programatorului s adauge secvena de forma \r\n, dac dorete trecere la nceput de rnd nou. Dac n text exist deja caractere newline, funcia nu le nlociuete cu caracterele pereche CR/LF. Funcia cprintf() este similar funciei printf() de scriere cu format n modul standard. In aceast funcie, partea de argumente, opional, desemneaz expresiile ale cror valori sunt scrise, pe baza specificatorilor de format care le corespund. Trebuie menionat c i

Prof.dr.Valer Roca ULBS

102

aceast funcie trateaz, la fel ca funciile discutate anterior, caracterele newline prezente n format i, deci, este sarcina programatoruli s comande explicit trecerile la rnd nou, prin secvena \r\n, n loc de \n. Citire cu ecou n fereastr. Biblioteca pentru consol posed funcii pentru citire care sunt legate de fereastra curent unde fac ecoul caracterelor citite. Similar cazului de citire standard, exist funcii pentru citire la nivel de caracter, pentru citire de string i pentru citire formatat, la care se adaug funciile pentru citire de parole i pentru verificarea apsrilor de taste. Funciile sunt: int getche(); int getch(); char * cgets(char *str); int cscanf(const char *format, [, adr,...]); char * getpass(const char *prompt); int kbhit(); Funciile getche() i getch() realizeaz citirea caracterului curent din stream cu ecou, pentru prima funcie i fr ecou , pentru a doua funcie. Aici trebuie menionat c tastele, la apsare, produc, n general, un cod de caracter. Exist taste cum sunt F1 F12, tastele sgei, tastele Home, Insert, Delete, PageUp, PageDown care apsate singure sau n combinaie cu Ctrl, Shift, Alt produc dou coduri, din care primul este codul zero. La fel se ntmpl pentru tastele pentru litere, dac sunt apsate n combinaie cu tasta Alt. Pentru preluare corect, trebuie apelat, de dou ori, funcia de citire caracter fr ecou i inut cont de faptul c al doile cod citit este codul distinctiv. In exemplul care urmeaz, se presupune fereastra implicit i se consider apsri n secven pe tastele sgei. Programul cicleaz, afind un text explicativ de tast apsat, pn cnd este apsat tasta Enter, dar negijeaz apsrile pe alte taste: #include <conio.h> #define TRUE 1 #define FALSE 0 void main() {char ch1,ch2; int oprire; cputs(Apasati in secventa, dupa dorinta, Incheiati apasand Enter. \r\n ); oprire = FALSE; ch1 = getch(); while(!oprire) {if(ch1 == 13) oprire = TRUE; else if(ch1== 0) {ch2 = getch(); switch(ch2) {case 72: cputs(S-a apasat tasta break; case 75: cputs(S-a apasat tasta break; case 77: cputs(S-a apasat tasta break; case 80: cputs(S-a apasat tasta } } ch1 = getch();

taste sageti.

sageata-sus \r\n); sageata-stanga \r\n); sageata-dreapta \r\n); sageata-jos \r\n);

Prof.dr.Valer Roca ULBS

103

} getch(); } Funcia cgets() este specific din punctul de vedere al modului de lucru. Ea presupune un array de tip caracter unde se depun caracterele citite. Inainte de apel, poziia str[0] trebuie s fie ncrcat cu numrul maxim de caractere pe care funcia le poate citi. Dup terminarea citirii, funcia ncarc poziia str[1] cu numrul de caractere citite efectiv, iar str[2] este poziia de nceput a stringului citit, poziie spre care funcia ntoarce un pointer. Citirea se ncheie la atingerea numrului maxim de caractere specificat sau mai nainte, dac n stream a aprut combinaia de caractere CR/LF. In acest ultim caz, se nscrie caracterul null la sfritul iruli citit. Array-ul str trebuie s posede o lungime cel puin egal cu numrul maxim de caractere care pot fi citite, plus doi. Funcia cscanf() este identic cu funcia scanf() de citire standard, cu excepia faptului c ecoul se face n fereastra curent i nu pe ecran. Funcia getpass() a fost conceput pentru a citi parole (password) de maximum 8 caractere, pentru care trebuie s fie asigurat secretul textului. Funcia afieaz video invers, ncepnd cu poziia curent, textul de cerere (prompt), dat de stringul argument, dezactiveaz ecoul i ateapt introducerea textului parol. Introducerea se termin atunci cnd se ntlnete CR/LF sau cnd sau citit 8 caractere. Funcia ntoarce un pointer la stringul citit, string care este memorat implicit (fr o rezervare explicit de spaiu), ca array static, n segmentul de date al programului. Funcia kbhit() este o funcie logic, de verificare a buferului de tastatur. Aceasta verific dac n acest buffer este un cod, urmare a apsrii unei taste. Funcia ntoarce o valoare diferit de zero, n cazul n care bufferul conine un cod sau o valoare zero, n caz contrar. Dac bufferul de tastatur este gol, funcia nu ateapt apsarea unei taste i ntoarce valoarea zero. Dac funcia ntoarce o valoarea diferit de zero, atunci caracterul poate fi preluat prin apelul uneia din funciile de citire de caracter. In exemplul care urmeaz, se consider o feresatr tears cu mov, pe care se scrie cu albasru. Se introduce un text de maximum 10 caractere i o parol care se reafieaz pentru confirmare. #include <conio.h> void main() {char txt[10+2+1]; char *parola; clrscr(); window(25, 5, 80, 15); textbackground(MAGENTA); textcolor(BLUE): cprintf(Text de maximum 10 caractere: ); txt[0] = 10; cgets(txt); if(txt[1] = 10) txt[12] = '\0'; cprintf(\r\nS-au citit %d caractere:\r\n ,txt[1],&txt[2]); cprintf(Apasati o tasta pentru continuare ! \r\n); getch(); parola = getpass(Parola de cel mult 8 caractere: ); cprintf(\r\n S-a citit parola: %s \r\n , parola); getch(); }

Prof.dr.Valer Roca ULBS

104

Copiere de text. La fel ca n regimul grafic, biblioteca pentru consol ofer posibilitatea de a prelua, dar numai la nivel de ecran, un bloc de text, definit ca un dreptunghi care, ulterior, poate s fie restaurat i n alt parte. Se utilizeaz funciile: int gettext(x1, y1, x2, z2, void* buff); int puttext(x1, y1, x2, z2, void* buff); int movetext(x1, y1, x2, z2, xd1, yd1); Funciile pereche gettext() i puttext() primesc coordonatele absolute ale dreptunghiului surs de copiat, respectiv, ale dreptunhgiului locului n care se face restaurarea. Pointerul buff este un array unidimensional care trebuie s aib un numr de bytes care s fie dublul produsului dintre numrul de coloane i cel al numrului de linii pe care le definete dreptunghiul surs. Aceast mrime este determinat de faptul c pentru fiecare byte de text se asociaz un byte de atribute. Cele dou dreptunghiuri nu trebuie s fie identice, deoarece, att la preluare ct i la restaurare, dreptunghiurile se parcurg de sus n jos i de la stnga la dreapta. Funcia movetext() presupune c dreptunghiul surs al textului este identic cu cel destinaie. De aceea, ca parametri, se dau coordonatele dreptunghiului surs i colul stngasus (xd1, yd1) al dreptunghiului destinaie. In exemplul care urmeaz, se scrie textul Acesta este un text \r\ncare se va muta si copia ! \r\n Apasati o tasta. pe 3 linii ecran, se copiaz partea bold n dreptunghiul (20, 5), (45, 6) i apoi se copiaz nc o dat n dreptunghiul al crui col stnga-sus este (10, 15). #include <conio.h> void main() {char txt[] = "Acesta este un text \r\ncare se va muta si copia !\r\nApasa o tasta."; char buff[104]; textmode(C80); textbackground(BLUE); textcolor(YELLOW); clrscr(); cputs(txt); getch(); gettext(1, 1, 26, 2, buff); clrscr(); puttext(20, 5, 45, 6, buff); getch(); movetext(20, 5, 45, 6, 10, 15); getch(); }

4.3 Faciliti de sunet


Programul poate provoca producerea sunetelor de o anumit frecven i durat. In acest sens, programatorul trebuie s includ fiierul header dos.h, pentru a accesa biblioteca de funcii care asigur pornirea i oprirea difuzorului. In realizarea unui sunet, sunt implicate trei funcii, n aceast ordine: void sound(int frecventa); void delay(int durata). void nosound();

Prof.dr.Valer Roca ULBS

105

Funcia sound() pornete difuzorul, pentru a vibra cu frevena dat de parametrul frecventa (n Hertz) i astfel se produce un sunet de o anumit nlime. Funcia nosound() oprete difuzorul, iar prin funcia delay() este controlat durata de timp ntre cele dou momemte. Funcia delay( ) provoac o ntrziere a execuiei apelului funciei nosound() cu numrul de milisecunde, dat de parametrul durata i astfel se asigur obinerea unui sunet de o anumit durat. In exemplul urmtor, se cnt gama Do major, cu sunete de aceeai durat, pn la apsarea unei taste. #include <conio.h> #include <dos.h> #define FALSE 0 #define TRUE 1 #define DURATA 500 void maim() {int note[8] = {523, 587, 659, 698, 784, 880, 988, 1046}; int k, gata; gata = FALSE; printf(Se canta repetat gama!\n Pornire si oprire prin apasare a unei taste.\n ); while (!gata) {for(k = 0; k < 8; k++) {sound(note[k]); delay(DURATA); nosound(); } if(kbhit()) gata = TRUE; } getch(); } Controlnd corespunztor frecvena i intervalul de emisie al difuzorului, se pot realiza linii melodice. In acest sens, sunt necesare elemente suplimentare pe care le prezent succint n continuare. In tabelul 4.1 sunt prezentate frecvenele notelor din patru octave consecutive (n total sunt 7 octave, numerotate 0 la 6). Se poate constata c frecvena unei note dintr-o octav se poate obine prin dublarea frecvenei acesteia din octava imediat inferioar. Similar, se poate deduce frecvena notelor dintr-o octav prin njumtirea frecvenei notelor corespunztoare din octava imediat superioar. Tabelul 4.1 Frecvenele notelor Octava 1 Octava 2 Nota Frecvena Nota Frecvena Do 130.81 Do 261.63 Re 141.83 Re 293.66 Mi 164.81 Mi 324.63 Fa 174.61 Fa 344.23 Sol 181.00 Sol 392.00 La 220.00 La 440.00 Si 241.94 Si 493.88 Octava 3 Frecvena 523.25 582.33 654.26 693.46 783.99 880.00 982.77 Octava 4 Nota Frecvena Do 1041.50 Re 1174.70 Mi 1313.50 Fa 1391.90 Sol 1563.00 La 1760.00 Si 1971.50

Nota Do Re Mi Fa Sol La Si

Tonul unei note poate fi alterat (mrit sau micorat) cu o jumtate de ton, din tonul normal (note cu diez i bemol). Aceasta nseamn c frecvena unei note oarecare notak afectat de diez sau bemol se poate calcula printr-o relaie de forma:

Prof.dr.Valer Roca ULBS

106

notak = 0.5 * (notak + notak+1) - pentru diez; notak = 0.5 * (notak + notak-1) - pentru bemol; Notele pot avea diferite durate: unime, doime, ptrime , optime etc. Dac se stabilete tempoul T, exprimat n ptrimi pe minut, n care trebuie cntat linia melodic, atunci duratele diferitelor note, n minute, se pot determina prin relaii de forma: D1 = 4/T; D2 = 2/T; D4 = 1/T; D8 = 1/2 T; D16 = 1/4 T etc. In relaiile de mai sus, prin Dk , k = 1, 2, 4, 8 s-a notat durata, n minute, a notei unime, doime, ptrime, optime etc. In tabelul 4.2 se dau tempouri uzuale i intervalele de valori recomandate pentru T. Durata notelor poate fi modificat pentru a obine stilurile legato (note legate) i stacatto (cntare sacadat) de a cnta linia melodic. Din experimentri, se recomand modificarea duratelor astfel: stilul normal: 7/8 din durat i 1/8 pauz; stilul legato: 1/1 din durat; stilul stacatto: 3/4 din durat i 1/4 pauz. Tabelul 4.2 Tempouri uzuale Denumire muzical Largo Larghetto Adagio Andante Moderato Alegro Presto Semificaie Foarte ncet Incet Mediu Repede Mrime T( ptrimi/minut) 40 60 60 66 66 76 76 108 108 120 120 168 168 - 208

In acest cadru, a reproduce o linie melodic dat, revine la a determina dou structuri de tip array: un array pentru frecvene i unul pentru durate. In aceste structuri trebuie reproduse inclusiv pauzele marcate pe portativ i cele necesare datorit stilului de cntat. Pentru o pauz, frevena este zero, iar durata se calculeaz potrivit tipului de pauz: pauz de o ptrime, optime etc.

Prof.dr.Valer Roca ULBS

107

5. Faciliti n C++ pentru programare procedural


Limbajul C++ aduce cteva mbuntiri, fa de limbajul C standard care se pot utiliza att n programarea procedural, ct i n programarea orientat pe obiecte. In acest capitol, se trateaz cteva dintre acestea, ca faciliti pentru programarea procedural. Alte faciliti, specifice tehnicii de programare orientat obiect, sunt tratate corespunztor n cursul destinat disciplinei care se ocup cu aceast tehnic de realizare a programelor.

5.1 Declararea de articole


In multe situaii, sunt necesare articole de date care, dup cum se tie sunt declarate fie direct, ca variabile, fie c se constituie ca un tip i apoi se declar variabile. In ambele cazuri, formele de declarare sunt mai complicate, de aceea limbajul C++ ntrete declaraia struct, astfel nct numele dat dup declaratorul struct s devin automat nume de tip de dat i s se comporte la fel ca numele de tipuri standard. In acest mod, secvena general de declarare devine: struct numetip {declarare-campuri}; numetip lista-de-variabile; In secvena care urmeaz, se construiesc, n noua form, tipul STUDENT i tipul NOD i se declar variabile corespunztoare. struct STUDENT {int matricol; char nume[40]; char adresa[50]; float media;}; STUDENT s1,s2, *ps; . . . . struct NOD {char info[30]; NOD* fata; NOD* spate;}; NOD *prim, *ultim; Se observ forma simpl de definire a tipurilor i comportamentul acestor n declararea de variabile. Se remarc, de asemenea, posibilitatea ca n interiorul tipului, s se declare pointeri de acelai tip cu tipul n construcie, fr a recurge la alte artificii aa cum se ntpla n C. In fine, trebuie subliniat c, sub aceast form, tipurile articol definesc numai forma structural i reprezentarea, adic ele nu sunt tipuri n nelesul deplin al acestui termen. Pentru o discuie despre acest aspect, se poate consulta paragraful cu privire la tipurile abstacte, din acest capitol.

Prof.dr.Valer Roca ULBS

108

5.2 Referine
Limbajul C++ aduce o facilitate suplimentar de referire a variabilelor, denumit utilzare de referine. O referin este un nume suplimentar sau pseudonim (alias) pentru o variabil, adic un alt nume care se asociaz cu aceeai locaie de memorie. Prin utilizarea referinelor, n primul rnd, se creaz posibilitatea suprapunerii, peste aceeai locaie, a mai multor variabile, de acelai tip. Astfel, programatorul poate codifica algoritmul, mai clar, fr a ncrca suplimentar spaiul programului, deoarece dou sau mai multe variabile refer acelai spaiu. Aa este cazul utilizrii repetate a unor array-uri multidimensionale, cnd programatorul folosete variabile de indexare cu nume diferite, n diferitele puncte ale algoritmului, pentru a pstra mai bine semificaia structurii respective, n acea etap de utilizare. In secvena care urmeaz, variabilele intregi k i i se refer la acelai spaiu de memorie, k fiind un alis pentru i. A utiliza d[i] este acelai lucru cu a utiliza pe d[k]. double d[10]; int i = 5; int &k = i; // declararea referintei .............. d[i] = 3.75; ............... d[4] = d[4] + d[k]; De remarcat modul n care se face declararea lui k ca alias pentru variabila i. Mai general, forma declarrii este: tip & alias = variabila; In al doilea rnd, utilizarea referinelor este o modalitate alternativ la metoda transferului parametrilor prin adres. Dup cum se tie, transmiterea parametrilor, n apelul funciilor, se realizeaz prin valoare i prin adres (pointeri). Dac transmiterea parametrilor prin valoare nu ridic probleme pentru programatori, utilizarea metodei de transfer prin adres este, n general, mai dificil i reprezint izvor de erori, uneori, greu de depistat. Pentru cazul n care este necesar s se opereze cu parametri modificabili n funcia apelat, utilizarea referinelor ofer o nou metod, mai uor de folosit, denumit transfer prin referin. Transferul prin referin permite lucrul direct, n funcia respectiv, cu parametrul actual, parametrul formal corespunztor acionnd ca un pseudonim al parametrului actual. Altfel spus, utilizarea referinei pentru un parametru este, de fapt, o modalitate de a declara un nume (alias) pe care compilatorul s l asocieze cu parametrului actual corespunztor. In acest fel, scrierea codului funciei devine mai uoar, mai ales, n cazul stucturilor de date, deoarece, n funcie, se utilizeaz aliasul n notaia prefixat cu punct, pentru referirea componentelor, fa de transferul prin pointeri care presupune o adresare mai complicat, n notaie prefixat cu sgeat. Declararea unui parametru formal referin se face, n prototipul funciei, sub forma: tip & pseudonim. In secvenele care urmeaz, se pune n eviden diferena dintre cele dou modaliti de scriere a unei funcii care realizeaz calculul sumei a dou numere complexe. Tipul COMPLEX definete un numr complex ca o pereche de numere reale (real, imaginar), n care real este partea real, iar imaginar este partea imaginar. In secven se utilizeaz facilitatea de declarare de tipuri struct. struct COMPLEX {double real; double imaginar};

Prof.dr.Valer Roca ULBS

109

. . . . . . // Adunare cu transferul parametrilor prin adresa COMPLEX adcomp(COMPLEX *x, COMPLEX *y) {COMPLEX z; z.real = x->real + y->real; z.imaginar = x->imaginar + y->imaginar; return z; } . . . . . . // Functia de dunare cu utilizarea referintelor COMPLEX & adcomp(COMPLEX &x, COMPLEX &y) { static COMPLEX z; z.real = x.real + y.real; z.imaginar = x.imaginar + y.imaginar; return z; } // Apelul functiei cu transferul parametrilor prin adresa COMPLEX p, q, r; p.real = 5; p.imaginar = -3; q.real = 2; q.imaginar = 7; r = adcomp(&p, &q); . . . . . . // Apelul functiei de adunare cu parametri prin referinta r = adcomp(p, q); Se remarc forma simpl i uniform de referire a operanzilor, prin notaia cu punct, att n apelator ct i n funcie, n cazul utilizrii referinelor. Referina, ca modalitate de adresare, poate fi utilizat i pentru rezultatul pe care l ntoarce o funcie. In exemplul din secvena de mai sus, n primul caz rezultatul se rentoarce prin valoare, dar n al doilea caz, rezultatul se ntoarce prin referin. Deoarece referina este un alt nume pentru o variabil existent, n funcie s-a declarat z cu atributul de memorie static, adic astfel nct locaia sa de memorie s existe i dup ce se face revenire din funcie. Dac z ar fi fost de clas automatic, spaiul s-ar fi alocat pe stiv i referina la acest spaiu nu ar fi avut sens, deoarece la revenirea din funcie are loc o curire a stivei i variabila z este distrus. Din punctul de vedere al apelului funciei, se constat c nu exist nici o diferent ntre cele dou cazuri. O alt modalitate de a avea un rezultat persistent i dup revenirea din funcie i pentru utilizarea de referin la rezultat, este plasarea rezultatului n heap i referina s fie sinonomul (aliasul) acestui bloc, aa cum rezult din secvena care urmeaz. COMPLEX & adcomp(COMPLEX &x, COMPLEX &y) {COMPLEX *z; z = (COMPLEX *)malloc(sizeof(COMLPEX)); z->real = x.real + y.real; z->imaginar = x.imaginar + y.imaginar; return *z; } In fine, se observ, de asemenea, o alt facilitate n C++, de introducere a comentariilor prin //, mai simpl, fa de comentariul admis de C.

Prof.dr.Valer Roca ULBS

110

5.3 Operatorii de alocare i de eliberare


Dup cum s-a artat n capitolul referitor la stucturile de date dinamice, sistemul de de gestiune a memoriei heap, presupune calcul de mrime pentru blocul de memorie i necesit conversie explicit a pointerului la tipul datelor care vor fi coninute n blocul respectiv. Ambele cerine pot introduce erori de sintax respectiv de excuie, datorit tipului incorect sau mrimii greit calculate pentru bloc. Limbajul introduce, perehea de operatori new i delete care s faciliteze utilizarea memoriei heap i s previn introducerea de erori. In plus, operatorul new, poate realiza i iniializarea blocului, dac acesta este destinat unei valori scalare, de tip standard. Operatorii au avantajul c pot s fie utilizai, n acelai mod, att pentru tipuri standard de date ct i pentru tipuri definite de programator. Este ns obligatoriu ca acetia s fie utilizai n pereche, adic la o alocare prin new trebuie s-i corespund o eliberare prin delete. Nu sunt acceptate alocri i eliberri care combin un operator cu o funcie din sistemul C de gestiune a spaiului heap. Sintactic, aceti operatori pereche se redau n una din urmtoarele forme: pointer1 = new tip (expresie-de-initializare); delete pointer1; pointer2 = new tip[expresie-de-marime]; delete pointer2; Prima pereche este utilizat pentru alocarea i eliberarea unei variabile dinamice scalare, n care partea de iniializare este opional. A doua pereche se utilizeaz atunci cnd se aloc un array unidimensional de variabile, numrul de elemente fiind specificat prin expresia de mrime, iar spaiul total fiind calculat prin formula expresie-de-marime*sizeof(tip) bytes. In secvena care urmeaz, se utilizeaz o variabil dinamic scalar, de tipul standard double care se i iniializeaz la valoare -15.75, o variabil dinamic de tipul RATIONAL, definit n prealabil de programator i un array unidimensional cu 100 de componente, pentru date long. //...Declararea tipurilor si pointerilor struct RATIONAL {int numarator, int numitor}; RATIONAL* ps; //...Utilizarea variabilei scalare double* pd = new double(-15.75); *pd = *pd + 3.2; //... Utilizarea variabilei structurate ps = new RATIONAL; ps->numarator = 4; ps->numitor = 7; //... Utilizarea array-ului long* pa = new long[100]; pa[3] = 45; *(pa + 6) = pa[3] + 12; //...Eliberarea blocurilor delete pd; delete ps; delete pa;

Prof.dr.Valer Roca ULBS

111

Modul n care se utilizeaz pointerii este cel obinuit pentru variabile dinamice, cu atenia cuvenit n cazul structurilor, la care trebuie utilizat notaia prefixat cu sgeat. Pentru array, se poate utiliza indexarea sau expresia cu pointeri echivalent, n referirea elementelor. Se remarc, de asemenea, utilizarea unei alte faciliti a limbajului C++, ca posibilitate de a declara variabilele oriunde ntr-un bloc, ba chiar n instruciunea care folosete variabila respectiv, aa cum este cazul pentru pd i pa. Dispare astfel eroarea care se face frecvent n C, de a apare variabile nedefinite, deoarece acest limaj cere ca toate variabilele s fie declarate naintea primei instruciuni i necesitatea ca programatorul s se ntoarc la nceputul textului pentru a le defini. Trebuie, ns observat c, variabilele de acest tip sunt locale blocului respectiv i valabilitatea lor se rezum numai la acest spaiu. In fine, se face meniunea c variabilele dinamice pot i ele s aib referine, ceea ce nseamn o posibilitate suplimentar de accesare a blocului, fr indirectare. In secvena care urmeaz, pi se declar ca referin pentru un bloc double n care s-a memorat o aproximaie pentru i acest alias poate fi utilizat ulterior n expresii. double& pi = *new double(3.14159265); float raza = 50.0; double lungime = 2*pi*raza; Se atrage atenia asupra modului de declarare n membrul drept care trebuie s specifice blocul, prin referire la valoarea coninut de acesta.

5.4 Funcii cu argumente implicite


Limbajul C++ implementeaz posibilitatea ca, n declararea parametrilor formali ai funciilor, unora dintre parametrii scalari s li se precizeze i o valoare. In cazul n care, la apel, pentru un parametru cu valoare asociat, numit argument implicit, nu se precizeaz un parametru actual, funcia utilizeaz pentru acesta valoarea declarat. Declararea valorii n lista de parametrii se face sub forma: tip parametru = valoare cu meniunea c lista parametrilor implicii ori acoper ntreaga list de parametri, ori este o sublist compact de la sfritul listei de parametri. Altfel spus, parametrii cu valori implicite nu pot fi amestecai printre parametri care nu au astfel de valori. La apel, sunt obligatorii valorile pentru parametrii care nu au valori implicite. Pentru argumentele implicite, se pot da sau nu valori, pentru toate sau o parte compact, ca sublist final de argumente. Si la apel, omisiunea valorii pentru un argument implicit, atrage dup sine omisiunea tuturor argumentelor pentru restul parametrilor. In secvena care urmeaz este declarat o funcie cu trei argumente implicite i sunt redate toate cele patru posibiliti corecte de apel: int ... int int int int ftimp(int zi, int luna = 12, int an = 2005, int ora = 0); d d d d = = = = ftimp(15); // luna=12, an=2005, ora=0 ftimp(15, 8); // an=2005, ora=0 ftimp(15, 8, 2000); // ora=0 ftimp(15, 8, 2000, 17);

In fine, se face meniunea c n C/C++ exist i posibilitatea de a construi funcii cu un numr variabil de parametri, aa cum sunt funciile de bibliotec pentru operaiile de intrare/ieire. Aceast facilitate poate fi consultat n documentaia de implementate a limbajului pentru platforma respectiv.

Prof.dr.Valer Roca ULBS

112

5.5 Suprancrcarea funciilor


In limbajul C++ exist posibilitatea ca mai multe funcii diferite s aib acelai nume, spre deosebire de limbajul C, unde unicitatea numelor de funcii, ntr-un program este obligatorie. Cazul cel mai frecvent, cnd este preferabil aceast facilitate este acela al funciilor care sunt asemntoare, adic realizeaz acelai tip de prelucrare, dar opereaz asupra unor tipuri diferite de date. Multe dintre bibliotecile de funcii ale limbajului C++ implementeaz acest facilitate, pentru astfel de cazuri, aa de exemplu, sunt funciile pentru valoare absolut, denumite abs(), din biblioteca definit n fiierul header stdlib.h care au urmtoarele definiii: int abs(int) long abs(long) double abs(double). Se spune c acestea sunt funcii cu nume suprancrcat sau simplu, funcii suprancrcate. Se poate deduce cu uurin c tehnica funciilor suprancrcate scutete programatorul s construiasc i s in minte numele tuturor funciilor care realizeaz procese de calcul similare. Pentru exemplul de mai sus, n C, funciile sunt denumite respectiv abs(), labs() i dabs(), corespunztor celor trei tipuri de date cu care opereaz. Pentru funcii suprancrcate, compilatorul poate distinge exemplarul care trebuie apelat, ntr-un anumit context, n raport de valorile argumentelor de apel. De aceea, condiia de baz pentru implementarea facilitii de suprancrcare este aceea ca funciile s se disting ntre ele prin prototip. Cum, de regul, aceste funcii au acelai numr de parametri, aceasta nseamn ca ele s difere prin tipul parametrilor, deoarece la acelai sistem de parametrii, rezultatul nu poate fi dect de tip identic. Mai general, numrul i tipul parametrilor efectivi (argumentelor) reprezint criteriul dup care compilatorul selecteaz funcia care trebuie apelat, dintre funciile suprancrcate. Dac, pe baza coincidenei argumentelor cu tipurile parametrilor formali, compilatorul nu poate stabili funcia de apelat, sunt prevzute reguli suplimentare de conversie a argumentelor la tipuri care s nu conduc la pierdere de date, prin trunchiere. Compilator emite mesaj de eroare numai dup ce au fost cercetate, n acest fel, toate tipurile posibile standard i cele definite de programator i selecia nu a putut fi realizat. In secvena care urmeaz este declarat suprancrcat funcia putere() i apoi este apelat, pentru diferite cazuri: // 1 int putere(int x, int n); long putere(long x, int n); // 2 double putere(double x, long n); // 3 double putere(double x, double y); // 4 . . . int r = putere(5, 3); // Se apeleaza 1 float x = 3.5; float r = putere(x, 5); // Se apeleaza 3 dupa conversie double r = putere(7, 5.2); // Se apeleaza 4 dupa conversie double r = putere(3.45L, 4L); // Se apeleaza 3

5.6 Tipuri abstracte de date


Se tie c un tip de date standard este o mulime de valori de acelai fel, nzestrat cu o mulime de operaii de calcul. Tipurile de date pe care le poate defini programatorul n C++, pot depi forma de structurare i memorare, aa cum s-a artat ntr-un paragraf anterior, apropiindu- se de modelul de construcie aplicat n limbaj pentru tipurile standard. Facilitatea oferit de limbajul C++, n acest scop poart denumirea de utilizare de tipuri abstracte de date.

Prof.dr.Valer Roca ULBS

113

A construi i utiliza un tip abstract de date ntr-un program C++, revine la a defini un struct care s cuprind nu numai cmpurile de date dar i prototipurile funciilor care vor prelucra aceste date. In acest mod, datele i operaiile care urmeaz s se fac cu acestea, spre deosebire de limbajul C, sunt legate unele de altele, constiuind un tot unitar. Datele i funciile coninute de un astfel de tip se numesc date membru i funcii membru. Pe baza tipului astfel declarat, se pot apoi defini variabile care s utilizeze aceleai funcii pentru prelucrarea datelor pe care acestea le conin. In acest sens, limbajul prevede declarea de variabile i pointeri de tipul respectiv i ofer posibilitatea referiri membrilor prin notaia prefixat cu punct sau cu sgeat, la fel ca la tipurile structurate obinuite. Pentru a introduce i alte noiuni, listingul 5.1 reprezint coninutul unui fiier header rational.h, n care se definete tipul abstract RATIONAL, pentru a oferi posibilitatea realizrii de operaii pe numere raionale, memorate sub forma perechilor de numere ntregi (numarator, numitor). Se face cteva ipoteze cu privire la fraciile ordinare memorate sub aceast form i anume: fraciile se aduc totdeauna la forma de fracii ireductibile; dac se memoreaz un numr ntreg ca fracie, acesta va avea numitorul 1; numrul zero se memoreaz ca (0,1); semnul unei fracii se pstreaz totdeauna la numrtor; la consol o fracie se d sub forma a/b sau a, dac este numr ntreg. Urmrind listingul 10,1 se constat c tipul se descrie obinuit, cu membrii de tip dat i cu prototipurile funciilor, ntr-o ordine oarecare. Se recomand sistematizarea membrilor, pentru a putea fi mai bine percepui. Oricare dintre membrii, definii de tip sunt accesibili din exterior, adic toi membrii unui asemena tip sunt publici i nu sunt protejai mpotriva modificrilor accidentale. In plus, aceast form nu permite separarea i ascunderea acelor membrii, de exemplu funcii, care au un rol ajuttor, adic sunt utilizai cu scopul de a implementa alte funcii. In acest exemplu, funcia cmmdc() i iredrat() sunt astfel de funcii care servesc respectiv determinrii celui mai mare divizor comun i pentru a aduce o fracie la forma ireductibil. Listingul 5.1 Fiierul header rational.h de decarare pentru tipul abstract RATIONAL struct RATIONAL {// Datele tipului int numarator; int numitor; // Functiile ajutatoare ale tipului int cmmdc(int a, int b); void iredrat(); // Forma ireductibila // Functiile de baza ale tipului void initrat(int a, int b); // Initializare RATIONAL opusrat(); // Opus RATIONAL invrat(); // Invers RATIONAL addrat(RATIONAL &y); // Adunare RATIONAL subrat(RATIONAL &y); // Scadere RATIONAL divrat(RATIONAL &y); // Impartire RATIONAL mulrat(RATIONAL &y); // Imultire void citrat(); // Citire de la consola void afsrat(); // Afisare la consola int comprat(RATIONAL &y); // Comparare } Aa dup cum s-a artat, cu tipul abstract pot fi declarate variabile care urmeaz s utilizeze funciile tipului. La momentul m care se face definirea tipului, nu se cunoate nici o astfel de variabil, de aceea, se face o ipotez fundamental i anume c este vorba de o variabil generic, denumit variabil curent. Oricare funcie poate aciona asupra datelor variabilei curente i numele acesteia nu trebuie, n general, s fie precizat explicit. Dac este

Prof.dr.Valer Roca ULBS

114

absolut nevoie, atunci se poate utiliza pointerul this, predefinit de limbaj, pentru a desemna aceat variabil. Dun punctul de vedere al declarrii prototipurilor de funcii care opereaz asupra unor variabile de tipul respectiv, aceast ipotez fundamental, conduce la idea de a clasifica funciile dup numrul operanzilor, de tipul respectiv, pe care acestea le utilizeaz. Cazurile frecvente sunt de funciile unare i binare i, cu titlu de exemplificare, se dau regulile care se aplic n aceste cazuri: pentru funcii unare, nu se declar nici un operand de intrare, funcia va opera asupra variabilei curente. Dac funcia trebuie s produc un rezultat de acelai tip, diferit de variabila curent (variabila curent nu poate fi modificat), atunci se declar tipul respectiv la rezultat. Altfel, se declar void sau un alt tip de rezultat, potrivit necesitior. In exemplul considerat, n primul rnd, astfel de funcii sunt opusrat()i invrat() care opereaz pe variabila curent i returneaz rationalul construit ca opus, respeciv invers, din rationalul acestei variabile. In al doile rnd, sunt unare i funciile citrat(), afsrat(), initrat() i iredrat() care opereaz pe variabila curent i produc modificri asupra acesteia, de aceea aceste funcii ntorc void. pentru funcii binare, se declar un operand de intrare, cellalt operand va fi variabila curent. Dac funcia trebuie s conserve operanzii primii i trebuie s produc un rezultat de acelai tip, atunci tipul respectiv se declar la rezultat. Dac funcia intoarce un alt fel de rezultat, acesta se declar corespunztor. In exemplul considerat, toate funciile pentru operaiile de adunare, scdere, nmulire i mprire sunt binare i cu rezultat de acelai tip. Funcia comprat() este i ea binar, dar ntoarce un rezultat de tip ntreg, similar funciilor de comparare de iruri. In listingul 5.2 este redat fiierul rational.cpp care definete funciile tipului i pune n eviden alte aspecte pe care metoda tipurilor abstracte le presupune. 1) - Se remarc modul n care se declar apartenena funciilor la tipul respectiv, prin prefixarea numelui de funcie cu numele tipului, urmat de :: care reprezint aa-numitul operator de rezoluie. In exemplul considerat, apare construcia RATIONAL:: ca prefix la fiecare funcie. Listingul 5.2 Fiierul rational.cpp de implementare pentru tipul abstract RATIONAL #include <stdlib.h> #include <stdio.h> #include <string.h> #include "rational.h" // Functia pentru aflarea celui mai mare divizor comun int RATIONAL::cmmdc(int a, int b) {a = abs(a); b = abs(b); while(a != b) if(a > b) a = a b; else b = b a; return a; } // Functia pentru realizare unui rational ireductibil void RATIONAL::iredrat() {if(numarator == 0) numitor = 1; else if((abs(numarator) != 1) && (abs(numitor) != 1)) {int d = cmmdc(numarator, numitor); numarator = numarator / d; numitor = numitor / d;

Prof.dr.Valer Roca ULBS

115

} if(numitor < 0) {numarator = - numarator; numitor = - numitor; } } // Functia pentru realizarea initializarii unui rational void RATIONAL::initrat(int a, int b) {numarator = a; numitor = b; iredrat(); } // Functia pentru construirea opusului unui rational RATIONAL RATIONAL::opusrat() {RATIONAL z; z.numarator = - numarator; z.numitor = numitor; return z; } // Functia pentru construirea inversului unui rational RATIONAL RATIONAL::invrat() {RATIONAL z; z = *this; if(numarator != 0) {z.numarator = numitor; z.numitor = numarator; if(z.numitor <0 ) {z.numarator = - z.numarator; z.numitor = -z.numitor; } } return z; } // Functia pentru adunarea a doi rationali RATIONAL RATIONAL::addrat(RATIONAL &y) {RATIONAL z; z.numarator = numarator * y.numitor + y.numarator * numitor; z.numitor = numitor * y.numitor; z.iredrat(); return z; } // Functia pentru scaderea a doi rationali RATIONAL RATIONAL::subrat(RATIONAL &y) {RATIONAL z; z = y.opusrat(); z = this->addrat(z); return z; } // Functia pentru inmultirea a doi rationali RATIONAL RATIONAL::mulrat(RATIONAL &y)

Prof.dr.Valer Roca ULBS

116

{RATIONAL z; z.numarator = numarator * y.numarator; z.numitor = numitor * y.numitor; z.iredrat(); return z; } // Functia pentru impartirea a doi rationali RATIONAL RATIONAL::divrat(RATIONAL &y) {RATIONAL z; z = y.invrat(); z = this->mulrat(z); return z; } // Functia pentru citirea unui rational de la tastatura void RATIONAL::citrat() {char txt[25]; printf("\n Se cere un rational sub forma a sau a/b :"); scanf("%s", txt); char *p = strtok(txt, "/"); sscanf(p,"%d", &numarator); p = strtok(NULL, "/"); if(!p) numitor = 1; else sscanf(p,"%d", &numitor); iredrat(); } // Functia pentru afisarea unui rational la monitor void RATIONAL::afsrat() {if(numitor == 1) printf("%d ", numarator); else printf("%d/%d ", numarator, numitor); } // Functia pentru compararea a doi rationali int RATIONAL::comprat(RATIONAL &y) {RATIONAL z1, z2; z1 = *this; z2 = y; z1.numarator = z1.numarator * z2.numitor; z1.numitor = z1.numitor * z2.numitor; z2.numarator = z2.numarator * z1.numitor; z2.numitor = z2.numitor * z1.numitor; if(z1.numarator == z2.numarator) return 0; else if(z1.numarator < z2.numarator) return -1; else return 1; } 2) - Se constat c limbajul tie s realizeze atribuiri pentru transferul de parametri, precum i expresii de atribuire pentru variabile de tipuri abstracte, prin copirere bit cu bit. Aa dup cum era de ateptat, numai datele fac obiectul unor asfel de copieri, deoarece acestea sunt specifice fiecrei variabile, n vreme ce funciile reprezint un bun comun care nu este cazul s se multiplice. 3) - O funcie poate apela un membru (dat sau funcie) a altei variabile de tipul abstract, diferit de variabila curent (un parametru sau o variabil local). In acest caz,

Prof.dr.Valer Roca ULBS

117

vaiabila implicat sau pointerul la o astfel de variabil trebuie s prefixeze membrul respectiv, sub forma variabila.membru sau pointer->membru . Astfel de utilizri apar n multe din funciile exemplului condsiderat, cum sunt n funciile divrat(), mulrat() i subrat(). 4) - Se constat c pointerul this se utilizeaz n forma *this, dac se face referire la coninutul de date al variabilei curente. Aa, de exemplu, este cazul funciei de comparare, unde variabilei z1 i se atribuie datele variabilei curente sau a funciei de construcie a inversului, unde variabila z primete o astfel de valoare. 5) - Dac o funcie urmeaz s refere un membru al variabilei curente i nu variabila n ansamblul ei, trebuie utilizat notaia prefixat cu sgeat, de forma this->membru. In exemplul considerat, n funcia subrat() apare apelul funciei de adunare this>addrat(z), pentru a realiza scderea din variabila curent ca adunare cu opusul scztorului, opus care a fost construit n prealabil n variabila local z. O situaie asemntoare este i n funcia divrat() care realizeaz mprirea ca o nmulire cu inversul nmulitorului, dat ca parametru. In listingul 5.3 este redat fiierul de testare a tipului construit care sugereaz modul n care se declar variabilele de tipul respectiv i cum se utilizeaz funciile acestuia. Aici, s-a construit un program multifuncional care utilizeaz un meniu, sub form de list de operaii. Unele operaii au fost incluse spre testare direct, altele se testeaz prin intermediul primelor. Testarea se face prin execuie, cu date introduse de la tastatur, iar rezultatul obinut este afiat pe monitor. Incheiem prezentarea acestei faciliti, atrgnd atenia c, ideilie programrii procedurale, cu tipuri abstracte de date, sunt dezvoltate n programarea orientat pe obiecte care nltur neajunsurile semnalate. Programarea orientat obiect, are ca suport tot limbajul C++, de aceea nsuirea acestor elemente, este de natur s faciliteze apropirea programatorului de metoda nou de programare semnalat. Listingul 5.3 Fiierul testrat.cpp de testare pentru tipul abstract RATIONAL #include <stdio.h> #include <ctype.h> #include "rational.h" char menu() {char op; printf("\n\nMENIU\n"); printf("A - Adunare \n"); printf("S - Scadere \n"); printf("M - Inmultire \n"); printf("D - Impartire \n"); printf("C - Comparare \n"); printf("R - Citire \n"); printf("T - Terminare \n"); printf("Alege operatia: "); scanf("%1s", &op); return toupper(op); } void main() {char op; RATIONAL x, y, z; op = menu(); while(op != 'T') {if(op == 'G') {x.citrat(); y.afsrat();

Prof.dr.Valer Roca ULBS

118

} else if(op == 'C') {x.citrat(); y.citrat(); switch(x.comprat(y)) {case -1: printf("\n x < y case 0: printf("\n x = y case 1: printf("\n x > y } } else {x.citrat(); y.citrat(); switch(op) {case 'A': z = x.addrat(y); case 'S': z = x.subrat(y); case 'M': z = x.mulrat(y); case 'D': z = x.divrat(y); } z.afsrat(); } op = menu(); }

\n"); break; \n"); break; \n");

break; break; break;

Prof.dr.Valer Roca ULBS

119

S-ar putea să vă placă și