Pointerii sunt variabile care primesc ca valoare adrese de memorie. Sunt mecanismul care face posibile alocarea dinamică, structurile de date înlănțuite și modificarea parametrilor unei funcții. Sunt totodată sursa celor mai frecvente erori grave dintr-un program C.
1Obiectivele lucrării
- Declararea pointerilor și înțelegerea rolului simbolului
* - Folosirea operatorilor
&(adresă) și*(dereferențiere) - Aplicarea regulilor aritmeticii pointerilor
- Exploatarea legăturii dintre pointeri și tablouri
- Modificarea parametrilor unei funcții prin pointeri și prin referințe
2Ce este un pointer
Fiecare variabilă ocupă o zonă de memorie care are o adresă. Un pointer este o variabilă obișnuită, cu deosebirea că valoarea pe care o conține este chiar o astfel de adresă.
| Categorie | Conținut | Exemplu |
|---|---|---|
| Pointeri de date | adrese de variabile sau constante | int *p = &x; |
| Pointeri de funcții | adrese de cod executabil | int (*f)(int); |
3Declarare și operatori
tip * nume_variabila; int *p_i; // pointer la int float *p_f; // pointer la float int *tab_p[10]; // tablou de 10 pointeri catre intregi float **p_p; // pointer la pointer void *p_v; // pointer generic: poate primi adresa oricarui tip
| Operator | Nume | Ce face | Exemplu |
|---|---|---|---|
& | referențiere | returnează adresa unei variabile | p = &x; |
* (la declarare) | marcaj de tip | arată că variabila este pointer | int *p; |
* (în expresii) | dereferențiere | accesează valoarea de la adresa memorată | y = *p; |
int *p; steluța face parte din tip.
În *p = 7; steluța este operator și înseamnă „scrie la adresa conținută de p".
Distincția se face după context: declarație sau expresie.void * poate primi adresa oricărui tip de date,
dar nu poate fi dereferențiat direct - compilatorul nu știe câți octeți să citească.
Este necesară o conversie explicită: *(int *)p_v.4Simulator de memorie
Schimbați ținta pointerului și urmăriți diferența dintre p, *p
și &p. Selectați NULL pentru a vedea ce înseamnă un pointer care nu indică nimic.
5Aritmetica pointerilor
Adunarea unui întreg la un pointer nu adaugă octeți, ci elemente. Compilatorul înmulțește automat cu dimensiunea tipului indicat.
| Operație | Efect real | Pentru int * (4 octeți) |
|---|---|---|
p + 1 | avansează cu 1 element | adresa crește cu 4 |
p + n | avansează cu n elemente | adresa crește cu 4·n |
p - q | numărul de elemente dintre ele | (diferența adreselor) / 4 |
p++ | trece la elementul următor | adresa crește cu 4 |
p + 1 avansează cu 1 octet pentru
char *, cu 4 pentru int * și cu 8 pentru double *.
Tipul pointerului este esențial, nu decorativ.6Pointeri și tablouri
Numele unui tablou este, în majoritatea contextelor, chiar adresa primului element. De aceea indexarea și aritmetica pointerilor sunt notații echivalente:
| Notație cu indice | Notație cu pointer | Semnificație |
|---|---|---|
v | &v[0] | adresa primului element |
v[i] | *(v + i) | elementul de pe poziția i |
&v[i] | v + i | adresa elementului i |
p[i] | *(p + i) | funcționează și pentru pointeri |
sizeof(v)
pentru int v[10] dă 40, în timp ce sizeof(p) pentru int *p
dă 4 sau 8 - dimensiunea unei adrese. În plus, numele tabloului nu poate fi modificat:
v++ este eroare, p++ este permis.7Transmiterea parametrilor la funcții
În C, parametrii se transmit prin valoare: funcția primește o copie. Modificările făcute în interior se pierd la revenire. Pentru a modifica variabila originală, se transmite adresa ei.
| Mod | Sintaxă | Poate modifica originalul? | Disponibil în |
|---|---|---|---|
| Prin valoare | void f(int x) | nu | C și C++ |
| Prin pointer | void f(int *x) | da, cu *x = ... | C și C++ |
| Prin referință | void f(int &x) | da, direct cu x = ... | doar C++ |
8Variabile referință (doar în C++)
O referință este un nume alternativ pentru o variabilă existentă. Spre deosebire de pointer, nu ocupă memorie proprie și nu are sintaxă specială la utilizare.
int x = 10; int &r = x; // r devine un alt nume pentru x r = 20; // modifica direct pe x, fara nicio steluta cout << x; // afiseaza 20
| Aspect | Pointer | Referință |
|---|---|---|
| Trebuie inițializat la declarare | nu | da, obligatoriu |
| Poate fi redirecționat spre altceva | da | nu, niciodată |
| Poate fi NULL | da | nu |
| Sintaxă la utilizare | *p | r (ca variabila obișnuită) |
| Ocupă memorie proprie | da | de regulă nu |
9Erori frecvente
int *p; *p = 5; scrie la o adresă aleatoare - programul se oprește sau, mai rău,
corupe alte date. Inițializați întotdeauna: int *p = NULL;if (p != NULL) { *p = 5; }int* gresit() { int x = 5; return &x; }int *p, q; declară un pointer și un întreg obișnuit, nu doi pointeri.
Steluța se aplică fiecărei variabile separat: int *p, *q;10Cod sursă
#include <stdio.h>
int main(void)
{
int x = 42;
int *p = &x; // p primeste adresa lui x
printf("x = %d\n", x); // 42
printf("&x = %p\n", (void*)&x);
printf("p = %p\n", (void*)p); // aceeasi adresa
printf("*p = %d\n", *p); // 42 - valoarea de la acea adresa
printf("&p = %p\n", (void*)&p); // unde este stocat pointerul insusi
*p = 100; // modifica x prin intermediul pointerului
printf("\ndupa *p = 100, x = %d\n", x); // 100
printf("sizeof(x) = %zu, sizeof(p) = %zu\n", sizeof(x), sizeof(p));
return 0;
}
#include <stdio.h>
int main(void)
{
int v[5] = {10, 20, 30, 40, 50};
int *p = v; // echivalent cu &v[0]
printf("Prin indice: ");
for (int i = 0; i < 5; i++) printf("%d ", v[i]);
printf("\nPrin pointer: ");
for (int i = 0; i < 5; i++) printf("%d ", *(p + i));
printf("\nCu incrementare: ");
p = v;
for (int i = 0; i < 5; i++) printf("%d ", *p++);
// adresele cresc din 4 in 4 octeti
printf("\n\nAdrese:\n");
for (int i = 0; i < 5; i++)
printf(" &v[%d] = %p\n", i, (void*)&v[i]);
// diferenta a doi pointeri = numarul de elemente dintre ei
int *inceput = &v[0], *sfarsit = &v[4];
printf("\nElemente intre inceput si sfarsit: %ld\n", sfarsit - inceput);
return 0;
}
#include <stdio.h>
void swapGresit(int a, int b) // primeste COPII
{
int aux = a; a = b; b = aux; // schimba doar copiile
}
void swapCorect(int *a, int *b) // primeste ADRESE
{
int aux = *a; *a = *b; *b = aux;
}
int main(void)
{
int x = 1, y = 2;
swapGresit(x, y);
printf("Dupa swapGresit: x=%d y=%d\n", x, y); // 1 2 - neschimbate
swapCorect(&x, &y);
printf("Dupa swapCorect: x=%d y=%d\n", x, y); // 2 1 - schimbate
return 0;
}
#include <iostream>
using namespace std;
void swapRef(int &a, int &b) // referinte: sintaxa curata
{
int aux = a; a = b; b = aux; // fara steluta
}
void adaugaLa(int &valoare, int cat)
{
valoare += cat;
}
int main()
{
int x = 1, y = 2;
swapRef(x, y);
cout << "x=" << x << " y=" << y << endl; // 2 1
adaugaLa(x, 10);
cout << "x dupa adaugare = " << x << endl; // 12
// o referinta nu poate fi redirectata
int a = 5, b = 9;
int &r = a;
r = b; // ATENTIE: copiaza valoarea lui b in a,
// NU face ca r sa se refere la b
cout << "a=" << a << " b=" << b << endl; // 9 9
return 0;
}
11Atelier de cod
Aici modul Pas cu pas face diferența: panoul din dreapta afișează adresa fiecărei variabile și, pentru pointeri, către ce anume arată în acel moment.
p din panoul din dreapta: adresa crește cu 8 (adică 2 × 4 octeți), iar eticheta din dreapta se schimbă din v[0] în v[2].12Sarcini de lucru
- Declarați o variabilă și un pointer către ea; afișați valoarea, adresa și rezultatul dereferențierii.
- Modificați variabila exclusiv prin pointer și verificați efectul.
- Comparați
sizeofpentru unint, undoubleși pointerii către ele. - Parcurgeți un tablou în trei moduri: cu indice, cu
*(p+i)și cu*p++. - Afișați adresele elementelor și verificați că diferența este
sizeof(tip). - Implementați funcția de interschimbare, mai întâi greșit (prin valoare), apoi corect (prin pointeri).
- Scrieți o funcție care primește un vector și returnează prin pointeri atât minimul, cât și maximul.
- În C++, rescrieți interschimbarea folosind referințe și comparați lizibilitatea codului.
13Aplicație de aprofundare
int (*operatii[4])(int, int)) care permite selectarea operației aritmetice dintr-un meniu.