Δείκτες: Διευθύνσεις, Αποαναφορά και Αριθμητική
Κατανοήστε τους δείκτες ως διευθύνσεις μνήμης, μάθετε τους τελεστές διεύθυνσης και παραπομπής, την αριθμητική του δείκτη μέσα σε πίνακες, την ορθότητα με τους δείκτες και πότε οι ακατέργαστοι δείκτες εξακολουθούν να είναι κατάλληλοι στο σύγχρονο C++.
What Is a Pointer?
A pointer is a variable that stores a memory address. Every object in C++ lives at some address in memory, and a pointer lets you refer to that object indirectly. Pointers are fundamental to C++ — they enable dynamic data structures, polymorphism, and efficient parameter passing.
A pointer type is declared with*:int* p;declares a pointer to anint. The pointer itself occupies memory (typically 8 bytes on 64-bit systems) and its value is an address, not the data it points to.
Address-Of & Dereference
The two fundamental pointer operators are&(address-of) and*(dereference). The&operator obtains the address of a variable. The*operator follows the address to access the pointed-to object.
#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
}Pointer Arithmetic & Array Decay
Pointer arithmetic is only valid within an array (or one past the end). The compiler scales the arithmetic bysizeof(T). An array name decays to a pointer to its first element in most contexts — this is why C-style arrays lose their size information when passed to functions.
#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
The placement ofconstrelative to*determines what is immutable:
- const int* p (orint const* p) — pointer to const: you cannot modify the pointed-to value throughp, but you can re-seatpto point elsewhere.
- int* const p — const pointer: the pointer itself is immutable (cannot re-seat), but you can modify the value it points to.
- const int* const p — both the pointer and the pointed-to value are immutable.
Read declarations right to left:int* const preads as "pis a const pointer to int."
Void pointers (void*) can hold any address but cannot be dereferenced without casting. They are primarily used in C-style APIs and allocator implementations.
In modern C++, raw pointers should be used exclusively as non-owning, observer pointers. They say: "I can see this object, but I am not responsible for its lifetime." Common legitimate uses:
- Implementing data structures (tree nodes pointing to parent)
- Function parameters that observe but don't own (though references are often better)
- Interfacing with C APIs
- Iterators in custom containers
If a pointer owns a resource, wrap it instd::unique_ptrorstd::shared_ptr. If you find yourself writingdelete, you are almost certainly doing it wrong in modern C++.
Οι δείκτες που κρέμονται είναι το #1 σφάλμα δείκτη. Ένας δείκτης κρέμεται όταν το αντικείμενο στο οποίο δείχνει καταστρέφεται:
- Επιστροφή ενός δείκτη σε μια τοπική μεταβλητή
- Διαγραφή αντικειμένου ενώ ένας άλλος δείκτης εξακολουθεί να το αναφέρει
- Δείκτης σε έναstd::vectorπου ανακατανέμεται μετά τοpush_back
Οι μη αρχικοποιημένοι δείκτες περιέχουν διευθύνσεις σκουπιδιών. Πάντα αρχικοποιείτε σεnullptrεάν δεν έχετε ακόμη στόχο. Ο μεταγλωττιστής ΔΕΝ θα προειδοποιήσει για όλες τις μη αρχικοποιημένες χρήσεις δείκτη.
Pointer vs Reference: Πότε να χρησιμοποιήσετε ποιο
Οι αναφορές και οι δείκτες παρέχουν αμφότερα έμμεσα, αλλά διαφέρουν με σημαντικούς τρόπους. Οι αναφορές δεν μπορούν να είναι μηδενικές, δεν μπορούν να επανατοποθετηθούν και δεν απαιτούν ρητή αποαναφορά. Προτιμήστε αναφορές για παραμέτρους συνάρτησης. χρησιμοποιήστε δείκτες όταν απαιτείται μηδενισμός ή επανατοποθέτηση.
#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
}- Το
&παίρνει μια διεύθυνση, το*την ακολουθεί — αυτές είναι αντίστροφες πράξεις - Η αριθμητική των δεικτών είναι έγκυρη μόνο σε πίνακες και κλίμακες από το
sizeof(T) - Τα ονόματα πινάκων διασπώνται σε δείκτες, χάνοντας πληροφορίες μεγέθους
- Διαβάστε την τοποθέτηση
constαπό δεξιά προς τα αριστερά:const int*vsint* const - Στο σύγχρονο C++, οι πρωτογενείς δείκτες θα πρέπει να είναι μόνο παρατηρητές που δεν ανήκουν στην ιδιοκτησία
- Προτιμήστε τις αναφορές σε σχέση με τους δείκτες, εκτός εάν χρειάζεστε μηδενισμό ή επανατοποθέτηση
Κουίζ — Δοκιμάστε τις γνώσεις σας
(15 XP)1. Τι σημαίνει το`const int* p`;
2. What happens when an array is passed to a function expecting a pointer parameter?
3. Πότε ισχύει η αριθμητική του δείκτη στο C++;