Documente Academic
Documente Profesional
Documente Cultură
Sibiu 2008
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.
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.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.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
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
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;
(*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
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;
} 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).
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;
} . . . . . . . . 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.
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.
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
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
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
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.
12
Exemplul 5:
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
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:
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);
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.
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
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
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.
19
d1 cap
d2
dn-1
dn
d1
d1
d1
d1
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
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.
21
cap d1
d2
d3
d4
d5
d6
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.
22
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.
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
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; }
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;
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;
27
*inceput = p; }; return 0; }
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.
28
inceput
inceput
inceput
sfarsit
sfarsit
inceput
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; }
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:
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
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);
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); } }
33
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); }
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,
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.
37
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.
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;
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);
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; }
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); } }
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.
44
linie
linie
linie
linie
linie
EOF
a) Fiier text
b bloc bloc bloc bloc bloc bloc EOF
0.b
1.b
2.b
5.b
b) Fiier binar
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
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.
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
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
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;
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.
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.
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.
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.
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
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.
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
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.
56
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
Poziie curent
+ offset
Sfrit fiier
SEEKSET
SEEKCUR
SEEKEND
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.
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.
59
STAR
Inceput_fisie
Prelucrare
STOP
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
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
contpag++
'\f'
CapRap
Scrie in g numar pagina Scrie in g antet Scrie in g titlu Scrie in g cap tabel
IESIRE
contrand = rc
IESIRE
IESIRE
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
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
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
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)
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.
64
Fier baz
10
11
A 2 B 1 C 6 D 3 E 7 F 0 L 10 M 4 O 8 P9 Q 11
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,
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
m=0 n = -1
n=n+1 NU a.IS=1 DA
T[0].B = m
Sortare(T)
IESIRE
IESIRE
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;
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
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
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
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.
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");
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);
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 */
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.
73
(0, 0)
(x, y)
(xmax, ymax)
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.
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.
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(); }
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);
77
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);
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);
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)
80
(0, 0)
(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.
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.
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;
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.
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();
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 .
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
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;
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++; }
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).
89
x (v1, v3)
y Y (w1, w3)
(xe0, ye0)
(xe, ye)
(v2, v4)
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 =
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:
#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];
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;
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); } */
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
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.
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);
96
} free(p); closegraph();
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 */
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;
98
y (x, y)
lmax
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.
99
6 5
4 3
2 1
Culoare de scriere (text color) Culoare de fond (background color) Clipire (blinking)
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).
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);
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
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.
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(); }
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(); }
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:
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.
107
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};
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.
110
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.
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.
112
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
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;
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)
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,
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();
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(); }
119