Document, comentariu, eseu, bacalaureat, liceu si facultate
Top documenteAdmitereTesteUtileContact
      
    


 


Ultimele referate adaugate

Adauga referat - poti sa ne ajuti cu un referat?

Politica de confidentialitate



Ultimele referate descarcare de pe site
  CREDITUL IPOTECAR PENTRU INVESTITII IMOBILIARE (economie)
  Comertul cu amanuntul (economie)
  IDENTIFICAREA CRIMINALISTICA (drept)
  Mecanismul motor, Biela, organe mobile proiect (diverse)
  O scrisoare pierduta (romana)
  O scrisoare pierduta (romana)
  Ion DRUTA (romana)
  COMPORTAMENT PROSOCIAL-COMPORTAMENT ANTISOCIAL (psihologie)
  COMPORTAMENT PROSOCIAL-COMPORTAMENT ANTISOCIAL (psihologie)
  Starea civila (geografie)
 

Ultimele referate cautate in site
   domnisoara hus
   legume
    istoria unui galban
   metanol
   recapitulare
   profitul
   caract
   comentariu liric
   radiolocatia
   praslea cel voinic si merele da aur
 
despre:
 
Structuri si reuniuni ale limbajului de programare C
Colt dreapta
Vizite: ? Nota: ? Ce reprezinta? Intrebari si raspunsuri
 

O structura este o colectie de una sau mai multe variabile, de acelasi tip sau de tipuri diferite, grupate impreuna sub un singur nume pentru a putea fi tratate impreuna (in alte limbaje, structurile se numesc articole). t1q1qc
Structurile ajuta la organizarea datelor complicate, in special in programele mari, deoarece permit unui grup de variabile legate sa fie tratate ca o singura entitate. Vom ilustra in acest capitol modul de utilizare a structurilor.

10.1. Elemente de baza

Sa revenim asupra rutinei de conversie a datei prezentata in capitolul 9.
O data consta din zi, luna si an, eventual numarul zilei din an si numele lunii. Aceste cinci variabile pot fi grupate intr-o singura structura astfel:

struct date A int day; int month; int year; int yearday; char mon_namea4i;
S;

Cuvintul cheie struct introduce o declaratie de structura care este o lista de declaratii inchise in acolade.
Cuvintul struct poate fi urmat optional de un nume, numit marcaj de structura sau eticheta de structura, cum este in exemplul nostru numele date. Acest marcaj denumeste acest tip de structura si poate fi folosit in continuare ca o prescurtare pentru declaratia de structura detaliata careia ii este asociat.
Elementele sau variabilele mentionate intr-o structura se numesc membri ai structurii. Un membru al structurii sau o eticheta si o variabila oarecare, nemembru, pot avea acelasi nume fara a genera conflicte, deoarece ele vor fi intotdeauna deosebite una de alta din context.
Acolada dreapta care incheie o lista de membri ai unei structuri poate fi urmata de o lista de variabile, la fel ca si in cazul tipurilor de baza. De exemplu: struct A. . .S x,y,z; este din punct de vedere sintactic analog cu: int x,y,z;
in sensul ca fiecare declaratie declara pe x, y si z ca variabile de tipul numit (structura in primul caz si intreg in al doilea) si cauzeaza alocarea de spatiu pentru ele.
O declaratie de structura care nu este urmata de o lista de variabile nu aloca memorie; ea descrie numai un sablon, o forma de structura. Daca structura este marcata sau etichetata, atunci marcajul ei poate fi folosit mai tirziu pentru definirea unor alte variabile de tip structura, cu acelasi sablon ca structura marcata. De exemplu, fiind data declaratia: struct date d; ea defineste variabila d, ca o structura de acelasi fel (sablon) ca structura date.
O structura externa sau statica poate fi initializata, atasind dupa definitia ei o lista de initializatori pentru componente, de exemplu: struct date d = A4,7,1984,185,"iulie"S;
Un membru al unei structuri este referit printr-o expresie de forma: nume-structura.membru
in care operatorul membru de structura ’.’ leaga numele membrului de numele structurii. Ca exemplu fie atribuirea:




leap = (d.year%4==0) && (d.year%100!=0)
|| (d.year%400==0);

sau verificarea numelui lunii: if (strcmp(d.mon_name,"august")==0) ...

Structurile pot fi imbricate; o inregistrare de stat de plata, de exemplu, poate fi de urmatoarea forma:

struct person A char nameaNAMESIZEi; char addressaADRSIZEi; long zipcode; long ss_number; double salary; struct date birthdate; struct date hiredate;
S;

Structura person contine doua structuri de sablon date. Declaratia: struct person emp; defineste si aloca o structura cu numele emp de acelasi sablon ca si person. Atunci: emp.birthdate.month se refera la luna de nastere. Operatorul de membru de structura ’.’ este asociativ de la stinga la dreapta.

10.2. Structuri si functii

Exista un numar de restrictii asupra structurilor in limbajul C. Singurele operatii care se pot aplica unei structuri sint accesul la un membru al structurii si considerarea adresei ei cu ajutorul operatorului &. Acest lucru implica faptul ca structurile nu pot fi atribuite sau copiate ca entitati si ca ele nu pot fi transmise ca argumente la functii si nici returnate din functii. Structurile de clasa automatic, ca si masivele de aceeasi clasa, nu pot fi initializate; pot fi initializate numai structurile externe si statice, regulile de initializare fiind aceleasi ca pentru masive.
Pointerii la structuri nu se supun insa acestor restrictii, motiv pentru care structurile si functiile pot coexista si conlucra prin intermediul pointerilor.
Ca un exemplu, sa rescriem programul de conversie a datei, care calculeaza ziua anului, din luna si zi.

day_of_year(struct date *pd) A
/* calculul zilei anului */ int i, day, leap; day = pd->day; leap = (pd->year%4==0) &&
(pd->year%100!==0) ||
(pd->year%400==0); for (i=1; i<pd->month; i++) day += day_tabaleapiaii; return day;
S

