Programare Competitivă

Pointeri

Un pointer este o variabilă care stochează adresa altei variabile din memorie.

Până acum, variabilele noastre stocau valori: int x = 5 înseamnă “x conține valoarea 5”. Un pointer nu conține o valoare, ci locul în memorie unde se află acea valoare.


De ce avem nevoie de pointeri?

  • Pentru a înțelege cum funcționează șirurile de caractere și vectorii în C++
  • Pentru a transmite date la funcții eficient (fără a copia totul)
  • Pentru a lucra cu memorie dinamică (alocare la rulare)
  • Sunt baza multor structuri de date avansate (liste, arbori, grafuri)

Adresa unei variabile

Fiecare variabilă din program ocupă un loc în memorie. Acel loc are o adresă - un număr care identifică poziția.

Operatorul & ne dă adresa unei variabile:

#include <iostream>
using namespace std;

int main()
{
    int x = 42;

    cout << "Valoarea: " << x << endl;
    cout << "Adresa: " << &x << endl;

    return 0;
}
Output:
Valoarea: 42
Adresa: 0x7ffd5c3e4a8c

Adresa este un număr hexazecimal (începe cu 0x). Nu contează valoarea exactă - important este conceptul: fiecare variabilă are o adresă unică.


Declararea unui pointer

Un pointer se declară cu * după tipul de date:

int *p;     // p este un pointer către un int
char *s;    // s este un pointer către un char
double *d;  // d este un pointer către un double

int *p se citește: “p este un pointer care pointează (arată) către o valoare de tip int”.


Legarea unui pointer de o variabilă

Folosim & pentru a obține adresa și o atribuim pointerului:

int x = 42;
int *p = &x;  // p pointează către x

Acum p conține adresa lui x.

Memorie:
  x:  [  42  ]  adresa: 1000
  p:  [ 1000 ]  adresa: 2000
       ↓
       x

Dereferențierea - accesarea valorii

Operatorul * aplicat pe un pointer ne dă valoarea de la adresa respectivă:

int x = 42;
int *p = &x;

cout << p;   // afișează adresa (ex: 0x7ffd...)
cout << *p;  // afișează 42 (valoarea lui x)

*p se citește: “valoarea de la adresa pe care o conține p” sau mai simplu “valoarea pe care o indică p”.

Modificarea prin pointer

Putem modifica variabila originală prin pointer:

int x = 42;
int *p = &x;

*p = 100;       // modificăm valoarea de la adresa lui p
cout << x;      // afișează 100 (x s-a schimbat!)

Asta funcționează pentru că p și x se referă la aceeași locație din memorie.


Exemplu complet pas cu pas

#include <iostream>
using namespace std;

int main()
{
    int a = 10;
    int b = 20;
    int *p;

    p = &a;          // p arată către a
    cout << *p;      // 10

    *p = 15;         // a devine 15
    cout << a;       // 15

    p = &b;          // p arată acum către b
    cout << *p;      // 20

    *p = *p + 5;     // b devine 25
    cout << b;       // 25

    return 0;
}
Linie p arată către *p a b
p = &a a 10 10 20
*p = 15 a 15 15 20
p = &b b 20 15 20
*p = *p + 5 b 25 15 25

Pointeri și vectori

Numele unui vector este de fapt un pointer către primul element:

int v[5] = {3, 7, 2, 9, 4};

cout << v;      // adresa primului element
cout << *v;     // 3 (valoarea primului element)
cout << v[0];   // 3 (echivalent cu *v)

Aritmetica pe pointeri

Dacă p pointează către un element al vectorului, p + 1 pointează către următorul element:

int v[5] = {3, 7, 2, 9, 4};
int *p = v;     // p arată către v[0]

cout << *p;       // 3  (v[0])
cout << *(p + 1); // 7  (v[1])
cout << *(p + 2); // 2  (v[2])
cout << *(p + 3); // 9  (v[3])

v[i] este echivalent cu *(v + i)

Aceasta este legătura fundamentală între vectori și pointeri în C++.

