Sunteți pe pagina 1din 12

Cursul 11

POINTERI (I)
1. Operatorul adres (operatorul &)
Pentru a putea lucra cu un obiect (variabil simpl, tablou, funcie, etc), programatorul trebuie s dispun de o modalitate de referire (de indicare) a respectivului obiect. Cea mai simpl modalitate este referirea direct, prin utilizarea numelui obiectului, adic a identificatorului asociat acestuia n momentul declarrii lui. Dar nu orice obiect are un nume, de exemplu fiecare dintre cele 10 elemente ale tabloului tab declarat astfel
int tab[10];

este o variabil anonim de tip int. Pentru a accesa valorile unor astfel de variabile trebuie s folosim o modalitate de referire indirect, cum ar fi, n acest caz, utilizarea operatorului de indexare [ ]:
tab[2]=13;

Modalitatea general de referire indirect a unui obiect const n precizarea adresei sale de memorie prin intermediul unei variabile specializate, capabile s rein o astfel de adres. Orice obiect trebuie s ocupe, n momentul utilizrii, o anumit zon de memorie i, n consecin, orice obiect are o adres de memorie. Zona de memorie ocupat de un obiect se numeste locaia obiectului i, prin definiie, adresa unui obiect este adresa primului octet al locaiei sale. Amintim c memoria este organizat la nivel de octet, octeii fiind numerotai ncepnd de la numrul 1, adresele sunt memorate pe 32 bii (n cazul PC-urilor cu micro-procesoare pe 32 de bii) i, n consecin, spaiul de adrese este de la 1 la 232-1. Dac dispunem deja de o cale de referire la un obiect (printr-o expresie l-valoare: nume, expresie cu indici, etc.), putem afla adresa acestuia folosind operatorul adres (operatorul &), astfel:
#include<iostream> using namespace std; int main(){ int i; cout<<"adresa lui i char c; cout<<"adresa lui c struct Punct{ cout<<"\nOBS1 membru:\n"<<endl; cout<<"adresa cout<<"adresa cout<<"adresa

"<<&i<<endl; "<<(int*)&c<<endl;

double x,y;} A; adresa unei structuri==adresa primului lui A "<<&A<<endl; lui A.x "<<&A.x<<endl; lui A.y "<<&A.y<<endl;

int tab[10]; cout<<"\nOBS2 numele unui tablou==adresa tabloului:\n"<<endl; cout<<"adresa lui tab= "<<&tab<<endl; cout<<"numele lui tab= "<<tab<<endl;

cout<<"adresa lui tab[0]="<<&tab[0]<<endl; cout<<"adresa lui tab[1]="<<&tab[1]<<endl; int suma(int, cout<<"\nOBS3 cout<<"adresa cout<<"numele return 0; int); numele unei functii==adresa functiei:\n"<<endl; functiei suma="<<&suma<<endl; functiei suma="<<suma<<endl;

} int suma(int i, int j){ return i+j; } /* adresa lui i 0042FECC adresa lui c 0042FEC3 OBS1 adresa unei structuri==adresa primului membru: adresa lui A 0042FEA8 adresa lui A.x 0042FEA8 adresa lui A.y 0042FEB0 OBS2 numele unui tablou==adresa tabloului: adresa numele adresa adresa lui lui lui lui tab= 0042FE78 tab= 0042FE78 tab[0]=0042FE78 tab[1]=0042FE7C

OBS3 numele unei functii==adresa functiei: adresa functiei suma=00EC11F9 numele functiei suma=00EC11F9 Press any key to continue . . . */