Declaratia: struct date * pd; indica faptul ca pd este un pointer la o structura de sablonul lui date. Notatia: pd->year indica faptul ca se refera membrul "year" al acestei structuri. In general, daca p este un pointer la o structura p->membru-structura se refera la un membru particular (operatorul ’->’ se formeaza din semnul minus urmat de semnul mai mare).
Deoarece pd este pointer la o structura, membrul year poate fi de asemenea referit prin:
(*pd).year
Notatia "->" se impune ca un mod convenabil de prescurtare. In notatia (*pd).year, parantezele sint necesare deoarece precedenta operatorului membru de structura ’.’ este mai mare decit cea a operatorului *.
Ambii operatori ’.’ si ’->’ sint asociativi de la stinga la dreapta, astfel incit: p->q->membru emp.birthdate.month sint de fapt:
(p->q)->membru
(emp.birthdate).month
Operatorii ’->’ si ’.’ ai structurilor, impreuna cu () pentru listele de argumente si ai pentru indexare se gasesc in virful listei de precedenta (vezi sectiunea 4.16), fiind din acest punct de vedere foarte apropiati. Astfel, fiind data declaratia: struct A int x; int *y;S *p; unde p este un pointer la o structura, atunci expresia:
++p->x incrementeaza pe x, nu pointerul p, deoarece operatorul ’->’ are o precedenta mai mare decit ’++’. Parantezele pot fi folosite pentru a modifica ordinea operatorilor data de precedenta. Astfel:
(++p)->x incrementeaza mai intii pe p si apoi acceseaza elementul x, din structura nou pointata.
In expresia (p++)->x se acceseaza mai intii x, apoi se incrementeaza pointerul p.
In mod analog, *p->y indica continutul adresei pe care o indica y. Expresia *p->y++ acceseaza mai intii ceea ce indica y si apoi incrementeaza pe y. Expresia (*p->y)++ incrementeaza ceea ce indica y. Expresia *p++->y acceseaza ceea ce indica y si apoi incrementeaza pointerul p.

10.3. Masive de structuri

Structurile sint in mod special utile pentru tratarea masivelor de variabile legate prin context. Pentru exemplificare vom considera un program care numara intrarile fiecarui cuvint cheie dintr-un text. Pentru aceasta avem nevoie de un masiv de siruri de caractere, pentru pastrarea numelor cuvintelor cheie si un masiv de intregi pentru a memora numarul aparitiilor. O posibilitate este de a folosi doua masive paralele keyword si keycount declarate prin: char *keywordaNKEYSi; int keycountaNKEYSi; respectiv unul de pointeri la siruri de caractere si celalalt de intregi. Fiecarui cuvint cheie ii corespunde perechea: char *keyword; int keycount; astfel incit putem considera cele doua masive ca fiind un masiv de perechi. Atunci, declaratia de structura:

struct key A char *keyword; int keycount;
S keytabaNKEYSi;

defineste un masiv keytab de structuri de acest tip si aloca memorie pentru ele. Fiecare element al masivului keytab este o structura de acelasi sablon ca si structura key.
Definitia masivului keytab poate fi scrisa si sub forma:

struct key A char *keyword; int keycount;
S; struct key keytabaNKEYSi;

Deoarece masivul de structuri keytab contine, in cazul nostru, o multime constanta de cuvinte cheie, este mai usor de initializat o data pentru totdeauna chiar in locul unde este definit. Initializarea structurilor este o operatie analoaga cu initializarea unui masiv in sensul ca definitia este urmata de o lista de initializatori inchisi in acolade.
Atunci initializarea masivului de structuri keytab va fi urmatoarea:

struct key A char * keyword; int keycount;
S keytabai = A
"break",0,
"case",0,
"char",0,
/* ... */
"while",0S;

Initializatorii sint perechi care corespund la membrii structurii. Initializarea ar putea fi facuta si incluzind initializatorii fiecarei structuri din masiv in acolade ca in:
A"break",0S,A"case",0S.... dar parantezele interioare nu sint necesare daca initializatorii sint variabile simple sau siruri de caractere si daca toti initializatorii sint prezenti.
Compilatorul va calcula, pe baza initializatorilor, dimensiunea masivului de structuri keytab motiv pentru care, la initializare, nu este necesara indicarea dimensiunii masivului.
Programul de numarare a cuvintelor cheie incepe cu definirea masivului de structuri keytab. Rutina principala main citeste textul de la intrare prin apel repetat la o functie getword, care extrage din intrare cite un cuvint la un apel. Fiecare cuvint este apoi cautat in tabelul keytab cu ajutorul unei functii de cautare binary, descrisa in sectiunea 7.5. Lista cuvintelor cheie trebuie sa fie in ordine crescatoare pentru ca functia binary sa lucreze corect. Daca cuvintul cercetat este un cuvint cheie atunci functia binary returneaza numarul de ordine al cuvintului in tabelul cuvintelor cheie, altfel returneaza ?1.

#define MAXWORD 20

binary(char *word, struct key tabai, int n) A int low,high,mid,cond; low = 0; high = n - 1;
while (low<=high) A mid =(low + high) / 2; if (
(cond=strcmp(word,tabamidi.keyword))
<0) high = mid - 1; else if (cond>0) low = mid + 1; else return mid;
S return -1;
S

main() A /* numara cuvintele cheie */ int n,t; char wordaMAXWORDi;
while ((t=getword(word,MAXWORD))!=EOF) if (t==LETTER) if (
(n=binary(word,keytab,NKEYS))
>=0) keytabani.keycount++; for (n=0; n<NKEYS; n++) if (keytabani.keycount>0) printf("%4d %s\n", keytabani.keycount, keytabani.keyword);
S

