Sunteți pe pagina 1din 15

POINTERI

În capitolele anterioare, variabilele au fost explicate ca locații în memoria calculatorului ce pot fi


accesate cu ajutorul indentificatorilor (numele lor). În acest fel, programul nu mai trebuie să aibă grijă de
adresele fizice ale datelor din memorie; pur și simplu folosește identificatorul de fiecare dată când are
nevoie să se refere la variabilă.

Pentru un program C++, memoria unui calculator ca o succesiune de celule de memorie, fiecare dintre
ele ocupând un byte și având o adresă unică. Aceste celule de un byte sunt ordonate astfel încât
reprezentarea datelor mai largi de un byte să ocupe celule de memorie cu adrese consecutive.

În acest fel, fiecare celulă poate fi ușor localizată în memorie au ajutorul adresei unice. De exemplu,
celula de adresă 1776 este întotdeauna imediat după celula cu adresa 1775 și o precede pe cea cu
adresa 1777, fiind la distanță de exact o mie de celule după 776 și exact o mie de celule înainte de 2776.

Când se declară o variabilă, spațiul de memorie necesar pentru a stoca valoarea corespunzătoare este
rezervat la o anumită locație de memorie (adresa de memorie). În general, programele C++ nu decid
adresele exacte de memorie unde vor fi stocate variabilele. Din fericire, această sarcină revine mediului
în care va rula programul - în general, un sistem de operare care alocă spații de memorie la momentul
rulării programelor. Totuși, ar putea fi util ca un program să poată obțină adresa unei variabile în timpul
rulării, astfel încât să poată accesa celulele cu informații situate într-o anumită poziție față de aceasta.

Operatorul de adresare (&)

Adresa unei variabile poate fi obținută punând în fața numelui variabilei un simbol ampersand (&),
cunoscut ca operatorul de adresare. De exemplu:

foo=&variabila_mea;

Aceasta atribuie lui foo adresa variabilei variabila_mea; punând în fața numelui variabilei variabila_mea


operatorul de adresare (&), nu mai atribuim conținutul variabilei, ci chiar adresa ei lui foo.

Adresa exactă a unei variabile din memorie nu poate fi cunoscută înainte de rularea programului, dar să
prespupunem, în vederea clarificării unor concepte, că variabila_mea se găsește în timpul rulării la
adresa 1776.

În acest caz, să considerăm următoarea secvență de cod:

variabila_mea=25;
foo=&variabila_mea;
bar=variabila_mea;
Valorile conținute în fiecare variabilă după executarea acestei secvențe se pot vedea în următoarea
diagramă:

În primul rând, am atribuit valoarea 25 lui variabila_mea (o variabilă a cărei adresă de memorie am


presupus că este 1776).

A doua instrucțiune atribuie lui foo adresa lui variabila_mea, pe care am presupus-o a fi 1776.

În fine, a treia instrucțiune atribuie valoarea memorată în variabila_mea lui bar. Aceasta este o


operație obișnuită de atribuire, așa cum am făcut de mai multe ori în capitolele anterioare.

Principala diferență între a doua și a treia instrucțiune este apariția operatorului de adresare (&).

Variabila care memorează adresa unei alte variabile (ca foo în exemplul anterior) în C++ se
numește pointer. Pointerii sunt caracteristică foarte puternică a limbajului și au foarte multe
întrebuințări în programarea la nivel jos. Puțin mai târziu, vom vedea cum se declară și se folosesc
pointerii.

Operatorul de deferențiere (*)

Tocmai am văzut că o variabilă care memorează adresa altei variabile se numește pointer. Se spune că
pointerii "pointează spre" variabilele ale căror adrese sunt memorate.

O proprietate interesantă a pointerilor este aceea că pot fi folosite pentru a accesa direct variabila spre
care pointează. Pentru aceasta, se precede numele pointerului cu operatorul de dereferențiere (*).
Operatorul însuși poate fi citit ca "valoarea spre care pointează".

De aceea, dăm mai jos valorile din exemplul anterior, cu următoarea instrucțiune:

baz=*foo;

