المؤشرات والعناوين في ++C

لم يثر موضوعا من موضوعات البرمجة من الرهبة والإثارة معا ما يثيره موضوع المؤشرات , وهذا المقال مكرس لإزالة الرهبة , مع استيفاء الإثارة .

لما نحتاج للمؤشرات ؟ إليك بعضا من الأسباب :

· التعامل مع عناصر المصوفات

· إمرار المصفوفات والعبارات النصية للدوال

· الحصول علي قدر من الذاكرة من النظام

· إنشاء أنواع متطورة من هياكل البيانات مثل القوائم المترابطة .

وتستخدم المؤشرات في لغة السي بقدر أكبر مما تستخدم في لغة أخري , فهل هذا ضروري حقا ؟ بإمكانك تنفيذ العديد من المهام دون اللجوء لها , فبإمكانك مثلا التعامل مع عناصر المصفوفات دون اللجوء لمفهوم المؤشرات (سوف تري الفرق حالا) .

علي أن المؤشرات تستخدم في لغة السي لتزيد من قوتها في مواقف كثيرة منها مثلا إنشاء هياكل متطورة من البيانات مثل القوائم المترابطة linked lists والأشجار الثنائية binary trees بال توجد ي الواقع إمكانيات يلزم لها المؤشرات , كالدوال الاعتبارية virtual function والمؤشر this (سوف نتعرض لذلك في مقالات قادمة ) . وسوف نتدرج بك في هذا المقال في توضيح مفهوم المؤشرات , بدءا من الأساسيات فيها فإذا كان لك إلمام بلغة السي التقليدية فيمكنك أن تمر علي النصف الأول منه مر الكرام .

المؤشرات والعناوين

إن الأفكار التي يتضمنها موضوع المؤشرات ليست معقدة وإليك أول مفهوم فيها . كل موضع لبايت في الذاكرة لها عنوان يعرف به , يبدأ من الصفر , ويتدرج إلي أخر موضع فيها فإذا كانت الذاكرة 640 كيلو بايت مثلا , فإن أخر عنوان هو 655.359 وأخر عنوان معين هو أو بايت من أي منهما (أنظر الشكل )

Object-Oriented Programming in C   _Page_0457_Image_0001

شكل عناوين الذاكرة

مؤثر العنوان

يمكنك أن تري عنوان أي متغير باستخدام "مؤثر العنوان Address of operator وشكله : "&" , وإليك برنامجا صغيرا لذلك .

varaddr.cpp


 


// varaddr.cpp


// addresses of variables


#include <iostream.h>


#include <conio.h>


 


void main()


   {


   int var1 = 11;           // define and initialize


   int var2 = 22;           // three variables


   int var3 = 33;


 


   cout << endl << &var1    // print out the addresses


    << endl << &var2    // of these variables


    << endl << &var3;


   getche();


   }




وتعتمد العناوين علي عوامل متعددة منها نوع الحاسب ونع نظام التشغيل وعدد البرامج الجارية علي أن العنوان المحتمل يظهر علي الصورة التالية Ox8f4fff4 : .



والعنوان الذي يظهره المؤثر << يكون بالنظام السداسي عشري كما يبين ذلك حرفي Ox الذين يسبقان الأرقام , وهو النظام الذي تكتب به عناوين الذاكرة عادة وتلاحظ أن الفرق بين كل عنوان والأخر هو 2 لأن العدد الصحيح يحتل 2 بايت من الذاكرة كما قدمنا كما تلاحظ أيضا أن الأرقام مرتبة تنازليا لأن المتغيرات تخزن في مكدس في الذاكرة ولو كانت المتغيرات معرفة خارجية لبدت الأرقام متصاعدة فبهذا الشكل تخزن في الذاكرة , . هذه التفاصيل لست بحاجة لشغل بالك بها فالمترجم يعرف عمله بشأنها .



تجنب الخلط!



