11.07.2015 Views

LUCRAREA NR. 6 POINTERI

LUCRAREA NR. 6 POINTERI

LUCRAREA NR. 6 POINTERI

SHOW MORE
SHOW LESS

Create successful ePaper yourself

Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.

<strong>LUCRAREA</strong> <strong>NR</strong>. 6<strong>POINTERI</strong>Un pointer este o variabilă care păstrează adresa unui obiect de tip corespunzător. Forma generală pentru declarareaunei variabile pointer este:tip * nume_variabila;unde tip poate fi oricare din tipurile de bază admise în C, iar nume_variabila este numele variabilei pointer. Tipulde bază al pointerului defineşte tipul variabilelor spre care indică pointerul.6.1. Operatori pointerExistă doi operatori pointer speciali * şi &:• Operatorul & este un operator unar care oferă (returnează) adresa unei variabile (adresa operandului său).• Operatorul * este complementarul lui &. Este un operator unar care returnează valoarea variabilei plasată laadresa care urmează după acest operator.Exemplu:# include void main (void) {int *count_addr, count, val;count = 100; /* int count are valoarea 100 */count_addr = &count; /*count_addr indica spre count. */val = count_addr; /* val preia valoarea de la adresa count_addr. */printf ("%d", val); /*Se va tipari numarul 100 */}6.1.1. Importanţa tipului de bazăConsiderăm declaraţia: val = *count_addr;Se pune întrebarea: care va fi numărul de bytes ce va fi transferat variabilei val de la adresa indicată prin*count_addr. Sau, mai general, de unde ştie compilatorul câţi bytes să transfere în cazul oricărei asignăricare utilizează pointeri. Răspunsul la aceste întrebări este acela că, tipul de bază al pointeruluidetermină tipul datei spre care indică pointerul.Exemplu:/* Acest program nu lucreaza corect */# include void main (void) {float x = 10.12, y;short int *p; /* pointer la intreg */p = &x; /* p preia adresa lui x */y = *p; /* y preia valoarea de la adresa p */printf ("x = %f y = %f",x,y); }Acest program nu va atribui valoarea lui x lui y, deoarece în program se declară p ca fiind pointer laîntreg scurt şi compilatorul va transfera în y numai 2 bytes (corespunzători reprezentării unui întreg scurt)şi nu 4 bytes, corespunzători unui număr real în virgulă mobilă.6.1.2. Expresii în care intervin pointeriÎn general, expresiile în care intervin pointeri respectă aceleaşi reguli ca orice alte expresii dinlimbajul C.• Atribuirea pointerilorCa orice variabilă, un pointer poate fi folosit în membrul drept al unei instrucţiuni de asignare (atribuire),pentru atribuirea valorii unui pointer unui alt pointer.Exemplu:1


# include void main (void) {int x;int *p1,*p2; /* pointeri la intregi */p1 = &x; /* p1 indica spre x */p2 = p1 /* p2 indica tot spre x */printf ("p1 = %p p2 = %p", p1, p2); }/* Se afiseaza valoarea hexa a adresei lui x, nu valoarea lui x */Se observă că în funcţia printf() tipărirea se face cu formatul %p care specifică faptul că variabileledin listă vor fi afişate ca adrese pointer.• Operaţii aritmetice efectuate asupra pointeriloro Utilizarea operatorilor de incrementare şi decrementareFie secvenţa:int *p1; /* pointer la intreg */p1++;De fiecare dată când se incrementează p1, acesta va indica spre următorul întreg. Astfel, dacă p1 = 2000,după efectuarea instrucţiunii p1++, acesta va fi p1 = 2004 (va indica spre următorul întreg).‣ După fiecare incrementare a unui pointer, acesta va indica spre următorul element al tipului său debază.‣ După fiecare decrementare a unui pointer, acesta va indica spre elementul anterior.Valoarea pointerilor va fi crescută sau micşorată în concordanţă cu lungimea tipului datelor spre careaceştia indică, aşa cum se poate vedea în exemplul următor:Cum valoarea indirectată de un pointer este o l-valoare, ea poate fi asignată şi incrementată ca oricealtă variabilă. O l-valoare (left value) este un operand care poate fi plasat în stânga unei operaţii deatribuire. Verificaţi utilizarea pointerilor din programul următor:# include void main(void) {short *pi, *pj, t;long *pl; double *pd;short i, j;i=1; j=2; t=3;printf("i= %d, j= %d\n", i, j);pi=&i; pj=&j;printf("pi= %p, pj= %p\n", pi, pj);*pj /= *pi+1;printf("*pi= %d *pj= %d\n", *pi, *pj);*pj /= *pi+2;printf("*pi= %d *pj= %d\n", *pi, *pj);printf("++pj= %p, ++*pj= %d\n",++pj,++*pj); }o Utilizarea operatorilor de adunare şi de scădereLa sau dintr-un pointer, se pot aduna sau scădea valori de tip întreg. Rezultatul este un pointer deacelaşi tip cu cel iniţial, indicând spre un alt element din tablou. De exemplu,p1 = p1 + 9;face ca p1 să indice spre al 9-lea element având tipul lui p1, considerând că elementul curent este indicatde p1. Evident că valoarea pointerului se va modifica corespunzător lungimii tipului datei indicată prinpointer.Exemplu:int *p1; /* Pointer la intreg */p1 = p1 + 9;Dacă valoarea p1 = 3000, atunci p1 + 9 va avea valoarea:(valoarea lui p1)+9*sizeof(int)=3000+9*4=3036Aceleaşi considerente sunt valabile în cazul în care un întreg este scăzut dintr-un pointer. Dacă doipointeri de acelaşi tip sunt scăzuţi, rezultatul este un număr întreg cu semn care reprezintă deplasamentuldintre cei doi pointeri (pointerii la obiecte vecine diferă cu 1).În cazul tablourilor, dacă pointerul rezultat indică în afara tabloului, rezultatul este nedefinit.Dacă p indică spre ultimul membru dintr-un tablou, atunci (p+1) are valoare nedeterminată.Observaţii :• Nu se pot aduna sau scădea valori de tip float sau double la/sau dintr-un pointer.2


