C++: حساب المؤشرات المتقدم

آخر تحديث: 2026-08-26

في الدرس 23، تعلمنا أساسيات حساب المؤشرات (p + n، p - q).

لكن هناك تفاصيل كثيرة أخرى في حساب المؤشرات — مثلًا، هل يمكن مقارنة المؤشرات؟ ما هو مؤشر void*؟ ما هي المؤشرات إلى مؤشرات؟



1. مقارنة المؤشرات

(1) 1.1 هل يمكن مقارنة المؤشرات؟

نعم! لكن فقط المقارنات بين مؤشرات تشير إلى نفس المصفوفة لها معنى.

▶ مثال 2: تطبيق برمجي أساسي (صعوبة ⭐)

TEXT 📖 للعرض فقط
#include <iostream>

int main() {
 int arr[5] = {10, 20, 30, 40, 50};
 int* p1 = &arr[0];
 int* p2 = &arr[3];
 
 if (p1 < p2) { // ✅ p1 pointed element is before p2
 std::cout << "p1 is before p2" << std::endl;
 }
 
 if (p2 - p1 == 3) { // ✅ two pointers are 3 elements apart
 std::cout << "p1 and p2 are 3 elements apart" << std::endl;
 }
 
 return 0;
}

الخرج:

TEXT 📖 للعرض فقط
p1 is before p2
p1 and p2 are 3 elements apart

💡 نقطة مهمة: مقارنة المؤشرات تُقارن قيم العناوين (أي مواقع العناصر في المصفوفة).


(2) 1.2 مقارنة تساوي المؤشرات

CPP
#include <iostream>

int main() {
 int x = 5;
 int* p1 = &x;
 int* p2 = &x;
 
 if (p1 == p2) { // ✅ two pointers point to the same variable
 std::cout << "p1 and p2 point to the same variable" << std::endl;
 }
 
 int y = 10;
 int* p3 = &x;
 int* p4 = &y;
 
 if (p3 != p4) { // ✅ two pointers point to different variables
 std::cout << "p3 and p4 point to different variables" << std::endl;
 }
 
 return 0;
}


2. مؤشرات void*

(1) 2.1 ما هو مؤشر void*؟

void* هو مؤشر عام — يمكنه الإشارة إلى أي نوع من البيانات.

TEXT 📖 للعرض فقط
#include <iostream>

int main() {
 int x = 5;
 double y = 3.14;
 char c = 'A';
 
 void* p1 = &x; // ✅ void* can point to int
 void* p2 = &y; // ✅ void* can point to double
 void* p3 = &c; // ✅ void* can point to char
 
 return 0;
}

💡 الخصائص:


(2) 2.2 استخدامات void*

الاستخدام 1: كتابة دوال عامة (ستتعلم لاحقًا أن std::memcpy تستخدم void*)

CPP
#include <iostream>
#include <cstring>

int main() {
 int arr1[5] = {1, 2, 3, 4, 5};
 int arr2[5];
 
 // std::memcpy Use void* to accept any type's pointer
 std::memcpy(arr2, arr1, 5 * sizeof(int));
 
 for (int i = 0; i < 5; i++) {
 std::cout << arr2[i] << " ";
 }
 std::cout << std::endl;
 
 return 0;
}

الاستخدام 2: تخصيص الذاكرة الديناميكي (ستتعلم لاحقًا أن malloc تُرجع void*)


(3) 2.3 تحويل void*

لاستخدام القيمة التي يشير إليها void*، يجب تحويله إلى مؤشر من نوع محدد.

TEXT 📖 للعرض فقط
#include <iostream>

int main() {
 int x = 5;
 void* p = &x; // void* pointing to int
 
 // ❌ Error: Cannot directly dereference void*
 // std::cout << *p << std::endl;
 
 // ✅ Correct: First convert to int*
 int* intPtr = static_cast<int*>(p);
 std::cout << *intPtr << std::endl; // Output 5
 
 return 0;
}

💡 نصيحة: static_cast هو عامل تحويل النوع في C++ (ستُغطى لاحقًا)، وهو أكثر أمانًا من تحويل C القديم (int*)p.



3. المؤشرات متعددة المستويات (مؤشرات إلى مؤشرات)

(1) 3.1 ما هو المؤشر إلى مؤشر؟

المؤشر إلى مؤشر هو مؤشر يخزن عنوان مؤشر آخر (مثل الدمى المتداخلة).

CPP
#include <iostream>

int main() {
 int x = 5;
 int* p = &x; // p points to x
 int** pp = &p; // pp points to p (pointer to pointer)
 
 std::cout << "x = " << x << std::endl;
 std::cout << "*p = " << *p << std::endl; // Dereference once: get x value
 std::cout << "**pp = " << **pp << std::endl; // Dereference twice: get x value
 
 return 0;
}

(2) 3.2 تعريف المؤشرات متعددة المستويات

التعريف المعنى
int x; x هو int
int* p; p هو int* (مؤشر إلى int)
int** pp; pp هو int** (مؤشر إلى int*)
int*** ppp; ppp هو int*** (مؤشر إلى int**)

💡 كيفية القراءة: اقرأ من اليمين إلى اليسار.


(3) 3.3 استخدامات المؤشرات متعددة المستويات

الاستخدام 1: مصفوفات ثنائية الأبعاد الديناميكية (ستُغطى لاحقًا)

TEXT 📖 للعرض فقط
// Dynamically allocate a 3x4 2D array
int** matrix = new int*[3]; // matrix is a pointer to pointer
for (int i = 0; i < 3; i++) {
 matrix[i] = new int[4];
}

الاستخدام 2: دوال تحتاج لتعديل مؤشر (نادر، فقط كن على علم)

CPP
#include <iostream>

