Sunteți pe pagina 1din 15

Ministerul Educaiei i Tineretului al Republicii Moldova Academia de Studii Economice din Moldova Facultatea Cibernetica,Statistica si Informatica Economica Catedra

Cibernetica si Informatica Economica

Laborator 2 Utilizarea variabile dinamice n limbajele de programare

Tema 3 : Pointeri i referine

Controlat:
Linga Ion

Elaborat:
Stud.gr.CIB102, Cracan Alexandru

Chisinau,2011
1

Sarcina:
Citirea adresei de memorie pentru scalari de diferite tipuri n limbajele C++, Pascal, Perl, Visual Basic

1.

2. Lucrul cu variabile de tip pointer i referin n limbajele C++, Pascal: - declararea lor; - crearea i alocarea memoriei; - nscrierea valorilor n zona de memorie alocat i citirea lor; - eliberarea memoriei.
3.

Formarea i lucrul cu liste dinamice n limbajele C++, Pascal - Declararea unei structuri ce conine referin la structur similar. - Formarea unei liste FIFO; efectuarea operaiilor de adugare, inserare, eliminare a elementelor; parcurgerea listei. - Formarea unei liste FILO; efectuarea operaiilor de adugare, inserare, eliminare a elementelor; parcurgerea listei.

1. Lucrul cu variabile de tip pointer i referin n limbajele C++, Pascal: - declararea lor; - crearea i alocarea memoriei; - nscrierea valorilor n zona de memorie alocat i citirea lor; - eliberarea memoriei.
Un pointer este o variabila ce contine adresa unei alte variabile. Pointerii au tip : aceasta inseamna ca un pointer nu poate fi folosit decit pentru a contine adrese de variabile de un singur tip. De exemplu, prin declaratiile: int *pi; char *pc; se specifica faptul ca pi este un pointer la o variabila de tip intreg iar pc un pointer la o variabila de tip "char". Operatorul unar "&" numit operator de referentiere sau adresare, aplicat unei variabile, permite obtinerea adresei acesteia: int i; pi = &i; Operatorul unar "*", dereferentiere, indirectare, permite derefentierea unui pointer, furnizind valoarea variabilei pointate de acesta: int x; x = *pi x = i; Utilizarea operatorului "*" este destul de delicata, chiar periculoasa am spune: acesta permite citirea sau scrierea in orice zona de memorie. Utilizarea unui pointer care este initializat gresit este o eroare des intilnita in programare si din pacate consecintele sunt imprevizibile. O aplicatie importanta privind utilizarea pointerilor este transferul prin referinta al parametrilor unei functii. Asa cum s-a precizat la capitolul "Functii", in limbajul C transferul implicit al parametrilor este prin valoare. Daca se doreste modificarea unui parametru formal, trebuie transmisa functiei adresa variabilei iar in corpul functiei si se foloseasca operatorul de dereferentiere. Exemplul clasic pentru acest procedeu este acela al functiei de interschimbare a doua variabile. Forma "clasica" a functiei, incorecta (semantic) in limbaj C, este: void schimbare (int a, int b) {
3

int temp = a; a = b; b = temp; } Un apel al acestei functii (prin schimbare (x, y);) nu are nici un efect asupra variabilelor x,y (actuale) pentru ca functia lucreaza cu variabilele ei locale a si b. Varianta corecta este: void schimbare_corecta (int *p, int *q){ int temp; temp = *p; *p = *q; *q = temp; } Apelul corespunz\tor este: int x = 1, y = 2; schimbare_corecta (&x, &y);