Inainte de a scrie functia getword este suficient sa spunem ca ea returneaza constanta simbolica LETTER de fiecare data cind gaseste un cuvint in textul de intrare si copiaza cuvintul in primul ei argument.
Cantitatea NKEYS este numarul cuvintelor cheie din keytab (dimensiunea masivului de structuri). Desi putem calcula acest numar manual, este mai simplu si mai sigur s-o facem cu calculatorul, mai ales daca lista cuvintelor cheie este supusa modificarilor. O posibilitate de a calcula NKEYS cu calculatorul este de a termina lista initializatorilor cu un pointer NULL si apoi prin ciclare pe keytab sa detectam sfirsitul lui. Acest lucru este mai mult decit necesar deoarece dimensiunea masivului de structuri este perfect determinata in momentul compilarii. Numarul de intrari se determina impartind dimensiunea masivului la dimensiunea structurii key.
Operatorul sizeof descris in sectiunea 4.2 furnizeaza dimensiunea in octeti a argumentului sau.
In cazul nostru, numarul cuvintelor cheie este dimensiunea masivului keytab impartita la dimensiunea unui element de masiv. Acest calcul este facut intr-o linie #define pentru a da o valoare identificatorului NKEYS:
#define NKEYS (sizeof(keytab) / sizeof(struct key))
Sa revenim acum la functia getword. Programul pe care-l vom da pentru aceasta functie este mai general decit este necesar in aplicatia noastra, dar nu este mult mai complicat.
Functia getword citeste cuvintul urmator din textul de intrare, printr-un cuvint intelegindu-se fie un sir de litere si cifre, cu primul caracter litera, fie un singur caracter. Functia returneaza constanta simbolica LETTER daca a gasit un cuvint, EOF daca a detectat sfirsitul fisierului sau caracterul insusi, daca el nu a fost alfabetic.

getword(char *w, int lim) A /* citeste un cuvint */ int t;
while (--lim>0) A t = type(*w++=getchar()); if (t==EOF) return EOF; if (t!=LETTER && t!=DIGIT) break;
S
*(w-1) = '\0'; return LETTER;
S

Functia getword apeleaza functia type pentru identificarea tipului fiecarui caracter citit la intrare cu ajutorul functiei getchar. Versiunea functiei type pentru caractere ASCII este:

type(int c) A /* returneaza tipul caracterului */ if (c>='a' && c<='z' || c>='A' && c<='Z') return LETTER; if (c>='0' && c<='9') return DIGIT; return c;
S

Constantele simbolice LETTER si DIGIT pot avea orice valoare care nu vine in contradictie cu caracterele nealfabetice si EOF. Valori posibile pot fi:
#define LETTER 'a'
#define DIGIT '0'

10.4. Pointeri la structuri

Pentru a ilustra modul de corelare dintre pointeri si masivele de structuri, sa rescriem programul de numarare a cuvintelor cheie dintr-un text, de data aceasta folosind pointeri, in loc de indici de masiv.

Declaratia externa a masivului de structuri keytab nu necesita modificari, in timp ce functiile main si binary da. Prezentam, in continuare, aceste functii modificate.

#define MAXWORD 20

struct key *binary(char *word, struct key tabai, int n) A
/* cauta cuvint */ int cond; struct key * low; struct key *high; struct key *mid; low = &taba0i; high = &taban-1i;
while (low<=high) A mid = low + (high-low) / 2; if ((cond=strcmp(word,mid->keyword))<0) high = mid - 1; else if (cond>0) low = mid + 1; else return mid;
S return NULL;
S

main() A /* numara cuvintele cheie, versiune cu pointeri */ int t; char wordaMAXWORDi; struct key *binary(), *p;
while ((t=getword(word.MAXWORD))!=EOF) if (t==LETTER) if ((p=binary(word,keytab,NKEYS))
!= NULL) p->keycount++; for (p=keytab; p<keytab+NKEYS; p++) if (p->keycount>0) printf("%4d %s\n",p->keycount, p->keyword);
S

Sa observam citeva lucruri importante in acest exemplu. In primul rind, declaratia functiei binary trebuie sa indice ca ea returneaza un pointer la o structura de acelasi sablon cu structura key, in loc de un intreg. Acest lucru este declarat atit in functia principala main cit si in functia binary. Daca binary gaseste un cuvint in structura key, atunci returneaza un pointer la el; daca nu-1 gaseste, returneaza NULL. In functie de aceste doua valori returnate, functia main semnaleaza gasirea cuvintului prin incrementarea cimpului keycount corespunzator cuvintului sau citeste urmatorul cuvint.
In al doilea rind, toate operatiile de acces la elementele masivului de structuri keytab se fac prin intermediul pointerilor. Acest lucru determina o modificare semnificativa in functia binary. Calculul elementului mijlociu nu se mai poate face simplu prin: mid = (low + high) / 2 deoarece adunarea a doi pointeri este o operatie ilegala, nedefinita. Aceasta instructiune trebuie modificata in: mid = low + (high-low) / 2 care face ca mid sa pointeze elementul de la jumatatea distantei dintre low si high.
Sa mai observam initializarea pointerilor low si high, care este perfect legala, deoarece este posibila initializarea unui pointer cu o adresa a unui element deja definit.
In functia main avem urmatorul ciclu: for(p=keytab; p<keytab+NKEYS; p++)...
Daca p este un pointer la un masiv de structuri, orice operatie asupra lui p tine cont de dimensiunea unei structuri, astfel incit p++ incrementeaza pointerul p la urmatoarea structura din masiv, adunind la p dimensiunea corespunzatoare a unei structuri. Acest lucru nu inseamna ca dimensiunea structurii este egala cu suma dimensiunilor membrilor ei deoarece din cerinte de aliniere a unor membri se pot genera „goluri” intr-o structura.
In sfirsit, cind o functie returneaza un tip complicat si are o lista complicata de argumente, ca in: struct key *binary(char *word, struct key tab, int n) functia poate fi mai greu vizibila si detectabila cu un editor de texte. Din acest motiv, se poate opta si pentru urmatoarea forma: struct key *binary(word,tab,n) char *word; struct key tab; int n; unde inainte de acolada de deschidere se precizeaza tipul fiecarui parametru.
Alegeti forma care va convine si care vi se pare mai sugestiva.

