C++: المؤشرات والمصفوفات
آخر تحديث: 2026-08-26
في الدرس 16، تعلمنا عن المصفوفات، لكننا لم نناقش ما هو اسم المصفوفة فعليًا.
في الواقع، اسم المصفوفة هو مؤشر — يخزن عنوان العنصر الأول للمصفوفة.
فهم هذا يساعد في تفسير لماذا المصفوفات "تتحول إلى مؤشرات" عند تمريرها كمعلمات.
1. اسم المصفوفة هو مؤشر
(1) 1.1 الحقيقة الأساسية
اسم المصفوفة هو مؤشر للعنصر الأول.
#include <iostream>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
std::cout << "arr = " << arr << std::endl;
std::cout << "&arr[0] = " << &arr[0] << std::endl;
// Both output the same! Because arr is &arr[0]
return 0;
}
الخرج (مثال):
arr = 0x7ffd4a3b
&arr[0] = 0x7ffd4a3b
💡 نقطة مهمة: arr و &arr[0] هما نفس العنوان.
2. الوصول إلى عناصر المصفوفة بالمؤشرات
(1) 2.1 الاستخدام الأساسي
#include <iostream>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* p = arr; // p points to first element
// Method 1: Subscript(same as array)
std::cout << "arr[0] = " << p[0] << std::endl; // 10
std::cout << "arr[1] = " << p[1] << std::endl; // 20
// Method 2: Dereference
std::cout << "*p = " << *p << std::endl; // 10
std::cout << "*(p+1) = " << *(p+1) << std::endl; // 20
return 0;
}
💡 نقطة مهمة: p[i] هي في الواقع *(p+i) (حساب المؤشرات سيُشرح لاحقًا).
3. حساب المؤشرات
(1) 3.1 الجمع والطرح مع المؤشرات
عند جمع أو طرح عدد صحيح من مؤشر، لا يُضاف/يُطرح من قيمة العنوان ببساطة — بل يُضاف/يُطرح عدد × sizeof(نوع) بايت.
#include <iostream>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* p = arr; // Pointing to arr[0]
std::cout << "p = " << p << std::endl; // Assume 0x1000
std::cout << "p + 1 = " << p + 1 << std::endl; // 0x1004(Not 0x1001!)
std::cout << "p + 2 = " << p + 2 << std::endl; // 0x1008
return 0;
}
كيف تعمل:
intيأخذ 4 بايتp + 1يضيف فعليًا1 × 4 = 4بايت- لذا
p + 1يشير إلىarr[1]
(2) 3.2 طرح المؤشرات
طرح مؤشرين يعطي المسافة بينهما (عدد العناصر)، وليس عدد البايتات.
#include <iostream>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* p1 = &arr[0]; // Pointing to arr[0]
int* p2 = &arr[3]; // Pointing to arr[3]
std::cout << "p2 - p1 = " << p2 - p1 << std::endl; // 3(Number of elements)
return 0;
}
💡 نقطة مهمة: طرح المؤشرات له معنى فقط عندما يشيران إلى عناصر من نفس المصفوفة.
4. اجتياز مصفوفة بالمؤشرات (ثلاث طرق)
▶ مثال 1: اجتياز مصفوفة (صعوبة ⭐)
#include <iostream>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
// Method 1: Subscript(Most common)
std::cout << "Method 1: Subscript" << std::endl;
for (int i = 0; i < 5; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// Method 2: Pointer arithmetic
std::cout << "Method 2: Pointer arithmetic" << std::endl;
for (int* p = arr; p < arr + 5; p++) {
std::cout << *p << " ";
}
std::cout << std::endl;
// Method 3: Use standard library function (will learn later)
// std::cout << "Method 3: std::begin/end" << std::endl;
// for (int* p = std::begin(arr); p != std::end(arr); p++) {
// std::cout << *p << " ";
// }
return 0;
}
الخرج:
Method 1: Subscript
10 20 30 40 50
Method 2: Pointer arithmetic
10 20 30 40 50
💡 توصية: الطريقة 1 (الفهرس) هي الأكثر بديهية — فضّلها. الطريقة 2 (حساب المؤشرات) شائعة في الكود منخفض المستوى.
5. تحلل المصفوفة عند تمريرها كمعلمة
(1) 5.1 المشكلة: المصفوفات تتحول إلى مؤشرات
عند تمرير مصفوفة إلى دالة، تتحول إلى مؤشر (تفقد معلومات الحجم).
#include <iostream>
void printSize(int arr) {
// arr is actually a pointer, not an array!
std::cout << "sizeof(arr) = " << sizeof(arr) << std::endl; // 8(64-bit system, pointer size)
}
int main() {
int arr[5] = {10, 20, 30, 40, 50};
std::cout << "sizeof(arr) = " << sizeof(arr) << std::endl; // 20(5 x 4 = 20 bytes)
printSize(arr);
return 0;
}
💡 نقطة مهمة: داخل دالة printSize، arr هو في الواقع مؤشر، لذا sizeof(arr) تُرجع حجم المؤشر (8 بايت)، وليس حجم المصفوفة (20 بايت).
(2) 5.2 الحل: تمرير حجم المصفوفة
#include <iostream>
// Must additionally pass array size
void printArray(int arr, int size) {
for (int i = 0; i < size; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
int main() {
int arr[5] = {10, 20, 30, 40, 50};
printArray(arr, 5); // Pass array name (which is first element address)
return 0;
}
💡 نصيحة: لاحقًا ستتعلم std::array و std::vector، اللذين يحافظان على معلومات الحجم وهما أكثر أمانًا.
6. تطبيق: عكس مصفوفة
▶ مثال 2: عكس مصفوفة بالمؤشرات (صعوبة ⭐⭐)
#include <iostream>
void reverseArray(int* arr, int size) {
int* left = arr; // Pointing to first element
int* right = arr + size - 1; // Pointing to last element
while (left < right) {
// Swap values pointed to by left and right
int temp = *left;
*left = *right;
*right = temp;
left++; // Move right
right--; // Move left
}
}
int main() {
int arr[5] = {10, 20, 30, 40, 50};
std::cout << "Before reversal: ";
for (int i = 0; i < 5; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
reverseArray(arr, 5);
std::cout << "After reversal: ";
for (int i = 0; i < 5; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
return 0;
}
الخرج:
Before reversal: 10 20 30 40 50
After reversal: 50 40 30 20 10
7. الأخطاء الشائعة
(1) 7.1 خروج المؤشر عن الحدود
#include <iostream>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* p = arr;
std::cout << *(p + 10) << std::endl; // ❌ Out of bounds! p+10 points outside array
// Outputs garbage value, or program crashes
return 0;
}
💡 نصيحة: C++ لا تتحقق تلقائيًا من حدود المؤشرات! هذا أحد أسباب سرعة C++، لكنه أيضًا مصدر شائع للأخطاء.
(2) 7.2 طرح مؤشرات بلا معنى
#include <iostream>
int main() {
int x = 5;
int y = 10;
int* p1 = &x;
int* p2 = &y;
std::cout << p2 - p1 << std::endl; // ❌ Undefined behavior! p1 and p2 point to different arrays
// Only subtracting pointers to the same array is meaningful
return 0;
}
❓ أسئلة شائعة
arr و &arr لهما نفس القيمة لكن أنواع مختلفة؟arr من نوع int* (مؤشر إلى int) > - &arr من نوع int(*)[5] (مؤشر إلى "مصفوفة من 5 أعداد صحيحة")* و / في حساب المؤشرات؟p + n، p - n) > - طرح مؤشرين (p2 - p1) > - مقارنة (p1 < p2)▶ مثال 3: اجتياز مصفوفة بالمؤشرات (صعوبة ⭐)
#include <iostream>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int* p = arr;
for (int i = 0; i < 5; i++) {
std::cout << "*(p+" << i << ")=" << *(p + i) << std::endl;
}
return 0;
}
الخرج:
*(p+0)=10
*(p+1)=20
*(p+2)=30
*(p+3)=40
*(p+4)=50
*(p + i) مكافئ لـ p[i] أو arr[i]. جمع/طرح الأعداد الصحيحة من المؤشرات يراعي حجم النوع تلقائيًا.
- أسماء المصفوفات هي مؤشرات (تشير إلى عنوان العنصر الأول)
- جمع/طرح الأعداد الصحيحة من المؤشرات يضيف
عدد × sizeof(نوع)بايت - طرح مؤشرين يعطي عدد العناصر بينهما، وليس البايتات
- المصفوفات تتحول إلى مؤشرات عند تمريرها كمعلمات؛ يجب تمرير الحجم بشكل منفصل
- هناك ثلاث طرق لاجتياز المصفوفات بالمؤشرات: الفهرس، حساب المؤشرات، دوال المكتبة القياسية
📖 ملخص
- اسم المصفوفة: عنوان العنصر الأول، مكافئ لمؤشر
- اجتياز المؤشرات:
*(p + i)مكافئ لـp[i] - المصفوفة كمعلمة: تتحول إلى مؤشر، تتطلب تمرير الطول بشكل منفصل
- المصفوفات متعددة الأبعاد: تُجتاز باستخدام مؤشرات إلى مصفوفات
📝 تمارين
-
أساسي (صعوبة ⭐): عرّف مصفوفة
intبقيمarr[5] = {1, 2, 3, 4, 5}، ثم عرّف مؤشرint*باسمpيشير إلىarr. أخرج جميع العناصر باستخدام المؤشر. -
متوسط (صعوبة ⭐⭐): اكتب دالة
int* findElement(int* arr, int size, int target)تبحث عن قيمة هدف في مصفوفة. إذا وُجدت، أرجع مؤشرًا إلى ذلك العنصر؛ إذا لم تُوجد، أرجعnullptr. -
تحدي (صعوبة ⭐⭐⭐): اكتب برنامجًا ينفذ "الفرز الفقاعي" باستخدام المؤشرات:
-
اجتز المصفوفة، قارن العناصر المتجاورة
-
إذا كانت بترتيب خاطئ، بدّل بينها
-
كرر هذه العملية حتى تُفرز المصفوفة
- أسماء المصفوفات هي مؤشرات ثابتة (عنوان العنصر الأول)
- حساب المؤشرات: p + n يُزيح بعدد n عنصر، وليس n بايت
- الوصول بالفهرس arr[i] مكافئ لـ *(arr + i)
- اجتياز المصفوفات بالمؤشرات أكثر مرونة من الفهرس
- المصفوفات تتحول إلى مؤشرات عند تمريرها كمعلمات دالة
8. 🚀 الخطوة التالية
الآن بعد أن تعلمت عن المؤشرات والمصفوفات، لنتابع إلى حساب المؤشرات المتقدم (الدرس 24) — نظرة أعمق على أنواع المؤشرات، مؤشرات void*، والمؤشرات متعددة المستويات.