Numele unui tablou este o constant care are ca valoare adresa tabloului, care coincide cu adresa primului element al tabloului; analog, numele unei funcii este o constant care are ca valoare adresa funciei, adic adresa primului octet al zonei de memorie ocupate de codul funciei. Adresa unei variabile simple sau a unei variabile de tip structur poate fi aflat numai cu ajutorul operatorului &. Operatorul & aplicat unei l-valori formeaz o expresie care, ca oricare alt expresie n C, are un anumit tip. Dac variabila x are tipul tip_x, atunci expresia &x are tipul pointer ctre tip_x. Mai precis, &x este o constant de tip pointer ctre tip_x. Un pointer este o variabil care are ca valoare adresa unei locaii de memorie. O variabil pointer este o variabil simpl, ea ocup numai spaiul necesar pentru scrierea unei adrese, (patru octei n cazul compilatorului MS Visual C++ 9.0). Dei toate variabilele pointer au valorile de acelai fel (adrese, adic numere n domeniul 1, 2, ... , 232-1), ele se deosebesc ntre ele prin tipul variabilelor la care fac referire (tipul intei). Avem astfel pointer ctre int, pointer ctre tablouri de 10 ntregi, etc. 2

De exemplu, dac x este o variabil de tip int i dorim s reinem undeva adresa sa, vom declara o variabil p de tip pointer ctre int astfel
int *p;

i apoi i atribuim valoarea pe care vrem s o reinem:


int main(void){ int x; int *p; p=&x; cout<< "x se afla la adresa " << p << endl; return 0; } /* x se afla la adresa 0012FF60 Press any key to continue . . .*/

Dup se observ, o declaraie de pointer declar de fapt tipul intei pointerului, astfel nct compilatorul s poat organiza calculele cu data referit de acel pointer.

2. Operatorul int (opreatorul *)


O variabil pointer, dup cum am vzut, are ca valoare adresa unui obiect (n general anonim) de un tip bine precizat, numit inta pointerului. Pentru a putea utiliza inta unui pointer trebuie s facem o referire ctre ea. Calea ctre inta pointerului p se obine prin aplicarea operatorului unar prefixat *, expresia *p desemneaz obiectul a crui adres este coninut de pointerul p, att ca r-valoare ct i ca l-valoare. Expresia *p este de fapt numele intei, poate s apar ntr-o expresie oriunde poate apare numele unui obiect de tipul intei (att n dreapta ct i n stnga unei atribuiri). Exemplu:
int i; int *p,*q; p=&i; i=13; cout<<"i="<<i<<" *p="<<*p<<endl;//i=13 *p=13 *p=12; cout<<"i="<<i<<" *p="<<*p<<endl;//i=12 *p=12 q=p; *q=11; cout<<"i="<<i<<" *p="<<*p<<endl;//i=11 *p=11 cout<<i+*p+*q<<endl; //33

In acest exemplu, pointerul p a fost iniializat cu adresa variabilei i i astfel inta lui p este i, iar expresia *p este sinonim cu numele variabilei i. Operatorul * ,inta lui , se aplic unui pointer i are ca rezultat o referin ctre inta pointerului. Amintim c operatorul & se aplic unei referine (unei l-valori) i are ca rezultat o constant de tip pointer ctre

Operatorii adres i int sunt unul inversul celuilalt (cu anumite precizri). Din acest motiv ei sunt numii (oarecum impropriu) operatorul de refereniere & i operatorul de derefereniere *. Dac x este o variabil de tip_x, atunci expresiile x i *&x sunt sinonime (au aceeai r-valoare i aceeai l-valoare, ambele nu au efecte secundare).
int j,k; k=12; j=*&k; cout<<"j="<<j<<endl;//j=12 *&k=13; cout<<"k="<<k<<endl;//k=13

De fapt, compilatorul sterge orice pereche *& ntlnit:


004114CC rep stos int h; h=108; 004114CE mov *&h=666; 004114D5 mov dword ptr es:[edi]

dword ptr [h],6Ch dword ptr [h],29Ah

...

Reciproc, dac p este un pointer, atunci p i &*p au aceeai r-valoare. Totui, deoarece rezultatul aplicrii operatorului adres nu are l-valoare, fiind o constant de tip pointer, cele dou expresii nu sunt complet echivalente:
int *p,*q; int h=10; p=&h; q=&*p; // p=0012FF48 q=0012FF48 cout<<"p="<<p<<" q="<<q<<endl; &*p=q; // error C2106: '=' : left operand must be l-value

//

3. Chestiuni elementare despre pointeri


a.) Declaraii de pointeri. In definirea unei variabile de tip pointer simbolul * poate fi interpretat ca fiind simbolul operatorului int. Mai precis, declaraia
int * pi;

poate fi scris sub forma echivalent


int* pi;

i citit pi este o variabil de tip int*, adic un pointer ctre int, sau poate fi scris, la fel de bine, sub forma
int *pi;

i citit aa: inta lui pi este o variabil de tip int, adic pi este un pointer a crei int este de tip int. Cnd declarm mai muli pointeri de acelai tip se utilizeaz numai aceast ultim interpretare: instruciunea
int *pi, *qi, *ti;

declar de fapt c intele celor trei pointeri au tipul int. Tipul abstract int*, util n multe alte situaii, aici poate provoca confuzii: instruciunea
int* p1, p2, p3;

nu declar trei variabile de tip int*, aa cu ar fi de ateptat, ci numai una, p1, celelalte dou fiind de tip int. Compilatorul o nelege sub forma
int (*p1), p2, p3;

Declararea unui pointer ctre un tip compus (tablou, funcie) se face dup aceeai regul ca n cazul pointerilor ctre tipurile simple: declarm de fapt tipul intei, dar acum mai trebuie sa inem cont i de prioritatea operatorilor implicai - amintim c operatorul unar * leag mai slab dect operatorii postfixai ( ) i [ ] . De exemplu, dac dorim s definim un pointer p ctre un tablou de 10 caractere, regula practic este urmtoarea: definim o variabil x de tipul intei:
char x[10];

dup care nlocuim numele x cu inta lui p, adic *p, scris ntre paranteze rotunde:
char (*p)[10];

Uneori parantezele rotunde nu sunt necesare, dar n cazul de mai sus ele nu pot fi omise, deoarece declaraia
char *p[10];

nu declar un pointer ci un tablou de 10 elemente de tip char*, adic un tablou de 10 pointeri ctre char. Explicaie: n declaraia de mai sus asupra operandului p acioneaz doi operatori: * i [ ], cum operatorul de indexare [ ] leag mai tare dect operatorul int * compilatorul citete aceast ultim declaraie sub forma:
char *(p[10]);

de unde deducem c p este un tablou cu 10 elemente, fiecare element e al su fiind dat de declaraia pe care o obinem nlocuind p[10] cu e
char *(e);

care este echivalent cu


char *e;

i care declar pe e ca pointer ctre char. Alt exemplu: cu instruciunea


double *f(int, int);

declarm o funcie f cu dou argumente int i care ntoarce un rezultat de tip double*, adic un pointer ctre double, iar cu instruciunea
double (*pf)(int, int);

declarm un pointer pf a crui int este dat de declaraia


double tinta(int, int);

adic este o funcie cu dou argumente int i un rezultat de tip double. In exemplele de mai sus observm utilitatea declaratorilor abstraci de tip secvene de cod care descriu tipurile compuse, cum ar fi int* (pentru tipul pointer catre int), double*, etc. Declaratorul abstract al unui anumit tip se obine tergnd pur i simplu numele variabilei din declaraia unei variabile de acel tip. Exemple: - din int x obinem int tipul int; - din double mat[10] obinem double[10] tablou de 10 elemente de tip double; - din int *p obinem int *, adic pointer ctre int - din char (*ptab)[5] obinem char (*)[5], adic pointer ctre tablouri de tip char [5] - din void (*p)(int, int) rezult void (*)(int, int), adic pointer ctre functii de tip void (int, int)

Deoarece declaraiile de pointeri ctre tipuri compuse pot deveni foarte complicate, este recomandat utilizarea lui typedef pentru redenumirea tipului int. Comparai cele dou modaliti echivalente de declarare a pointerului p din programul urmtor:
#include<iostream> using namespace std; void unu(){ char (*p)[10]; char text[10]="mesaj"; p=&text; cout<<*p<<endl; } void doi(){ typedef char Text[10]; Text *p; Text text="mesaj"; p=&text; cout<<*p<<endl; } int main(void){ unu(); //mesaj doi(); //mesaj return 0; }