Printre functiile de alocare a memoriei (vezi Anexa ), "maloc" si "free" joaca un rol important pentru ca ele permit alocarea si dealocarea in mod dinamic a spatiului din memorie. Acest spatiu este posibil de accesat cu ajutorul variabilelor de tip pointer. # include <stdlib.h> void *malloc (size_t dim); void free(void *ptr); Alocarea dinamica a memoriei este deosebit de utila atita timp cit programatorul foloseste structuri de date pentru care nu stie dimensiunea la scrierea programului. In cursul executiei programul poate aloca memorie in limitele impuse de sistem. Functia malloc are ca argument numarul de octeti doriti a fi alocati si intoarce ca rezultat un pointer catre zona de memorie libera alocata de sistem. Pointerul returnat este de tip "void" ce corespunde unui pointer la o variabila de tip oarecare. Pentru specificarea marimii unui tip particular, se poate utiliza operatorul "sizeof" (vezi sectiunea urmatoare). Daca nu mai este memorie disponibila (nu se poate face alocarea) func]ia malloc returneaza valoarea NULL. Iata un exemplu: char *tampon; element *ptr; /*presupunem ca tipul element este definit undeva */ -------tampon = malloc(512); /* s-a alocat o memorie tampon de 512 caractere*/ ptr = malloc(32*sizeof(element)); /*s-a alocat o zona de 32 de "elemente"*/ if ((tampon == NULL | | ptr == NULL)) { printf ("Alocare imposibila ! \n");
4

... ...