• Nu se pot efectua operaţii de înmulţire şi împărţire cu pointeri.Exemplu: Scăderea a doi pointeri este exemplificată în programul:# include void main(){int i=4, j;float x[] = {1,2,3,4,5,6,7,8,9}, *px;j = &x[i]-&x[i-2];px = &x[4]+i;printf("%d %f %p %p\n",j,*px,&x[4],px); }o Compararea pointerilorDoi pointeri de acelaşi tip se pot compara printr-o expresie relaţională, astfel: dacă p şi q sunt doipointeri, atunci instrucţiunile:if (p < q)printf (“ p indica spre o adresa mai mica decit q \n “);sunt corecte.Compararea pointerilor se utilizează când doi sau mai mulţi pointeri indică spre acelaşi obiectcomun.Exemplu: Un exemplu interesant de utilizare a pointerilor constă în examinarea conţinutului locaţiilor dememorie ale calculatorului./*Programul afiseaza continutul locatiilor de memorie dela o adresa specificata*/# include # include dump (start);void main (void) {unsigned long int start; /* start = adresa de inceput */printf (“Introduceti adresa de start: “);scanf (“ %lu “, &start);dump (start); } /* Se apeleaza functia dump () */dump (start) /* Se defineste functia dump() */unsigned long int start;{ char far *p;int t;p = (char far *) start; /*Conversie la un pointer*/for (t = 0; ; t++, p++) {if (!(t%16)) printf ("/n");printf ("%2X ", *p); /*Afiseaza in hexazecimal continutul locatieide memorie adresata cu *p*/if (kbhit()) return;} } /* Stop cand se apasa orice tasta */o Utilizarea pointerilor ca parametri formali ai funcţiilorÎn exemplele de până acum, s-au folosit funcţii C care atunci când erau apelate, parametrii acestorfuncţii erau (întotdeauna) actualizaţi prin pasarea valorii fiecărui argument. Acest fapt ne îndreptăţeşte sănumim C-ul ca un limbaj de apel prin valoare. Există totuşi o excepţie de la această regulă atunci cândargumentul este un tablou. Această excepţie este explicată, pe scurt, prin faptul că valoarea unui nume alunui tablou (vector, matrice etc.) neindexate este adresa primului său element.Folosind variabile pointer se pot pasa adrese pentru orice tip de date. Spre exemplu, funcţia scanf()acceptă un parametru de tip pointer (adresă): scanf(“%f“,&x);Ceea ce este important de evidenţiat este cum anume se poate scrie o funcţie care să accepte caparametri formali sau ca argumente pointeri ?.Funcţia care recepţionează o adresă ca argument va trebui să declare acest parametru ca ovariabilă pointer. De exemplu, funcţia swap() care va interschimba valorile a doi întregi poate fi declaratăastfel:# include void swap(); /*Prototipul functiei swap()*/void main(void){ int i,j; i=1; j=2;printf("i= %d j= %d\n", i, j);swap(&i,&j); /* Apelul functiei */printf("i= %d j= %d\n", i, j); }void swap(int *pi, int *pj){ int t;t = *pi; *pi = *pj; *pj = t; }3


