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

المصفوفات ثنائية الأبعاد في لغة C: المصفوفات متعدّدة الأبعاد وتوزيع الذاكرة والمصفوفات الرياضية

كيفية تعريف المصفوفات ثنائية الأبعاد في C وتهيئتها والمرور عليها، وما يعنيه توزيع الصفوف أولًا فعليًا في الذاكرة، ولماذا يتطلّب تمرير مصفوفة ثنائية إلى دالة عددَ الأعمدة، ومثال معالَج على المصفوفات الرياضية.

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

المصفوفة متعدّدة الأبعاد مصفوفة عناصرها مصفوفات بدورها. فـ int grid[3][4]; ليست نوع شبكة خاصًّا - بل ثلاثة عناصر، كلٌّ منها مصفوفة من أربعة أعداد صحيحة، مخزَّنة متتابعة. وبمجرّد استيعاب ذلك، يتبع بقية الموضوع: التوزيع، وحسابات الفهرسة، والقاعدة المحيّرة بغير ذلك عن تمريرها إلى الدوال.

والبعدان يغطّيان كل استخدام عملي تقريبًا - الشبكات والجداول والمصفوفات الرياضية ورقع الألعاب والصور - فذلك ما تعمل به هذه الصفحة.

التعريف والتهيئة

int grid[3][4];        // 3 rows, 4 columns - 12 ints

الرقم الأول هو عدد الصفوف، والثاني عدد الأعمدة. ويمكن كتابة المهيّئات مسطّحة أو بأقواس معقوفة داخلية؛ والأقواس تستحق الاستخدام لأنها تُظهر الشكل.

وصيغة int e[][3] تهمّ: فيمكنك ترك عدد الصفوف فارغًا ودع المهيّئ يقرّر، أما عدد الأعمدة فليس اختياريًا أبدًا. ويشرح القسم التالي السبب.

توزيع الصفوف أولًا

تخزّن C المصفوفة ثنائية الأبعاد بـترتيب الصفوف أولًا: الصف 0 كاملًا، ثم الصف 1 كاملًا، وهكذا، في كتلة ذاكرة واحدة غير منقطعة. ولا توجد مصفوفة من مؤشّرات الصفوف خلف الكواليس.

int grid[3][4] = {{ 1, 2, 3, 4},
                  { 5, 6, 7, 8},
                  { 9,10,11,12}};

how you picture it            how it actually sits in memory
+----+----+----+----+
|  1 |  2 |  3 |  4 |   row 0   +--+--+--+--+--+--+--+--+--+--+--+--+
+----+----+----+----+           | 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|
|  5 |  6 |  7 |  8 |   row 1   +--+--+--+--+--+--+--+--+--+--+--+--+
+----+----+----+----+            \__ row 0 __/\__ row 1 __/\_ row 2 _/
|  9 | 10 | 11 | 12 |   row 2
+----+----+----+----+           grid[i][j] is at element index i*4 + j

وتلك الصيغة، i * columns + j، هي الآلية كلها - وهي سبب وجوب معرفة المترجم عددَ الأعمدة لفهرسة أي شيء. ولا يدخل عدد الصفوف في الحساب إطلاقًا.

ويمكنك رؤية التوزيع مباشرةً بطباعة العناوين:

تتصاعد العناوين بمقدار sizeof(int) بلا فجوات، بما في ذلك حيث ينتهي صفّ ويبدأ التالي. والحلقة المسطّحة تثبت ذلك - فـ flat[k] تمشي على العناصر الاثني عشر كلها كمجرى واحد.

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

الحلقات المتداخلة

يريد البعدان حلقتَي for: الخارجية تختار الصف، والداخلية تمسح أعمدة ذلك الصف.

سمِّ العدّادات بما تعنيه (i/row للصفوف، وj/col للأعمدة) وأبقِ الترتيب متّسقًا - grid[row][col] في كل مكان. فنصف أخطاء المصفوفات ثنائية الأبعاد زوج فهارس مقلوب.

والأحجام المعرَّفة بـ #define ليست زينة أيضًا: فحدود الحلقة والتعريف لم يعد بإمكانهما الانحراف عن بعضهما حين تغيّر الشكل.

تمرير مصفوفة ثنائية الأبعاد إلى دالة

إليك القاعدة التي توقع بالجميع: يجب أن يصرّح وسيط الدالة بعدد الأعمدة.

والسبب هو التحوّل. فتمرير grid يحوّلها إلى مؤشّر إلى عنصرها الأول - وعناصرها صفوف، فالنوع هو int (*)[4]: مؤشّر إلى مصفوفة من 4 أعداد int. ولتعني grid[i][j] شيئًا، يجب أن يعرف المترجم كم يبلغ طول صفّ واحد، وذلك هو الرقم 4. أما عدد الصفوف فغائب فعلًا عن النوع، ولهذا يسافر كوسيط منفصل.

ولاحظ أن int (*grid)[COLS] وint grid[][COLS] هما الوسيط نفسه مكتوبًا بطريقتين - والأقواس مطلوبة، إذ ستكون int *grid[COLS] مصفوفةَ مؤشّرات بدلًا من ذلك. وذلك التمييز مغطّى في المؤشّرات والمصفوفات.

وإن كان عدد الأعمدة معروفًا في وقت التشغيل فقط، فإن الوسائط متغيّرة التعديل في C99 تتيح لك تمريره أولًا:

void print_any(int rows, int cols, int grid[rows][cols]);

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

data[i * cols + j] هي بالضبط ما يكتبه المترجم نيابةً عنك في حالة الحجم الثابت. وأداؤها يدويًا يكلّف سطرًا واحدًا ويعمل لأي شكل يُقرَّر في وقت التشغيل.

مثال على المصفوفات الرياضية

يجمع ضرب المصفوفات الصفحةَ كلها - ثلاث حلقات متداخلة على تخزين الصفوف أولًا.

تفصيلان يستحقّان النقل. تقرن حلقة k الداخلية a[i][k] بـ b[k][j] - فأحد الفهرسين يمشي في صفّ والآخر يمشي في عمود. ويبدأ التبديل حلقته الداخلية عند j = i + 1: فالبدء من 0 كان سيبدّل كل زوج مرتين ويترك المصفوفة دون تغيير.

ثلاثة أبعاد وما فوق

يمتدّ النمط، وكذلك القاعدة عن وسائط الدوال - فكل بُعد عدا الأول يجب التصريح به.

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

أخطاء شائعة

  • كتابة grid[i, j]. يقيّم عامل الفاصلة i ويهملها ويفهرس بـ j. وهي تُترجَم. وهي خاطئة. استخدم grid[i][j].
  • قلب الفهرسين. فـ grid[col][row] تقرأ عنصرًا حقيقيًا من الموضع الخاطئ، فلا يوجد خطأ يلتقطها. أبقِ ترتيب [row][col] في كل مكان.
  • حذف حجم العمود في وسيط. فـ void f(int grid[][]) لا تُترجَم، وذلك هو المترجم ينقذك.
  • الخروج عن الحدود. كما في أي مصفوفة، لا يوجد فحص حدود. فـ grid[0][5] على شبكة [3][4] تقرأ grid[1][1] بصمت، لأن التوزيع متّصل والحساب لا يبالي.

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

كيف تعرّف مصفوفة ثنائية الأبعاد في لغة C؟

ضع حجمين بين قوسين معقوفين: int grid[3][4]; تعرّف 3 صفوف من 4 أعمدة - 12 عددًا صحيحًا إجمالًا. اقرأها هكذا: "مصفوفة من 3 أشياء، كلٌّ منها مصفوفة من 4 أعداد int"، وهذا حرفيًا كيف تخزّنها C.

كيف تُخزَّن المصفوفة ثنائية الأبعاد في الذاكرة في لغة C؟

بترتيب الصفوف أولًا: كل عناصر الصف 0، ثم كل عناصر الصف 1، وهكذا، في كتلة متّصلة واحدة. وتعيش grid[i][j] على إزاحة i * columns + j عنصرًا من البداية، ولهذا يكون عدد الأعمدة هو الرقم الذي يحتاجه المترجم.

كيف تمرّر مصفوفة ثنائية الأبعاد إلى دالة في لغة C؟

يجب أن يصرّح الوسيط بعدد الأعمدة: void print(int grid[][4], int rows) أو ما يكافئها void print(int (*grid)[4], int rows). ويمكن حذف عدد الصفوف لأن المصفوفة تتحوّل إلى مؤشّر إلى صفّ - لكن بدون حجم العمود لا يستطيع المترجم حساب أين يبدأ الصف.

هل يمكن تهيئة مصفوفة ثنائية الأبعاد بأصفار كلها؟

نعم: int grid[3][4] = {0}; تصفّر كل عنصر، لأن أي عنصر لا تُدرِجه يُهيَّأ بصفر. وint grid[3][4] = {{1, 2}}; تضبط أول مدخلتين من الصف 0 وتترك العشر الأخرى أصفارًا.

Coddy programming languages illustration

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

ابدأ الآن