Functia "free" permite dealocarea spatiului de memorie alocat in prealabil de malloc. De pilda, dupa utilizarea memoriei tampon si ptr (in exemplul de mai sus) se scrie (in program) free (tampon); free (((char*)ptr);
O referinta este un alias al unui obiect. Atunci cnd crem o referint, aceast se initializeaz cu numele unui obiect, numit obiect tint. Din acest moment referinta actioneaz ca un nume alternativ al obiectului tint iar toate operatiile care se vor efectua asupra referintei se vor rsfrnge asupra obiectului tint. Declararea unei referinte se face respectnd urmtoarea sintaxa: <tip> & <nume_referinta>=<obiect_tint>; <tip> --- este acelasi cu tipul obiectului tint; <nume_referint>---este un identificator al limbajului de programare (C++); Pentru a putea declara o referint asupra unui obiect tint trebuie ca obiectul tint s fi fost deja declarat (anterior referintei).Operatorul de referentiere &, care apare n declararea referintei are acelasi simbol cu operatorul de adres, dar nu sunt unul si acelasi operator. Asa cum am vzut mai sus, o variabil poate fi gestionat n direct prin pointeri. Dar acest lucru poate fi fcut ntr-o manier mai simpl prin referinte , Cu o mic diferent, referintele nu pot fi reinitializate, fiind pentru totdeauna aliasurile obiectelor tint pentru care au fost construite initial. n lucrul cu subprograme (functii si proceduri), se ntmpin probleme din dou motive: argumentele functiilor fiind transmise prin valoare, efectul operatiilor asupra lor nu era sesizat dect n interiorul functiei, si nu se poate returna din functie dect o valoare, instructiunea return
5

acceptnd un singur parametru Eliminarea acestor neajunsuri se poate face prin transmiterea parametrilor prin referinte. n C++, acest lucru se poate face utiliznd pointerii sau referintele. Sintaxa difer n cele dou cazuri dar rezultatul este acelasi. De exemplu: #include<stdio.h> #include<conio.h> void cub(int x) void main()

Iesirea va fi: a=10 x=1000 si o linie mai jos a=10.Se observ c variabila "a" nu si-a modificat valoarea dup apelul functiei "cub". Aceasta deoarece transmiterea parametrilor n cazul functiei "cub" s-a fcut prin valoare. Se observ n schimb c parametrul x si-a schimbat valoarea n cadrul functiei. Pentru c aceast schimbare s se observe si n programul apelant, utilizm pointerii sau referintele. Astfel rescriem functia "cub" n cele dou variante: #include<stdio.h> #include<conio.h> void cub_pointeri(int *x) void cub_referinte(int &x) main() iesirea va fi: a din apelul cub_pointeri=8 a din apelul cub_referinte=512
6

Deoarece cub_pointeri lucreaz cu pointeri, vom identifica valoarea prin cotinutul de la adresa x. De aceea apare *x. Apelul functiei trebuie s transmit adresa lui x; prin urmare apare ca parametru efectiv &a. Iar functia cub_referinte lucreaz cu aliasul variabilei a si att apelul ct si manevrarea variabilelor n cadrul functiei este mult mai natural. Din cele artate mai sus, ar fi de preferat utilizarea referintelor. Acestea sunt clare si usor de utilizat. Totusi referintele nu sunt reasignate. Astfel dac doriti s retineti adresa unui obiect ntro variabil si apoi aceeasi variabil s retin adresa altui obiect, trebuie s utilizati pointerii. Referintele nu pot fi nule. Prin urmare dac un obiect ntr-o conditie se va analiza vis-a-vis de valoarea NULL, nu vor fi utile referintele, ci pointerii. Dac operatorul new nu poate aloca memorie de heap, returneaz un pointer NULL. Pentru ca referintele nu pot avea valoarea NULL, nu trebuie s se initializeze o referint cu un pointer pn nu se verific dac pointerul nu are valoarea NULL. CONCLUZII: Utilizati transmiterea prin referint ori de cte ori este posibil; Nu lucrati cu pointeri dac nu se poate lucra cu referinte; Nu returnati referinta nici unui obiect local;

b.n Pascal se opereaz cu dou tipuri de date dinamice - referin i pointer primele fiind "cu tip" iar celelalte "fr tip". Tipul referin are sintaxa: tip_referin=^tip_de_baz;. Simbolul ^ are semnificaia de "indirect". Datorit asocierii cu un tip de baz, variabilele tip_referin se mai numesc i variabile cu referin legat. La compilare, pentru astfel de variabile, se vor rezerva n segmentul de date dou cuvinte i la referirea lor se vor genera instruciuni cod main conform tipului de baz, dar cu adresare indirect. nainte de referire, n variabilele de tip_referin trebuie s se ncarce adresele variabilelor de tipul
7

tip_de_baz. Declararea unui tip referin permite referirea anterior declarrii tipului de baz. Astfel, urmtoarea secven de declarri este corect: TYPE pointer_a=^vector; vector=ARRAY[1..20] OF REAL; Construcia sintactic a referirii unei variabile dinamice depinde de caracteristicile tipului su de baz: este de forma identificator^ n cazul tipurilor nestructurate sau celor structurate care permit referirea global (STRING, RECORD i SET); conine prefixul identificator^, urmat de elementele specifice modului de referire a componentelor, n cazul tipurilor structurate care permit referirea pe componente (ARRAY, STRING i RECORD). Aceste posibiliti de referire sunt ilustrate n programul Alocare_dinamic_1. Tipul pointer este desemnat prin cuvntul rezervat pointer. Variabilele de tip pointer pot fi denumite variabile cu referin liber, deoarece pot fi folosite la memorarea adreselor pentru variabile de orice tip. Tehnica de lucru cu astfel de variabile este asemntoare celei prezentate la tipul referin. Utilizarea efectiv presupune i n acest caz o asociere explicit cu un anumit tip de baz, dar soluia folosit este diferit. La tipul referin, asocierea se face prin declarare, iar n cazul tipului pointer asocierea se realizeaz la utilizare, prin diverse tehnici. O posibilitate este asigurat de referina typecasting (transfer de tip), care are forma general: tip(variabil), unde tip este un tip standard sau declarat anterior de utilizator iar variabil poate fi cu/fr tip sau o referin prin pointer, de forma variabil_pointer^. Alocarea i eliberarea zonelor pentru variabile de tip pointer se realizeaz cu procedurile GetMem, respectiv FreeMem, definite n unitatea System astfel: GetMem(VAR p:pointer; l:WORD), FreeMem(VAR p:pointer; l:WORD). Efectul acestora este asemntor procedurilor New i Dispose, cu precizarea c este necesar specificarea lungimii, care nu poate fi dedus implicit, neexistnd un tip de baz. Un exemplu simplu de folosire a variabilelor pointer din heap este ilustrat n programul Alocare_dinamic_3. PROGRAM Alocare_dinamica_3; VAR p: POINTER; BEGIN GetMem(p,6); REAL(p^):=5.25; WriteLn(REAL(p^):4:2); FreeMem(p,6) END.
8

2.Citirea adresei de memorie pentru scalari de diferite tipuri n

limbajele C++, Pascal, Perl, Visual Basic


Clasa de memorare arat cnd, cum si unde se aloc memorie pentru o variabil sau un vector. Orice variabil C are o clas de memorare care rezult fie dintr-o declaratie explicit, fie implicit din locul unde este definitvariabila.Exist trei moduri de alocare a memoriei, dar numai dou corepund unor clase de memorare: - Static: memoria este alocat la compilare n segmentul de date din cadrul programului si nu se mai poate modifica n cursul executiei. Variabilele externe, definite n afara functiilor, sunt implicit statice, dar pot fi declarate static si variabile locale, definite n cadrul functiilor. - Automat: memoria este alocat automat, la activarea unei functii, n zona stiv alocat unui program si este eliberat automat la terminarea functiei. Variabilele locale unui bloc (unei functii) si argumentele formale sunt implicit din clasa auto. Memoria se aloc n stiva atasat programului. - Dinamic: memoria se aloc la executie n zona heap atasat programului, dar numai la cererea explicit a programatorului, prin apelarea unor functii de bibliotec (malloc, calloc, realloc). Memoria este eliberat numai la cerere, prin apelarea functiei free. Variabilele dinamice nu au nume si deci nu se pune problema clasei de memorare (clasa este atribut al variabilelor cu nume). Variabilele statice pot fi initializate numai cu valori constante (pentru c are loc la compilare), dar variabilele auto pot fi initializate cu rezultatul unor expresii (pentru c are loc la executie). Toate variabilele externe (si statice) sunt automat initializate cu valori zero (inclusiv vectorii). In timpul compilarii, la intalnirea unei variabile alocate dinamic, se va aloca o zona de memorie de patru bytes care, dupa alocarea dinamica pe parcursul executarii programului, va contine adresa de memorie din zona heap a primei locatii de memorie libera unde se va aloca spatiul necesar pentru variabila referita sau adresata de variabila dinamica. Dupa cum s-a precizat, alocarea dinamica a spatiului de memorie din zona heap pentru variabila referita de variabila dinamica (pointer catre variabila referita) se face cu functia malloc() dupa cum urmeaza: tip_variabila_referita *ptr_variabila_reterita; ptr_variabila_reterita = malloc(size_t numar_de_octeti); unde: - ptr_variabila_reterita este un pointer (variabila dinamica) catre o variabila de un tip precizat de tip_variabila_referita. - tip_variabila_referita este tipul variabilei referita de variabila de tip pointer si poate fi un tip standard sau definit de utilizator. - numar_de_octeti este numarul de octeti precizat sau calculat ai zonei de memorie din zona heap, alocata dinamic, unde se va memora variabila referita de variabila dinamica (ptr_variabila_reterita). Eliberarea spatiului de memorie ocupat de variabila referita prin variabila dinamica (ptr_variabila_reterita) se face cu functia free() cu sintaxa: free(ptr_variabila_reterita).

3.Formarea i lucrul cu liste dinamice n limbajele C++, Pascal - Declararea unei structuri ce conine referin la structur similar.
9

- Formarea unei liste FIFO; efectuarea operaiilor de adugare, inserare, eliminare a elementelor; parcurgerea listei. - Formarea unei liste FILO; efectuarea operaiilor de adugare, inserare, eliminare a elementelor; parcurgerea listei.
Liste dublu inlantuite in Limbajul C/C++. O lista dublu inlantuita, spre deosebire de lista simplu inlantuita, presupune realizarea inlantuirii componentelor sale in ambele sensuri, de la prima componenta spre ultima si invers, de la ultima componenta spre prima componenta. In acest sens, in fiecare componenta, trebuie prevazute doua adrese de inlantuire (2 pointeri), una care sa precizeze adresa urmatoarei componente din lista, si alta care sa precizeze adresa componentei anterioare, ca in exemplul urmator: ............................................................................................. struct tipstudent { int matricol; char nume[15] char prenume[15]; float nota1; float nota2; float media; struct tipstudent *urmator; struct tipstudent *anterior; } var_student, primul_student, ultimul student; Tipul de date de mai sus declara structuri recursive, in ambele sensuri, datorita campurilor "urmator" si "anterior", care furnizeaza adresa urmatoarei componente (nod), respectiv adresa componentei anterioare, si care nu sunt altceva decat pointeri spre structuri de date de tipul celei din care fac parte insusi campurile respective ("urmator" si "anterior"). In cazul listelor dublu inlantuite campul "urmator", pentru ultimul nod, are valoare nula NULL iar campul "anterior" pentru primul nod are, de asemenea, valoarea nula NULL Liste simplu inlantuite in Limbajul C/C++. Eliminarea incovenientelor semnalate mai sus se poate face prin implemantarea listelor cu ajutorul structurilor de date dinamice. In aceste tipuri de liste componentele acestora se pot aloca in zone necontinue de memorie realizandu-se inlantuirea acestora printr-un sistem de adrese ca in exemplu urmator: ............................................................................................. struct tipstudent { int matricol; char nume[15] char prenume[15]; float nota1; float nota2; float media; struct tipstudent *urmator; } varstudent, primul_student, ultimul student;

10

Declarrile tipurilor de date Pascal pentru definirea structurilor de liste dinamice simplu i, respectiv, dublu nlnuite sunt: Stiva Se numete stiv o list organizat astfel nct operaiile de inserare i eliminare sunt permise numai la prima component. Acest mod de organizare corespunde unei gestiuni LIFO (Last In First Out) a informaiei stocate. Modelul corespunde unei stive de cri. Adugarea unei noi cri n stiv se face deasupra primei cri, iar extragerea este posibil numai pentru prima carte. Operaiile de inserare i eliminare ntr-o stiv pot fi descrise prin intermediul procedurilor inserare_la_inceput1, inserare_la_inceput2, elimina_prima1, elimina_prima2. Operaiile elementare pentru gestiunea informaiei memorate ntr-o stiv sunt: - push(S,d,test) inserarea informaiei d n stiva S; - pop(d,S,test) preluarea cu eliminare a informaiei memorate n prima celul a stivei S; - top(d,S,test) preluarea fr eliminare a informaiei deinute de prima component a stivei S. Parametrul test returneaz true dac operaia este posibil, altfel false. Coada Se numete coad o list organizat astfel nct operaia de inserare este permis la ultima component, iar operaia de eliminare este permis numai la prima component. Acest mod de organizare corespunde unei gestiuni FIFO (First In First Out) a informaiei stocate. Modelul corespunde unei cozi de ateptare la un magazin. O nou persoan se aaz la coad dup ultimul cumprtor, iar persoana care i achit nota de plat (primul cumprtor) prsete coada. Implementarea unei liste coad poate fi efectuat att printr-o structur static (masiv unidimensional), ct i printr-o structur dinamic de tip list. n scopul eficientizrii operaiilor de inserare/extragere, n cazul implementrii cozilor prin structuri dinamice lineare, este necesar utilizarea a dou informaii: adresa primei componente i adresa ultimei componente. Aceste informaii pot fi meninute explicit prin utilizarea a doi pointeri sau prin utilizarea unui pointer i a unei structuri de list circular. IN C++: O stiva este o lista particulara in care accesul la elementele listei se face numai pe la un capat, si anume pe la sfarsitul acesteia. Singurele operatii admise asupra elementelor unei stive sunt adaugarea unui element in stiva pe la sfarsitul acesteia si stergerea unui element, echivalenta cu extragerea acestuia, din stiva pe la sfarsitul acesteia. Pentru a ne imagina o stiva ne vom referi la un vraf de farfurii aflate pe o masa. Prima farfurie din vraf va fi ultima folosita, iar prima farfurie folosita va fi ultima asezata in vraf. Un alt exemplu de stiva o constituie vagoanele aflate pe o linie moarta (blocata la un capat), in triaj. Ultimul vagon trimis in triaj va fi si primul vagon care va putea fi scos din triaj pentru folosire. Adaugarea sau scoaterea unui vagon in sau din triaj se va face pe la un singur capat si anume pe la sfarsit. Generalizand, o stiva este o lista de elemente cu acces pe la un capat (sfarsit), care foloseste metoda de acces cunoscuta sub numele ultimul adaugat primul extras (sau sters) sau ultimul intrat primul iesit (sau scos) iar in literatura de specialitate este cunoscuta sub denumirea de Last In, First Out=LIFO. In prelucrarea elementelor unei stive se cunosc doua operatii de baza: adaugarea sau memorarea si stergerea sau extragerea sau cu alte cuvinte, scrierea si citirea unui element din stiva pe la sfarsitul acesteia, numite in mod traditional
11

push (a pune) si pop (a scoate). Pentru a implementa o stiva in Limbajul C este nevoie de alocarea unei zone de memorie pentru stocarea elementelor stivei, de o functie de adaugare (introducere sau scriere) a unui element in stiva, numita traditional push() si de o functie de stergere (extragere sau citire) a unui element din stiva numita traditional pop(). Pentru zona de memorie a stivei se poate folosi un tablou de tipul elementelor stivei sau se poate folosi o functie de alocare dinamica (malloc()). Operatia de citire a stivei nu face altceva decat extrage un element din varful stivei si-l distruge (sterge), daca nu este stocat in alta parte. Stivele sunt foarte des utilizate in programele sistemului de operare, incvlusiv in programele traducatoare si in compilatoare. Mai jos este dat un tabel care simuleaza introducerea si extragerea de numere intr-o stiva Functia Push(X) Push(Y) Push(Z) Push(W) pop() citeste W Push(A) pop() citeste A pop() citeste Z pop() citeste Y pop() citeste X Push(Q) Continutul stivei X YX ZYX WZYX ZYX AZYX ZYX YX X stiva goala, vida Q

Declararea si utilizarea cozilor in in C/C++ O stiva este o lista liniara particulara de date in care accesul la elementele listei se face numai pe la un capat, si anume pe la inceputul acesteia. Singurele operatii admise asupra elementelor unei cozi sunt adaugarea unui element in coada pe la sfarsitul acesteia si stergerea unui element, echivalenta cu extragerea acestuia, din coada pe la inceputul acesteia. Pentru a ne imagina o coada ne vom referi la un sir de oameni care asteapta sa execute operatii financiare la o banca, la un convoi de vagoane cisterna care asteapta sa fie incarcate cu petrol, la un sir de masini care asteapta sa se alimenteze cu benzina, etc. Coada de masini care asteapta sa se alimenteze cu benzina functioneaza dupa metoda primul care intra la alimentare este primul care iese din coada sau in cazul general primul intrat, primul iesit din coada iar in literatura de specialitate metoda este cunoscuta sub numele First In, First Out=FIFO. In prelucrarea elementelor unei cozi se cunosc doua operatii de baza: adaugarea sau memorarea si stergerea sau extragerea sau cu alte cuvinte, scrierea si citirea unui element din stiva pe la sfarsitul acesteia, numite in mod traditional push (a pune) si pop (a scoate). Pentru a implementa o stiva in Limbajul C este nevoie de alocarea unei zone de memorie pentru stocarea elementelor stivei, de o functie de adaugare (introducere sau scriere) a unui element in coada, numita traditional qstore() si de o functie de stergere (eliminare sau citire) a primului element din coada, returnand valoarea sa, numita traditional qretrive(). Pentru zona de memorie a cozii se poate folosi un tablou de tipul elementelor cozii sau se poate folosi o functie de alocare dinamica (malloc()). Operatia de citire a cozii nu face altceva decat sa extraga primul element din coada si sa-l distruga daca acesta nu este stocat in alta parte. In acest mod este posibil ca o coada sa devina vida, goala, deoarece toate elementele sale au fost extrase (citite) si eliminate prin inceputul cozii. Cozile sunt foarte des utilizate in foarte multe operatiuni de programare, cum ar fi simularile, planificarile unui sistemului de operare, incvlusiv pentru
12

gestionarea zonelor de memorie tampon ale dispozitivelor de intrare/iesire (Buffering Input/Output). Mai jos este dat un tabel care simuleaza introducerea si extragerea de date dintr-o coada Functia Continutul scozii qstore(X) qstore(Y) qstore(Z) qstore(W) qretrive() returneaza X qstore(A) qretrive() returneaza Y qretrive() returneaza Z qretrive() returneaza W qretrive() returneaza A Qstore(Q) X XY XYZ XYZW YZW YZWA ZWA WA A Coad goala, vida Q

13

CONCLUZIE

14

Bibliografie: 1. Herbert Schildt C 2. Liviu Negrescu

- Manual Complet, Bucuresti, Ed. Teora 1998 Limbajele C si C++ pentru incepatori, vol I- III, Cluj-

Napoca Volumul I - Limbajul C Volumul II - Limbajul C++ Volumul III - Limbajele C si C++ in Aplicatii

15

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