المصفوفات في البرمجة، Arrays !

السلام عليكم ورحمة الله وبركاته

وقت القراءة: ≈ 15 دقيقة

المقدمة

انتظر لحظة هل يوجد مصفوفات في البرمجة ؟

نعم يوجد مصفوفات في البرمجة وهي تعتبر من أهم المفاهيم في البرمجة وتسمى Array وهي عبارة عن مجموعة من العناصر التي يمكن تخزينها في متغير واحد
لكن عندما نطلق لفظ مصفوفة قد يأتي للبعض أنها تقبل أرقام فقط لكن في البرمجة يمكن تخزين أي نوع من البيانات في المصفوفة سواء كانت نصوص أو أرقام أو رموز أو حروف ... الخ

المصفوفة تعني Array وهذا هو الاسم الشائع لها لذا ستجدني أثناء الشرح أكتب أراي وأعني بها مصفوفة
الأراي هي أول Data Structure سنتعلمها وهي من أهم هياكل البيانات في البرمجة وأكثرها استخدامًا
بالطبع يوجد غيرها من هياكل البيانات مثل Linked List و Stack و Queue و Tree و Graph وغيرها
لكن كل واحدة تحتاج لمقالة أومقالتين خاص بها

على كل حال لنبدأ في شرح الأراي وكيفية استخدامها والتعامل معها

كيفية إنشاء أراي في البرمجة

في الـ C++ يمكنك إنشاء أراي بالطريقة التالية:

int ages[5]; // int تم إنشاء أراي من 5 عناصر من نوع

ماذا يعني هذا السطر؟
هنا كأننا أنشأنا متغير من نوع int لكن الفرق أننا كتبنا [] بعده ووضعنا رقم يعبر عن عدد العناصر التي تريد تخزينها في الأراي

في ذاكرة الجهاز يتم حجز مساحة تخزينية لـ 5 عناصر من نوع int ويتم تخزينهم في مكان واحد بجانب بعضهم البعض

                        memory (RAM)
            ذاكرة الجهاز التي تخزن فيها البيانات
        +-----------------------------------------------+
        |                                               |
        |         ages = { , , , , }                    |
        |                 5 * 4 = 20 bytes              |
        |                                               |
        +-----------------------------------------------+

لاحظ أن قيم الأراي فارغة لأننا لم نقم بتخزين أي قيم فيها بعد


شكل تعريف الأراي قد يختلف من لغة لأخرى فمثلًا في لغة TypeScript يكون تعريف الأراي كالتالي:

let ages: number[5]; // number تم إنشاء أراي من عناصر من نوع

كما تلاحظ أن الطريقة تختلف من لغة لأخرى لكن تعلمنا أن مهما اختلف الطريق يظل الفكرة العامة هي نفسها
مع بعض المميزات والاختلافات التي تقدمها كل لغى برمجة

فائدة إنشاء الأراي

الأراي تساعدك في تخزين مجموعة من البيانات المترابطة مع بعضها في مكان واحد
فمثلا إذا كنت تريد تخزين أعمار مجموعة من الأشخاص بالطريقة العادية كنت ستقوم بإنشاء متغير لكل شخص وتخزين عمره فيه

int age1 = 12;
int age2 = 16;
int age3 = 24;
int age4 = 25;
int age5 = 28;

لاحظ أنك تحتاج لترقيم المتغيرات وكل متغير له اسم مختلف لا يوجد ترابط أو علاقة بين هذه المتغيرات
متغير يحتاج مساحة تخزينية خاصة به

                        memory (RAM)
            ذاكرة الجهاز التي تخزن فيها البيانات
        +-----------------------------------------------+
        |      age1 = 12                                |
        |       4 bytes              age4 = 25          |
        |                             4 bytes           |
        |     age5 = 28                                 |
        |      4 bytes                                  |
        |                          age2 = 16            |
        |                           4 bytes             |
        |       age3 = 24                               |
        |        4 bytes                                |
        +-----------------------------------------------+

لكن مع الأراي يمكنك تخزين هذه الأعمار في مكان واحد تحت اسم واحد مشترك

int ages[5];

هكذا خزنها مساحة لخمس أعمار في مكان واحد، لكن كيف نسند القيم لها؟

كيفية تخزين القيم في الأراي