Aceasta ar putea fi citită ca: "baz ia valoarea spre care pointează foo", iar instrucțiunea, de fapt,
atribuie valoarea 25 lui baz, când foo este 1776 și valoarea spre care pointează 1776 (conform
exemplului de mai sus) ar fi 25.
Este foarte important să înțelegem că foo se referă la valoarea 1776, în timp ce *foo (cu
asterisc * precedând identificatorul) se referă la valoarea memorată la adresa 1776, care, în acest caz,
este 25. Să remarcăm diferența între a include sau nu operatorul de dereferențiere (Am adăugat un
comentariu explicativ referitor la cum ar trebui citite aceste două expresii):
baz=foo; // baz ia valoarea lui foo (1776)
baz=*foo; // baz ia valoarea spre care pointează foo (25)
Deci, operatorii de referențiere și de dereferențiere sunt complementari:
& este operatorul adresă și poate fi citit pur și simplu ca "adresa lui"
* este operatorul de dereferențiere și poate fi citit ca "valoarea spre care
pointează"

Așadar, ei au sensuri opuse: O adresă obținută cu & poate fi dereferențiată cu *.

Mai devreme, am executat următoarele două operații:

variabila_mea=25;
foo=&variabila_mea;

Chiar după aceste două instrucțiuni, toate expresiile de mai jos au ca rezultat valoarea true:

variabila_mea == 25
&variabila_mea == 1776
foo == 1776
*foo ==

Prima expresie este foarte clară, având în vedere că operația de atribuire executată asupra
lui variabila_mea a fost variabila_mea=25. Cea de-a doua folosește operatorul de adresare
(&), care returnează adresa lui variabila_mea, care am presupus că are valoarea 1776. A treia
este, oarecum, evidentă, căci a doua expresie a fost adevărată și operația de atribuire realizată asupra
lui foo a fost foo=&variabila_mea. A patra expresie folosește operatorul de dereferențiere (*)
care poate fi citit ca "valoarea spre care pointează", iar valoarea spre care pointează foo este într-
adevăr 25.

Deci, după toate acestea, atât timp cât adresa spre care pointează foo rămâne neschimbată ,
următoarea expresie are tot valoarea true:
*foo ==
Declararea pointerilor

Datorită posibilității unui pointer de a accesa direct valoarea spre care pointează, un pointer are
proprietăți diferite când pointează spre un char față de un pointer care pointează spre un int sau
spre un float. Pentru dereferențiere, trebuie cunoscut tipul de dată. De aceea, declarația unui pointer
trebuie să includă tipul de dată spre care va pointa pointerul respectiv.

Declarația pointerilor respectă următoarea sintaxă:


tip* nume;
unde tip este tipul de dată spre care pointează pointerul. Acesta nu este tipul pointerului însuși, ci tipul
datei spre care pointează acesta. De exemplu:

int * numar;
char * caracter;
double * zecimal;

Acestea sunt trei declarații de pointeri. Fiecare dintre ei are rolul de a pointa spre un tip de dată diferit,
dar, de fapt, toți sunt pointeri și toți ocupă același spațiu de memorie (spațiul de memorie ocupat de un
pointer depinde de platforma pe care rulează programul). Cu toate acestea, informațiile spre care ei
pointează nici nu ocupă același spațiu de memorie, nici nu sunt de acelasi tip: primul pointează la
un int, al doilea la char, iar ultimul la double. Așadar, deși aceste trei exemple de variabile sunt
pointeri, ele au tipuri diferite: int*, respectiv char* și double*, în funcție de tipul spre care
pointează.

Să observăm că asterisk-ul (*) folosit la declararea unui pointer semnifică numai că este pointer
(face parte din expresia specificatorului de tip) și nu trebuie confundat cu operatorul de
dereferențiere pe care l-am studiat ceva mai devreme, dar pentru care folosim, de asemenea, un
asterisk (*). Sunt, pur și simplu, două lucruri diferite reprezentate cu acelasi semn.

Să vedem un exemplu cu pointeri:


// Primul meu pointer valoare_1 este 10
#include <iostream> valoare_2 este 20
using namespace std;

int main ()
{ int valoare_1, valoare_2;
int * pointerul_meu;

pointerul_meu = &valoare_1;
*pointerul_meu=10;
pointerul_meu=&valoare_2;
*pointerul_meu=20;
cout<<"valoare_1 este "<<valoare_1<<'\n';
cout<<"valoare_2 este "<<valoare_2<<'\n';
return 0;
}
Să observăm că deși nici valoare_1 nici valoare_2 nu au atribuite valori directe în program,
ambele vor avea o valoare atribuită indirect au ajutorul variabilei pointerul_meu. Iată ce se
întâmplă:

Mai întâi, pointerul_meu primește adresa lui valoare_1 prin folosirea operatorului adresă


(&). Apoi, variabilei spre care pointează pointerul_meu i se atribuie valoarea 10. Deoarece, în
acest moment, pointerul_meu pointează spre zona de memorie a lui valoare_1, se va schimba
chiar valoarea lui valoare_1.

Pentru a demonstra că un pointer poate pointa spre mai multe variabile diferite pe parcursul unui
program, exemplul repetă procedeul cu valoare_2 și același pointer, pointerul_meu.

Iată un exemplu puțin mai complicat:


// mai multi pointeri
#include <iostream>
using namespace std;

int main ()
{ int valoare_1=5, valoare_2=15;
int * p1, * p2;

p1=&valoare_1; // p1=adresa lui valoare_1


p2=&valoare_2; // p2=adress lui valoare_2
*p1=10; // valoarea spre care pointeaza p1=10
*p2=*p1; // valoarea spre care
pointeaza p2=valoarea spre care pointeaza p1
p1=p2; // p1=p2 (este copiata valoarea pointer-ului)
*p1=20; // valoarea spre care pointeaza p1=20

cout<<"valoare_1 este "<<valoare_1<<'\n';


cout<<"valoare_2 este "<<valoare_2<<'\n';
return 0;
}

Fiecare operație de atribuire include un comentariu despre modul în care ar trebui citită fiecare linie:
i.e., înlocuirea lui ampersand(&) cu "adresa lui", respectiv a lui asterisk(*) cu "valoarea spre
care pointeaza".

Să remarcăm că avem expresii cu pointerii p1 și p2, cu și fără operatorul de deferențiere (*).


Semnificația unei expresii care folosește operatorul de dereferențiere (*) este foarte diferită față de una
care nu îl folosește. Când acest operator precede numele pointerului, expresia se referă la valoarea
spre care se pointează, în timp ce numele unui pointer fără acest operator se referă chiar la valoarea
pointerului (adică, adresa pe care o indică pointerul respectiv).

Un alt lucru de care trebuie să aveți grijă este linia:


int * p1, * p2;
Aceasta declară cei doi pointeri utilizați în exemplul anterior. Dar să observăm că este câte un
asterisk(*) pentru fiecare pointer, astfel încât ambii să fie tip int* (pointer spre int). Este
necesar datorită regulilor de precedență. Să remarcăm că dacă am fi avut codul:
int * p1, p2;

p1 ar fi fost de tip int*, dar p2 ar fi fost de tip int. Spațiile nu au nicio importanță în acest sens.
Oricum, este suficient să reținem să punem câte un asterisk pentru fiecare pointer atunci când declarăm
mai mulți pointeri într-o singură instrucțiune. Sau, poate mai simplu: folosirea unei instrucțiuni pentru
fiecare variabilă.

Pointeri și tablouri

Conceptul de tablou este strâns legat de pointeri. De fapt, un tablou poate fi convertit implicit într-un
pointer către tipul de bază al elementelor sale. De exemplu, să considerăm următoarele două declarații:
int tabloul_meu [20];
int * pointerul_meu;

Următoarea operație de atribuire este validă:


pointerul_meu=tabloul_meu;

Acum, pointerul_meu și tabloul_meu ar putea fi echivalente și ar avea proprietăți


asemănătoare. Principala diferență constă în faptul că pointerul_meu poate primi o nouă adresă,în
timp ce tabloul_meu nu-și poate schimba adresa și va reprezenta întotdeauna același bloc de 20 de
elemente de tip int. De aceea, atribuirea următoare nu este validă:
tabloul_meu=pointerul_meu;

Să vedem un exemplu care folosește și tablouri și pointeri:


// mai multi pointeri 10, 20, 30, 40, 50,
#include <iostream>
using namespace std;

int main ()
{ int numere[5];
int * p;
p=numere; *p=10;
p++; *p=20;
p=&numere[2]; *p=30;
p=numere + 3; *p=40;
p=numere; *(p+4)=50;
for (int n=0; n<5; n++)
cout<<numere[n]<<", ";
return 0;
}
Pointerii și tablourile suportă același set de operații, având aceleași semnificații pentru ambele. Singura
diferență o reprezintă faptul că pointerilor li se pot atribui noi adrese, în timp ce tablourilor nu.
În capitolul referitor la tablouri, parantezele drepte ([]) au fost explicate ca precizând indexul unui
element al tabloului. Ei bine, de fapt aceste paranteze sunt un operator de dereferențiere cunoscut
ca operatorul offset. Parantezele dereferențiază variabila pe care o succed exact cum face și *,
dar cuprind și un număr în interiorul lor, număr care precizează adresa ce trebuie dereferențiată. De
exemplu:
a[5]=0; // a [offset of 5]=0
*(a+5)=0; // pointed by (a+5)=0