10.5. Structuri auto-referite

Sa consideram problema mai generala, a numarului aparitiilor tuturor cuvintelor dintr-un text de intrare. Deoarece lista cuvintelor nu este cunoscuta dinainte, n-o putem sorta folosind algoritmul de cautare binara, ca in paragraful precedent. Nu putem utiliza nici o cautare liniara pentru fiecare cuvint, pe masura aparitiei lui pentru a vedea daca a mai fost prezent sau nu, pentru ca timpul de executie al programelor ar creste patratic cu numarul cuvintelor de la intrare.
Un mod de a organiza datele pentru a lucra eficient cu o lista de cuvinte arbitrare este de a pastra multimea de cuvinte, tot timpul sortata, plasind fiecare nou cuvint din intrare pe o pozitie corespunzatoare, relativ la intrarile anterioare. Daca am realiza acest lucru prin deplasarea cuvintelor intr-un masiv liniar, programul ar dura, de asemenea, foarte mult. De aceea, pentru rezolvarea eficienta a acestei probleme vom folosi o structura de date numita arbore binar.
Fiecare nod al arborelui va reprezenta un cuvint distinct din intrare si va contine urmatoarea informatie:
- un pointer la cuvint;
- un contor pentru numarul de aparitii;
- un pointer la descendentul sting al cuvintului;
- un pointer la descendentul drept al cuvintului. Nici un nod al arborelui nu va avea mai mult decit doi descendenti dar poate avea un descendent sau chiar nici unul.
Arborele se construieste astfel incit pentru orice nod, sub-arborele sting al sau contine numai cuvintele care sint mai mici decit cuvintul din nod, iar sub-arborele drept contine numai cuvinte, care sint mai mari decit cuvintul din nod, compararea facindu-se din punct de vedere lexicografic.
Pentru a sti daca un cuvint nou din intrare exista deja in arbore se porneste de la nodul radacina si se compara noul cuvint cu cuvintul memorat in nodul radacina. Daca ele coincid se incrementeaza contorul de numarare a aparitiilor pentru nodul radacina si se va citi un nou cuvint din intrare.
Daca noul cuvint din intrare este mai mic decit cuvintul memorat in nodul radacina, cautarea continua cu descendentul sting, altfel se investigheaza descendentul drept. Daca nu exista nici un descendent pe directia ceruta, noul cuvint nu exista in arbore si va fi inserat pe pozitia descendentului corespunzator. Se observa ca acest proces de cautare este recursiv, deoarece cautarea din fiecare nod utilizeaza o cautare intr-unul dintre descendentii sai.
Prin urmare se impune de la sine ca rutinele de inserare in arbore si de imprimare sa fie recursive.
Revenind la descrierea unui nod, el apare ca fiind o structura cu patru componente:

struct tnode A /* nodul de baza */ char *word; /* pointer la cuvint */ int count; /* numarator de aparitii */ struct tnode *left; /* descendent sting */ struct tnode *right; /* descendent drept */
S;

Aceasta declaratie „recursiva” a unui nod este perfect legala, deoarece o structura nu poate contine ca si componenta o intrare a ei insasi dar poate contine un pointer la o structura de acelasi sablon cu ea.
Declaratia: struct tnode *left; declara pe left ca fiind un pointer la structura (nod) si nu o structura insasi.
In program vom folosi rutinele getword, pentru citirea unui cuvint din intrare, alloc pentru rezervarea de spatiu necesar memorarii unui cuvint si alte citeva rutine pe care le cunoastem deja. Rutina principala main citeste prin intermediul rutinei getword un cuvint, si il plaseaza in arbore prin rutina tree.

#define MAXWORD 20

main() A /* contorizare aparitii cuvinte */ struct tnode *root, *tree(); char wordaMAXWORDi; int t; root = NULL;
while ((t=getword(word,MAXWORD))!=EOF) if (t==LETTER) root = tree(root,word); treeprint(root);
S

Rutina main gestioneaza fiecare cuvint din intrare incepind cu cel mai inalt nivel al arborelui (radacina). La fiecare pas, cuvintul din intrare este comparat cu cuvintul asociat radacinii si este apoi transmis in jos, fie descendentului sting, fie celui drept, printr-un apel recursiv la rutina tree. In acest proces, cuvintul fie exista deja, undeva in arbore, caz in care contorul lui de numarare a aparitiilor se incrementeaza, fie cautarea continua pina la intilnirea unui pointer NULL, caz in care nodul trebuie creat si adaugat arborelui. Cind se creeaza un nod nou, rutina tree returneaza un pointer la el, care apoi este introdus in nodul de origine (adica in nodul al carui descendent este noul nod) in cimpul left sau right dupa cum noul cuvint este mai mic sau mai mare fata de cuvintul origine. Rutina tree, care returneaza un pointer la o structura de sablon tnode are urmatorul cod:

struct tnode *tree(struct tnode *p, char *w) A /* introduce cuvintul w in nodul p */ struct tnode *talloc(int n); char *strsav(char *s); int cond; if (p==NULL) A /* a sosit un nou cuvint */ p = talloc(); /* creeaza un nod nou */ p->word = strsav(w); p->count = 1; p->left = p->right = NULL;
S else if ((cond=strcmp(w,p->word))==0) p->count++; else if (cond<0) /* noul cuvint mai mic */ p->left = tree(p->left,w); else /* noul cuvint mai mare */ p->right = tree(p->right,w); return p;
S