لتخزين القيم في الأراي يمكنك استخدام الفهرس index لكل عنصر في الأراي
الـ index هو يعبر عن مكان العنصر في الأراي ويبدأ من الصفر

بمعنى أن:

بالتالي اذا أردت تخزين قيمة في الأراي يمكنك فعل ذلك بالطريقة التالية:

تحدد رقم العنصر وتعرف الـ index الخاص به ثم تضع القيمة فيه
مثلاً نريد تخزين 12 في العنصر الأول و 16 في العنصر الثاني و 24 في العنصر الثالث وهكذا
يمكنك فعل ذلك بالطريقة التالية:

int ages[5];

ages[0] = 12; // تخزين القيمة 12 في العنصر الأول
ages[1] = 16; // تخزين القيمة 16 في العنصر الثاني
ages[2] = 24; // تخزين القيمة 24 في العنصر الثالث
ages[3] = 25; // تخزين القيمة 25 في العنصر الرابع
ages[4] = 28; // تخزين القيمة 26 في العنصر الخامس

لاحظ أننا لكي نصل لأي عنصر في الأراي نكتب اسم الأراي وبعدها نكتب الـ index بين []
بالتالي ages[0] تعني العنصر الأول في الأراي والذي خزنا فيه القيمة 10
ages[1] تعني العنصر الثاني في الأراي والذي خزنا فيه القيمة 20

                        memory (RAM)
            ذاكرة الجهاز التي تخزن فيها البيانات
        +-----------------------------------------------+
        |                                               |
        |         index: [0] [1] [2] [3] [4]            |
        |         ages = {12, 16, 24, 25, 28}           |
        |                 5 * 4 = 20 bytes              |
        |                                               |
        +-----------------------------------------------+

عليك أن تتخذها قاعدة في حياتك دائما الـ index يبدأ من الصفر
لاحظ أيضًا أن أخر index في الأراي هو 4 وليس 5 لأن الـ index يبدأ من الصفر
بالتالي لو لديك أراي من 100 عنصر فيكون index أو عنصر هو 0 و index أخر عنصر هو 99

int ages[100];

ages[0] = 12; // العنصر الأول
ages[99] = 75; // العنصر الأخير

إنشاء أراي واسناد لها القيم في نفس الوقت

مثل ما أنت يمكنك إنشاء المتغير واسناد له قيم في نفس الوقت هكذا int x = 5; يمكنك فعل ذلك مع المصفوفات

int ages[5] = {12, 16, 24, 25, 28};

cout << ages[3] // 25

بهذه الطريقة يتم إنشاء الأراي وتخزين القيم فيها في نفس الوقت
لاحظ هنا نستخدم الـ {} لتحديد القيم التي نريد تخزينها في الأراي

لكن الأمر قد يختلف من لغة للأخرى
فمثلًا في لغة TypeScript يكون هكذا بين [] وليس بين {}:

let ages: number[] = [12, 16, 24, 25, 28];

ولاحظ أننا نحدد النوع وأنها أراي في آن واحد هكذا number[]

لذا عليك أن تعرف كيف يكون شكل وطريقة تعريف الأراي في اللغة التي تتعلمها
وكيفية تخزين القيم فيها

اسناد قيم متنوعة في الأراي

كما ذكرنا في بداية الشرح يمكن تخزين إنشاء أراي من أي نوع من البيانات
يمكنك انشاء أراي نوعها char أو string أو float أو bool ... الخ

char alphabet[5] = {'A', 'B', 'C', 'D', 'E'};

cout << alphabet[3] // D

هنا أنشأنا أراي تدعى alphabet من نوع char خزنا فيها بضع حروف كما ترى
لاحظ أن كل عنصر في الأراي يجب أن يكون من نفس النوع الذي حددناه عند إنشاء الأراي

بمعنى أنه لا يمكننا تخزين أي نوع أخر من البيانات عدا التي من نوع char

char alphabet[5];

alphabet[0] = 'A'; // ✅
alphabet[1] = 'B'; // ✅
alphabet[2] = "Hello"; // ❌
alphabet[3] = 67; // it will store the ASCII value of 50 'C'
alphabet[4] = 89.99; // it will store the ASCII value of 89 'Y'

في هذا المثال أنشأنا أراي تدعى alphabet وحددنا أن نوعها char فقط
لذاك لا يمكنك تخزين أي شيء فيها غير الحروف والرموز التي من نوع char


لكن لاحظ أننا قمنا بتخزين القيمة 67 في العنصر الـ index رقم 3 وخزننا القيمة 89.99 في العنصر الـ index رقم 4
لكن لماذا لم يحدث أي خطأ ؟ بعض اللغات قد تعطيك خطأ عندما تقوم بتخزين قيمة غير متوافقة مع نوع الأراي
لكن بعض اللغات عندما تسند قيمة عددية لمتغير من نوع char فهو سيحول الرقم إلى رمز

فمثلا هنا في لغة C++ عندما تقوم بتخزين الرقم 67 في متغير من نوع char فهو سيحوله إلى الحرف C
لأنه يعتمد على جدول يسمى ASCII وهو جدول به 128 حرف ورمز وكل واحدة يقابلها عدد معين
فمثلا الحرف A يقابله الرقم 65 والحرف B يقابله الرقم 66 والحرف C يقابله الرقم 67 وهكذا

أما الرقم 89.99 فسيتم التخلص من الكسور وسيتم تخزين الرقم 89 فقط وسيقوم بالبحث عن الحرف المقابل له في جدول الـ ASCII ويقوم بتخزينه وهو الحرف Y

بعض اللغات مثل قد تجعل الـ char يقبل فقط ما هو موجود في جدول الـ ASCII ولا يقبل غيره
وعندما تسند له رقم فسيبحث عن الحرف أو الرمز المقابل له في جدول الـ ASCII ويقوم بتخزينه

char letter = 65; // A سيقوم بتخزين الحرف

يمكنك الاطلاع على جدول الـ ASCII من هنا


بالطبع كالعادة الأمر يتغير من لغة لأخرى فمثلًا في لغة Python يمكنك تخزين أي نوع من البيانات في الأراي

anything = [10, 'Hello', 24.99, True]

هنا في لغة Python أنشأنا أراي تدعى anything وخزنا فيها أرقام ونصوص وقيم منطقية وأرقام عشرية وأي شيء تريده

نفس الشيء في لغة Typescript يمكنك تخزين أي شيء في الأراي

let anything: any[] = [10, 'Hello', 24.99, true];

هنا في لغة TypeScript أنشأنا أراي تدعى anything وحددنا أن نوعها any وهو يعني أنها تقبل أي نوع من البيانات

الخروج من حدود الأراي

لاحظ أننا نضع ثابت يعبر عن عدد العناصر التي تريد تخزينها في الأراي عند إنشائها

int ages[5];

هنا قمنا بإنشاء أراي من 5 عناصر
وعرفنا أن أول index هو 0 والأخير هو 4

ماذا سيحدث إذا حاولنا تخزين قيمة في index خارج حدود الأراي ؟ مثلا 5 أو -1

int ages[5];

ages[5] = 12; // ❌ Error: Cannot access index 5
ages[-1] = 16; // ❌ Error: Cannot access index -1

هنا حاولنا الوصول للعنصر السادس وهو index رقم 5 وحاولنا اسناد قيمة له
لكن لم نستطع لأنه خارج حدود الأراي لآن الأراي ages تحتوي على 5 عناصر فقط و أخر index هو 4 وليس 5
وكذلك حاولنا الوصول لـ index برقم سالب مثل -1 وهذا خارج حدود الأراي أيضًا لأن الـ index يبدأ من الـ 0


لكن انتبه هنا أحيانًا قد تسمح لك بعض اللغات مثل الـ C++ بالوصول للعناصر خارج حدود الأراي دون أن تعاتبك
لكنك هكذا كأنك قمت بسرقة مساحة تخزينية ليست لك وقد تسبب مشاكل لك في البرنامج لاحقًا

كيف ذلك ؟

حسنا لنفترض أننا لدينا أراي من 3 عناصر
وقمنا باسناد قيم فيها

int numbers[3] = {10, 20, 30};

هكذا تبدو الأراي في الذاكرة

                        memory (RAM)
            ذاكرة الجهاز التي تخزن فيها البيانات
        +-----------------------------------------------+
        |                                               |
        |          index:   [0] [1] [2]                 |
        |         numbers = {10, 20, 30}                |
        |                 3 * 4 = 12 bytes              |
        |                                               |
        +-----------------------------------------------+

هنا قام البرنامج بحجز مساحة تخزينية لـ 3 عناصر وقام بتخزين القيم فيها
ركز على هذه الكلمة أنت قمت بحجز مساحة خاصة بك بالتالي لن يقوم أي برنامج بالتعدي على هذه المساحة إلا أنت