S definim i pointeri ctre funcii:


#include<iostream> using namespace std; typedef int Functie(int, int); int suma(int i, int j){ return i+j; } int main(void) { Functie *pf; pf=suma; cout<<(*pf)(2,3)<<endl; return 0; }

Iat i o alt variant a programului de mai sus:


#include<iostream> using namespace std; typedef int Functie(int, int); typedef Functie *PFunctie; int suma(int i, int j){ return i+j; } int main(void) { PFunctie pf; pf=suma; cout<<(*pf)(2,3)<<endl; return 0; }

b.) Pointeri ctre structuri. Orice tip de dat poate fi inta unui pointer, n particular datele de tip structur:
#include<iostream> using namespace std; struct Punct { double x,y,z;}; int main(void){ Punct A={1,20,300}; Punct *p; p=&A; //cout<<*p.x<<endl; //error C2228: left of '.x' must have class/struct/union //did you intend to use '->' instead? cout<<(*p).x<<endl; //1 cout<<p->x<<endl; //1 return 0; }

Observm c la accesarea indirect (adic printr-un pointer) a membrilor unei structuri trebuie s inem cont de prioritatea operatorilor: operatorul int leag mai slab dect operatorul punct (operatorul de selecie membru). Expresia *p.x este neleas de compilator ca *(p.x) i este eronat (p nu este o structur), referirea la membrul x al intei lui p trebuie scris cu parenteze (*p).x, dup cum se vede n codul de mai sus. Deoarece accesarea indirect a structurilor (i a claselor) este foarte frecvent n programare, fiind singura lor modalitate de accesare n cazul alocrii dinamice, limbajul a fost prevzut cu operatorul de accesare indirect, operatorul sgeat, definit astfel: dac p este un pointer ctre o structur care are un membru x, atunci expresia p->x este sintactic echivalent cu (*p).x (adic la compilare prima este tradus n a doua). Utilizarea operatorului sgeat simplific foarte mult scrierea i mrete lizibilitatea codului. c.) Adresa unui pointer. Orice variabil pointer are o locaie n memorie (de patru octei n MS Visual C++ 9.0) i deci are o adres. Adresa unui pointer nu trebuie confundat cu adresa coninut de acel pointer (care este valoarea pointerului, adic adresa intei sale) :
#include<iostream> using namespace std; int main(void){ int k=12; int *pi; pi=&k; cout<<"adresa lui pi, cout<<"valoarea lui pi, cout<<"valoarea tintei, return 0; } /* REZULTAT adresa lui pi, &pi=0012FF54 valoarea lui pi, pi=0012FF60 valoarea tintei, *pi=12 Press any key to continue . .

&pi="<<&pi<<endl; pi="<<pi<<" (adresa tintei)"<<endl; *pi="<<*pi<<endl;

(adresa tintei) . */

In exemplul de mai sus, expresia &pi este o constant de tip pointer ctre pi, deci de tip pointer ctre tipul lui pi, cum tipul lui pi este int* obinem c &pi este o constant de tip pointer ctre int*, adic de tip int** (pointer ctre pointer ctre int). O variabil pointer pp creia s i se poat atribui valoarea constantei &pi trebuie declarat astfel:
int **pp;

Aceast declaraie poate fi scris i sub forma


int *(*pp);

adic inta intei lui pp este un int. Urmtoarea secven de cod este echivalent cu precedenta:
int k=12; int *pi=&k; int **pp; pp=&pi; cout<<"adresa lui pi, &pi="<<pp<<endl; cout<<"valoarea lui pi, pi="<<*pp<<" (adresa tintei)"<<endl; cout<<"valoarea tintei, *pi="<<**pp<<endl;