void allocateInt(int** ptr) {
 *ptr = new int(5); // Modify *ptr (i.e., modify the original pointer)
}

int main() {
 int* p = nullptr;
 allocateInt(&p); // Pass the pointer address
 std::cout << *p << std::endl; // Output 5
 delete p; // Free memory
 return 0;
}


4. المؤشرات مع const

(1) 4.1 أربع تركيبات

التعريف المعنى يمكن تغيير المؤشر؟ يمكن تغيير القيمة المُشار إليها؟
int* p; مؤشر عادي ✅ نعم ✅ نعم
const int* p; مؤشر إلى int ثابت ✅ نعم ❌ لا
int* const p = &x; مؤشر ثابت (يجب التهيئة) ❌ لا ✅ نعم
const int* const p = &x; مؤشر ثابت إلى ثابت ❌ لا ❌ لا

💡 طريقة تذكر: اقرأ من اليمين إلى اليسار.


(2) 4.2 مثال: const int* (الأكثر استخدامًا)

TEXT 📖 للعرض فقط
#include <iostream>

void printArray(const int* arr, int size) {
 for (int i = 0; i < size; i++) {
 std::cout << arr[i] << " ";
 }
 std::cout << std::endl;
 
 // ❌ Cannot modify:arr[i] = 0; // Compile error
}

int main() {
 int arr[5] = {1, 2, 3, 4, 5};
 printArray(arr, 5);
 return 0;
}

الخرج:

TEXT 📖 للعرض فقط
1 2 3 4 5

💡 نقطة مهمة: const int* arr هو نوع معلمة الدالة الأكثر استخدامًا — يعني "أنا أقرأ فقط، لا أعدّل."



5. تطبيق: تنفيذ نسخ سلسلة بالمؤشرات

▶ مثال 1: strcpy مكتوبة يدويًا (صعوبة ⭐⭐)

CPP
#include <iostream>

void myStrcpy(char* dest, const char* src) {
 while (*src != '\0') {
 *dest = *src; // Copy character
 dest++; // Move pointer
 src++;
 }
 *dest = '\0'; // Add null terminator
}

int main() {
 char src[] = "Hello";
 char dest[20];
 
 myStrcpy(dest, src);
 std::cout << "src = " << src << std::endl;
 std::cout << "dest = " << dest << std::endl;
 
 return 0;
}
▶ جرّب الكود

الخرج:

TEXT 📖 للعرض فقط
src = Hello
dest = Hello

💡 نصيحة: هذا البرنامج يستخدم حساب المؤشرات والتحويل — إنه تطبيق ممتاز لفهم المؤشرات.


▶ مثال 3: اجتياز مصفوفة بالمؤشرات (صعوبة ⭐)

CPP
#include <iostream>

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int* p = arr; // p points to first array element

    std::cout << "Traversing array with pointers: " << std::endl;
    for (int i = 0; i < 5; i++) {
        std::cout << "*p = " << *p << std::endl;
        p++; // Move pointer to next element
    }

    // pointer subtraction calculates number of elements
    int* end = arr + 5;
    std::cout << "Number of elements:" << end - arr << std::endl;

    return 0;
}
▶ جرّب الكود

الخرج:

TEXT 📖 للعرض فقط
Traversing array with pointers: 
*p = 10
*p = 20
*p = 30
*p = 40
*p = 50
Number of elements:5

❓ أسئلة شائعة

س: لماذا لا يمكن تحويل void؟* ج: لأن void هو نوع بلا حجم — المترجم لا يعرف ما إذا كان سيقرأ 1 بايت، 4 بايت، أم 8 بايت.

يجب تحويله إلى مؤشر من نوع محدد (مثل int*، double*) حتى يعرف المترجم حجم الخطوة.

س: ما استخدامات المؤشرات متعددة المستويات؟ ج: في المشاريع الفعلية، int** تُرى غالبًا في: > 1. مصفوفات ثنائية الأبعاد الديناميكية (مثل new int*[3]) > 2. دوال تحتاج لتعديل المؤشر نفسه (مثل void foo(int** p) يمكنها تعديل ما يشير إليه p)

المؤشرات فوق مستويين (int*** إلخ) غير موصى بها — صعبة الفهم جدًا.

س: هل const int* و int const* متطابقان؟ ج: نعم! الشكلان التاليان متكافئان: > > const int p; // موصى به > int const p; // متكافئ، لكن أقل شيوعًا > > لكن int* const p مختلف — هذا مؤشر const (لا يمكن تغيير ما يشير إليه).**


📖 ملخص


📝 تمارين

  1. أساسي (صعوبة ⭐): عرّف متغير int بقيمة x = 5، ثم عرّف مؤشر int* باسم p يشير إلى x، ثم عرّف مؤشر int** باسم pp يشير إلى p. أخرج x، *p، **pp وتحقق من تساويها.

  2. متوسط (صعوبة ⭐⭐): اكتب دالة void swap(int** a, int** b) تبدّل ما يشير إليه مؤشران.

TEXT 📖 للعرض فقط
Input: p1 points to x, p2 points to y
Call swap(&p1, &p2)
Output: p1 points to y, p2 points to x
  1. تحدي (صعوبة ⭐⭐⭐): استخدم void* وحساب المؤشرات لكتابة دالة myMemcpy يدويًا:
  2. نموذج الدالة: void myMemcpy(void* dest, const void* src, size_t n)
  3. الوظيفة: نسخ n بايت من src إلى dest
  4. تلميح: انسخ بايت ببايت (استخدم char* لحساب المؤشرات)

6. 🚀 الخطوة التالية

الآن بعد أن تعلمت مفاهيم المؤشرات المتقدمة، لنتابع إلى المراجع (الدرس 25) — "النسخة الأكثر أمانًا" من المؤشرات، ميزة مهمة في C++!

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%