6.2. Pointeri şi tablouriÎntre pointeri şi tablouri există o strânsă legătură în limbajul C. Există însă o mare deosebire între tablourişi pointeri pe care trebuie să o avem mereu în vedere. Un tablou constă întotdeauna dintr-o mare cantitatede memorie, îndeajuns de mare pentru a reţine toţi octeţii corepunzători tuturor elementelor tabloului.Astfel, tabloul q declarat ca short q[100]; rezervă 2x100 octeţi de memorie iar int q[100] rezervă 4x100octeţi de memorie.În C numele unui tablou fără indici este adresa de start a tabloului. De fapt, numele tabloului esteun pointer la tablou. Ca o concluzie, un pointer declarat sau numele unui tablou fără indici reprezintăadrese, pe când numele unui tablou cu indici se referă la valorile stocate în acea poziţie în tablou.Pentru a avea acces la elementul unui tablou, în limbajul C, se folosesc 2 metode :1. utilizarea indicilor tabloului;2. utilizarea pointerilor .Deoarece a doua metodă este mai rapidă, în programarea în C, de regulă, se utilizează aceastămetodă. Pentru a vedea modul de utilizare a celor două metode, considerăm un program care tipăreşte culitere mici un şir de caractere introdus de la tastatură cu litere mari:Exemplu: Versiunea cu indicivoid main (void) {char sir[80]; int i;printf ("Introduceti un sir de caractere scrise cu litere mari: \n");gets (sir);printf ("Acesta este sirul in litere mici: \n");for(i=0;sir[i];i++)printf("%c", tolower(str[i]));}Exemplu: Versiunea cu pointerivoid main (void) {char sir[80] , *p;printf ("Introduceti un sir de caractere scrise cu litere mari: \n");gets (sir);printf (" Acesta este sirul in litere mici: \ n");p = sir; /* p preia adresa de inceput a sirului */while (*p) printf (" %c ", tolower(*p++)); }Pointerii sunt rapizi şi uşor de utilizat când se doreşte referirea elementelor unui tablou în ordine strictcrescătoare sau strict descrescătoare. Dacă însă se doreşte referirea aleatoare a elementelor unui tablou,atunci indexarea tabloului este cel mai simplu şi sigur de utilizat.6.2.1. Indexarea pointerilorÎn C, dacă p este un pointer, iar i este întreg, p[i] este identic cu *(p+i). Dacă avem declaraţiile:short q[100];short *pqatunci sunt permise şi posibile următoarele declaraţii:Varianta cutabloupq=&q[0]pq=qq[n]Varianta cupointeripq=&q[0]pq=qpq[n]*(pq+n)4DescrierePointerul pq indică adresaprimului element altabloului qpq[n] înseamnă acelaşilucru cu *(pq+n)În C, dacă se pune un index unui pointer, ca în pq[n], atunci se consideră această utilizareechivalentă cu *(pq+n). Cu alte cuvinte, orice referire la pq[n] înseamnă acelaşi lucru cu valoarea de laadresa (pq+n).Exemplu: Programul următor realizează tipărirea pe ecran a numerelor cuprinse între 1 şi 10.#include void main (void) {int v[10]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };int *p, i;p = v; /* p indica spre v */for (i=0;i