d.) Adresa adresei. Operatorul & nu poate fi aplicat n mod succesiv: &x este o constant (entitate fr locaie de memorie) iar &&x, adresa unei constante, nu are sens. In schimb, operatorul de derefereniere * poate fi aplicat succesiv, att timp ct rezultatul fiecarei dereferenieri este tot de tip pointer (vezi exemplul de mai sus). e.) Conversii ntre pointeri. Tipul pointerului trebuie respectat cu strictee. Dei toi pointerii au valorile de acelai fel (adrese), nu sunt permise atribuiri ntre pointeri de tip diferit. Exemplu:
int k; double *g; g=&k;// error C2440: cannot convert from 'int *' to 'double *'

Programatorul poate fora, pe propria raspundere, astfel de atribuiri prin conversii explicite utilizand operatorul cast:
#include<iostream> using namespace std; int main(){ int j,k; double jj,*pp; k=12; pp=(double *)&k; jj=*pp; cout<<"jj="<<jj<<endl; //jj=-9.25596e+061 j=(int)*pp; cout<<"j="<<j<<endl; j=*(int*)pp; cout<<"j="<<j<<endl;

//j=-2147483648

//j=12

jj=(double)*(int*)pp; cout<<"jj="<<jj<<endl; //jj=12 jj=-9.25596e+061;

j=(int)jj; cout<<"j="<<j<<endl; return 0; }

//j=-2147483648

Observm c pointerul pp, de tip double*, a fost ncrcat (printr-o expresie cast) cu adresa unui ntreg. Pentru a regsi n mod corect valoarea intei, trebuie s folosim iarai o conversie explicit: expresia (int*)pp are nelesul: dei pp este de tip double*, acum el este utilizat ca i cum ar fi de tip int*. Aceste conversii sunt necesare pentru c tipul intei precizeaz formatul intern al datei intite, iar formatul intern al unei date de tip int este complet diferit de al uneia de tip double. Un caz special l constituie pointerii de tip void*. Un pointer pv de tip void* poate conine, prin definiie, adrese ctre date de orice tip, n consecin, la o atribuire ctre pv nu mai sunt necesare conversii explicite, n schimb inta sa, *pv, are tipul nedeterminat i trebuie precizat prin expresii cast:
char ch,c='A'; char *pc=&c; int n=1; int *pn=&n; void *pv; //OK pv=pc; ch=*pv; // error: illegal indirection ch=*(char *)pv; cout<<"rez1: ch="<<ch<<endl; //rez1: ch=A pv=pn; cout<<"rez2: n="<<*(int *)pv<<endl; //rez2: n=1 pc=pv; //error: cannot convert from 'void *' to 'char *' pc=(char *)pv; //OK

//

//

f.) Conversii ntre pointeri i ntregi. Adresele (valorile pointerilor), dei sunt exprimate prin numere ntregi (obinute n urma numerotrii octeilor de memorie), nu sunt date de tip ntreg. In consecin, nu sunt permise atribuiri ntre pointeri i tipuri aritmetice. Sunt permise (dar ne-uzuale) doar conversii explicite ntre pointeri i tipul int.
int k; double d=13.13; double *p; p=&d; cout<<"p="<<p<<" *p="<<*p<<endl;//p=0012FF50 *p=13.13 k=(int)d; cout<<"k="<<k<<endl; //k=1245008 cout<<hex<<"k="<<k<<dec<<endl; //k=12ff50 k=1245008; p=(double *)k; cout<<"p="<<p<<endl; //p=0012FF50

Dup cum vom vedea, limbajul a fost prevzut cu operaii speciale asupra pointerilor, asa numita aritmetic a pointerilor, care fac inutile conversiile exemplificate mai sus. Este permis, fr conversie explicit, numai atribuirea
double *p;

p=0;

deoarece zero are o semnificaie special n acest context (vezi mai jos). g.) Iniializarea pointerilor. Ca orice alt variabil, un pointer trebuie definit i iniializat nainte de a fi folosit. Utilizarea unui pointer definit dar neiniializat conduce la erori grave de execuie:
#include<iostream> using namespace std; int main(void){ int *pn; cout<<"adresa lui pn:"<<&pn<<endl; //adresa lui pn:0012FF60 cout<<"valoarea lui pn:"<<pn<<endl; //valoarea lui pn:CCCCCCCC cout<<"valoarea tintei lui pn:"<<*pn<<endl; //Please tell Microsoft about this //problem! }

