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.
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
nullptr sau cu adresa unei variabile valide.
Operatorii * și &
Returnează adresa de memorie a unei variabile.
int x = 10; int* p = &x; // p = adresa lui x // ex: p poate fi 0x7ffee4b8a
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; }
&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
| Expresie | Echivalent | Descriere |
|---|---|---|
p + n | &v[n] | Avansează n elemente |
*(p + n) | v[n] | Accesează elementul n |
p1 - p2 | număr de elemente | Distanț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.
int* p = new int; // aloc un int pe heap *p = 42; cout << *p; // 42 delete p; // eliberare memorie! p = nullptr; // bună practică
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;
- Fiecare
newnecesită undeletecorespunzător. - Fiecare
new[]necesită undelete[](cu paranteze!). - Lipsa eliberării = memory leak.
- La BAC, vectori mari (
int v[1000000]) trebuie declarați global sau cunew.
Comparație stivă vs. heap
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!
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ță (&).
void dublare(int x) { x *= 2; // x e copie } int a = 5; dublare(a); // a rămâne 5!
void dublare(int* p) { *p *= 2; // modifică valoarea } int a = 5; dublare(&a); // a = 10 ✓
Pointer vs. 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ție | Array | Listă înlănțuită |
|---|---|---|
| Acces element[i] | O(1) | O(n) |
| Inserare la început | O(n) | O(1) |
| Inserare la sfârșit | O(1)* | O(n) / O(1) cu tail |
| Ștergere element | O(n) | O(n) căutare + O(1) ștergere |
| Memorie | Contiguă, cache-friendly | Fragmentată |
Probleme tip BAC cu pointeri
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
&) în loc de pointeri pentru parametri de funcție — sunt echivalente funcțional și mai simple sintactic.