Memoria pentru noul nod se aloca de catre rutina talloc, care este o adaptare a rutinei alloc, pe care am vazut-o deja. Ea returneaza un pointer la un spatiu liber, in care se poate inscrie noul nod al arborelui. Vom discuta rutina talloc mai tirziu. Noul cuvint se copiaza in acest spatiu cu ajutorul rutinei strsav, care returneaza un pointer la inceputul cuvintului, contorul de aparitii se initializeaza la 1 si pointerii catre cei doi descendenti se fac NULL. Aceasta parte de cod se executa numai cind se adauga un nou nod.
Rutina treeprint tipareste arborele astfel incit pentru fiecare nod se imprima sub-arborele lui sting, adica toate cuvintele mai mici decit cuvintul curent, apoi cuvintul curent si la sfirsit sub-arborele drept, adica toate cuvintele mai mari decit cuvintul curent. Rutina treeprint este una din cele mai tipice rutine recursive.

treeprint(struct tnode *p) A
/* tipareste arborele p recursiv */ if (p!=NULL) A treeprint(p->left); printf("%5d %s\n",p->count,p->word); treeprint(p->right);
S
S

Este important de retinut faptul ca in algoritmul de cautare in arbore, pentru a ajunge la un anumit nod, se parcurg toate nodurile precedente, pe ramura respectiva (stinga sau dreapta), incepind intotdeauna cu nodul radacina. Dupa fiecare iesire din rutina tree, din cauza recursivitatii, se parcurge acelasi drum, de data aceasta de la nodul gasit spre radacina arborelui, refacindu-se toti pointerii drumului parcurs.
Daca considerati ca nu ati inteles suficient de bine recursivitatea, desenati-va un arbore si imprimati-l cu ajutorul rutinei treeprint, avind grija sa memorati fiecare iesire din tree si treeprint.
O observatie legata de acest exemplu: daca arborele este „nebalansat“, adica cuvintele nu sosesc in ordine aleatoare din punct de vedere lexicografic, atunci timpul de executie al programului poate deveni foarte mare. Cazul limita in acest sens este acela in care cuvintele de la intrare sint deja in ordine, (crescatoare sau descrescatoare), caz in care programul nostru simuleaza o cautare liniara intr-un mod destul de costisitor.

Sa ne oprim putin asupra alocarii de memorie. Cu toate ca se aloca diferite tipuri de obiecte, este de preferat sa existe un singur alocator de memorie intr-un program. Relativ la acest alocator de memorie se pun doua probleme: in primul rind cum poate satisface el conditiile de aliniere ale obiectelor de un anumit tip (de exemplu intregii trebuie alocati la adrese pare); in al doilea rind cum se poate declara ca alocatorul returneaza pointeri la tipuri diferite de obiecte.
Cerintele de aliniere pot fi in general rezolvate cu usurinta pe seama unui spatiu care se pierde, dar care este nesemnificativ ca dimensiune. De exemplu, alocatorul alloc returneaza totdeauna un pointer la o adresa para. In cazul in care cererea de alocare poate fi satisfacuta si de o adresa impara (pentru siruri de caractere, de exemplu) se pierde un caracter.
In ceea ce priveste declararea tipului alocatorului alloc (adica a tipului de obiect pe care il indica pointerul returnat de alloc), un foarte bun procedeu in limbajul C este de a declara ca functia alloc returneaza un pointer la char si apoi sa convertim explicit acest pointer la tipul dorit printr-un cast. Astfel daca p este declarat in forma: char *p; atunci:
(struct tnode *)p; converteste pe p dintr-un pointer la char intr-un pointer la o structura de sablon tnode, daca el apare intr-o expresie. Si atunci, o versiune a alocatorului talloc poate fi urmatoarea:

struct tnode *talloc() A char *alloc(); return (struct tnode *) alloc
(sizeof(struct tnode));
S
10.6. Cautare in tabele

O alta problema legata de definirea si utilizarea structurilor este cautarea in tabele. Cind se intilneste de exemplu, o linie de forma:
#define YES 1 simbolul YES si textul de substitutie 1 se memoreaza intr-o tabela. Mai tirziu, ori de cite ori textul YES va aparea in instructiuni, el se va inlocui cu constanta 1.
Crearea si gestionarea tabelelor de simboluri este o problema de baza in procesul de compilare. Exista doua rutine principale care gestioneaza simbolurile si textele lor de substitutie. Prima, install(s,t) inregistreaza simbolul s si textul de substitutie t intr-o tabela, s si t fiind siruri de caractere. A doua, lookup(s) cauta sirul s in tabela si returneaza fie un pointer la locul unde a fost gasit, fie NULL daca sirul s nu figureaza in tabel.
Algoritmul folosit pentru crearea si gestionarea tabelei de simboluri este o cautare pe baza de hashing. Fiecarui simbol i se calculeaza un cod hash astfel: se aduna codurile ASCII ale caracterelor simbolului si se ia restul provenit din impartirea numarului obtinut din adunare si dimensiunea tabelului. Astfel, fiecarui simbol i se asociaza un cod hash H care verifica relatia:
0<=H<0x100 (in hexazecimal)
Codul hash astfel obtinut va fi folosit apoi ca un indice intr-o tabela de pointeri. Un element al acestei tabele (masiv) indica inceputul unui lant de blocuri care descriu simboluri cu acelasi cod hash. Daca un element al tabelei este NULL inseamna ca nici un simbol nu are valoarea respectiva de hashing.
Un bloc dintr-un lant indicat de un element al tabelei este o structura care contine un pointer la simbol, un pointer la textul de substitutie si un pointer la urmatorul bloc din lant. Un pointer NULL la urmatorul bloc din lant indica sfirsitul lantului.
Sablonul unei structuri (nod) este urmatorul:

struct nlist A char *name; char *def; struct nlist *next;/ * urmatoarea intrare in lant */
S;

Tabelul de pointeri care indica inceputurile lantului de blocuri ce descriu simboluri de acelasi cod hash este:

#define HASHSIZE 0x100 static struct nlist *hashtabaHASHSIZEi;

Algoritmul de hashing pe care-l prezentam nu este cel mai bun posibil, dar are meritul de a fi extrem de simplu:

hash(char *s) A
/* formeaza valoarea hash pentru sirul s */ int hashval; for (hashval=0; *s!='\0';) hashval += *s++; return hashval % HASHSIZE;
S