Aceste două expresii sunt echivalente și valide, nu numai dacă a este un pointer, dar și dacă a este un
tablou. Să ne amintim că dacă este un tablou numele său poate fi folosit exact ca un pointer către primul
său element.

Inițializarea pointerilor

Pointerii pot fi inițializați chiar în momentul în care sunt definiți:


int variabila_mea;
int * pointerul_meu=&variabila_mea;

Starea variabilelor după acest cod este aceeași ca după a codului următor:
int variabila_mea;
int * pointerul_meu;
pointerul_meu=&variabila_mea;

La inițializarea unui pointer se inițializează de fapt adresa spre care pointează el


(i.e., pointerul_meu), nu valoarea reținută la acea adresă (i.e., *pointerul_meu). De aceea, să
nu confundăm codul de mai sus cu următorul:
int variabila_mea;
int * pointerul_meu;
*pointerul_meu=&variabila_mea;

Care oricum nu prea are sens (și nu este o secvență validă).

Asteriscul (*) din declarația pointerului (linia 2) indică doar faptul că este un pointer și nu este
operatorul de dereferențiere (ca în linia 3). Este doar o coincidență folosirea aceluiași simbol: *. Ca de
obicei, spațiile nu sunt relevante și nu schimbă semnificația expresiei.
Pointerii pot fi inițializați atât cu adresa unei variabile (ca în cazul de mai sus), cât și cu valoarea unui alt
pointer (sau tablou):
int variabila_mea;
int *foo=&variabila_mea;
int *bar=foo;

Aritmetica pointerilor

Operațiile realizate cu pointeri sunt puțin diferite de cele realizate cu numere întregi. Aici numai
adunsarea și scăderea sunt permise; celelalte nu au sens în lucrul cu pointeri. Dar atât adunarea cât și
scăderea se comportă diferit cu pointerii, în funcție de tipul de dată spre care aceștia pointează.

Când am introdus tipurile de date fundamentale, am văzut că ele au mărimi diferite. De


exemplu: char ocupă întotdeauna 1 byte, short este în general mai larg de atât,
iar int și long sunt chiar mai largi; dimensiunea exactă depinde de sistem. De exemplu, să ne
imaginăm că într-un anumit sistem, char are 1 byte, short are 2 bytes și long are 4.

Să presupunem acum că definim trei pointeri în acest compilator:


char *mychar;
short *myshort;
long *mylong;
și știm că pointează către locațiile de memorie 1000, 2000 și respectiv 3000.

De aceea, dacă scriem:


++mychar;
++myshort;
++mylong;
mychar, așa cum ne așteptăm, va conține valoarea 1001. Dar nu la fel de clar, myshort ar putea
conține valoarea 2002 și mylong ar conține 3004, chiar dacă fiecare dintre ele a fost incrementată o
singură dată. Motivul este că adunând unu la un pointer, el va pointa către următorul element de același
tip și, de aceea, se adaugă numărul de octeți (bytes) ocupați de tipul spre care pointează.

Această regulă se aplică atât la adunarea, cât și la scăderea unui număr la un pointer.
S-ar fi întâmplat exact la fel dacă am fi scris:
mychar=mychar + 1;
myshort=myshort + 1;
mylong=mylong + 1;

In ceea ce privește operatorii de incrementare (++) și decremantare (--), ambii pot fi folosiți atăt ca
prefixe, cât și ca sufixe ale unei expresii, dar cu o ușoară diferență în comportament: ca prefix,
incrementarea se realizează înainte ca expresia să fie evaluată, iar ca sufix, incrementarea se face după
ce expresia este evaluată. La fel se aplică expresiilor de incrementare sau decrementare a pointerilor,
care pot aparține unor expresii mai complicate, conținând la rândul lor operatori de dereferențiere (*).
De la regulile de precedență pentru operatori, să ne amintim că operatorii postfixați, precum cel de
incrementare și decrementare, au prioritate față de operatorii prefixați precum operatorul de
dereferențiere (*).
De aceea, următoarea expresie:
*p++

