Sunteți pe pagina 1din 11

POINTERI Pointerul este o variabil care conine o adres de memorie. Aceast variabila conine adresa unei variabile.

Avantajele utilizrii pointerilor sunt: - ofer posibilitatea de a modifica argumentele de apelare a funciilor; - permit o alocare dinamic a memoriei; - pot mbuntii eficiena anumitor rutine. Pointerii reprezint una din cele mai puternice caracteristici ale limbajului C, dar i periculoase. Dac pointerii nu sunt iniializai corect sau dac conin valori incorecte pot determina blocarea calculatorului, sau s conduc la erori greu de depistat. Variabile de tip pointer Dac o variabil urmeaz s conin un pointer( o adresa, aceasta trebuie declarat astfel: tip *nume; unde: - tip este tipul de baz al pointerului, si specific tipul obiectului pe care pointerul l poate puncta (indica, pointa); - nume este numele variabilei de tip pointer; - * - asterix operator de indirectare. Tipul de baz al pointerilor definete tipul de baz ctre care indic acesta. De exemplu, dac o variabil numit px conine adresa unei variabile x de tip ntreg se spune c px pointeaz pe x. Dac x se afl n memorie la adresa 200, atunci px va avea valoarea 200. Fie declaraia: int *px, x; / px pointer la ntreg, x variabil de tip ntreg */ Operatorii folosii n cazul pointerilor sunt & i *. Operatorul & adresa luireturneaz adresa de memorie a variablilei pe care o precede. px = & x; /* px contine adresa lui x */ Operatorul * (de la adresa) returneaz valoarea de la adresa pe care o precede. y = *px; /*daca px este adresa lui x, y devine egal cu x , adica cu ce e la adresa px*/ Este important ca variabilele de tip pointer s indice ntotdeauna corect tipul de date. De exemplu, dac declarai un pointer de tipul int, compilatorul nelege c adresa pe care o conine memoreaz o variabila de tip ntreg, i nu de alt tip. Fie urmtorul program: # include <stdio. h> void main (void) { int *p; float a, b; p = &a; /* p indic ctre un float, dei este pointer de tip ntreg */ b = *p; }

Observatie. Compilatorul nu d mesaj de eroare (eventual avertisment), dei pointerului p de tip ntreg i se atribuie adresa unui float (a). n final lui b nu i se atribuie valoarea lui a. Pointerul p fiind de tip ntreg, vor fi transferai n b doar doi octei, nu toi cei opt care formeaz n mod normal un numr n virgul mobil. Valoarea unei variabile poate fi referit printr-un pointer, proces numit indirectare. Operatorul * poate fi folosit n partea stng a unei instruciuni de asignare pentru a atribui o noua valoare folosind un pointer. Exemplu: # include <stdio.h> void main (void) { int a, *b; a = 55; // variabila a primete valoarea 55 b = &a; // b primete adresa lui a *b = 135; // asigneaz lui a valoarea 135 folosind pointerul b printf ( valoarea lui a = %d , a); } programul asigneaz lui a o valoare, indirect, folosind pointerul b. Prin declararea unui pointer, se creaz o variabil capabil s conin o adres de memorie. Iniializarea unui pointer nu are nici un sens, este o greeal. Exemplu: # include <stdio.h> void main (void) { int *a; *a = 123; // incorect a nu puncteaz nici un obiect } Printr-o instruciune de atribuire unui pointer poate s i se atribuie un alt pointer. Exemplu: # include <stdio.h> void main (void) } int a = 1234; int *b, *c; b = &a; c = b; printf (Valoarea lui a = %d Adresa lui a este %p , a,c); } Aritmetica pointerilor In plus, fa de operatorii * i &, n expresiile cu pointeri pot fi aplicai numai operatorii aritmetici +, ++, - i --. Mai mult, unui pointer i pot fi adunate/sczute numai cantiti ntregi. Operaiile cu pointeri sunt efectuate relativ la tipul pointerului. De fiecare dat cnd un pointer este incrementat, el va puncta articolul urmtor. De exemplu, dac avem declaraia: int *p; i c el conine adresa 1000. Dup executarea instruciunii: p ++; i dac tipul int se reprezint pe 2 bytes, p va avea valoarea de adres 1002 i nu 1001. Pointerul p va indica spre urmtorul ntreg. Acelai lucru este valabil i pentru
2

decrementare. n aceleai condiii: p --; determin ca p s aibe valoarea de adres 998. Dac p ar fi un tip float (tipul float reprezint de exemplu 4 bytes, octeti) si adresa lui p ar fi 1000: float *p; execuia instruciunii: p ++; pointerul p va conine valoare 1004. n concluzie de fiecare dat cnd este incrementat/decrementat un pointer, el va indica spre locaia din memorie a urmtorului/predecesorului element de acelai tip cu tipul su de baz. Se pot aduna/scdea ntregi la/sau din pointeri. De exemplu instruciunile: int *a, *b; ........ b = b 6; a = a +5; face ca pointerul a s indice al 5-lea element de acelai tip cu a dup cel pe care l indic n mod curent, respectiv b indic ntregul aflat n memorie cu 6 ntregi naintea sa. n afara adunrii, scderii dintre un pointer i un ntreg, incrementrii i decrementrii, este permis diferena ntre 2 pointeri pentru a determina numrul de obiecte de acelai tip care separ cei doi pointeri. Operaiile de incrementare/decrementare pot fi aplicate asupra pointerului nsui sau asupra obiectului pe care l puncteaz. Trebuie procedat cu pruden dac se ncearc s se incrementeze obiectul pe care l puncteaz un pointer. De exemplu: int p; instruciunea: p ++; obine mai nti valoarea pe care o puncteaz p i apoi p este incrementat pentru a puncta elementul urmtor. Pentru a incrementa obiectul pe care l puncteaz p trebuie s folosim instruciunea: ( *p ) ++; Parantezele fac ca valoarea punctat de p s fie incrementat. Compararea pointerilor Sunt admise compararea a doi pointeri folosind operatorii relaionali, dac pointerii puncteaz acelai obiect, aa cum este tabloul sau irul de caractere. Exemplu: Program ce asigur utilizarea unei stive de tip LIFO (ultimul intrat primul deservit). Funciile depune() i extrage() simuleaz instruciunile push i pop. Dac valoarea introdus de la tastatur este diferita de 0, valoarea se depune n stiv, n caz contrar se extrage ultima valoare introdus. Bucla creat se termin la introducerea valorii 1. # include <stdio.h> # include <stdlib> # define DIM 100 void depune (int i); // push int extrage (void); // pop int *vrf, *p, stiva [DIM]; void main (void)
3

{ int val; vrf = stiv; // indic vrful stivei p = stiv; // iniializeaz p cu adresa stivei do { printf (Introducei valoarea:); scanf( %d, &val); if (val ! = 0) depune(val); else printf (valoarea extras este : %d \n, extrage () ); } while (val! = -1); } void depune(int i) { p ++; if (p = = (virf + DIM)) { printf ( Depire stiv); exit (1); } *p = i; } int extrage (void) { if (p = = vrf) } printf(Stiv vid); exit (1); } p - -; return *(p + 1); } Variabila vrf pstrez adresa din memorie a vrfului stivei, implementat prin tabloul de ntregi stiv. Cu ajutorul variabilei p avem acces efectiv la stiv. n funciile depune () i extrage () se efectueaz un test relaional asupra pointerului p pentru a testa erorile de depire a limitelor stivei. Astfel n funcia depune(), variabila p este comparat cu sfritul stivei adunndu-se la vrf valoarea DIM (dimensiunea stivei), iar n funcia extrage() p este comparat cu vrf pentru a se asigura c nu a aprut o depire inferioar. Observaie. n instruciunea return sunt necesare paranteze deoarece fr ele instruciunea ar arta astfel: return * p + 1 ; i n acest caz ar returna valoarea locaiei p la care s-a adugat valoarea 1 i nu valoarea locaiei p + 1. Pointeri i tablouri ntre pointeri i tablouri exist o strns legtur, ei sunt interschimbabili. Aceast legtur face ca implementarea lor mpreun s fie unic i puternic. Dac folosii numele unui tablou fr indice, generai de fapt un pointer ctre primul element al tabloului. Acest lucru permite ca aceast valoare s fie asignat unui alt pointer i astfel devine posibil accesarea elementelor tabloului folosind pointerul. De exemplu:
4

char ir [40], * p; p = ir; Variabila p a fost iniializat cu adresa primului element din ir, ir[0]. Pentru a accesa al treilea element din ir se poate scrie: ir[2] sau *(p + 2) Ambele construcii vor returna al treilea element. Elementele unui tablou ncep de la 0. Pentru a avea acces la al treilea element, trebuie folosit indicele 2 pentru ir sau s adugai 2 la pointerul p, deoarece p indic efectiv spre primul element din ir( spre sir[0] ). Limbajul C ofer dou metode de acces la elementele unui tablou: aritmetica pointerilor sau indicii tablourilor. Prima este mai rapid. Exemplu: # include <stdio.h> void main (void) { int *p, a[5] = {1,2,3,4,5}; int i; p = a; //asigneaz lui p adresa de nceput a tabloului printf (Afiarea tabloului folosind indicii tabloului \n); for (i =0; i <5; i++) printf(a[%d] = %d \n, i, a[i] ); printf (Afiarea tabloului folosind pointeri, nu indici de tabel \n); for (i = 0; i <5; i ++) printf ( a[%d] = %d \n, i, * (p + i) ); } Ambele instruciuni printf() afieaz acelai lucru. n expresia * (p + i) sunt necesare paranteze deoarece operatorul * referitor la pointeri are o preceden mai mare dect operatorul +. Exemplu. Se cere un program care s citeasc de la tastatur un ir de caractere, apoi s-l afieze cu lietere mari (folosind funcia toupper () respectiv cu litere mici (funcia tolower () ). O prima versiune ar fi folosind aceea n care pentru a accesa caracterele din ir se indexeaz numele tabloului, iar alta ar fi folosirea unui pointer pentru a accesa caracterele irului. # include <stdio.h> # include <ctype.h> void main (void) { char ir [40], *p; int i; printf(Introducei irul : \n); gets (ir); for (i = 0; sir[i]; i++) //ultimul caracter din sir este /0 sir[i] = toupper[i]; printf ( %s \n, ir); // afiare cu litere mari prima variant p = ir; // p pointeaz primul element din ir while (*p) { * p = toupper(p); p ++;
5

} printf ( %s \n, ir); // afiare cu litere mari varianta 2 for (i = 0; sir[i]; i++) ir[i] = tolower (ir[i]); printf ( %s \n , ir); // afiare cu litere mici prima variant p = ir; // resetarea pointerului p while (*p) { *p = tolower(p); p ++; } printf ( %s \n , ir); //afiare cu litere mici a 2-a variant } Instruciunea: while (*p) { * p = tolower (p); p ++; } poate fi scris i astfel: while (*p) p++ = tolower (*p); Asemntor modului n care se incrementeaz un pointer se poate decrementa un pointer. Exemplu. Program care copiaz coninutul unui ir ntr-un alt ir n ordine invers. # include <stdio.h> # include <string .h> void main (void) { char ir1 = Acesta este un ir; char ir2 [40], * p1, *p2; p1 = ir1 + strlen(ir1) 1; // p1 pointeaz sfritul irului ir1 p2 = ir2; while (p1 >=ir1) *p2 ++ = * p1-- ; *p2 = \0; // caracterul null ncheie irul ir2 printf ( %s \n %s, ir1, ir2); } Pointerul p1 puncteaz sfritul irului sir1, iar pointerul p2 nceputul irului ir2. Compararea pointerilor n bucla while, asigur oprirea procesului de copiere atunci cnd este atins nceputul irului ir1. Pointeri i iruri iniializate (Iniializarea pointerilor) La ntlnirea unui ir iniializat, compilatorul depune irul in tabela de iruri a programului i genereaz un pointer ctre el. # include <stdio.h> void main (void) }
6

char *p, ir [ ] = Acesta este un ir; p = ir; printf (p); p = Acesta este un ir; printf (p); } Variabila p este declarata ca pointer de tip char, deci el poate puncta un tablou de caractere. La compilarea liniei: p = Acesta este un ir; compilatorul depune irul n tabloul de iruri al programului i asigneaz lui p adresa irului n tablou. Programul poate fi rescris astfel: # include <stdio.h> void main (void) { char *p = Acesta este un ir; printf(p); } Variabila p este iniializat s puncteze un ir. Exemplu. Program care citete iruri de caractere pn se introduce parola corect. # include <stdio.h> # include <string.h> char *p = parola; void main (void) { char str [40]; do { printf (Introducei parola corect \n); gets(s); } while (strcmp (p, ir)) ; } Tablouri de pointeri Pointerii pot fi organizai sub forma de tablouri ca oricare alt tip de date. Declararea unui tablou de pointeri de tip int de 10 elemente este: int *x[10]; Pentru a atribui adresa unei variabile de tip ntreg cu numele num elementului al doilea al tabloului de pointeri x, se va scrie: x [1] = & num; iar pentru a obine valoarea num se va scrie: *x[1]. Tablourile de pointeri sunt folosite deobicei pentru a pstra pointeri ctre iruri de caractere . Exemplu. Funcia eroare ( ) afieaz un mesaj de eroare pe baza valorii parametrului num_err. char * p [ ] = { Lipsete ; , Lipsete ( ,
7