Algoritmul de hashing produce un indice in masivul de pointeri hashtab. In procesul de cautare a unui simbol, daca el exista, el trebuie sa fie in lantul de blocuri care incepe la adresa continuta de elementul din hashtab cu indicele respectiv.
Cautarea in tabela de simboluri hashtab se realizeaza cu functia lookup. Daca simbolul cautat este prezent undeva in lant, functia returneaza un pointer la el; altfel returneaza NULL.

struct nlist *lookup(char *s) A
/* cauta sirul s in hashtab */ struct nlist *np; for (np=hashtabahash(s)i; np!=NULL; np=np->next) if (strcmp(s,np->name)==0) return np; /* s-a gasit s */ return NULL; /* nu s-a gasit s */
S

Rutina install foloseste functia lookup pentru a determina daca simbolul nou care trebuie introdus in lant este deja prezent sau nu. Daca mai exista o definitie anterioara pentru acest simbol, ea trebuie inlocuita cu definitia noua. Altfel, se creeaza o intrare noua pentru acest simbol, care se introduce la inceputul lantului. Functia install returneaza NULL, daca din anumite motive nu exista suficient spatiu pentru crearea unui bloc unu.

struct nlist *install(char *name, char
*def) A /* scrie (nume, def) in htab */ struct nlist *np, *lookup(); char *strsav(), *alloc(); int hashval; if ((np=lookup(name))==NULL) A /* nu s-a gasit */ np = (struct nlist*)alloc(sizeof(*np)); if (np==NULL) return NULL; /* nu exista spatiu */ if ((np->name=strsav(name))==NULL) return NULL; hashval = hash(np->name); np->next = hashtabahashvali; hashtabahashvali = np;
S else /* nodul exista deja */ free(np->def); /* elibereaza definitia veche */ if ((np->def=strsav(def))==NULL) return NULL; return np;
S

Deoarece apelurile la functiile alloc si free pot aparea in orice ordine si deoarece alinierea conteaza, versiunea simpla a functiei alloc, prezentata in capitolul 9 nu este adecvata aici. In biblioteca standard exista functii de alocare fara restrictii, care se apeleaza implicit sau explicit de catre utilizator dintr-un program scris in C pentru a obtine spatiul de memorie necesar. Deoarece si alte actiuni dintr-un program pot cere spatiu de memorie intr-o maniera asincrona, spatiul de memorie gestionat de functia alloc poate sa fie necontiguu. Astfel, spatiul liber de memorie este pastrat sub forma unui lant de blocuri libere, fiecare bloc continind o dimensiune, un pointer la urmatorul bloc si spatiul liber propriu-zis. Blocurile sint pastrate in ordinea crescatoare a adreselor iar, ultimul bloc, de adresa cea mai mare, indica primul bloc, prin pointerul lui la blocul urmator din lant, astfel incit lantul este circular.
Cind se lanseaza o cerere, se examineaza lista spatiului liber, pina se gaseste un bloc suficient de mare pentru cererea respectiva. Daca blocul are exact dimensiunea ceruta, el se elibereaza din lantul blocurilor libere si este returnat utilizatorului. Daca blocul este mai mare se descompune, astfel incit partea ceruta se transmite utilizatorului, iar partea ramasa se introduce inapoi in lista de spatiu liber. Daca nu se gaseste un bloc suficient de mare pentru cererea lansata se cauta un alt bloc de memorie.
Eliberarea unei zone de memorie prin intermediul rutinei free cauzeaza, de asemenea, o cautare in lista de spatiu liber, pentru a gasi locul corespunzator de inserare a blocului de memorie eliberat. Daca blocul de memorie eliberat este adiacent cu un bloc din lista de spatiu liber la orice parte a sa, el este alipit la acel bloc, creindu-se un bloc mai mare, astfel ca memoria sa nu devina prea fragmentata. Determinarea adiacentei este usurata de faptul ca lista de spatiu liber se pastreaza in ordinea crescatoare a adreselor de memorie.
Exemplul de utilizare a acestor functii initializeaza elementele masivului hashtab cu NULL. In continuare se asteapta de la tastatura introducerea unui nume si a unei definitii pentru acest nume. Daca numele introdus nu exista in lista hashtab atunci se afiseaza un mesaj corespunzator, altfel se afiseaza vechea definitie care este apoi inlocuita de noua definitie introdusa.

main() A char numa30i,defa30i; int i; struct nlist *np; for (i=0; i<HASHSIZE; i++) hashtabaii = NULL; do A getword(num); getword(def); if ((np=lookup(num))==NULL) printf("New name\n"); else printf("Old definition: %s\n", np->def); install(num,def);
S while (1);
S

10.7. Cimpuri

Un cimp se defineste ca fiind o multime de biti consecutivi dintr-un cuvint sau intreg. Adica din motive de economie a spatiului de memorie, este utila impachetarea unor obiecte intr-un singur cuvint masina. Un caz frecvent de acest tip este utilizarea unui set de flaguri, fiecare pe un bit, pentru tabela de simboluri a unui compilator.
Fiecare simbol dintr-un program are anumite informatii asociate lui, cum sint de exemplu, clasa de memorie, tipul, daca este sau nu cuvint cheie s.a.m.d. Cel mai compact mod de a codifica aceste informatii este folosirea unui set de flaguri, de cite un bit, intr-un singur intreg sau caracter.
Modul cel mai uzual pentru a face acest lucru este de a defini un set de masti, fiecare masca fiind corespunzatoare pozitiei bitului m interiorul caracterului sau cuvintului. De exemplu:

#define KEYWORD 01
#define EXTERNAL 02
#define STATIC 04

definesc mastile KEYWORD, EXTERNAL si STATIC care se refera la bitii 0, 1 si respectiv 2 din caracter sau cuvint. Atunci accesarea acestor biti se realizeaza cu ajutorul operatiilor de deplasare, mascare si complementare, descrisi intr-un capitol anterior. Numerele trebuie sa fie puteri ale lui 2.