عليك ألا تخلط بين الرمز & حين يستخدم كمؤثر للعناوين ( ويكون سابقا علي اسم المتغير ) ويبين استخدامه كمؤثر للمعاملات المرجعية ( ويكون لاحقا لاسم المتغير  , أي حين يستخدم مؤشرا للإستدعاء غير المباشر (بالإشارة) . ] تذكر دائما : الصيغة &var تعني عنوان المتغير var [



متغيرات المؤشرات



إن إظهار العناوين علي الشاشة قد يكون أمرا مثيرا ولكنه ليس بذي قيمة تذكر فالقيمة الحقيقة هي حين تخزن هذه العناوين في متغيرات , فيمكن استغلالها في البرمجة ويسمي المتغير حين يخزن عنوانا لمتغير أخر "مؤشر Pointer " , بمعني أن يختزن ما يشير لذلك المتغير .



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






ptrvar.cpp


 


// ptrvar.cpp


// pointers (address variables)


#include <iostream.h>


#include <conio.h>


 


void main()


   {


   int var1 = 11;             // two integer variables


   int var2 = 22;


 


   cout << endl << &var1      // print addresses of variables


    << endl << &var2;


 


   int* ptr;                  // pointer to integers


 


   ptr = &var1;               // pointer points to var1


   cout << endl << ptr;       // print pointer value


 


   ptr = &var2;               // pointer points to var2


   cout    << endl << ptr;       // print pointer value


   getche();


   }




لقد تم تعريف متغير مؤشر في هذا البرنامج بصيغة غريبة بعض الشئ :



Int* ptr;



إن رمز النجمة (*) هنا هام للغاية فهو الذي يدل علي أن المتغير ptr يخزن عناوين وليس قيما عادية ليس هذا فقط بل هو يخزن عناوين لمتغيرات من نوع الأعداد الصحيحة فالصيغة المبينة تترجم إلي " هذا متغير يخون فيه عناوين متغيرات من النوع int "



لما هذا التخصيص في نوع المتغيرات التي يختزن عناوينها ؟ لماذا لا يكون المؤشر عاما يختزن عناوين المتغيرات بأنواعها المختلفة ؟ الإجابة هو أن المترجم يريد أن يعرف نوع البيان صاحب العنوان حتي يمكنه أن يجري حساباته علي عناوين المتغيرات المختلفة كما سيتضح لنا قيما بعد ( راجع لاحقا المؤشرات والمصفوفات ) . ولذلك عليك أن تنتبه جيدا لمثل الصيغ التالية لتعريف المؤشرات :



Char* ptr1; متغير يضم عناوين لمحارف



Float* ptr2; متغير يضم عناوين لأعداد كسرية



Distance* prt3; متغير يضم عناوين لكائنات من الفئة المبينة



ويمكن وضع رمز النجمة بجوار اسم المتغير كالتالي : int *ptr فالأمر بالنسبة لمحول الصياغة سيان ولكنا نفضل الصيغة الأولي التي تبين أن النجمة بيان لنوع المتغير (مؤشر) وليست جزءا من اسمه .



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



Char* ptr1, * ptr2, * ptr3; // three variables of type char



وفي البرنامج المعطي , خصص للمتغير ptr عنوان المتغير الأول مرة , وذلك بالأمر :



Ptr = &var1; // put address of var in ptr



ثم عنوان المتغير الثاني مرة أخري بنفس الطريقة . ويبين الشكل تغيير محتوي المؤشر من عنوان متغير للأخر .



Object-Oriented Programming in C   _Page_0462_Image_0001



شكل تغيير محتوي مؤشر



ومن البديهي أن يكون خرج البرنامج في حالة الأمرين التاليين واحدا :



Cout << &var1;



Cout << ptr;



فكلاهما يعبر عن عنوان المتغير ما , ولكنك لا تعرف أي متغير هو عل يمكنك الوصول لقيمة هذا المتغير من خلال عنوان ؟ ( لعله من المستغرب أن ينسي مبرمج أسماء متغيرات برنامجه , ولكنك سوف تري أن هناك متغيرات لا نعرف أسماءها بالفعل )



البرنامج التالي يبين لك الصيغة المطلوبة لكي تصل لقيمة متغير من خلال عنوانه .






ptracc.cpp


 


// ptracc.cpp


// accessing the variable pointed to


#include <iostream.h>


#include <conio.h>


 


void main()


   {


   int var1 = 11;             // two integer variables


   int var2 = 22;


 


   int* ptr;                  // pointer to integers


 


   ptr = &var1;               // pointer points to var1


   cout << endl << *ptr;      // print contents of pointer (11)


 


   ptr = &var2;               // pointer points to var2


   cout    << endl << *ptr;      // print contents of pointer (22)


   getche();


   }




من البرنامج تعلم أن الصيغة المطلوبة للوصول إلي متغير بعنوانه دون أسمه هي *ptr , فهذه الصيغة تعني "المتغير صاحب العنوان المخزن في ptr " . ولهذا السبب يسمي رمز النجمة قبل اسم المؤشر " موثر اللامباشرة indirection operator لكونه يتيح أن تتعامل مع المتغير بطريقة غير مباشرة فحينما يخصص المؤثر ptr للمتغير var1 فإن إظهار ptr يعني إظهار قيمة عنوان المتغير var1 في حين أن إظهار *ptr يعني إظهار القيمة المحتواة في المتعير var1 وهي 11 في حالتنا هذه (راجع الشكل )



Object-Oriented Programming in C   _Page_0464_Image_0001



شكل الوصول لمتغير من خلال عنوانه



وقد يطلق علي مؤثر اللامباشرة أحيانا مؤثر المحتويات contents operator .



وليس إظهار القيمة المخزنة في المتغير المشار إليه هو كل ما يمكننا عمله في التعامل معه بل يمكننا أن نجري عليه ما نشاء من عمليات في البرنامج التالي سوف نخصص قيمة لمتغير عن طريق مؤشره .






ptrto.cpp


// ptrto.cpp


// other access using pointers


#include <iostream.h>


#include <conio.h>


 


void main()


   {


   int var1, var2;          // two integer variables


   int* ptr;                // pointer to integers


 


   ptr = &var1;             // set pointer to address of var1


   *ptr = 37;               // same as var1=37


   var2 = *ptr;             // same as var2=var1


 


   cout << endl << var2;    // verify var2 is 37


   getche();


   }




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



Int* ptr;



تعني النجمة أن المتغير ptr سيختزن عناوين لمتغيرات من نوع int وفي السطر :



*ptr = 37;



تعني أن المتغير صاحب العنوان المختزن في المتغير ptr ( مهما كان اسمه ) قيمته 37 .و التعامل مع المتغير من خلال الذي يختزن عنوانه يطلق عليه المخاطبة غير المباشرة indirect addressing كما يطلق عليها أيضا استظهار المؤثر dereferencing the pointer .



والسطرين التاليين يظهران الفرق بين المخاطبة المباشرة وغير المباشرة :



المتغير صاحب هذا الأسم قيمته v=3; 3



*p = 3; 3 صاحب العنوان المخزن في هذا المؤشر قيمته



ففي السطر الأول كان التعامل مع المتغير عن طريق أسمه , وفي الثانية كان التعامل من خلال المؤشر الذي يشير إليه (يختزن عنوانه) .



فالتخاطب المباشر يشبه أن تودع خطابا لصديقك في صندوق خطابات منزله (مباشرة) , والتخاطب غير المباشر هو أن ترسل له الخطاب عن طريق مصلحة البريد حيث يدل عنوانه عليه .



المؤشر الانوعي



المبدأ العام هو كل مؤشر يختص معين من المتغيرات يختزن عناوينها بحسب ما عرف به فلا يمكنك أن تعرف مؤشرا علي أنه أعداد صحيحة ثم تخزن به متغيرا عرف علي أنه عدد كسري . علي أنه استثناء من ذلك يوجد نوع من المؤشرات عام الاستخدام يسمي المؤشر اللانوعي void operator يكون تعريفه علي الصورة :



Void* ptr;



ولمثل هذا المؤشر استخدامات خاصة منها ترحيل مؤشر لدالة تعمل دون ارتباط بأسماء المتغيرات المشار إليها.

التسميات: