انتقل إلى المحتوى

المؤشرات: العناوين وإلغاء الإشارة والحساب

فهم المؤشرات كعناوين للذاكرة، وتعلم عوامل العنوان وإلغاء الإشارة، وحساب المؤشر داخل المصفوفات، وصحة الثبات مع المؤشرات، ومتى تظل المؤشرات الأولية مناسبة في 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.

address_deref.cpp
#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.

pointer_arithmetic.cpp
#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.

ptr_vs_ref.cpp
#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 bysizeof(T)
  • Array names decay to pointers, losing size information
  • Readconstplacement 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++؟