A Crash Course in C - Part 5

Pointers and Arrays.

المؤشرات Pointers

المؤشر هو متغير يحمل عنوان الذاكر الخاص بمتغير أخر. و توفر المؤشرات طريقة مباشرة للتعديل و الوصول للبيانات في الذاكرة. و يتم اعتبار تلك الخاصية احد اقوي الخصائص في لغة C. و طريقة الإعلان عن مؤشر تكون مماثلة لطريقة الإعلان عن متغير. الفرق الوحيد هي العلامة * بين نوع المؤشر و اسمه.

pointers-in-c

و يحدد حجم المؤشر القيمة التي يمكنه تخزينها و التعامل معها، مثال مؤشر بحجم 16Bit يمكنه التعامل مع عناوين الذاكرة بين 0 لـ 65535 و مؤشر بحجم 32Bit يمكنه التعامل مع عناوين بين 0 لـ 4294967295. المثال التالي يوضح الشكل العام للمؤشرات:

لاحظ كيف تتشابه أنواع المؤشرات مع انواع المتغيرات المُشار اليها حيث انه من الخطأ استخدام المؤشرات للإشارة لأنواع مختلفة من البيانات بدون استخدام الـ casts, مثال:

المثال التالي يشرح استخدام المؤشرات في تعيين قيمة جديدة للمتغير.

pointer

لاحظ كيف يمكننا:

 

ترتبط المؤشرات و المصفوفات بقوة حيث انه كلما تم التعريف عن مصفوفة تم تحويلها تلقائيا إلي مؤشر يشير الي عنوان عنصرها الأول.

و بستخدام المؤشرات يمكننا التنقل بين البيانات داخل الذاكرة فكمثال يمكننا بإضافة 1 لمؤشر يشير الي مصفوفة ان نتقدم بين عناصرها كما في المثال التالي:

pointer2

كما يمكننا استبدال المصفوفات بالمؤشرات لتخزين النصوص كما في المثال التالي:

pointers3

لاحظ استخدامنا (mystring+i)* بدلا من ++mystring* حتي لا نزيد من قيمة المؤشر فيشير الي قيمة أخري في الذاكرة. المثال التالي يوضح ما يمكن ان يحدث في حالة استخدمنا ++mystring*:

pointers4

لاحظ الأن mystring اصبح يشير الي قيمة string في الذاكرة بعد الإنتهاء من تنفيذ الحلقة، يظهر ذلك عند محاولة طباعة ما يحتويه والذي تغير ليساوي SomeStringAfter بدلا من HELLO.

 

عندما يتم تمرير المتغير المحلي الخاص بدالة إلي دالة أخري فإنه يتم دائما تمرير قيمته إلي وسيط الدالة pass by value. نتيجة لذلك، اي عملية تتم بستخدام الوسيط تبقي في النطاق المحلي للدالة و لا تؤثر علي قيمة المتغير الذي تم تمرير قيمته.

مع ذلك بستخدام المؤشرات يمكننا التعديل علي المتغيرات المحلية بستخدام عنوانها في الذاكرة و يسمي ذلك النوع من تمرير الوسائط pass by reference. المثال التالي يمرر عنوان الذاكرة الخاص بالمتغير num للمؤشر الوسيط ptr من ثمة يتم التعديل علي القيمة داخل عنوان الذاكرة المُشار إليه.

pointer4

 

و يفتح استخدام المؤشرات للتعديل علي البيانات الباب للكثير من الأفكار البرمجية التي تسهل كتابة برامج اكثر تعقيدا. كمثال يمكننا تمثيل الدالة strcpy و هي احدي دوال المكتبة القياسية و تقوم بنسخ نص من متغير الي متغير اخر في الشكل التالي:

pointer5

لاحظ كيف كتبنا الشرط الخاص بحلقة while ليكون غير صحيح في حالة كان source* يساوي 0\ هذا بسبب ان النصوص تنتهي بعلامة null terminator داخل الذاكرة، بمعني ان النص Hello World يتم التعامل معه علي انه Hello World\0 و عن طريق ذلك يمكننا إدراك متي ينتهي النص كما تم في حلقة while.

 