بمعنى هل تتخيل مثلا قمت بتخزين قيمة 35 في index أو في متغير ما ثم لاحقا وجدته فجأة أصبحت قيمته 50 أو A أو true
أكيد لا فلن يحدث هذا إلا إذا قمت أنت بتغيير قيمته

حسنا الآن ماذا سيحدث عندما تتعدى أنت على مساحة ليست لك ؟

int numbers[3] = {10, 20, 30};

numbers[3] = 40;

هنا قمنا بتخزين قيمة 40 في index خارج حدود الأراي وهو 3
هل سيحدث خطأ ؟ بعض اللغات تعاتبك وأخرى لك
هل سيحدث مشكلة حقًا أم لا ؟

تخيل شكل الأراي في الذاكرة

                        memory (RAM)
            ذاكرة الجهاز التي تخزن فيها البيانات
        +-----------------------------------------------+
        |                                               |
        |          index:   [0] [1] [2]   [3]           |
        |         numbers = {10, 20, 30}   40           |
        |                                   ٨           |
        |                                   |           |
        |                       مساحة ليست لك           |
        |                                               |
        +-----------------------------------------------+

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

int numbers[3] = {10, 20, 30};

numbers[3] = 40;

int x = 999

cout << numbers[3];

لاحظ هنا أننا قمنا بتخزين قيمة 40 في index خارج حدود الأراي
ثم قمنا بإنشاء متغير x وتخزين قيمة 999 فيه
ثم قمنا بطباعة numbers[3]

هل تعتقد أن النتيجة ستكون 40 كما هي ؟
أو ستكون 999 ؟

النتيجة ستكون 999 هل تعرف السبب ؟

السبب ببساطة أننا حرامية قمنا بالتعدي على مساحة ليست لنا وقمنا بتخزين قيمة فيها
بالتالي عندما نعرف متغير جديد فغالبًا سيقوم البرنامج بحجز مساحة جديدة له وقد تكون نفس المساحة التي قمنا بالتعدي عليها

لذا المتغير x غالبًا حجز نفس المساحة التي تعدينا عليها واصبحت له الآن فقام بالتعديل عليها

دعونا نرى شكل الذاكرة بعد ما عرفنا المتغير x

                        memory (RAM)
            ذاكرة الجهاز التي تخزن فيها البيانات
        +-----------------------------------------------+
        |                                               |
        |          index:   [0] [1] [2]        [3]      |
        |         numbers = {10, 20, 30}   x = 999      |
        |                                               |
        +-----------------------------------------------+

ستجد أن المتغير x قد حجز نفس المساحة التي كنا بتخزين فيها قيمة 40 والتي وصلنا لها عن طريق numbers[3]
وطالما أن هذه المساحة لم تكن محجوزة لأحد فقد قام المتغير x بحجزها ووضع القيمة 999 فيها
لذا عندما طبعنا numbers[3] طبع لنا قيمة 999 وليس 40


بعض اللغات تعاتبك عندما تحاول التعدي على مساحة ليست لك أو عندما تخرج خارج حدود الأراي
هناك بعض اللغات ستجدها تستخدم الـ index السالب للوصول للعناصر من ناحية اليمين
مثلا numbers[-1] تعني العنصر الأخير في الأراي
و numbers[-2] تعني العنصر قبل الأخير وهكذا
لكن هذا ليست ميزة متواجدة في جميع اللغات

واحدة من اللغات التي تدعم هذه الميزة بشكل مباشر هي لغة Python

numbers = [12, 16, 24, 25, 28]

print(numbers[-1]) # 28
print(numbers[-2]) # 25
print(numbers[-3]) # 24
print(numbers[-4]) # 16
print(numbers[-5]) # 12

هنا في لغة Python يمكنك استخدام الـ index السالب للوصول للعناصر بالعكس

فهنا في حالة أن الأراي تتكون من 5 عناصر

باقي اللغات تدعمها لكن عن طريق دوال وسنتعرف عن هذه الدوال في مقالة أخرى

الـ string ما هو إلا أراي من الـ char !!

نعم في بعض اللغات البرمجية خصوصًا القديمة ليس لديها مفهوم الـ string كنوع بيانات مستقل
بل يعتبر الـ string مجرد أراي من الـ char، بمعنى أن الـ string تم إنشاءه عن طريق أراي من الـ char

