Indicatori: adrese, dereferențiere și aritmetică
Înțelegeți pointerii ca adrese de memorie, învățați operatorii de adresă și dereferință, aritmetica pointerului în cadrul matricelor, corectitudinea constantă cu pointerii și când pointerii brute sunt încă adecvate în C++ modern.
Ce este un pointer?
Un pointer este o variabilă care stochează o adresă de memorie. Fiecare obiect din C++ se află la o anumită adresă din memorie, iar un pointer vă permite să vă referiți indirect la acel obiect. Pointerii sunt fundamentale pentru C++ - permit structuri dinamice de date, polimorfism și trecerea eficientă a parametrilor.
Un tip de pointer este declarat cu*:int* p;declară un pointer către unint. Pointerul în sine ocupă memorie (de obicei 8 octeți pe sisteme pe 64 de biți) și valoarea sa este o adresă, nu datele către care indică.
Adresă și anulare de referință
Cei doi operatori de bază indicatori sunt&(adresă-de) și*(referință). Operatorul&obține adresa unei variabile. Operatorul*urmează adresa pentru a accesa obiectul indicat.
#include <iostream>
int main() {
int value = 42;
int* ptr = &value; // ptr holds the address of value
std::cout << "value: " << value << '\n'; // 42
std::cout << "&value: " << &value << '\n'; // e.g. 0x7ffd5a3c
std::cout << "ptr: " << ptr << '\n'; // same address
std::cout << "*ptr: " << *ptr << '\n'; // 42 (dereference)
*ptr = 100; // modify value through the pointer
std::cout << "value after *ptr = 100: " << value << '\n'; // 100
int* null_ptr = nullptr; // points to nothing — safe sentinel
// *null_ptr = 5; // UNDEFINED BEHAVIOR — crash on most systems
}Aritmetica pointerului și dezintegrarea matricei
Aritmetica pointerului este validă doar într-o matrice (sau una după sfârșit). Compilatorul scalează aritmetica prinsizeof(T). Un nume de matrice decade la un pointer către primul său element în majoritatea contextelor - de aceea matricele în stil C își pierd informațiile despre dimensiune atunci când sunt transmise funcțiilor.
#include <iostream>
#include <cstddef> // std::ptrdiff_t
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* p = arr; // array decays to pointer to first element
std::cout << *p << '\n'; // 10
std::cout << *(p + 2) << '\n'; // 30 (moves 2*sizeof(int) bytes)
std::cout << p[3] << '\n'; // 40 (p[i] is *(p + i))
// Iterating via pointer
for (int* it = arr; it != arr + 5; ++it) {
std::cout << *it << ' ';
}
std::cout << '\n'; // 10 20 30 40 50
// Pointer difference
std::ptrdiff_t diff = (arr + 4) - arr; // 4 (elements, not bytes)
std::cout << "diff: " << diff << '\n';
// WARNING: arithmetic outside the array is UNDEFINED BEHAVIOR
// int* bad = arr + 10; // UB — don't do this
}const Pointers vs Pointer-to-const
Plasarea luiconstîn raport cu*determină ceea ce este imuabil:
- const int* p (sauint const* p) — pointer către const: nu puteți modifica valoarea indicată prinp, dar puteți reașezappentru a indica în altă parte.
- int* const p — indicatorul const: indicatorul în sine este imuabil (nu se poate reașeza), dar puteți modifica valoarea către care indică.
- const int* const p — atât indicatorul, cât și valoarea indicată sunt imuabile.
Citiți declarații de la dreapta la stânga:int* const pcitește ca „peste un pointer const către int.”
Void pointers (void*) pot deține orice adresă, dar nu pot fi anulate de referință fără proiectare. Ele sunt utilizate în principal în API-uri în stil C și implementări de alocători.
În C++ modern, pointerii bruti ar trebui să fie utilizați exclusiv ca indicatori de observator neproprietari. Ei spun: „Pot vedea acest obiect, dar nu sunt responsabil pentru durata lui de viață”. Utilizări legitime comune:
- Implementarea structurilor de date (noduri de arbore care indică către părinte)
- Parametrii funcției care observă, dar nu dețin (deși referințele sunt adesea mai bune)
- Interfața cu API-urile C
- Iteratoare în containere personalizate
Dacă un pointer deține o resursă, includeți-o înstd::unique_ptrsaustd::shared_ptr. Dacă te trezești că scriidelete, aproape sigur că o faci greșit în C++ modern.
Dangling pointers sunt bug-ul numărul 1. Un indicator devine atârnând atunci când obiectul către care indică este distrus:
- Returnarea unui pointer către o variabilă locală
- Ștergerea unui obiect în timp ce un alt pointer încă îl face referire
- Indicator către unstd::vectorcare este realocat dupăpush_back
Indicatorii neinițializați conțin adrese de gunoi. Inițializați întotdeauna lanullptrdacă nu aveți încă o țintă. Compilatorul NU va avertiza despre toate utilizările de pointer neinițializate.
Indicator vs referință: când să folosiți care
Referințele și indicatorii oferă atât indirecte, dar diferă în moduri importante. Referințele nu pot fi nule, nu pot fi reașezate și nu necesită dereferențiere explicită. Preferați referințe pentru parametrii funcției; utilizați indicatori atunci când este necesară anularea sau reașezarea.
#include <iostream>
void increment_ref(int& val) { ++val; } // cannot be null
void increment_ptr(int* val) { if (val) ++(*val); } // must null-check
int main() {
int x = 10;
increment_ref(x); // clean call syntax
increment_ptr(&x); // caller must take address
std::cout << x << '\n'; // 12
int* p = &x;
p = nullptr; // OK — pointers can be re-seated and nulled
int& r = x;
// r = ???; // references cannot be re-seated
// int& bad; // ERROR — references must be initialized
}&primește o adresă,*o urmează - acestea sunt operații inverse- Aritmetica pointerului este valabilă numai în matrice și scale de
sizeof(T) - Numele de matrice se transformă în pointeri, pierzând informații despre dimensiune
- Citiți poziția
constde la dreapta la stânga:const int*vsint* const - În C++ modern, indicatorii bruti ar trebui să fie doar observatori care nu dețin
- Preferați referințele decât indicatorii, cu excepția cazului în care aveți nevoie de anulare sau relocare
Test — Verifică-ți cunoștințele
(15 XP)1. Ce înseamnă`const int* p`?
2. What happens when an array is passed to a function expecting a pointer parameter?
3. Când este valabilă aritmetica indicatorului în C++?