Utilizarea constantelor şir în locul poinetrilor la caractere este posibilă dar nu este uzuală.Exemplu:# include void main(){char *sir = "To be or not to be", *altsir;printf("%s\n", "That don't impress me much"+5);printf("%c\n",*("12345"+3));printf("%c\n","12345"[1]);puts("string\n");altsir = "American pie";printf("sir = %s\naltsir = %s\n",sir,altsir);}6.2.2. Pointeri şi şiruriDeoarece numele unui tablou fără indici este un pointer la primul element al tabloului, pentruimplementarea unor funcţii care manipulează şiruri, se pot utiliza pointeri. Stim că funcţia strcmp(s1, s2)realizează compararea şirurilor s1 şi s2 şi întoarce 0 dacă s1 = s2, o valoare negativă, dacă s1 < s2 şi ovaloare pozitivă, dacă s1 > s2.Exemplu: Prezentăm o variantă de scriere a funcţiei strcmp(s1,s2)char *s1, *s2;{ while (*s1)if (*s1 - *s2)return *s1-*s2; /* Returneaza diferenta */else {s1++; s2++;}return '\0';} //Se returneaza 0 in caz de egalitateReamintim că un şir în C se termină cu caracterul NULL. De aceea, instructiunea while(*s1)rămâne adevărată până când se întâlneşte caracterul NULL, care este o valoare falsă.Dacă într-o expresie se utilizează un şir constant, calculatorul tratează constanta ca pointer laprimul caracter al şirului.Exemplu: Programul următor afişează pe ecran mesajul " Acest program funcţionează ":# include void main (void) {char *s;s = " Acest program functioneaza ";printf (s); }6.2.3. Preluarea adresei unui element al unui tablouPână acum s-a văzut că un pointer poate să adreseze primul element al unui tablou. Este posibil săse adreseze orice element al unui tablou aplicând operatorul & unui tablou indexat. De exemplu,p = &x[2];plasează adresa celui de-al 3-lea element al vectorului x în pointerul p. Un domeniu în care aceastăpractică este esenţiala constă în găsirea unui subşir într-un şir dat.Exemplu: Programul următor afişează ultima parte a unui şir introdus de la tastatură, din punctul în carese întâlneşte primul spaţiu:# include void main (void) {char s[80];char *p;int i;printf (" Introduceti un sir : \n ");gets (s); /* Gaseste primul spatiu sau sfarsitul sirului */for (i = 0; s[i] && s[i] != ' '; i++)p = & s[i+1];printf (p); }Dacă p indică spre un spaţiu, programul va afişa spaţiul şi apoi subşirul rămas. Dacă în şirulintrodus nu este nici un spaţiu, p indică spre sfârşitul şirului şi atunci nu se va afişa nimic. De exemplu,dacă se introduce “my friend“, atunci printf() afişează mai întâi un spaţiu şi apoi “friend“.6.2.4. Tablouri de pointeriPutem construi tablouri de pointeri în aceeaşi manieră în care se definesc alte tipuri de date.Exemplu:int *x[10]; // Vector de 10 pointeri la intregi5


char *p[20]; // Vector de 20 pointeri la caracterPentru atribuirea unei variabile întregi, var, celui de al treilea element al tabloului de pointeri*x[10], se va scrie:x[2] = &var;Pentru găsirea valorii lui var, se va scrie:y = *x[2]; //Valoarea lui var este atribuita lui yAtentie !. Trebuie facută distincţia între:int *v[10]; // Tablou de 10 pointeri la intregiint (*v)[10]; // Pointer la un tablou de 10 intregiPentru aceasta trebuie ţinut cont de faptul că * este un operator prefixat, iar [] şi () sunt operatoripostfixaţi. Deoarece prioritatea operatorilor postfixaţi este mai mare decât cea a operatorilor prefixaţi,atunci când se doreşte schimbarea priorităţii, trebuie folosite paranteze.6.2.5. Pointeri la pointeriUn tablou de pointeri este ceea ce numim pointeri la pointeri. Conceptul de tablou de pointeri estesimplu, deoarece indexarea tabloului conduce la clarificarea semnificaţiei lui.Un pointer la un pointer este o formă de indirectare multiplă sau un lanţ de pointeri.În cazul unui pointer la pointer, primul pointer conţine adresa celui de-al doilea pointer, care indică sprevariabila ce conţine valoarea dorită:Pointer Pointer VariabilăAdresă ---------> Adresă ---------> ValoareDeclararea indirectărilor multiple se face sub forma:Pentru a avea acces la o valoare indirectată printr-un pointer la pointer este necesară, de asemenea,utilizarea operatorului * de două ori, aşa cum se vede în exemplul următor:# include void main (void) {int x, *p, **q;x = 10;p = &x; /* p preia adresa lui x */q = &p; /* q preia adresa lui p */printf(" %d ", **q); } /*Se afiseaza valoarea lui x*/6.2.6. Alocarea dinamică a memorieiExistă două metode principale prin care un program C poate memora informaţii în memoriaprincipală a calculatorului.• Prima metodă foloseşte variabilele globale şi locale. În cazul variabilelor globale, memoria ce li sealocă este fixă pe tot timpul execuţiei programului. Pentru variabilele locale, programul alocă memorie înspaţiul stivei, în timpul execuţiei programului. Deşi variabilele locale sunt eficient de implementat, în C,de multe ori, utilizarea acestora, necesită cunoaşterea în avans a cantităţii de memorie necesare în fiecaresituaţie.• A doua metodă de alocare a memoriei, constă în utilizarea funcţiilor de alocare dinamică malloc()şi free(). Prin această metodă, un program alocă memorie pentru diversele informaţii în spaţiul memorieilibere numită heap, plasată între programul util şi memoria sa permanentă şi stivă. Se observă că stivacreşte în jos, iar dimensiunea acesteia depinde de program.H ighLowStivaM emorie liberápentru alocare(heap)V ar iabile globale(statice)Pr ogr am6M em oriaUn program cu multe funcţii recursive va folosi mult mai intens stiva în comparaţie cu un programce nu utilizeaza funcţii recursive, aceasta deoarece adresele de retur şi variabilele locale corespunzătoareacestor funcţii sunt salvate în stivă.