مثل لغة C ليس لديها نوع بيانات string وكان يتم عمله عن طريق أراي من الـ char

char name[5] = {'A', 'h', 'm', 'e', 'd'};

printf("%s", name); // Ahmed� ^@

هنا قمنا في لغة C بإنشاء أراي من الـ char تحتوي على اسم Ahmed
ثم قمنا بطباعتها عن طريق الـ printf الخاصة بلغة الـ C ولكن لاحظ أن الناتج لم يكن كما توقعنا
لقد طبع Ahmed� ^@ وليس Ahmed بسبب أن اللغات القديمة لا تعتبر الـ string نوع بيانات مستقل بل تعتبره أراي من الـ char
لذا في لغة C و C++ وبعض اللغات الأخرى يجب أن تضيف في نهاية الأراي شيء يسمى null terminator وهو اشارة لتعبير عن نهاية الـ string
والـ null terminator هو الـ \0 وهو يعني null أي شيء ليس له قيمة أو وجود
وهنا نحن نستخدمه للتعبير عن نهاية الـ string لا أكثر

إذا لم تضف الـ \0 في نهاية الأراي وحاولت طباعتها فسيقوم البرنامج بطباعة حرف حرف وسيخرج من حدود الأراي
ويظل يطبع كل ما في طريقه حتى يجد الـ \0 ويعتبره نهاية الـ string
لهذا طبع لنا Ahmed� ^@ مع حروف زائدة حصل عليها من الذاكر

يمكننا محاكاة الأمر هكذا

                        memory (RAM)
            ذاكرة الجهاز التي تخزن فيها البيانات
        +-----------------------------------------------------+
        |                                                     |
        |            داخل حدود الأراي                         |
        | index:  [0]  [1]  [2]  [3]  [4]  خارج حدود الأراي   |
        | name = {'A', 'h', 'm', 'e', 'd'} � ^@\0             |
        |                                       ٨             |
        |                                       |             |
        |                      عندما وصل لهنا وقف             |
        |                                                     |
        +-----------------------------------------------------+

لاحظ أن خرج وظل يطبع حتى صادف الـ \0 وعندما وصل لها توقف
لذا يجب أن نضع الـ \0 في نهاية الأراي ليعرف البرنامج أن هذه نهاية الـ string

char name[6] = {'A', 'h', 'm', 'e', 'd', '\0'};

printf("%s", name); // Ahmed

هنا قمنا بإضافة الـ \0 في نهاية الأراي للتعبير عن نهاية الـ string
وبهذا تم طباعة الـ string بشكل صحيح

                        memory (RAM)
            ذاكرة الجهاز التي تخزن فيها البيانات
        +-----------------------------------------------------+
        |                                                     |
        | index:  [0]  [1]  [2]  [3]  [4]  [5]                |
        | name = {'A', 'h', 'm', 'e', 'd', '\0'} � ^@\0       |
        |                                    ٨                |
        |                                    |                |
        |                   عندما وصل لهنا وقف                |
        |                                                     |
        +-----------------------------------------------------+

في لغة C يمكنك كتابتها بشكل آخر

char name[6] = "Ahmed";
printf("%s", name); // Ahmed

لاحظ هنا أننا كتبنا الـ string بشكل مباشر وليس بين الـ {}
ولاحظ أننا هنا لم نحتاج لوضع الـ \0 في نهاية الـ string لأن اللغة تقوم بذلك تلقائيًا
ولاحظ أن
لكن انتبه أن برغم من كلمة Ahmed تحتوي على 5 حروف إلا أننا قمنا بإنشاء الأراي بـ 6 خانات
لأننا نحسب حساب الـ \0
وإذا قمنا بجعل الأراي 5 خانات فسيحدث خطأ لأن الـ \0 لن يكون له مكان للتخزين

char name[5] = "Ahmed";
printf("%s", name); // Ahmed� ^@

لذا يجب أن تأخذ في الاعتبار الـ \0 عند إنشاء الـ string كأراي من الـ char
أو يمكنك عدم كتابة طول الأراي وترك اللغة تقوم بذلك تلقائيًا

char name[] = "Ahmed";

printf("%s", name); // Ahmed