este echivalentă cu *(p++). Iar efectul este este de a crește valoarea lui p (așa că el pointează acum
spre următorul element), dar deoarece ++ este folosit în forma postfixată, întreaga expresie este
evaluată cu valoarea spre care a pointat inițial (adresa spre care pointa înainte de a fi incrementat).

În esență, există patru combinații ale operatorului de deferențiere cu operatorul de incrementare atât în
forma prefixată cât și în cea postfixată (același lucru și pentru operatorul de decrementare):

*p++ // la fel ca *(p++): incrementeaza pointerul și


dereferentiaza adresa neincrementata
*++p // la fel ca *(++p): incrementeaza pointerul si
dereferentiaza adresa incrementata
++*p // la fel ca ++(*p): dereferentiaza pointerul si
incrementeaza valoarea spre care pointeaza
(*p)++ // dereferentiaza pointerul si post-incrementeaza
valoarea spre care pointeaza

O expresie tipică, dar nu chiar simplă, care implică acești operatori este:
*p++=*q++;

Deoarece ++ are prioritate față de *, atât p cât și q sunt incrementate, dar pentru că ambii operatori (++)
sunt folosiți în forma postfixată și nu prefixată, valoarea atribuită lui *p este *q înainte de a se
incrementa atât p cât și q. Apoi sunt incrementate ambele. Ar fi echivalent cu:
*p=*q;
++p;
++q;

Ca de obicei, parantezele permit eliminarea confuziilor aducând lizibilitate expresiilor.


Pointeri și constante

Pointerii pot fi folosiți pentru a accesa o variabilă prin adresa sa, iar acest acces poate include și
modificarea valorii spre care pointează. De asemenea, este posibilă declararea de pointeri care să
pointeze spre o anumită valoare, dar fără să o modifice. Pentru aceasta, este suficientă marcarea tipului
spre care pointează cu const. De exemplu:
int x;
int y=10;
const int * p=&y;
x=*p; // ok: citirea lui p
*p=x; // eroare: modificarea lui p,
care este marcat cu const

Aici p pointează spre o variabilă, dar este marcat cu const, ceea ce înseamnă că poate citi valoarea
spre care pointează, însă nu o poate și modifica. Să remarcăm, de asemenea, că expresia &y este de
tip int*, dar este atribuită unui pointer de tip const int*. Acest lucru este permis: un pointer
către non-const poate fi convertit implicit la un pointer către const. Dar conversia nu merge și în
sens invers! Ca o măsură de siguranță, pointerii către const nu se convertesc implicit la pointeri către
non-const.

Unul din cazurile de folosire a pointerilor către elemente const îl reprezintă parametrii funcțiilor: o
funcție care are ca parametru un pointer către non-const poate modifica valoarea transmisă ca
parametru, în timp ce o funcție care are ca parametru un pointer către const nu poate schimba
valoarea parametrului.
// pointerii ca parametri: 11
#include <iostream> 21
using namespace std; 31

void increment_all (int* start, int* stop)


{
int * current=start;
while (current != stop) {
++(*current); // incrementeaza valoarea
pointata
++current; // incrementeaza
pointerul
}
}

void print_all (const int* start, const int*


stop)
{
const int * current=start;
while (current != stop) {
cout<<*current<<'\n';
++current; // incrementeaza
pointerul
}
}

int main ()
{
int numere[]={10,20,30};
increment_all (numere,numere+3);
print_all (numere,numere+3);
return 0;
}

Să observăm că print_all folosește pointeri care pointează către elemente constante. Acești


pointeri nu pot schimba conținutul, dar ei însuși nu sunt constanți: adică, pointerii pot fi incrementați și
li se pot atribui diverse adrese, deși nu pot schimba valorile memorate la adresele spre care pointează.

And this is where a second dimension to constness is added to pointers: Pointers can also be themselves
const. And this is specified by appending const to the pointed type (after the asterisk):
Să vedem o altă dimensiune a invarianței pointerilor: pointerii pot fi ei însuși constanți. Acest lucru se
poate specifica marcând cu modificatorul const chiar tipul de dată spre care pointează (după asterisc):
int x;
int * p1=&x; // non-const pointer către non-const int
const int * p2=&x; // non-const pointer către const int
int * const p3=&x; // const pointer către non-const int
const int * const p4=&x; // const pointer către const int

Sintaxa cu const și pointeri este foarte înșelătoare și identificarea cazului potrivit fiecărei situații
necesită ceva experiență. în orice caz, este important să acordăm invarianță pointerilor (și referințelor)
mai bine mai devreme decăt prea tărziu. Dar nu trebuie să vă faceți prea multe griji dacă este prima dată
când lucrați cu marcatorul const și pointeri. În capitolele următoare vom arăta mai multe exemple.

Ca să adăugăm și mai multă încurcătură la sintaxa pointerilor cu const, marcatorul const poate


precede sau urma tipului de dată către care pointează, dar sensul rămâne același:
const int * p2a=&x; // non-const pointer către const int
int const * p2b=&x; // tot non-const pointer către const int

Ca și spațiile din jurul asteriscului, poziția lui const în acest caz este ține doar de stil. Acest capitol
folosește prefixul const doar pentru că în timp s-a dovedit a fi mai utilizat, dar formele sunt
echivalente. Avantajele fiecărui stil sunt încă intens dezbătute pe internet.
Pointeri și literali de tip string

Așa acum am arătat mai înainte, literalii string sunt tablouri conținând secvențe de caractere terminate
cu caracterul nul. În secțiunile anterioare, literalii string au fost folosiți pentru a fi inserați direct
în cout, pentru a inițializa string-uri și tablouri de caractere.

Dar pot fi accesați și direct. Literalii string sunt tablouri având ca tip de bază acel tip de dată care conține
șiruri de caractere terminate cu caracterul nul, iar fiecare element al tabloului este de
tipul const char (ca literali, ele nu pot fi modificate niciodată). De exemplu:
const char * foo="hello";

Această secvență declară un tablou care reprezintă literalul "hello", deci un pointer către primul său
element se atribuie lui foo. Dacă presupunem că "hello" este stocat într-o locație de memorie
începând cu adresa 1702, putem reprezenta declarația anterioară astfel:

Să observăm că aici foo este un pointer care conține valoarea 1702, nu este 'h' și nici "hello", deși
1702 este într-adevăr adresa amândurora.

Pointerul foo pointează către o secvență de caractere. Si pentru că pointerii și tablourile se comportă


asemănător în expresii, foo poate fi folosit pentru a accesa caracterele în același fel ca tablourile de
secvențe de caractere terminate cu caracterul nul. De exemplu:
*(foo+4)
foo[4]

Ambele expresii au valoarea 'o' (al cincilea element al tabloului).

Pointeri de pointeri

C++ permite folosrea pointerilor care pointează către pointeri, care, la răndul lor, pointează către o dată
(sau chiar către alți pointeri). Sintaxa implică doar un asterisc (*) pentru fiecare nivel de direcționare în
declarația pointerului:
char a;
char * b;
char ** c;
a='z';
b=&a;
c=&b;
Presupunând că au fost alese aleator locațiile de memorie pentru fiecare variabilă
la 7230, 8092 și 10502, s-ar putea reprezenta astfel:

unde valoarea fiecărei variabile este scrisă în interiorul fiecărei celule, iar adresa ocupată în memorie
este scrisă dedesubt.

Noutatea în acest exemplu o reprezintă variabila c, care este un pointer către un pointer și și poate fi
folosit în trei niveluri de direcționare, fiecare nivel corespunzând unei valori diferite:
 c este de tip char** și are valoarea 8092
 *c este de tip char* și are valoarea 7230
 **c este de tip char și are valoarea 'z'

Pointeri void

Tipul void de pointer este un tip special de pointer. În C++, void reprezintă absența tipului de dată.
De aceea, pointerii void sunt pointeri care pointează către o valoare fără tip (și deci are lungime
nedeterminată și proprietăți de dereferențiere nedeterminate).

Aceasta dă pointerilor void o mare flexibilitate, căci sunt capabili să pointeze către orice tip de dată, de
la o valoare întreagă sau reală la un șir de caractere. În schimb, au o mare constrângere: informația
pointată de ei nu poate fi dereferențiată direct (ceea ce este logic, căci nu avem tip pe care să îl
dereferențiem) și din acest motiv orice adresă dintr-un pointer void trebuie să fie transformată într-un
alt tip de pointer care pointează către un tip de dată concret ce poate fi dereferențiat.

