:: 7 – كيفية التعامل مع المؤشرات ::
الطريقة الأولى / استخدام عنوان متحول آخر للإشارة إلى محتوياته :
أخذنا في بداية هذا الدرس كيفية عمل ذلك عن طريق استخدام الرمز ( & ) .. و ذلك كالتالي :
كود:
int x=10;
int* ptr;
ptr=&x;
ملاحظة / يجب أن يكون نوع المؤشر مطابق لنوع المتحول الذي سيشير إليه ذلك المؤشر .
الأن لو أردنا طباعة القيمة ( 10 ) نستطيع ذلك بطريقتين .. إما عن طريق استخدام المتحول x أو عن طريق المؤشر ptr :
كود:
cout << x << endl;
cout << *ptr << endl;
وقلنا أن الرمز ( * ) يعني : القيمة التي يشير إليه المؤشر .. و بهذه الطريقة نستطيع استخدام المؤشر ptr بدلاً من المتحول x .. مع ملاحظة أنه إذا تغيرت ( *ptr ) فستتغير قيمة المتحول ( x ) و العكس صحيح .. فلو كتبنا التالي :
كود:
(*ptr)++;
cout << *ptr << endl << x << endl;
هنا سيتم زيادة القيمة 10 إلى 11 .. و بالتالي تغيرت قيمة x إلى 11 مع أننا استخدمنا المؤشر ptr لذلك .. لاحظ نتائج الطباعة حيث أنه سيتم طباعة العدد 11 مرتين .
ملاحظة 1 / العملية ( ++ ) لها أولية على الرمز ( * ) لذلك استخدمنا الأقواس لكي نزيد القيمة التي يشير إليها المؤشر واحد و ليس قيمة المؤشر .. فبدون استخدام الأقواس سيتم زيادة قيمة ptr واحد و بذلك فلن يشير هذا المؤشر إلى المتحول x لأنه أصبح لديه قيمة جديدة و هي عنوان جديد .. و بذلك خطورة كبيرة لأننا لا نعرف على ماذا يحتوي ذلك العنوان الجديد في الذاكرة .. و بالتالي سيحدث خطأ منطقي و ليس قواعدي .
ملاحظة 2 / تتطلب هذه الطريقة ( الطريقة الأولى ) وجود متحول آخر تم تعريفه مسبقاً في البرنامج و ذلك لكي يستطيع المؤشر أن يؤشر على ذلك المتحول .. و تعتبر الفكرة السابقة أحد عيوب هذه الطريقة .. على عكس الطريقة الثانية التي لا تحتاج إلى متحول آخر من أجل عملية التأشير .
الطريقة الثانية / استخدام المؤشر عن طريق الحجز الديناميكي و التحرير الديناميكي ( اليدوي ) و ذلك خلال زمن تنفيذ البرنامج :
أولاً : الحجز الديناميكي :
و يتم ذلك على خطوتين و هما كالتالي :
1 – حجز موقع جديد يتسع لقيمة معينة .. و هذا يعني باختصار : التصريح عن مؤشر جديد يتسع لتخزين القيمة التي نريدها .. و ذلك تماماً كالسابق .. مثال :
2 – البحث عن موقع جديد فارغ في الذاكرة و من ثم وضع عنوان ذلك الموقع كقيمة في المؤشر الذي قمنا بتعريفه في الخطوة الأولى .. و ذلك عن طريق التالي :
كود:
Pointer = new Types;
حيث أن :
Pointer : هو المؤشر الذي قمنا بتعريفه في الخطوة الأولى .
new : هي كلمة محجوزة ( ستظهر باللون الأزرق ) .. وظيفتها هي إيجاد موقع جديد فارغ في الذاكرة حجمه يطابق حجم النوع الذي سيتم وضعه بعدها ( Types ) .
Types : أي نوع نريده سواءً أكان أحد الأنواع مسبقة التعريف مثل ( int,float,double .. إلخ ) أو أحد الأنواع المعرفة من قبل المستخدم كالسجلات و الكائنات .
و في مثالنا هنا نستطيع كتابة التالي :
ملاحظة / يمكن اختصار الخطوتين السابقتين على الشكل التالي :
و لمزيد من التوضيح .. نفرض أن المؤشر ( ptr ) قد أخذ العنوان ( 320 ) و ذلك في الخطوة الأولى التي تم فيها تعريف ذلك المؤشر .. و لاحظ أيضاً كيف أنه في هذه الخطوة لم يأخذ قيمة بعد ( و هي عنوان موقع آخر في الذاكرة ) و هذا يعني أن المؤشر لا يشير إلى قيمة .. انظر الصورة :
و في الخطوة الثانية سيتم البحث عن موقع فارغ في الذاكرة ذو حجم معين ( 4 بايت قي مثالنا السابق من أجل النوع int ) .. و سيتم إعطاء عنوان ذلك الموقع للمؤشر المعرف قي الخطوة الأولى و سنفرض أن عنوان ذلك الموقع هو ( 120 ) .. انظر الصورة :
بهذه الطريقة أصبح المؤشر ( ptr ) يؤشر على موقع جديد عنوانه ( 120 ) .. و لكن لاحظ أن ذلك الموقع لا يحتوي على قيمة !!! إذا كيف نستطيع تخزين قيمة داخل ذلك الموقع ؟ ببساطة .. نستطيع ذلك عن طريق المؤشر ( prr ) و ذلك طبعاً باستخدام الرمز ( * ) ..
مثال :
تماماً كما كنا نفعل في السابق .. و لكن افرض أننا نريد إعطاء قيمة ابتدائية لذلك الموقع .. نستطيع فعل ذلك في الخطوة الثانية عن طريق وضع قوسين بعد النوع و وضع القيمة الابتدائية للموقع الجديد بين هذين القوسين ..
مثال : في الخطوة الثانية /
هنا أصبح الموقع الجديد الذي يشير إليه المؤشر ( ptr ) يحتوي على القيمة ( 5 ) .. و انتبه إلى أن هذه القيمة هي ليست قيمة المؤشر ( ptr ) !!! بل كما قلنا هي قيمة الموقع الذي يشير إليه ذلك المؤشر .. انظر الصورة ( و ذلك مع افتراض نفس القيم السابقة للعناوين ) :
للتأكد سوف نقوم بطباعة قيمة المؤشر ( ptr ) و أيضاً القيمة التي يشير إليها المؤشر ( ptr* ) و شاهد كيف أن تلك القيم تختلف و كيف أن ( ptr* ) تساوي ( 5 ) .. و ذلك عن طريق كتابة التالي :
كود:
cout << ptr << endl << *ptr << endl;
ثانياً / التحرير ( الحذف أو الإلغاء ) الديناميكي :
بعدما قمنا بحجز موقع جديد في الذاكرة عن طريق الأمر ( new ) .. يجب علينا الأن أن نعرف كيف نقوم بعملية تحرير ( إلغاء ) ذلك الموقع من الذاكرة .. لأن كل عملية حجز يدوية يجب أن تقابلها عملية تحرير يدوية للذاكرة .. و ذلك لفتح المجال أمام متغيرات أخرى لكي تأخذ ذلك الموقع .. و لكي لا تحصل أخطاء غير مرغوب بها متعلقة بانتهاك للذاكرة العشوائية .. لذلك يجب علينا في هذه الحالة أن نقوم نحن بتحرير ذلك الموقع من الذاكرة بأنفسنا .. و نستطيع عمل ذلك في أي مكان نريد و كقاعدة عامة / قم بتحرير الموقع الذي قمت بإنشائه عن طريق الأمر ( new ) فور الانتهاء منه نهائياً بحيث لن تحتاج له بعد ذلك .. و إذا لم تستطع تقدير ذلك المكان قم بعملية التحرير في نهاية البرنامج ( في نهاية التابع main ) .. ( تم التصحيح هنا ) .
قاعدة / نستطيع القيام بعملية التحرير الديناميكي عن طريق كتابة الأمر ( delete ) و من ثم نضع اسم المؤشر و من ثم نضع الفاصلة المنقوطة :
حيث أن :
delete : كلمة محجوزة ستظهر باللون الأزرق .. و تستخدم من أجل عملية التحرير الديناميكي .
Pointer : اسم المؤشر الذي تريد أن تقوم بتحريره من الذاكرة .
ملاحظة / يجب عليك استخدام عملية حجز جديدة بعد أي عملية تحرير و ذلك إن أردت استخدام المؤشر مرة أخرى .. إذا نستنتج من ذلك أننا نستطيع أن نقوم بعملية الحجز و التحرير الديناميكي أكثر من مرة و ذلك لمؤشر واحد فقط .. (
تم التصحيح هنا ) ..
مثال :
كود:
int* p;
p = new int(10);
cout << *p << endl;
delete p;
p = new int(20);
cout << *p << endl;
delete p;
هنا سيتم إنشاء موقع جديد يشير إليه المؤشر ( p ) و يحمل القيمة ( 10 ) .. و بعدها سنقوم بإلغاء ذلك الموقع مع ملاحظة أن العملية ( delete p ) لا تقوم بإلغاء المؤشر نفسه كما قلنا بل تلغي الموقع الذي يشير إليه ذلك المؤشر .. و بعدها قمنا بحجز موقع جديد آخر يختلف عنوانه عن العنوان السابق و وضعنا فيه القيمة ( 20 ) ..
الأن لاحظ كيف أنه بعملية التحرير سيلغى الموقع المحجوز و لن توجد القيمة ( 100 ) في ذلك الموقع ..
انظر المثال التالي /
كود:
int* p;
p = new int(100);
delete p;
cout << *p << endl << p << endl;
و يمكننا تلخيص طريقة عمل الحجز الديناميكي و التحرير الديناميكي عن طريق مثالنا السابق على الشكل التالي :
كود:
int* ptr;
ptr = new int(5);
cout << *ptr << endl;
(*ptr)--;
cout << *ptr << endl;
*ptr = 3;
cout << *ptr << endl;
cin >> *ptr;
cout << *ptr << endl;
delete ptr;
ملاحظة / لاحظ كيف أن هذه الطريقة ( الحجز الديناميكي و التحرير الديناميكي ) لا تحتاج إلى وجود متحول آخر لكي يشير إليه المؤشر كما في الطريقة الأولى .. و ذلك بسبب استخدام الأمر ( new ) الذي يبحث عن موقع فارغ جديد لا يشغله أي متغير آخر .