p       p+1     p+2     p+3     p+4
↓       ↓       ↓       ↓       ↓
[  3  ] [  7  ] [  2  ] [  9  ] [  4  ]
v[0]    v[1]    v[2]    v[3]    v[4]

Pointeri și șiruri de caractere

Un șir de caractere char s[] = "Salut" este un vector de char. Deci s este un pointer către primul caracter:

char s[] = "Salut";
char *p = s;        // p arată către 'S'

cout << *p;         // S
cout << *(p + 1);   // a
cout << *(p + 2);   // l

Parcurgerea unui șir cu pointer

char s[] = "Algoritm";
char *p = s;

while (*p != '\0') {
    cout << *p;
    p++;
}

La fiecare pas, p avansează la următorul caracter. Când ajunge la '\0', bucla se oprește.

Aceasta este exact ceea ce face for (int i = 0; s[i] != '\0'; i++) - dar cu pointer în loc de indice.


Pointer nul

Un pointer care nu arată către nimic se inițializează cu NULL sau nullptr:

int *p = NULL;     // stilul C
int *p = nullptr;  // stilul C++ modern (recomandat)

Nu accesați niciodată *p dacă p este nul - programul se oprește cu eroare.

int *p = nullptr;

if (p != nullptr) {
    cout << *p;    // sigur, p pointează către ceva
}

Transmiterea prin referință (pointer)

Dacă vrem ca o funcție să modifice o variabilă, îi transmitem adresa:

#include <iostream>
using namespace std;

void dubleaza(int *p) {
    *p = *p * 2;
}

int main()
{
    int x = 5;
    dubleaza(&x);  // transmitem adresa lui x
    cout << x;     // 10 (x s-a modificat)

    return 0;
}

Fără pointer, funcția ar lucra pe o copie și x ar rămâne 5.

Transmitere prin valoare vs pointer

Prin valoare: funcția primește o copie. Modificările nu afectează originalul.

void f(int a) {
a = 100; // modifică doar copia
}

Prin pointer: funcția primește adresa. Modificările afectează originalul.

void f(int *p) {
*p = 100; // modifică valoarea originală
}

C++ oferă și referințe (int &a) care fac același lucru mai elegant, dar pointerii sunt baza pe care se construiesc referințele.


Rezumat operatori

Operator Semnificație Exemplu
&x Adresa lui x int *p = &x;
*p Valoarea de la adresa p cout << *p;
int *p Declarare pointer int *p = &x;
p + 1 Următoarea adresă *(p + 1) = elementul următor
p++ Avansează pointerul Trece la elementul următor

Greșeli frecvente

1. Utilizarea unui pointer neinițializat

int *p;
*p = 5;  // GREȘIT! p nu arată către nimic valid

Un pointer trebuie mereu inițializat cu adresa unei variabile sau cu nullptr.


2. Confuzia între * la declarare și * la utilizare

int *p = &x;  // * aici înseamnă "p este pointer"
*p = 10;      // * aici înseamnă "valoarea de la adresa p"

Aceeași notație, două semnificații diferite:

  • La declarare: indică tipul (pointer)
  • La utilizare: dereferențiere (accesare valoare)

3. Dereferențierea unui pointer nul

int *p = nullptr;
cout << *p;  // EROARE! Programul se oprește

Verifică mereu dacă pointerul nu este nul înainte de a-l accesa.


4. Confuzia între adresă și valoare

int x = 5;
int *p = &x;

cout << p;   // afișează adresa (un număr mare hexazecimal)
cout << *p;  // afișează 5 (valoarea)

Ce să reții

  • Un pointer stochează adresa unei variabile, nu o valoare.
  • &x dă adresa lui x, *p dă valoarea de la adresa p.
  • Numele unui vector este un pointer către primul element.
  • v[i] este echivalent cu *(v + i).
  • Pointerii permit modificarea variabilelor din funcții.
  • Un pointer neinițializat sau nul nu trebuie dereferențiat.
  • Pointerii sunt baza pe care funcționează șirurile de caractere, vectorii și structurile de date avansate.