الآن في لغة C++ تم اختصار الأراي من الـ char وتم انشاء الـ string لتسهيل الأمر واعطاء مميزات أكثر
وكذلك مع باقي اللغات التي أصبحت تدعم الـ string كنوع بيانات مستقل

string name = "Ahmed";

cout << name; // Ahmed

لاحظ أننا في لغة C++ لم نحتاج لوضع الـ null terminator في نهاية الـ string لأن اللغة تقوم بذلك تلقائيًا
وأيضًا تم توفير العديد من الدوال والميزات لتسهيل التعامل مع الـ string التي ستتعرفها فيما بعض في مقالة أخرى


لكن تذكر أنه بما أن الـ string في الواقع أراي من الـ char فإنه يمكنك الوصول للحروف في الـ string بنفس الطريقة التي نصل بها للعناصر في الأراي عن طريق الـ index

string name = "Ahmed";

cout << name[0]; // A
cout << name[1]; // h
cout << name[2]; // m
cout << name[3]; // e
cout << name[4]; // d

كما ترى، يمكننا الوصول إلى كل حرف في الـ string باستخدام الـ index تمامًا مثل الأراي
ويمكنك التعديل في الـ string بنفس الطريقة

string name = "Ahmed";

name[0] = 'a';

cout << name; // ahmed

لكن التعديل يكون على مقدار حرف واحد فقط وليس على أكثر من حرف لأنك تتعامل مع كل عنصر كـ Char

string name = "Ahmed";

name[0] = "Kamal"; // ❌ Error

الأراي ثنائية الأبعاد

حتى الآن، تحدثنا عن الأراي ذات البعد الواحد أي صف واحد فقط
لكن في الواقع، يمكنك إنشاء أراي متعددة الأبعاد
يمكنك عمل أراي ثنائية الأبعاد وثلاثية الأبعاد و ... إلخ

الأراي ثنائية الأبعاد في C++ تكون كالتالي

int numbers[2][3] = {
    {1, 2, 3},
    {4, 5, 6},
};

هنا قمنا بإنشاء أراي ثنائية الأبعاد تحتوي على 2 صف و 3 أعمدة
لاحظ أن الأراي ذات البعد الواحد كانت تكتب هكذا numbers[3] وكانت كأنها 3 أعمدة في صف واحد
أما في الأراي ثنائية الأبعاد هذا الشكل numbers[2][3] يعني 2 صف و 3 أعمدة


لاحظ أن المتغير numbers هو أراي كل عنصر فيها هو أراي آخر

numbers[2][3] = {
   {العنصر الأول هو أراي يتكون من ثلاث عناصر},
   {العنصر الثاني هو أراي يتكون من ثلاث عناصر},
};

بالتالي عندما تريد هنا الوصول للصف الأول ستكتب numbers[0] وعندما تريد الوصول للصف الثاني ستكتب numbers[1] وهكذا
لكن تذكر أن numbers[0] هو أراي آخر يتكون من 3 عناصر
و numbers[1] هو أراي آخر يتكون من 3 عناصر

int numbers[2][3] = {
    {1, 2, 3},
    {4, 5, 6},
};

// numbers[0] = {1, 2, 3}
// numbers[1] = {4, 5, 6}

بالتالي لكي نصل للقيم والعناصر في كل عمود يجب ان نحدد الصف ثم العمود
فلكي نحصل على العنصر الذي في الصف الأول والعمود الأول نكتب numbers[0][0]
ولكي نحصل على العنصر الذي في الصف الأول والعمود الثاني نكتب numbers[0][1] وهكذا

وتذكر أن الـ index يبدأ من الـ 0 وليس من الـ 1 كما قلنا

int numbers[2][3] = {
    {1, 2, 3},
    {4, 5, 6},
};

// طباعة عناصر الصف الأول
cout << numbers[0][0]; // 1
cout << numbers[0][1]; // 2
cout << numbers[0][2]; // 3

// طباعة عناصر الصف الثاني
cout << numbers[1][0]; // 4
cout << numbers[1][1]; // 5
cout << numbers[1][2]; // 6

لاحظ أن الشق الأول من الـ index يعبر عن الصف والشق الثاني يعبر عن العمود

numbers[الصف][العمود]

بالطبع يمكنك إنشاء أراي ثنائية الأبعاد من أي نوع من البيانات

char alphabet[2][3] = {
    {'A', 'B', 'C'},
    {'D', 'E', 'F'},
};