Lipsete ) , Lipsete { , Lipsete } }; void eroare (int num err) { print ( p[num_err]); // printf( %s, p[num err]); } Pointerul p pstreaz pointeri ctre fiecare ir. De exemplu dac num _err = 2 se va afia: Lipsete ) Exemplu. Program ce folosete un tablou bidimensional de pointeri pentru a crea un tabel de iruri care leag diferite obiecte de locul n care se afl. # include <stdio.h> # include <sting.h> char * p [ ] [2] = { cartea, n bibliotec, creion, pe masa, pix, n penar, guma, sub mas, , //irul null ncheie tabela } void main (void) { int i; char obiectul [40]; printf (Introducei numele obiectului cutat: ); gets (obiectul); for (i = 0; * p[i] [0]; i ++) { if (! strcmp (obiectul, p[i] [0])) printf ( %s este %s \n, obiectul, p [i] [1]; } } Indirectare multipl Este posibil s avem un pointer care s puncteze un alt pointer. Aceast situaie este denumit indirectare multipl sau pointer la (ctre) pointer. Pointer Pointer Variabil
Adresa Adresa Valoare

Indirectare multipl Cnd un pointer puncteaz un alt pointer, primul pointer conine adresa celui de al doilea pointer care puncteaz locaia ce conine obiectul, valoarea dorit. Pentru a declara un pointer ctre un alt pointer, trebuie plasat un asterix suplimentar n faa numelui pointerului. De exemplu:
8

char ** pp; pp este un pointer ctre un alt pointer de tip char, i nu un pointer ctre un caracter. Accesarea valorii printr-un pointer ctre un alt pointer, cere ca operatorul asterix s fie aplicat de dou ori. De exemplu: p = &ch; // asigneaz lui p adresa lui ch pp = &p; //asigneaz lui pp adresa lui p **pp = c; // asigneaz lui ch valoarea c folosind indirectare multipl Observaie. Pointeri ctre pointeri pot produce confuzii. De aceea, indirectarea excesiv este derutant i surs de erori conceptuale. Pointeri la funcii Un pointer la o funcie este o variabil care conine adresa punctului de intrare al funciei. n urma compilrii i link-editrii fiecare punct de intrare are o adres fizic, adres care este folosit de fiecare dat cnd funcia este referit (apelat). Deci este normal s avem un pointer care s o puncteze. Folosind un pointer care puncteaz o funcie, este posibil s apelm funcia folosind pointerul. Pentru a crea o variabil care s poat puncta (indica) o funcie, trebuie declarat un pointer de acelai tip cu tipul funciei, urmat de parametrii funciei. De exemplu, declaraia: float (*pf) (float x, floar y); stabilete c pf este un pointer la o funcie care returneaz un float i are 2 parametrii x i y de tip float. Observaie. Este necesar ca, construcia *pf s fie inclus ntre paranteze rotunde din cauza regulilor de peceden a operatorilor. Pentru a asigura unui pointer adresa unei funcii, trebuie folosit numele funciei fr paranteze. Fie prototipul unei funcii: float dif (float x, float y); i pointerul pf declarat mai sus, instruciunea pf = dif; este corect. n urma acestei asignri, funcia dif ( ) se poate apela indirect prin pf, prin instruciunea de forma: reg = (*pf) (3.5, - 4.5); Programul complet pentru acest exemplu este: # include <stdio.h> float dif (float x, float y); void main (void) { float (*pf) (float x, float y); float rez; pf = dif; // adresa funciei se atribuie lui pf rez = (*pf) (3.5, - 4.5); printf (rezult = %lf \n , rez); } Una din cele mai importante utilizri ale pointerilor la funcii apare atunci cnd este creat un tablou de pointeri la funcii, fiecare element din tablou pointeaz la o alt funcie.
9

Pentru a apela o anumit funcie va trebui s indexm tabloul. Exemplu: # include <stdio.h> int sum (int a, int b); int dif (int a, int b); int mul (int a, int b); int div (int a, int b); int (* p[4]) (int a, int b); // void main (void) { int rez; int i, j, op; p[0] = sum; // adresa funciei sum ( ) // n p[1] = dif; // adresa funciei dif ( ) // n p[2] = mul; // adresa funciei mul ( ) // n p[3] = div; // adresa funciei div ( ) // n printf (Dai dou numere: ); scanf ( %d %d , &i, &j); printf ( Opiuni: 0 adunare, 1 scdere, 2 nmulire, 3 imprire \n); do { printf ( Introducei numrul opiunii:); scanf ( %d , &op); } while (op < 0 || op >3); rez = (* p[op] ) (i,j); printf ( %d, rez); } int sum (int a, int b) { return a + b; } int dif (int a, int b); } return a b; } int mul (int a, int b); } return a * b ; { int div (int a, int b); { if (b) return a/b; else return 0; } Programul cere utilizatorului introducerea a dou numere i i j i op numrul operaiei. Acest numr este folosit pentru a indica tabloul de pointeri la funcii, astfel nct s se apeleze indirect funcia dorit. Sigur c programul putea fi realizat cu instruciunea switch ( ), dar folosirea tabloului de pointeri la funcii este mai eficient. Tabloul de pointeri la funcii poate fi iniializat ca orice alt tablou deci programul poate fi rescris astfel: -----10

int (* p[4] ) (int a, int b) = {sum, dif, mul, div}; Folosirea pointerilor ca parametri Parametrii transferai unei funcii C pot fi valori sau adrese. Primul tip numit i apel prin valoare, copiaz valoarea unui argument (parametru) formal al funciei. Modificrile efectuate asupra parametrului nu au efect asupra argumentului. Al doilea mod de transfer de argumente ctre funcii este apelul prin referin (prin adres). n acest caz este copiat adresa unui argument. n interiorul funciei adresa este folosit pentru acces la argumentul folosit efectiv la apelare, deci modificrile asupra argumentului l afecteaz. Adresa argumentului nu este altceva dect un pointer. Exemplul 1. Program care apeleaz o funcie ce inverseaz ntre ele valorile a dou variabile de tip float indicate de ctre argumentele sale. # include <stdio.h> void inverseaz (float * x, float * y); void main (void) { float a = 12.55, b = -75.66; inverseaz (&a,&b); // paseaz adresele lui a i b printf (a = %f) b = % f \n, a, b): } void inverseaz (float * x, float * y) { float temp; temp = *x; * x = * y; * y = temp; } Dac un tablou este folosit ca argument al unei funcii, acesteia i este pasata adresa tabloului. Instruciunile funciei apeleaz asupra coninutului efectiv al tabloului i pot s-l modifice. Exemplul 2. Funcia majuscule ( ) afieaz caracterele unui argument de tip ir cu majuscule # include <stdio.h> # include <ctype.h> void majuscule (char * ir); void main (void) { char s [40]; gets(s) majuscule(s); } void majuscule (char * ir) { int t; for (t = 0; ir [t]; t ++) { ir[t] = toupper(ir[t] ); printf ( %c, ir[t]) ; }

11