MySQL Java JavaScript PHP Python HTML-CSS C-sharp C++ Go

Pointeri

Pointerii reprezintă obiecte a căror valoare este adresa altor obiecte (variabile, constante, alți pointeri) sau funcții. La fel ca referințele, pointerii se folosesc pentru accesul indirect la un obiect. Totuși, spre deosebire de referințe, pointerii oferă mai multe posibilități.

Definirea unui pointer

Pentru a defini un pointer, trebuie să specificăm tipul de date al obiectului spre care va pointa și simbolul steluță *:

tip_date* nume_pointer;

Mai întâi vine tipul de date, apoi simbolul *, urmat de numele pointerului.

De exemplu, definim un pointer la un obiect de tip int:

int* p;

Acest pointer poate stoca doar adresa unei variabile de tip int, dar momentan nu referă niciun obiect concret și conține o valoare aleatoare. Putem chiar să încercăm să-l afișăm în consolă:

#include <iostream>
 
int main()
{
    int* p;
    std::cout << p << std::endl;
}

De exemplu, în cazul meu, consola a afișat 0x8 – o anumită adresă în format hexazecimal (de obicei, adresele de memorie se reprezintă astfel). Putem însă inițializa pointerul cu o valoare:

int* p{};

Dacă nu este specificată o valoare concretă, pointerul va primi automat valoarea 0, care reprezintă o adresă specială ce nu pointează spre nimic. Se poate de asemenea inițializa explicit cu nullptr:

int* p{nullptr};

Deși nu este interzis să lăsăm pointerii neinițializați, în general este recomandat să îi inițializăm, fie cu o valoare concretă, fie cu zero, așa cum am văzut. O valoare nulă va permite, de exemplu, mai târziu, să verificăm dacă pointerul nu pointează nicăieri.

Este important de menționat că poziția steluței * nu afectează definirea pointerului – aceasta poate fi pusă fie lângă tipul de date, fie lângă numele variabilei. Ambele definiții sunt echivalente:

int* p1{};
int *p2{};

Dimensiunea pointerilor

Dimensiunea valorii unui pointer (adică a adresei stocate) nu depinde de tipul pointerului, ci de platforma pe care rulează programul. Pe platformele pe 32 de biți, dimensiunea este de 4 octeți, iar pe cele pe 64 de biți — 8 octeți. De exemplu:

#include <iostream>
 
int main()
{
    int *pint{};
    double *pdouble{};
    std::cout << "*pint size: " << sizeof(pint) << std::endl;
    std::cout << "*pdouble size: " << sizeof(pdouble) << std::endl;
}

În acest exemplu sunt definiți doi pointeri spre tipuri diferite – int și double. Variabilele acestor tipuri au dimensiuni diferite – 4 și 8 octeți. Dar dimensiunile pointerilor vor fi identice. În cazul meu, pe o platformă pe 64 de biți, ambii pointeri au dimensiunea de 8 octeți.

Obținerea adresei și operatorul &

Prin operatorul & putem obține adresa unui obiect, de exemplu adresa unei variabile. Această adresă poate fi apoi atribuită unui pointer:

int number {25};
int *pnumber {&number}; // pointerul pnumber stochează adresa variabilei number

Expresia &number returnează adresa variabilei number, deci pnumber va stoca această adresă. Este important ca tipul variabilei și tipul pointerului să corespundă – aici ambele sunt de tip int. Se poate de asemenea folosi cuvântul cheie auto:

int number {25};
auto *pnumber {&number}; // pointerul pnumber stochează adresa variabilei number

Dacă afișăm adresa în consolă, vom vedea o valoare hexazecimală:

#include <iostream>
 
int main()
{
    int number {25};
    int *pnumber {&number};
    std::cout << "number addr: " << pnumber << std::endl;
}

Ieșire în consolă în cazul meu:

number addr: 0x1543bffc74

În fiecare caz concret, adresa poate fi diferită și se poate schimba la rulări diferite ale programului. De exemplu, în cazul meu, adresa fizică a variabilei number este 0x1543bffc74. Adică, în memoria calculatorului există adresa 0x1543bffc74, la care este plasată variabila number. Deoarece variabila x este de tip int, pe majoritatea arhitecturilor ea va ocupa următorii 4 octeți (pe unele arhitecturi dimensiunea în memorie a tipului int poate fi diferită). Astfel, o variabilă de tip int va ocupa consecutiv celulele de memorie cu adresele 0x1543bffc74, 0x1543bffc75, 0x1543bffc76, 0x1543bffc77.

Și pointerul pnumber va face referire la adresa unde este plasată variabila number, adică la adresa 0x1543bffc74.

Așadar, pointerul pnumber stochează adresa variabilei number, dar unde este stocat pointerul pnumber însuși? Pentru a afla acest lucru, putem aplica operatorul & și asupra variabilei pnumber:

#include <iostream>
 
int main()
{
    int number {25};
    int *pnumber {&number}; // pointerul pnumber stochează adresa variabilei number
    std::cout << "number addr: " << pnumber << std::endl;
    std::cout << "pnumber addr: " << &pnumber << std::endl;
}

Ieșirea în consolă în cazul meu:

number addr: 0xe1f99ff7cc  
pnumber addr: 0xe1f99ff7c0

Obținerea valorii de la o adresă

Dar, deoarece un pointer stochează o adresă, putem obține valoarea stocată la acea adresă, adică valoarea variabilei number.

Pentru aceasta se folosește operatorul *, numit operator de dereferențiere („indirection operator” / „dereference operator”). Rezultatul acestei operații este întotdeauna obiectul spre care pointează pointerul. Aplicăm această operație pentru a obține valoarea variabilei number:

#include <iostream>
 
int main()
{
    int number {25};
    int *pnumber {&number};
    std::cout << "Address = " << pnumber<< std::endl;
    std::cout << "Value = " << *pnumber << std::endl;
}

Exemplu de afișare în consolă

Address = 0x44305ffd4c
Value = 25

Valoarea obținută în urma operației de dereferențiere poate fi atribuită altei variabile:

int n1 {25};
int *pn1 {&n1}; // pointerul pn1 stochează adresa variabilei n1
int n2 { *pn1 }; // n2 primește valoarea stocată la adresa din pn1
std::cout << "n2 = " << n2 << std::endl;  // n2 = 25

De asemenea, folosind un pointer, putem modifica valoarea de la adresa stocată în pointer:

int x = 10;
int *px = &x;
*px = 45;
std::cout << "x = " << x << std::endl;     // 45

Deoarece la adresa indicată de pointer se află variabila x, valoarea acesteia va fi modificată în consecință.