cout << alphabet[1][2]; // F

هنا قمنا بإنشاء أراي ثنائية الأبعاد من الـ char تحتوي على حروف الأبجدية


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

int numbers[3][3] = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9},
};

numbers[1][1] = 50;

cout << numbers[1][1]; // 50

هنا قمنا بتغيير قيمة العنصر الذي في الصف الثاني والعمود الثاني من 5 إلى 50

أراي من الـ string هي أراي ثنائية الأبعاد

بما أننا قلنا أن الـ string هي أراي من الـ char إذًا أراي من الـ string هي أراي ثنائية الأبعاد

string names[3] = {
    "Ahmed",
    "Kamal",
    "Mahmoud",
};

هنا قمنا بإنشاء أراي من الـ string تحتوي على 3 أسماء
لكي نحضر الإسم الأول نقوم بكتابة names[0]
لكي نحضر الإسم الثاني نقوم بكتابة names[1]

وهكذا

string names[3] = {
    "Ahmed",
    "Kamal",
    "Mahmoud",
};

cout << names[0]; // Ahmed
cout << names[1]; // Kamal
cout << names[2]; // Mahmoud

لكي نحضر أول حرف من كل إسم نقوم بكتابة names[0][0] للإسم الأول والحرف الأول
و names[1][0] للإسم الثاني والحرف الأول وهكذا

string names[3] = {
    "Ahmed",
    "Kamal",
    "Mahmoud",
};

// الحرف الأول من كل إسم
cout << names[0][0]; // A
cout << names[1][0]; // K
cout << names[2][0]; // M

// الحرف الثاني من كل إسم
cout << names[0][1]; // h
cout << names[1][1]; // a
cout << names[2][1]; // a

ماذا عن الأراي ثلاثية الأبعاد وأكثر ؟

بالطبع يمكنك إنشاء أراي ثلاثية الأبعاد أو رباعية الأبعاد وأكثر
والطريقة تكون كالتالي

int numbers[2][3][4] = {
    {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12},
    },
    {
        {13, 14, 15, 16},
        {17, 18, 19, 20},
        {21, 22, 23, 24},
    },
};

هنا قمنا بإنشاء أراي ثلاثية الأبعاد تحتوي على 2 صف و 3 عمود و 4 عمق
الأمر يكون معقدًا قليلًا لكنه في التخيل أو في انشاءها لكنها عندما تفكر فيها قليلًا ستجدها نفس الشيء
فهي تحتوي على بعد جديد لنسميه العمق

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

// numbers[0] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }
// numbers[0][0] = {1, 2, 3, 4}
// numbers[0][0][0] = 1

cout << numbers[0][0][0]; // 1
cout << numbers[0][0][1]; // 2
cout << numbers[0][0][2]; // 3
cout << numbers[0][0][3]; // 4

لاحظ هنا أن numbers[0] هي أراي ثنائية الأبعاد بالتالي يمكننا معاملتها كأراي ثنائية الأبعاد اعتيادية بالتالي لكي نصل للعنصر الذي في الصف الأول والعمود الأول نكتب numbers[0][0][0]
ولكي نصل لعنصر الذي في الصف الأول والعمود الثاني نكتب numbers[0][0][1] وهكذا


لكن لا تقلق لن تحتاج لأراي ثلاثية الأبعاد أو أكثر في معظم الوقت يمكنك أن تعيش حياتك دون أن تقابلك مسألة تحتاج لأراي ثلاثية الأبعاد أو أكثر
في غالب الوقت تكفيك الأراي ذات البعد الواحد والثنائية الأبعاد

الختام

الأراي هي من أهم الأدوات التي يجب عليك أن تتقنها في البرمجة
فهي تساعدك على تخزين البيانات والعناصر بشكل منظم وسهل

وفيما بعد في المقالات القادمة سنتعرف على العديد من الأدوات والميزات التي تساعدك على التعامل مع الأراي بشكل أفضل
وسنحل مسائل برمجية حقيقية تحتاج لأراي
لذا تأكد من فهمك لها جيدًا

بالطبع توجد تفاصيل أكثر وأكثر عن الأراي والتعامل معها ولكن هذا ما يكفي للبداية
وبالتأكيد ستتعلم المزيد والمزيد كلما تقدمت في البرمجة
وفي المقالات القادمة سنتطرق لها بشكل أعمق