Menu
flag Ar iconالعربيةdown icon

المصفوفات في C++: التعريف والفهرسة والأخطاء الشائعة

شرح المصفوفات الخام في C++: كيفية تعريفها وتهيئتها، والفهرسة بأمان، والمرور عليها باستخدام حجمها، وفخّ تحلّل المصفوفة إلى مؤشر، ولماذا يتفوّق عليها عادةً std::array وvector.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

صفّ من القيم بحجم ثابت

المصفوفة كتلة متجاورة من الذاكرة تحمل عددًا ثابتًا من القيم من النوع نفسه. فبينما يخزّن int واحد رقمًا واحدًا، تخزّن مصفوفة من int عددًا كبيرًا منها متتالية، يمكن الوصول إلى كلٍّ منها عبر فهرس صحيح.

تعرّف المصفوفة بإعطاء نوع العناصر واسمٍ وحجمٍ بين قوسين معقوفين. أضف قائمة محاطة بقوسين متعرجين لملئها:

تبدأ الفهرسة من الصفر: العنصر الأول هو scores[0]، ومصفوفة بحجم 4 لها فهارس صالحة من 0 إلى 3. يجب أن يكون الحجم ثابتًا معروفًا وقت الترجمة؛ فلا يمكنك كتابة int n = readInput(); int a[n]; في C++ القياسية (فذلك امتداد غير قابل للنقل). وعندما تحتاج إلى حجم يُحدَّد وقت التشغيل، استعن بدلًا من ذلك بـ vector.

تهيئة المصفوفات

أمامك عدة طرق لملء مصفوفة، وبضعة اختصارات تستحق المعرفة:

النقطة التي ينبغي استيعابها: حين تقدّم قيم تهيئة أقل من الحجم، تُهيّأ القيم المتبقية بالقيمة (صفرٌ للأنواع العددية)، لا أن تُترك كقيم مهملة. أما المصفوفة التي بلا أي قيمة تهيئة — مثل int e[4]; المعرّفة متغيرًا محليًا — فتحتوي على قيم غير محدّدة، وقراءتها قبل إسناد قيمٍ إليها سلوك غير معرّف.

المرور على مصفوفة

لأن العناصر مخزّنة بشكل متجاور، تمرّ على المصفوفة بحلقة فهرس بسيطة. وللبقاء ضمن الحدود، قُد الحلقة بالطول الحقيقي للمصفوفة بدلًا من رقم مكتوب يدويًا:

sizeof(scores) هو إجمالي عدد البايتات للمصفوفة كلها؛ وقسمته على sizeof(scores[0]) (حجم عنصر واحد) يعطي عدد العناصر. ومنذ C++17 توجد صيغة أنظف هي std::size(scores)، وهي أسهل قراءةً وترفض الترجمة إذا مرّرت إليها مؤشرًا بالخطأ. والأبسط من ذلك حين لا تحتاج سوى القيم: تتخطّى حلقة for المعتمدة على المدى حساب الفهرس بالكامل.

فخّ تجاوز الحدود

لا يجري C++ أي فحص للحدود على arr[i]. والفهرسة بعد العنصر الأخير لا تطلق استثناءً ولا تحذّر، بل تقرأ أو تكتب في الذاكرة التي تصادف وجودها هناك. وهذا أكثر أخطاء المصفوفات شيوعًا، وهو حالة كلاسيكية من السلوك غير المعرّف:

int a[3] = {1, 2, 3};
a[3] = 99;          // OOPS - valid indices are 0..2, not 3
cout << a[5];       // garbage, crash, or corruption - undefined behavior

عادةً ما يختبئ خطأ الزيادة بمقدار واحد في شرط الحلقة. فكتابة i <= n بدلًا من i < n تتقدّم خطوةً زائدة وتلمس arr[n] غير الموجود:

for (int i = 0; i <= n; i++)   // BUG: when i == n, arr[i] is out of bounds
    cout << arr[i];

