pointeri (*, &) în C++

Un pointer este o variabilă care ține adresa altei variabile; prin el poți citi și modifica valoarea de la acea adresă.

Sintaxă
int x = 5;
int* p = &x;     // p ține adresa lui x
*p = 10;         // modifică x prin p
p->membru        // membru al obiectului spre care arată p

Ce face pointeri (*, &)

Fiecare variabilă se află undeva în memorie, la o adresă. &x dă adresa lui x, iar un pointer (int* p) este o variabilă care ține o astfel de adresă. *p („dereferențiere”) înseamnă „valoarea de la adresa din p”: citind sau scriind *p, lucrezi de fapt cu x.

Pentru obiecte, p->membru este prescurtarea pentru (*p).membru. Un pointer care nu arată spre nimic trebuie să fie nullptr; verifică-l înainte de folosire. Numele unui tablou se transformă în pointer la primul element, iar p + 1 arată spre elementul următor.

În C++ modern, pointerii „simpli” (T*) se folosesc doar pentru a observa obiecte care aparțin altcuiva. Pentru obiecte create dinamic folosește std::unique_ptr și std::shared_ptr, iar pentru parametri care nu pot lipsi, referințe.

Exemple

Exemplul 1: Adresă, dereferențiere, modificare

Adresa în sine nu este afișată, pentru că diferă la fiecare rulare; afișăm doar faptul că p arată spre stoc.

#include <print>

int main() {
    int stoc = 20;
    int* p = &stoc;
    std::println("p arată spre stoc: {}", p == &stoc);
    std::println("*p = {}", *p);
    *p -= 5;  // modificăm stoc prin pointer
    std::println("stoc = {}", stoc);
}

Rezultat

p arată spre stoc: true
*p = 20
stoc = 15

Exemplul 2: Pointer ca parametru opțional

Funcția modifică variabila primită prin adresă. Când primește nullptr, pur și simplu nu scrie rezultatul.

#include <print>

bool imparte(int a, int b, int* rest) {
    if (b == 0) return false;
    if (rest != nullptr) {
        *rest = a % b;
    }
    return true;
}

int main() {
    int r = 0;
    if (imparte(17, 5, &r)) {
        std::println("Rest: {}", r);
    }
    std::println("Fără rest cerut: {}", imparte(17, 5, nullptr));
    std::println("Împărțire la 0: {}", imparte(1, 0, &r));
}

Rezultat

Rest: 2
Fără rest cerut: true
Împărțire la 0: false

Exemplul 3: Pointeri și tablouri, săgeata ->

#include <print>
#include <string>

struct Oras {
    std::string nume;
    int populatie;
};

int main() {
    int note[] = {7, 9, 10};
    int* p = note;  // pointer la primul element
    std::println("{} {} {}", *p, *(p + 1), p[2]);

    Oras iasi{"Iași", 271692};
    Oras* o = &iasi;
    o->populatie += 8;
    std::println("{} are {} locuitori", o->nume, iasi.populatie);
}

Rezultat

7 9 10
Iași are 271700 locuitori

Greșeli frecvente

  • Folosești un pointer neinițializat (int* p; *p = 5;). Arată spre o adresă oarecare: comportament nedefinit. Inițializează-l mereu (int* p = nullptr;).
  • Păstrezi un pointer spre o variabilă locală după ce funcția s-a terminat, sau spre un element al unui std::vector după push_back (vectorul se poate muta în altă zonă de memorie). Pointerul devine „atârnat”.
  • Confunzi * din declarație (int* p, „p este pointer”) cu * din expresie (*p, „valoarea de la p”). În int* a, b;, doar a este pointer; b este int.

Comenzi înrudite

  • referințe (&, &&): O referință este un alt nume pentru o variabilă existentă; tot ce faci cu referința se face de fapt cu variabila originală.
  • nullptr: Valoarea unui pointer care nu arată spre nimic; înlocuiește vechile NULL și 0.
  • new: Creează un obiect în memoria dinamică (heap) și returnează un pointer la el; obiectul trăiește până este distrus explicit cu delete.
  • delete: Distruge un obiect creat cu new și eliberează memoria; scris ca = delete, interzice folosirea unei funcții.
  • const: Marchează o valoare care nu mai poate fi modificată după inițializare; compilatorul oprește orice încercare de schimbare.
  • std::unique_ptr: Pointer inteligent care deține singur un obiect și îl șterge automat când iese din domeniu.