🔗 Pointeri & Alocare Dinamică

Referințe, gestiunea memoriei heap, liste simplu înlănțuite

Pointerinew/deleteHeapListe înlănțuiteMemorie dinamică
📖 12 min
0

Ce este un pointer?

Un pointer este o variabilă care stochează adresa de memorie a altei variabile. În loc să conțină direct valoarea, pointerul „arată" spre locul din memorie unde se află acea valoare.

Analogie: Un pointer este ca o adresă poștală — nu conține casa, ci locația ei. Adresa te ajută să ajungi la casă (valoare).

Declararea unui pointer

int  x = 42;       // variabilă normală, valoare = 42
int* p = &x;      // p este pointer la int; stochează adresa lui x

// Tipuri de pointeri:
double* pd;       // pointer la double
char*   pc;       // pointer la char
int*    pn = nullptr; // pointer neinițializat corect = nullptr
Atenție! Un pointer neinițializat (dangling pointer) are o valoare aleatoare și poate cauza crash-uri. Inițializează întotdeauna cu nullptr sau cu adresa unei variabile valide.

Operatorii * și &

& — operatorul de adresă (address-of)

Returnează adresa de memorie a unei variabile.

int x = 10;
int* p = &x;  // p = adresa lui x
// ex: p poate fi 0x7ffee4b8a
* — operatorul de dereferențiere (dereference)

Accesează valoarea de la adresa stocată în pointer.

int val = *p;  // val = 10 (valoarea lui x)
*p = 20;       // acum x == 20

Exemplu complet

#include <iostream>
using namespace std;

int main() {
  int x = 5;
  int* p = &x;

  cout << "Valoarea lui x: " << x << endl;   // 5
  cout << "Adresa lui x:   " << p << endl;   // ex: 0x7fff...
  cout << "Prin pointer:   " << *p << endl;  // 5

  *p = 99;
  cout << "x dupa *p=99:  " << x << endl;   // 99
  return 0;
}
Regulă de memorat: &variabilă → adresă. *pointer → valoare. Cele două operații sunt inverse una față de cealaltă.

Aritmetica pointerilor

Pointerilor li se pot aplica operații aritmetice. Incrementarea unui pointer cu 1 avansează cu sizeof(tip) bytes, nu cu 1 byte.

int v[] = {10, 20, 30, 40};
int* p = v;   // p pointează la v[0]

cout << *p;       // 10 (v[0])
p++;
cout << *p;       // 20 (v[1]) — a avansat cu sizeof(int)=4 bytes
cout << *(p+1);   // 30 (v[2])
cout << *(p-1);   // 10 (v[0])

// Parcurgere cu pointer (echivalentă cu indexare)
for (int* q = v; q < v + 4; q++) {
  cout << *q << " ";
} // 10 20 30 40
ExpresieEchivalentDescriere
p + n&v[n]Avansează n elemente
*(p + n)v[n]Accesează elementul n
p1 - p2număr de elementeDistanța între 2 pointeri
p++Avansează la următorul element

Alocare dinamică: new și delete

Alocarea normală (pe stivă) este limitată. Cu new alocăm memorie pe heap — o zonă mai mare, gestionată manual.

Alocare pentru o singură valoare
int* p = new int;     // aloc un int pe heap
*p = 42;
cout << *p;              // 42
delete p;               // eliberare memorie!
p = nullptr;           // bună practică
Alocare pentru un array
int n = 100000;
int* v = new int[n];   // array dinamic
v[0] = 1; v[1] = 2; // folosire normală
delete[] v;             // delete[] pentru array!
v = nullptr;
Reguli critice:
  • Fiecare new necesită un delete corespunzător.
  • Fiecare new[] necesită un delete[] (cu paranteze!).
  • Lipsa eliberării = memory leak.
  • La BAC, vectori mari (int v[1000000]) trebuie declarați global sau cu new.

Comparație stivă vs. heap

Stivă (stack)

Alocare automată, eliberare automată la ieșirea din funcție. Dimensiune limitată (~1-8 MB). Rapidă.

int v[1000]; // OK
int v[1000000]; // Stack overflow!
Heap (alocare dinamică)

Alocare explicită cu new, eliberare explicită cu delete. Capacitate mare. Mai lentă.

int* v = new int[1000000]; // OK
delete[] v; // nu uita!

Pointeri ca parametri de funcție

Pasarea unui pointer ca parametru permite funcției să modifice variabila originală — similar cu pasarea prin referință (&).

Prin valoare — NU modifică originalul
void dublare(int x) {
  x *= 2; // x e copie
}
int a = 5;
dublare(a);
// a rămâne 5!
Prin pointer — MODIFICĂ originalul
void dublare(int* p) {
  *p *= 2; // modifică valoarea
}
int a = 5;
dublare(&a);
// a = 10 ✓

Pointer vs. Referință (&)

La BAC, pasarea prin referință (int& x) este preferată față de pointeri pentru simplitate. Pointerii sunt necesari pentru alocare dinamică și structuri de date complexe.

Array-uri ca parametri

// Array-ul se transmite ca pointer la primul element
void afisare(int* v, int n) {   // echivalent cu: int v[]
  for (int i = 0; i < n; i++)
    cout << v[i] << " ";
}

int main() {
  int arr[] = {1, 2, 3};
  afisare(arr, 3);   // arr se convertește automat la &arr[0]
}

Liste simplu înlănțuite

O listă simplu înlănțuită este o structură de date în care fiecare element (nod) conține o valoare și un pointer la nodul următor. Spre deosebire de array, nu ocupă memorie contiguă.

// Definiția unui nod
struct Nod {
  int   val;    // valoarea nodului
  Nod*  next;   // pointer la nodul următor
};

// Creare listă: 1 -> 2 -> 3 -> nullptr
Nod* head = new Nod{1, nullptr};
head->next = new Nod{2, nullptr};
head->next->next = new Nod{3, nullptr};

// Parcurgere
for (Nod* p = head; p != nullptr; p = p->next)
  cout << p->val << " ";   // 1 2 3

// Inserare la început
Nod* nou = new Nod{0, head};
head = nou;   // 0 -> 1 -> 2 -> 3

// Eliberare memorie
Nod* p = head;
while (p != nullptr) {
  Nod* tmp = p->next;
  delete p;
  p = tmp;
}
head = nullptr;
OperațieArrayListă înlănțuită
Acces element[i]O(1)O(n)
Inserare la începutO(n)O(1)
Inserare la sfârșitO(1)*O(n) / O(1) cu tail
Ștergere elementO(n)O(n) căutare + O(1) ștergere
MemorieContiguă, cache-friendlyFragmentată

Probleme tip BAC cu pointeri

La BAC M-I, pointerii apar în contextul: pasare array la funcții (implicit ca pointer), alocare array mare cu new, și ocazional structuri cu pointeri.

Problemă 1 — Array dinamic citit din fișier

#include <fstream>
using namespace std;

int main() {
  ifstream fin("date.in");
  ofstream fout("date.out");
  int n;
  fin >> n;
  int* v = new int[n];   // alocare dinamică
  for (int i = 0; i < n; i++) fin >> v[i];

  // procesare: suma elementelor
  long long suma = 0;
  for (int i = 0; i < n; i++) suma += v[i];
  fout << suma;

  delete[] v;
  return 0;
}

Problemă 2 — Swap prin pointer (clasic BAC)

// Intrebare BAC: ce se afișează?
void swap(int* a, int* b) {
  int t = *a; *a = *b; *b = t;
}
int main() {
  int x = 3, y = 7;
  swap(&x, &y);
  cout << x << " " << y;  // 7 3
}

Problemă 3 — Trasare algoritm cu pointer

// Ce valori ia p și *p?
int a[] = {5, 3, 8, 1};
int* p = a;
p++;           // p pointează la a[1] = 3
*p = 10;       // a[1] = 10
p += 2;        // p pointează la a[3] = 1
cout << *p;    // 1
// a = 1
Sfat BAC: Dacă nu știi pointeri avansați, poți folosi întotdeauna referințe (&) în loc de pointeri pentru parametri de funcție — sunt echivalente funcțional și mai simple sintactic.

Exerciții interactive — testează cunoștințele