Expresii de forma: flags | = EXTERNAL | STATIC; apar frecvent si ele seteaza bitii 1 si 2 din caracterul sau intregul flags (in exemplul nostru)

in timp ce expresia: flags &= (EXTERNAL | STATIC); selecteaza bitii 1 si 2 din flags.

Expresia: if (flags & (EXTERNAL | STATIC)) ... este adevarata cind cel putin unul din bitii 1 sau 2 din flags este unu.

Expresia: if (!(flags & (EXTERNAL | STATIC))) ... este adevarata cind bitii 1 si 2 din flags sint ambii zero.

Limbajul C ofera aceste expresii, ca o alternativa, pentru posibilitatea de a defini si de a accesa bitii dintr-un cuvint, in mod direct, folosind operatorii logici pe biti.

Sintaxa definitiei cimpului si a accesului la el se bazeaza pe structuri. De exemplu constructiile #define din exemplul de mai sus pot fi inlocuite prin definirea a trei cimpuri:

struct A unsigned is_keyword: 1; unsigned is_external:1; unsigned is_static: 1;
S flags;

Aceasta constructie defineste variabila flags care contine 3 cimpuri, fiecare de cite un bit. Numarul care urmeaza dupa ’:’ reprezinta lungimea cimpului in biti. Cimpurile sint declarate unsigned pentru a sublinia ca ele sint cantitati fara semn. Pentru a ne referi la un cimp individual din variabila flags folosim o notatie similara cu notatia folosita pentru membrii structurilor. flags.is_keyword flags.is_static
Cimpurile se comporta ca niste intregi mici fara semn si pot participa in expresii aritmetice ca orice alti intregi. Astfel, expresiile anterioare pot fi scrise mai natural sub forma urmatoare: flags.is_extern = flags.is_static = 1; pentru setarea bitilor 1 si 2 din variabila flags, flags.is_extern = flags.is_static = 0; pentru stergerea bitilor, iar:

if (flags.is_extern==0 && flags.is_static==0)

pentru testarea lor.
Un cimp nu trebuie sa depaseasca limitele unui cuvint. In caz contrar, cimpul se aliniaza la limita urmatorului cuvint. Cimpurile nu necesita sa fie denumite. Un cimp fara nume, descris numai prin caracterul ’:’ si lungimea lui in biti este folosit pentru a rezerva spatiu in vederea alinierii urmatorului cimp. Lungimea zero a unui cimp poate fi folosita pentru fortarea alinierii urmatorului cimp la limita unui nou cuvint, el fiind presupus a contine tot cimpuri si nu un membru obisnuit al structuri, deoarece in acest ultim caz, alinierea se face in mod automat. Nici un cimp nu poate fi mai lung decit un cuvint. Cimpurile se atribuie de la dreapta la stinga.
Cimpurile nu pot constitui masive, nu au adrese, astfel incit operatorul '&' nu se poate aplica asupra lor.

10.8. Reuniuni

O reuniune este o variabila care poate contine, la momente diferite, obiecte de diferite tipuri si dimensiuni; compilatorul este cel care tine evidenta dimensiunilor si aliniamentului.
Reuniunile ofera posibilitatea ca mai multe tipuri diferite de date sa fie tratate intr-o singura zona de memorie, fara a folosi in program vreo informatie dependenta de masina.
Sa reluam exemplul tabelei de simboluri a unui compilator, presupunind ca constantele pot fi de tip int, float sau siruri de caractere.
Valoarea unei constante particulare trebuie memorata intr-o variabila de tip corespunzator, cu toate ca este mai convenabil, pentru gestiunea tabelei de simboluri, ca valoarea sa fie memorata in aceeasi zona de memorie, indiferent de tipul ei si sa ocupe aceeasi cantitate de memorie. Acesta este scopul unei reuniuni: de a furniza o singura variabila care sa poata contine oricare dintre valorile unor tipuri de date. Ca si in cazul cimpurilor, sintaxa definitiei si accesului la o reuniune se bazeaza pe structuri. Fie definitia:

union u_tag. A int ival; float fval; char *pval;
S uval;

Variabila uval va fi suficient de mare ca sa poata pastra pe cea mai mare dintre cele trei tipuri de componente. Oricare dintre tipurile de mai sus poate fi atribuit variabilei uval si apoi folosit in expresii in mod corespunzator, adica tipul in uval este tipul ultim atribuit. Utilizatorul este cel care tine evidenta tipului curent memorat intr-o reuniune.
Sintactic, membrii unei reuniuni sint accesibili printr-o constructie de forma: nume-reuniune. membru sau pointer-la-reuniune->membru
Daca variabila utype este utilizata pentru a tine evidenta tipului curent memorat in uval, atunci fie urmatorul cod:

if (utype==INT) printf ("%d\n",uval.ival); else if (utype== FLOAT) printf("%f\n",uval.fval); else if (utype==STRING) printf("%s\n",uval.pval); else printf("tip incorect %d in utype\n", utype);

Reuniunile pot aparea in structuri si masive si invers. Sintaxa pentru accesarea unui membru al unei reuniuni, dintr-o structura, sau invers este identica cu cea pentru structurile imbricate. Pe exemplu, in masivul de structuri symtabaNSYMi definit de:

struct A char * name; int flags; int utype; union A int ival; float fval; char *pval;
S uval;
S symtabaNSYMi;

variabila ival se refera prin: symtabaii.uval.ival iar primul caracter al sirului pointat de pval prin:
*symtabaii.uval.pval
De fapt, o reuniune este o structura in care toti membrii au deplasamentul zero, structura fiind suficient de mare pentru a putea pastra pe cel mai mare membru. Alinierea este corespunzatoare pentru toate tipurile reuniunii. Ca si la structuri, singurele operatii permise cu reuniuni sint accesul la un membru al reuniunii si considerarea adresei ei.
Reuniunile nu pot fi atribuite, transmise la functii sau returnate de catre acestea. Pointerii la reuniuni pot fi folositi in mod similar cu pointerii la structuri.