In exemplul de mai sus ncercm s citim un int aflat la adresa CCCCCCCCh, care se dovedete a fi ntr-o zon protejat memorie, fapt sancionat de sistemul de operare prin ntreruperea executrii programului. Nu trebuie confundat iniializarea unui pointer cu atribuirea unei valori intei sale; urmtorul program trece de compilare dar la rulare este i el scos din execuie:
#include<iostream> using namespace std; int main(void){ int a=5; int *p; *p=a; //Please tell Microsoft ... cout<<*p<<endl; return 0; }

Varianta corect este urmtoarea:


#include<iostream> using namespace std; int main(void){ int a=5; int *p; p=&a; cout<<*p<<endl;//5 return 0; }

Unui pointer i se poate atribui n mod corect o valoare iniial numai n urmatoarele trei moduri: 1. prin atribuirea adresei unui obiect deja definit, aa cum am vzut mai sus:
int a=5;

10

int *p; p=&a;

2. prin atribuirea valorii unui pointer deja iniitializat:


int a=5; int *p,*q; p=&a; q=p;

Un caz particular l constitue iniializarea cu pointerul nul:


double *p; p=NULL;

Pointerul NULL este de fapt o constant simbolic definit n <stdio.h> dat, n funcie de compilator, prin
#define NULL ((void *)0)

sau prin
#define NULL 0

astfel nct p=NULL atribuie pointerului p valoarea zero, fapt care semnaleaz c pointerul p conine o adres invalid (standardul limbajului C/C++ garanteaz c zero nu este adresa nici unei locaii de memorie). Biblioteca <stdio.h> este inclus n <iostream> i nu trebuie cerut n mod explicit printr-o directiv include. In C++ putem folosi (i chiar este recomandat) direct atribuirea p = 0. Atenie, nu avem voie s citim date de la adresa zero; prin urmare i acest program este scos din execuie:
#include<iostream> using namespace std; int main(void){ int *p; p=0; cout<<*p<<endl; //Please tell Microsoft ... return 0; }

Pointerul nul este util n multe situaii, de exemplu o funcie care returneaz un pointer poate semnala c rezultatul este invalid returnnd pointerul nul. Este clar c ntr-o astfel de abordare funcia care primete pointerul trebuie s testeze mai nti validitatea acestuia. Comarul programatorilor are un nume: null pointer exception. 3. prin atribuirea adresei unei locaii de memorie din zona alocat programului, adres obtinut prin utilizarea operatorului new (specific C++) sau a funciilor de alocare de memorie malloc, calloc, etc (specific C). Asupra acestei modaliti vom reveni pe larg mai trziu. O modalitate legal dar cu efecte imprevizibile de iniializare a unui pointer const n atribuirea unei valori ntregi printr-o expresie cast:
char *pc; pc=(char *)0x0012ff60;

O astfel de iniializare este complet hazardat i fr anse de reuit, programatorul nu poate ti a priori care va fi zona de memorie alocat programului la rulare. Prezentm, n final, un exemplu de utilizare a pointerilor ctre funcii: 11

#include<iostream> using namespace std; int fmax(int x, int y){ return x<y ? y : x; } int fmin(int x, int y){ return x<y ? x : y; } int fzero(int x, int y){ return 0; } int main(void){ int x, y; char c; int (*p)(int,int); cout<<"x="; cin>>x; cout<<"y="; cin>>y; cout<<"tastati <M ENTER> pentru maxim" <<" sau <m ENTER> pentru minim"<<endl; cout<<"c="; cin>>c; //numele unei functii este o constanta //de tip pointer catre acea functie: if(c=='M') p=fmax;// <=> p=&fmax; else if (c=='m') p=fmin; else p=fzero; cout<<"rezultat="<<(*p)(x,y)<<endl; return 0; }

12

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