يمكننا تعيين مؤشرات للدوال كذلك، و بستخدام المؤشرات للدوال يمكننا تمرير الدوال كوسائط لدوال اخري او الجمع بين عدة مؤشرات للدوال في مصفوفة واحدة و استخدامها لإستدعاء الدالة حسب موقعها في تلك المصفوفة، المثال التالي يوضح استخدم مؤشرات الدوال add sub mul div لتمريرها كوسائط للدالة execute_operation.

funptr

 

المصفوفات و النصوص Arrays and Strings

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

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

ان كان عدد القيم المستخدمة في المصفوفة اقل من الحجم المحدد لها فسيتم تعين باقي العناصر لتساوي 0 و لكن لا يمكن ان يتعدي عدد عناصر المصفوفة حجمها، و في حالة عدم تحديد عدد عناصر الدالة فسيتم تعينها بشكل ديناميكي لتناسب عدد عناصر المصفوفة، مثال:

المثال التالي يوضح استخدام المؤثر sizeof لمعرفة عدد عناصر الدالة في جملة شرطية داخل حلقة for:

arr

لاحظ كيف يمكننا الوصول الي القيم داخل المصفوفة عن طريق استخدام الرقم التسلسلي الخاص بقيمة معينة فالقيمة a تمتلك الرقم التسلسلي 0 لأنها اول عنصر داخل المصفوفة يليها القيمة b بالرقم 1 و هكذا ..

و تمتلك المصفوفات من النوع char خصائص تميزها بسبب علاقتها بالنصوص. فكمثال يمكننا التعريف عن نص بستخدام المصفوفات بالشكل التالي.

strarr

لاحظ حجم المصفوفة يساوي 6 و ليس 5 بسبب ان النصوص تنتهي بـ 0\ مما يضيف عنصرا جديدا للمصفوفة بشكل تلقائي، هذا يساوي كتابة البرنامج السابق علي الشكل التالي.

و استخدام المصفوفات بهذا الشكل يتيح لنا التعديل علي قيم المتغيرات داخلها كما في المثال التالي:

strarr2

 

يمكننا كذلك تعيين مؤشر لثابت نصي، في تلك الحالة لن يمكننا التعديل علي قيمة النص بعد تعينه هذا بسبب ان الثوابت النصية تمتلك مدي ثابت ( يتم تخصيص مساحة المصفوفة في الذاكرة قبل بدئ تنفيذ البرنامج و تبقي تلك المساحة حتي انتهاء تنفيذه في الذاكرة ) و يشير مؤشر الثابت النصي الي بداية تلك المصفوفة، مثال:

و تفيد المؤشرات للثوابت النصية في امكانية استخدام النصوص بعد انتهاء الدالة. المثال التالي يوضح كيفية تعيين القيمة المرجعة للدالة retStr لتكون مؤشر لثابت نصي و استخدامه خارج نطاق الدالة.

strarr3

مصفوفات من المؤشرات Arrays of Pointers

نظرا لأن المؤشرات مجرد نوع من المتغيرات، يمكننا تخزينها داخل مصفوفات تماما كأي متغيرات آخري. في المثال التالي تم تعيين المصفوفة pa لتحمل داخلها العنصرين val& و array+1 من ثم قمنا بطباعة قيمة هذين العنصرين.

parr

الأمر لا يتوقف هنا حيث يمكننا التعديل و التلاعب بالقيم التي تشير إليها المؤشرات مثل اي مؤشرات عادية، مثال:

ptrarr

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

stringarr

 

مثال آخر شائع هو تخزين مؤشرات لمجموعة دوال تتشارك في نفس نوع و عدد الوسائط و نفس القيمة المرجعة و يتم استدعاء الدوال بناء علي موقع فهرستها داخل المصفوفة، المثال التالي يوضح الشكل العام للتعريف عن مصفوفة من مؤشرات الدوال:

السطر السابق يقوم بتعريف المتغير pf، و الذي يمثل مصفوفة من مؤشرات الدوال التي تتوافق في نوع و عدد الوسائط و القيمة المرجعة، كل دالة تأخذ وسيط بنوع البيانات * char و تقوم بإرجاع int.

المثال التالي يوضح استخدام مصفوفة من مؤشرات الدوال لكتابة آلة حاسبة بسيطة. كل دالة داخل المصفوفة تأخذ وسيطين من نوع double و تقوم بإعادة قيمة من نفس النوع.

arraysoffunctions

 

المصفوفات متعددة الأبعاد Multi-dimensional Arrays

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

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

multiDarr

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

3x3m