المؤشرات: العناوين وإلغاء الإشارة والحساب
فهم المؤشرات كعناوين للذاكرة، وتعلم عوامل العنوان وإلغاء الإشارة، وحساب المؤشر داخل المصفوفات، وصحة الثبات مع المؤشرات، ومتى تظل المؤشرات الأولية مناسبة في C++ الحديثة.
ما هو المؤشر؟
المؤشر هو متغير يقوم بتخزين عنوان الذاكرة. كل كائن في C++ موجود في عنوان ما في الذاكرة، ويتيح لك المؤشر الإشارة إلى هذا الكائن بشكل غير مباشر. تعد المؤشرات أساسية لـ C++ - فهي تتيح هياكل البيانات الديناميكية وتعدد الأشكال وتمرير المعلمات بكفاءة.
يتم الإعلان عن نوع المؤشر باستخدام*: يعلنint* p;عن مؤشر إلىint. يحتل المؤشر نفسه الذاكرة (عادةً 8 بايت على أنظمة 64 بت) وقيمته هي عنوان، وليست البيانات التي يشير إليها.
عنوان و المرجعية
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 مقابل مؤشر إلى 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++.
Dangling pointers are the #1 pointer bug. A pointer becomes dangling when the object it points to is destroyed:
- Returning a pointer to a local variable
- Deleting an object while another pointer still references it
- Pointer into astd::vectorthat is reallocated afterpush_back
Uninitialized pointers contain garbage addresses. Always initialize tonullptrif you don't have a target yet. The compiler will NOT warn about all uninitialized pointer uses.
Pointer vs Reference: When to Use Which
References and pointers both provide indirection, but they differ in important ways. References cannot be null, cannot be re-seated, and don't require explicit dereferencing. Prefer references for function parameters; use pointers when nullability or re-seating is needed.
#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
}&gets an address,*follows it — these are inverse operations- Pointer arithmetic is only valid within arrays and scales by
sizeof(T) - Array names decay to pointers, losing size information
- Read
constplacement right-to-left:const int*vsint* const - In modern C++, raw pointers should be non-owning observers only
- Prefer references over pointers unless you need nullability or re-seating
اختبار — اختبر معرفتك
(15 نقطة خبرة)1. What does`const int* p`mean?
2. ماذا يحدث عندما يتم تمرير مصفوفة إلى دالة تتوقع معلمة مؤشر؟
3. متى يكون حساب المؤشر صالحًا في C++؟