Funcţiile malloc() şi free()Aceste funcţii formează sistemul de alocare dinamică a memoriei în C şi fac parte din fisierulantet . Acestea lucrează împreună şi utilizează zona de memorie liberă plasată între codulprogram şi memoria sa permanentă (fixă) şi vârful stivei, în scopul stabilirii şi menţinerii unei liste avariabilelor memorate. De fiecare dată când se face o cerere de memorie, funcţia malloc() alocă o partedin memoria rămasă liberă. De fiecare dată când se face un apel de eliberare a memoriei, funcţia free()eliberează memorie sistemului.Declararea funcţiei malloc() se face sub forma:void *malloc (int numar_de_bytes);Aceasta întoarce un pointer de tip void, ceea ce înseamnă că trebuie utilizat un şablon explicit de tipatunci când pointerul returnat de malloc() se atribuie unui pointer de un alt tip. Dacă apelul lui malloc() seexecută cu succes, malloc() va returna un pointer la primul byte al zonei de memorie din heap ce a fostalocată. Dacă nu este suficientă memorie pentru a satisfce cererea malloc(), apare un eşec şi malloc()returnează NULL. Pentru determinarea exactă a numărului de bytes necesari fiecărui tip de date, se poatefolosi operatorul sizeof(). Prin aceasta, programele pot deveni portabile pe o varietate de sisteme.Funcţia free() returnează sistemului memoria alocată anterior cu malloc(). După eliberareamemoriei cu free(), aceasta se poate reutiliza folosind un apel malloc().Declararea funcţiei free() se realizează sub forma:free(void *p);Funcţia free() eliberează spaţiul indicat de p şi nu face nimic dacă p este NULL. Parametrul actual ptrebuie să fie un pointer la un spaţiu alocat anterior cu malloc(), calloc() sau realloc().Exemplu: Următorul program va aloca memorie pentru 40 de întregi, va afişa valoarea acestora, dupăcare eliberează zona, utilizând free():# include # include void main(void) {int t, *p;p = (int *) malloc(40*sizeof(int));if (!p) printf("Out of memory \n"); //Verificati daca p este un pointer corectelse {for (t=0; t


Pentru a găsi adresa unei variabile structură, se plasează operatorul & înaintea numelui variabileistructură. De exemplu, dându-se următorul fragment :struct balanta {float balance;char name[80];} person;struct balanta *p; /* se declara un pointer la structura */atunci: p = &person; plasează adresa lui person în pointerul p. Pentru a referi elementul balance, se vascrie: (*p).balanceDeoarece operatorul punct are prioritate mai mare decât operatorul *, pentru o referire corectă aelementelor unei structuri utilizând pointerii sunt necesare paranteze.Actualmente, pentru referirea unui element al unei variabile structură dându-se un pointer la aceavariabilă, există două metode: Prima metodă utilizează referirea explicită a pointerului nume-structură,iar a doua metodă, modernă, utilizează operatorul săgeată -> (minus urmat de mai mare).Exemplu: Pentru a vedea cum se utilizează un pointer-struct, examinăm următorul program care afişeazăora, minutul şi secunda utilizând un timer software.# include void actualizeaza();void afiseaza(), delay();struct tm { /* se defineste structura tm */int ore;int minute;int secunde;};void main(){struct tm time; // Declara structura time de tip tmtime.ore = 0;time.minute = 0;time.secunde = 0;for (;;) {actualizeaza (&time);afiseaza (&time); }}void actualizeaza(t)struct tm *t; {t->secunde ++;if (t->secunde == 60) { t->secunde = 0; t->minute ++; }if (t->minute == 60) { t->minute = 0; t->ore ++;}if (t->ore == 24) t->ore = 0; delay();}void afiseaza(t) // Se defineste functia afiseaza()struct tm *t; {printf ("%d : ", t->ore); printf ("%d : ", t->minute);printf ("%d ", t->secunde); printf ("\n");}void delay() /* Se defineşte funcţia delay() */{ long int t; for (t = 1;t

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!