Una dintre posibilele utilizări ar fi transmiterea de parametri generici unei funcții. De exemplu:
// increaser y, 1603
#include <iostream>
using namespace std;

void increase (void* data, int psize)


{
if ( psize == sizeof(char) )
{ char* pchar; pchar=(char*)data; ++(*pchar); }
else if (psize == sizeof(int) )
{ int* pint; pint=(int*)data; ++(*pint); }
}

int main ()
{
char a='x';
int b=1602;
increase (&a,sizeof(a));
increase (&b,sizeof(b));
cout<<a<<", "<<b<<'\n';
return 0;
}

sizeof este un operator definit în limbajul C++ care returnează dimensiunea în bytes a argumentului.
Pentru tipurile de date nedinamice, această valoare este o constantă. De aceea, de
exemplu, sizeof(char) este 1, deoarece char are întotdeauna exact un byte.

Pointeri invalizi și pointeri nuli

În principiu, pointerii sunt concepuți pentru a pointa către adrese valide, precum adresa unei variabile
sau adresa unui element într-un tablou. Dar, de fapt, pointerii pot pointa către orice adresă, inclusiv
adrese care nu se referă la niciun element valid. Exemple tipice de acest fel sunt pointerii neinițializați și
pointeri către elemente inexistente ale unui tablou:
int * p; // pointer neinițializat (variabilă locală)

int tabloul_meu[10];
int * q=tabloul_meu+20; // element din afara limitei

Nici p nici q nu pointează către adrese cunoscute care să conțină valori, dar niciuna dintre instrucțiunile
de mai sus nu generează vreo eroare. În C++, pointerii pot să rețină orice adresă, indiferent dacă este sa
nu memorat ceva la acea adresă. Ceea ce ar putea cauza o eroare ar fi dereferențierea unui asemenea
pointer (adică, încercarea de a accesa valoarea către care pointează). Accesarea unui asemenea pointer
poate cauza un comportament imprevizibil, de la o eroare în timpul execuției până la accesarea unei
valori aleatorii.

Dar, uneori, avem nevoie de un pointer care să nu pointeze către ceva anume, nu doar către o adresă
invalidă. Pentru asemenea situații axistă o valoare specială pe care o poate lua un pointer indiferent de
tip: valoarea pointer nul. Această valoare poate fi exprimată în C++ în două moduri: fie prin valoarea
întreagă zero, fie prin cuvăntul cheie nullptr:

int * p=0;
int * q=nullptr;

Aici, atât p cât și q sunt pointeri nuli, ceea ce înseamnă că ei în mod explicit nu pointează către ceva
anume și chiar sunt egali înre ei: toți pointerii nuli sunt egali cu toți ceilalți pointeri nuli. De asemenea, în
programele mai vechi se obișnuia folosirea constantei NULL definită pentru a se referi valoarea pointer
nul:
int * r=NULL;
NULL este definită în câteva fișiere antet din biblioteca standard și reprezintă un alias pentru valoarea
constantă pointer nul (la fel ca 0 sau nullptr).

Să nu confundăm pointerii nuli cu pointerii void! Un pointer nul este o valoare pe care o ia orice


pointer care nu pointează nicăieri, în timp ce pointerul void este un tip de pointer care poate pointa
undeva, fără a i se asocia un anumit tip de dată. Unul se referă la valoarea memorată în pointerm iar
celălalt la tipul de dată către care pointează.

Pointeri către funcții

C++ permite operații cu pointeri către funcții. Tipică este transmiterea unei funcții ca parametru pentru
o altă funcție. Pointerii către funcții sunt declarați cu aceeași sintaxă ca a unei declarații obișnuite de
funcție, cu excepția faptului că numele funcției este scris între paranteze () și se inserează un asterisc (*)
înaintea numelui:

// pointer către funcții 8


#include <iostream>
using namespace std;

int adunare (int a, int b)


{ return (a+b); }

int scadere (int a, int b)


{ return (a-b); }

int operation (int x, int y, int(*functocall)(int,int))


{ int g;
g=(*functocall)(x,y);
return (g);
}

int main ()
{ int m,n;
int (*minus)(int,int)=scadere;

m=operation (7, 5, adunare);


n=operation (20, m, minus);
cout <<n;
return 0;
}

În exemplul de mai sus, minus este un pointer către o funcție care are doi parametri de tip int. Este
inițializat să pointeze către funcția scadere:
int (* minus)(int,int)=scadere;

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