10.9. Declaratii de structuri, reuniuni si cimpuri

Deoarece specificatorii de structuri, reuniuni si cimpuri au aceeasi forma vom prezenta sintaxa lor generala in acest paragraf.

Specificator-structura-sau-reuniune: struct-sau-union A lista-declaratiilor S struct-sau-union identificator A lista-declaratiilor S struct-sau-union identificator
Struct-sau-union: struct union

Lista-declaratiilor este o secventa de declaratii pentru membrii structurii sau reuniunii.

Lista-declaratiilor: declaratie-structura declaratie-structura, lista-declaratiilor
Declaratie-structura: specificator-tip, lista-declarator;
Lista-declarator: declarator-structura declarator-structura, lista-declarator

In mod obisnuit, un declarator-structura este chiar un declarator pentru un membru al structurii sau reuniunii. Un membru al structurii poate fi constituit dintr-un numar specificat de biti, caz in care avem de-a face cu un cimp. Lungimea lui se separa de nume prin caracterul ’:’ Atunci:

Declarator-structura: declarator declarator : expresie-constanta
: expresie-constanta

Intr-o structura fiecare membru care nu este un cimp incepe la o adresa corespunzatoare tipului sau. Astfel intr-o structura pot exista zone fara nume neutilizate, rezultate din motive de aliniere.
Limbajul C nu introduce restrictii privind tipurile obiectelor care pot fi declarate cimpuri.
Un specificator-structura-sau-reuniune de forma a doua declara un identificator ca fiind eticheta (marcajul) structurii sau reuniunii. Atunci o declaratie ulterioara poate folosi forma a treia a unui specificator-structura-sau-reuniune.
Etichetele de structuri permit definirea structurilor auto-referite; de asemenea permit ca partea de declaratie a corpului structurii sa fie data o singura data si folosita de mai multe ori. Este interzisa declararea recursiva a unei structuri sau reuniuni, dar o structura sau o reuniune poate contine un pointer la ea.
Doua structuri pot partaja o secventa initiala comuna de membri; adica acelasi membru poate aparea in doua structuri diferite daca el are acelasi tip in ambele structuri si daca toti membri precedenti lui sint identici in cele doua structuri.

10.10. Typedef

Limbajul C ofera o facilitate numita typedef pentru a crea noi nume de tipuri de date. Specificatorul de tip typedef-nume are sintaxa: typedef-nume: declarator

Intr-o declaratie implicind typedef fiecare identificator care apare ca parte a unui declarator devine sintactic echivalent cu cuvintul cheie rezervat pentru tipul asociat cu identificatorul. De exemplu, declaratia: typedef int LENGTH;
il face pe LENGTH sinonim cu int. „Tipul” LENGTH poate fi folosit ulterior in declaratii in acelasi mod ca si tipul int.
LENGTH len, maxlen;
LENGTH *lengthai;
In mod similar, declaratia: typedef char *STRING;
il face pe STRING sinonim cu char*, adica pointer la caracter, care apoi poate fi utilizat in declaratii de tipul:
STRING p, lineptraLINESi, alloc();
Se observa ca tipul care se declara prin typedef apare pe pozitia numelui de variabila nu imediat dupa cuvintul rezervat typedef. Sintactic typedef este sinonim cu clasele de memorie extern, static etc, dar nu rezerva memorie pentru variabilele respective.
Ca un exemplu mai complicat sa reluam declaratia unui nod al unui arbore, de data aceasta folosind typedef pentru a crea un nou nume pentru un tip structura (vezi sectiunea 10.5).

typedef struct tnode A char *word; /* pointer la text */ int count; /* numar aparitii */ struct tnode *left; /* descendent sting */ struct tnode *right; /* descendent drept */
S TREENODE, *TREEPTR;

Aceasta declaratie creeaza doua nume noi de tipuri, numite TREENODE, care este o structura si TREEPTR, care este un pointer la o structura. Atunci rutina talloc poate fi scrisa sub forma:

TREEPTR talloc() A char *alloc(); return (TREEPTR)alloc(sizeof(TREENODE)));
S

Trebuie subliniat faptul ca declaratia typedef nu creeaza noi tipuri in nici un caz; ea adauga doar sinonime pentru anumite tipuri de date, deja existente. Variabilele declarate in acest fel au exact aceleasi proprietati ca si cele declarate explicit. De fapt, typedef se aseamana cu #define, cu exceptia faptului ca in timp ce #define este tratat de preprocesor, typedef este tratat de catre compilator. De exemplu: typedef int(*PFI)(); creeaza numele PFI pentru „pointer la o functie care returneaza un intreg”, tip care poate fi folosit ulterior intr-un context de tipul:
PFI strcmp, numcmp, swap;
in programul de sortare din capitolul 9.
Exista doua motive principale care impun folosirea declaratiilor typedef. Primul este legat de problemele de portabilitate. Cind se folosesc declaratii typedef pentru tipuri de date care sint dependente de masina, atunci pentru o compilare pe un alt sistem de calcul este necesara modificarea doar a acestor declaratii nu si a datelor din program.
Al doilea consta in faptul ca prin crearea de noi nume de tipuri se ofera posibilitatea folosirii unor nume mai sugestive in program, deci o mai rapida intelegere a programului.


Colt dreapta
Creeaza cont
Comentarii:

Nu ai gasit ce cautai? Crezi ca ceva ne lipseste? Lasa-ti comentariul si incercam sa te ajutam.
Esti satisfacut de calitarea acestui document, eseu, cometariu? Apreciem aprecierile voastre.

Nume (obligatoriu):

Email (obligatoriu, nu va fi publicat):

Site URL (optional):


Comentariile tale: (NO HTML)


Noteaza documentul:
In prezent fisierul este notat cu: ? (media unui numar de ? de note primite).

2345678910

 
Copyright© 2005 - 2024 | Trimite document | Harta site | Adauga in favorite
Colt dreapta