والحل هو الانضباط المذكور في القسم السابق: مرّ بالحلقة بـ i < size ولا تستخدم <= أبدًا، واحسب الحجم من المصفوفة بدلًا من إعادة كتابة قيمة حرفية تخرج عن المزامنة بمجرد إضافة عنصر.

تحلّل المصفوفة: المؤشر الخفيّ

أكثر سلوكيات المصفوفات تعقيدًا في C++ هو التحلّل (decay): فحين تمرّر مصفوفة إلى دالة، تتحوّل بصمت إلى مؤشر إلى عنصرها الأول. وتضيع معلومة الحجم، فيقيس sizeof داخل الدالة المؤشر لا المصفوفة.

لاحظ أن int arr[] وint* arr متطابقان كمعاملين لدالة، والقوسان المعقوفان مجرد زينة. ولأن العدد ضاع، عليك أن تمرّر الطول بنفسك إلى جانب المصفوفة:

int sum(const int* arr, int n) {
    int total = 0;
    for (int i = 0; i < n; i++) total += arr[i];
    return total;
}

هذا النمط القائم على "مرّر مؤشرًا وطولًا، وادعُ أن يتطابقا" هو بالضبط الاحتكاك الذي يدفع معظم شفرات C++ نحو std::array وstd::vector، إذ تحملان حجمهما الخاص ولا تتحلّلان أبدًا.

كلمة سريعة عن المصفوفات متعددة الأبعاد

يمكنك تداخل الأقواس المعقوفة لتكوين شبكة. والمصفوفة ثنائية الأبعاد هي في الحقيقة مصفوفة من المصفوفات، مرتّبة في الذاكرة صفًّا تلو صف:

تفهرسها على هيئة grid[row][col]. وتنطبق التحذيرات نفسها بشأن الحدود والتحلّل — بل تزداد سوءًا، إذ يتطلّب تمرير مصفوفة ثنائية الأبعاد إلى دالة كتابة كل الأبعاد عدا الأول صراحةً (void f(int g[][3])). ولأي شيء يتجاوز شبكة صغيرة ثابتة، يكون vector من vector أقل عرضةً للأخطاء بكثير.

التالي: Vector

المصفوفات الخام سريعة ويمكن التنبؤ بها، لكن حجمها الثابت وغياب الأمان عند الحدود وسلوك تحلّلها إلى مؤشر يجعلها غير عملية في الشفرات اليومية. سنتعرّف تاليًا على std::vector — مصفوفة قابلة لتغيير الحجم تنمو عند الحاجة، وتتذكّر حجمها بنفسها، وتتكامل مباشرةً مع خوارزميات الـ STL، فتمنحك تقريبًا كل ما تقدّمه المصفوفات مع طرقٍ أقل بكثير لإيذاء نفسك.

الأسئلة الشائعة

كيف تعرّف مصفوفة وتهيّئها في C++؟

اكتب نوع العناصر واسمًا والحجم بين قوسين معقوفين، ثم اختياريًا قائمة بين قوسين متعرجين: int scores[4] = {90, 75, 100, 60};. يمكنك حذف الحجم عند تقديم قيم التهيئة؛ فكتابة int scores[] = {90, 75, 100, 60}; تدع المترجم يعدّها نيابةً عنك.

كيف تحصل على طول مصفوفة في C++؟

للمصفوفة الحقيقية التي ما زالت ضمن نطاقها، استخدم std::size(arr) (في C++17) أو sizeof(arr) / sizeof(arr[0]). وهذا لا يعمل بعد أن تتحلّل المصفوفة إلى مؤشر (مثلًا داخل دالة استقبلت int arr[])، حيث يعطي sizeof حجم المؤشر لا حجم المصفوفة.

ماذا يحدث إذا وصلت إلى مصفوفة خارج حدودها في C++؟

هذا سلوك غير معرّف. لا يجري C++ أي فحص للحدود على arr[i]، لذا فإن القراءة أو الكتابة بعد النهاية قد تؤدي إلى انهيار البرنامج أو إعادة قيم غير صالحة أو إفساد الذاكرة المجاورة بصمت. أبقِ الفهرس دائمًا في المدى من 0 إلى size - 1.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن