🔗 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ă
🎮

Demo interactiv — Memoria în acțiune

Vizualizează cum funcționează pointerii în memorie. Fiecare „celulă" reprezintă o locație de memorie cu adresa și valoarea ei.

Normal
🧠 Memorie Apasă un buton pentru a începe
📋 Variabile
💻 Cod C++
 
🧩

Probleme rezolvate — pointeri

5 probleme practice cu pointeri, alocare dinamică și liste înlănțuite.

1 Suma elementelor folosind doar pointer Ușor
Citește n elemente într-un vector alocat dinamic. Calculează suma folosind doar aritmetică de pointeri (fără indici [i]).
Intrare:
5
1 2 3 4 5
Ieșire:
15
💡 Vezi soluția
int n; cin >> n;
int* v = new int[n];
for (int* p = v; p < v + n; p++) cin >> *p;
long long s = 0;
for (int* p = v; p < v + n; p++) s += *p;
cout << s;
delete[] v;

Idee: v + i e echivalent cu &v[i]. *p obține valoarea. Nu uita delete[].

2 Inversare vector cu doi pointeri Ușor
Inversează „pe loc" un vector alocat dinamic, folosind doi pointeri (stânga, dreapta) care se apropie.
Intrare:
5
1 2 3 4 5
Ieșire:
5 4 3 2 1
💡 Vezi soluția
int n; cin >> n;
int* v = new int[n];
for (int i = 0; i < n; i++) cin >> v[i];
int* st = v;
int* dr = v + n - 1;
while (st < dr) {
    int t = *st; *st = *dr; *dr = t;
    st++; dr--;
}
for (int i = 0; i < n; i++) cout << v[i] << ' ';
delete[] v;

Complexitate: O(n) timp, O(1) memorie suplimentară. Tehnică „two pointers" foarte folosită.

3 Realocare dinamică (vector care crește) Mediu
Citește numere până la −1. Stochează-le într-un vector alocat dinamic care își dublează capacitatea când e plin.
Intrare:
3 7 1 9 4 -1
Ieșire:
3 7 1 9 4
💡 Vezi soluția
int cap = 2, n = 0;
int* v = new int[cap];
int x;
while (cin >> x && x != -1) {
    if (n == cap) {
        cap *= 2;
        int* nou = new int[cap];
        for (int i = 0; i < n; i++) nou[i] = v[i];
        delete[] v;
        v = nou;
    }
    v[n++] = x;
}
for (int i = 0; i < n; i++) cout << v[i] << ' ';
delete[] v;

Idee: exact ce face std::vector intern. Dublarea garantează cost amortizat O(1) pe inserție.

4 Listă înlănțuită — inserare la început Mediu
Citește n numere și inserează-le pe rând la începutul unei liste simplu înlănțuite. Afișează lista (va fi în ordine inversă față de citire).
Intrare:
5
1 2 3 4 5
Ieșire:
5 4 3 2 1
💡 Vezi soluția
struct Nod { int val; Nod* urm; };
Nod* cap = nullptr;
int n; cin >> n;
for (int i = 0, x; i < n; i++) {
    cin >> x;
    Nod* nou = new Nod{x, cap};
    cap = nou;
}
for (Nod* p = cap; p; p = p->urm) cout << p->val << ' ';
// (opțional: eliberare memorie)
while (cap) { Nod* t = cap; cap = cap->urm; delete t; }

Cheia: noul nod pointează spre vechiul cap, apoi capul devine noul nod. Inserare la început = O(1).

5 Numără nodurile cu valoare pară Dificil
Având capul unei liste simplu înlănțuite (construită din citire până la 0), parcurge lista și afișează numărul nodurilor cu valoare pară. Eliberează apoi memoria.
Intrare:
3 4 7 8 2 0
Ieșire:
3
💡 Vezi soluția
struct Nod { int val; Nod* urm; };
Nod* cap = nullptr;
Nod* coada = nullptr;
int x;
while (cin >> x && x != 0) {
    Nod* nou = new Nod{x, nullptr};
    if (!cap) cap = coada = nou;
    else { coada->urm = nou; coada = nou; }
}
int cnt = 0;
for (Nod* p = cap; p; p = p->urm)
    if (p->val % 2 == 0) cnt++;
cout << cnt;
while (cap) { Nod* t = cap; cap = cap->urm; delete t; }

Detaliu: ținem și un pointer coada ca să facem inserare la sfârșit în O(1). Parcurgerea cu for (Nod* p = cap; p; p = p->urm) este idiomatic.

⚠️ Verifică totdeauna: 1) inițializează urm = nullptr; 2) verifică != nullptr înainte să dereferenți; 3) eliberează cu delete ce ai alocat cu new.

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