بدايـة :
هذا هـو أول موضـوع فـي الكتـاب ؛ بدايـة قويـة للغــاية ... إن سـبب وضعــي
فصلاً كاملاً للمؤشرات هـو بسبب أن غالبية من يتعلمون المؤشرات يتناسـون
الفائدة منها أو أن بعضهـم لم يحاول فهـم هذا الموضوع فهـماً كـاملاً متكــاملاً
... وهذا ما أحـاول أن أصبو إليه. هذا الفصـل لا يحــاول أن يتعــمق كثيـراً فـي
المؤشرات بل سيترك بعض مواضيع المؤشـرات لفصـول أخــرى مـن الكتــاب
فالغرض من هذا الفصل هـو إعـطاؤك القدرة على فهـم أفضل للمؤشرات
الذاكرة:
كبدايـة قم بكتابة هذا الكـود
CODE
1 // for pointer
2 #include <iostream.h>
3 int main( )
4 {
5 int c=2;
6 cout << &c;
7 return 0;
8 }
هذا الكـود بسيط جداً يقوم أولاً بتعريف متغير من نــوع int ويهيئــه بقيمــة 2
... لاحظ في السطر السادس أن مخرج البرنامج ليس c وإنما مخرجـه هـو c&
؛ ماذا تعـني هذه الكلمـة.. إن هـذه العلامــة & تعــني إشـارة أو عــنوان ؛ أي
أنك تطلب من المتـرجم أن يقـوم بطباعــة إشـارة c أو عــنوانها الموجـود فـي
الذاكرة ...
لنأخـذ مثــال صــناديق البريــد كمـا تلاحــظ فـإن لكــل صـــندوق بريــد عـــنوان أو
بالأحـرى رقــم صـندوق لنفـرض أنـه يوجـد فـي هـذا الصــندوق رسـالة هـذه
الرسـالة تحـــوي العــدد 2 ، وقــد طلــب منـك طباعـــة إشــارة أو عـــنوان هــذه
الرسالة ؛ أنت لن تطبع محتـوى الرسالة بل سـتطبع رقـم صـندوق البريـد أي
عـنوان تلك الرسالة ؛ وهذا ما يقوم بـه البرنـامج السـابق فهــو يطبـع عــنوان
المتغير c وليس ما يحـويه هذا المتغير ...
لنعـد إلى المثال السابق مرة أخـرى وبالتحديـد فـي السـطر الخـامس .. كمـا
تلاحظ فإنك أعـلنت عـن متغير هـو c وقد تم حجز مقـدار لـه فـي الـذاكرة مـن
نـوع int والتي لها حجم محدد من البايت ... الذي فعله المترجم هـو أنـه قـام
بإنشاء صـندوق بريد ذو عـنوان معـين هذا الصندوق له حجم معــين يسـتطيع
إحتمالــه وهـــو 2 بايــت ثــم يــأتي البرنــامج برســالة تـحـــوي العـــدد 2 ويقــوم
بتخزينها في ذلك الصـندوق ... عليك أن تفهـم هذه النقطـة جيـداً.. وهــو أنـك
تستطيع تغيير الرسائل الموجودة في هذا الصندوق من رسالة تحـوي العــدد
2 إلى رسالة تحـوي العدد 6 ؛ لكنك لن تستطيع تغيير عـنوان هذا الصـندوق ؛
جرب المثال التالي ؛ وسأترك لك مسألة فهـمه:
CODE
1 // for pointer
2 #include <iostream.h>
3 int main( )
4 {
5 int c=2;
6 cout << &c;
7 c=4
8 cout << &c;
9 return 0;
10 }
الآن أتينا إلى نقطـة مهـمـة لنفترض أن لدى البرنامج مساحـتين مـن الـذاكرة
أول مساحـــة تسمـــى stack المساحـــة الثانيـــة هــي heap أي الكـــومة ..
المساحـة الأولـى تحتـوي على عدد صناديق بريد كثيرة جداً إلا أنها ثابتـة وإذا
انتهت فلن يجد البرنامج مكـان آخر لتخـزين المتغيـرات أمـا المساحــة الثانيــة
heap فهي واسعـة جـداً إلا أنهـا فارغــة ولا تحــوي أي صــندوق بريـد ولكنهـا
تمتلـك ميـزة عـظيمــة وهـي أنـك تسـتطيع أنـت بنفسـك إنشـاء مـا تريـد مـن
-ناديق البريد وهـناك ميزة ثانيـة لها أنها أوسع من المساحـة الأولـى بمئـات
المــرات .... كمـــا رأيـــت فـــي المثـــال الســـابق فـــنحن لـــم نتعامـــل إلا مـــع
المساحـةstack فنحن لا نستطيع إنشاء صناديق بريـد كمـا نريـد بـل يجـب أن
نلتزم بعدد ثابت من الصناديق نحدده نحن أثناء كتابـة البرنـامج ولـن نسـتطيع
تغييــره مهـــما حاولنــا أثنــاء تنفيــذ البرنــامج ... مــا رأيــك الآن أن نتعامــل مــع
المساحـة الواسعـة والديناميكيـة heap.
المؤشرات:
من الضروري أن تكون قد فهـمت ما كنت أعـنيه في مقدمـة هذا الفصل حتى
تعرف فائدة المؤشرات وخـواصها
للإعـلان عـن أي مؤشر يجب أن يسبق بالمعـامل * ثم يكتب إسم المتغير
فاصلة منقوطـة إسم المؤشر يسبقه المعامل نـوع المؤشر
int * pPointer ;
حسناً الآن ما هـو المؤشر ؛ المؤشـر هــو متغيـر يشـتمل علـى أحـد عــناوين
الذاكرة... لاحظ أنـه يشتمل على أحد عـناوين الذاكرة ولـيس بالتـالي قيمــة ؛
حتى تفهـم ما هـو المؤشر فلنعـد إلى مثال صـناديق البريـد ؛ المؤشـر يقـوم
بحجــز مكـــان فــي الــذاكرة (أي صــندوق بريــد ) ثــم يشــير إلــى عـــنوان هــذا
الصـندوق ... بالتالي لن تستطيع أن تقول:
int *pAge=x;
السطر السـابق خطـأ ؛ تـذكر المؤشـر يحمـل عــناوين ويشـير إلـى قيمهـا ولا
يحمل القيمة بحد ذاتهـا؛ وحتى تستطيع إسـناد قيمــة x إلـى المؤشـر pAge
فعليك أن تكتب التالي:
pAge=&x;
لقـد أصــبح الإسـناد هــكذا صــحيحاً فكأنـك تقـول خـذ عــنوان المتغيـر x وقـم
بوضعـــه فــي عـــنوان المؤشــر pAge* .. الآن حينمــا تريــد طباعـــة القيمــة
الموجودة في المؤشر فإنك تكتب هذا السطر
cout << *pAge;
هذا يعـني أنك تقول للمترجم أيها المترجم هل ترى العـنوان الذي يشـير إليـه
المؤشر ؛ قم بطباعـة القيمة الموجودة في العـنوان الذي يشير إليه المؤشر.
بالنسبة لمثالنا السابق (أقصد هـنا مثـال صـناديق البريـد) فـإن المؤشـر هــو
رقم صندوق البريـد المكتــوب علـى الرسـالة ؛ والـذي تسـتطيع أنـت شطبــه
وتغييره متى ما أردت بل وحتـى تعـديل ما هـو مكتـوب فـي الرسـالة وجعلهـا
تحـوي بيانـات أكثر وما إلى ذلك أما بالنسبـة للإشارة والتـي تقابـل هــنا رقـم
-ندوق البريد فهي ثابتـة ولن تتغير
الآن دعـنا من هذا الكلام ؛ ودعـنا نلقي نظرة فاحصـة على هذا الكود:
CODE
#include <iostream.h>
void main ( )
{
int p,g;
int *x;
p=5;g=7;
cout << p << "\t" << g << "\n " << &p << "\t" << &g
<< endl;
x=&p;
cout << *x << "\t" << x << endl;
x=&g;
cout << "\n\n" << *x << "\t" << x;
}
حسناً كما ترى قمنا بتعريف متغيرين p و g ومؤشر واحـد هــو x قمنـا بإسـناد
القيم للمتغيرين في السطر السادس ثم قمنـا فـي السـطر السـابع بطباعــة
قيم المتغيرين وأسـفل كـل قيمــة طبعــنا عــنوانها فـي الـذاكرة (أو بـالاحرى
عـنوان المتغير الذي يحويها) قمنا في السطر التاسع بجعل المؤشر x يحمـل
عـنوان المتغير p وقمنـا بطباعــة قيمـة المؤشـر وعــنوان هـذا المؤشـر فـي
السطر العاشر ؛ الآن لو كنت شديد الملاحظـة فسـتلاحظ أن عــنوان المؤشـر
x هـو نفسه عـنوان المتغير p ؛ قمنا بعد ذلك في السطر الحادي عشر بجعل
المؤشــر يحمــل عـــنوان المتغيــر g وقمنــا بطباعـــة قيمــة المؤشــر وعـــنوانه
وستلاحظ أيضاً أن عـنوان المؤشر هـو نفسه عـنوان المتغير g.
الآن أعـتقد أنك عرفت فائدة المثال السابق .. للمؤشـر ميـزة عـظيمــة وهـي
أنـه دائماً يقوم بتغيير عـنوانـه في الذاكرة (ستتعلم أنـه يسـتطيع تغييـر عـدد
البيانات التي يحويها) بعكس المتغيرات والإشارات .. المتغيرات قيمها متغيـرة
إلا أن عـناوينها ثابتـة أما الإشارات فعـنوانها ثابـت وقيمتهـا ثابتــة ولـن يمكنـك
تغيير قيمة الإشارة بـل يجـب عليـك تهيئتهـا عــند الإعـلان عــنها أمـا المؤشـر
فبإمكـانك تغيير عـنوانـه وقيمتـه.
لنعـد إلى المثال الكـودي الأخير لنفرض أني قمـت بإضافــة هـذين السـطرين
في الكـود بين السطر العاشر والحادي عشر
هذا هـو أول موضـوع فـي الكتـاب ؛ بدايـة قويـة للغــاية ... إن سـبب وضعــي
فصلاً كاملاً للمؤشرات هـو بسبب أن غالبية من يتعلمون المؤشرات يتناسـون
الفائدة منها أو أن بعضهـم لم يحاول فهـم هذا الموضوع فهـماً كـاملاً متكــاملاً
... وهذا ما أحـاول أن أصبو إليه. هذا الفصـل لا يحــاول أن يتعــمق كثيـراً فـي
المؤشرات بل سيترك بعض مواضيع المؤشـرات لفصـول أخــرى مـن الكتــاب
فالغرض من هذا الفصل هـو إعـطاؤك القدرة على فهـم أفضل للمؤشرات
الذاكرة:
كبدايـة قم بكتابة هذا الكـود
CODE
1 // for pointer
2 #include <iostream.h>
3 int main( )
4 {
5 int c=2;
6 cout << &c;
7 return 0;
8 }
هذا الكـود بسيط جداً يقوم أولاً بتعريف متغير من نــوع int ويهيئــه بقيمــة 2
... لاحظ في السطر السادس أن مخرج البرنامج ليس c وإنما مخرجـه هـو c&
؛ ماذا تعـني هذه الكلمـة.. إن هـذه العلامــة & تعــني إشـارة أو عــنوان ؛ أي
أنك تطلب من المتـرجم أن يقـوم بطباعــة إشـارة c أو عــنوانها الموجـود فـي
الذاكرة ...
لنأخـذ مثــال صــناديق البريــد كمـا تلاحــظ فـإن لكــل صـــندوق بريــد عـــنوان أو
بالأحـرى رقــم صـندوق لنفـرض أنـه يوجـد فـي هـذا الصــندوق رسـالة هـذه
الرسـالة تحـــوي العــدد 2 ، وقــد طلــب منـك طباعـــة إشــارة أو عـــنوان هــذه
الرسالة ؛ أنت لن تطبع محتـوى الرسالة بل سـتطبع رقـم صـندوق البريـد أي
عـنوان تلك الرسالة ؛ وهذا ما يقوم بـه البرنـامج السـابق فهــو يطبـع عــنوان
المتغير c وليس ما يحـويه هذا المتغير ...
لنعـد إلى المثال السابق مرة أخـرى وبالتحديـد فـي السـطر الخـامس .. كمـا
تلاحظ فإنك أعـلنت عـن متغير هـو c وقد تم حجز مقـدار لـه فـي الـذاكرة مـن
نـوع int والتي لها حجم محدد من البايت ... الذي فعله المترجم هـو أنـه قـام
بإنشاء صـندوق بريد ذو عـنوان معـين هذا الصندوق له حجم معــين يسـتطيع
إحتمالــه وهـــو 2 بايــت ثــم يــأتي البرنــامج برســالة تـحـــوي العـــدد 2 ويقــوم
بتخزينها في ذلك الصـندوق ... عليك أن تفهـم هذه النقطـة جيـداً.. وهــو أنـك
تستطيع تغيير الرسائل الموجودة في هذا الصندوق من رسالة تحـوي العــدد
2 إلى رسالة تحـوي العدد 6 ؛ لكنك لن تستطيع تغيير عـنوان هذا الصـندوق ؛
جرب المثال التالي ؛ وسأترك لك مسألة فهـمه:
CODE
1 // for pointer
2 #include <iostream.h>
3 int main( )
4 {
5 int c=2;
6 cout << &c;
7 c=4
8 cout << &c;
9 return 0;
10 }
الآن أتينا إلى نقطـة مهـمـة لنفترض أن لدى البرنامج مساحـتين مـن الـذاكرة
أول مساحـــة تسمـــى stack المساحـــة الثانيـــة هــي heap أي الكـــومة ..
المساحـة الأولـى تحتـوي على عدد صناديق بريد كثيرة جداً إلا أنها ثابتـة وإذا
انتهت فلن يجد البرنامج مكـان آخر لتخـزين المتغيـرات أمـا المساحــة الثانيــة
heap فهي واسعـة جـداً إلا أنهـا فارغــة ولا تحــوي أي صــندوق بريـد ولكنهـا
تمتلـك ميـزة عـظيمــة وهـي أنـك تسـتطيع أنـت بنفسـك إنشـاء مـا تريـد مـن
-ناديق البريد وهـناك ميزة ثانيـة لها أنها أوسع من المساحـة الأولـى بمئـات
المــرات .... كمـــا رأيـــت فـــي المثـــال الســـابق فـــنحن لـــم نتعامـــل إلا مـــع
المساحـةstack فنحن لا نستطيع إنشاء صناديق بريـد كمـا نريـد بـل يجـب أن
نلتزم بعدد ثابت من الصناديق نحدده نحن أثناء كتابـة البرنـامج ولـن نسـتطيع
تغييــره مهـــما حاولنــا أثنــاء تنفيــذ البرنــامج ... مــا رأيــك الآن أن نتعامــل مــع
المساحـة الواسعـة والديناميكيـة heap.
المؤشرات:
من الضروري أن تكون قد فهـمت ما كنت أعـنيه في مقدمـة هذا الفصل حتى
تعرف فائدة المؤشرات وخـواصها
للإعـلان عـن أي مؤشر يجب أن يسبق بالمعـامل * ثم يكتب إسم المتغير
فاصلة منقوطـة إسم المؤشر يسبقه المعامل نـوع المؤشر
int * pPointer ;
حسناً الآن ما هـو المؤشر ؛ المؤشـر هــو متغيـر يشـتمل علـى أحـد عــناوين
الذاكرة... لاحظ أنـه يشتمل على أحد عـناوين الذاكرة ولـيس بالتـالي قيمــة ؛
حتى تفهـم ما هـو المؤشر فلنعـد إلى مثال صـناديق البريـد ؛ المؤشـر يقـوم
بحجــز مكـــان فــي الــذاكرة (أي صــندوق بريــد ) ثــم يشــير إلــى عـــنوان هــذا
الصـندوق ... بالتالي لن تستطيع أن تقول:
int *pAge=x;
السطر السـابق خطـأ ؛ تـذكر المؤشـر يحمـل عــناوين ويشـير إلـى قيمهـا ولا
يحمل القيمة بحد ذاتهـا؛ وحتى تستطيع إسـناد قيمــة x إلـى المؤشـر pAge
فعليك أن تكتب التالي:
pAge=&x;
لقـد أصــبح الإسـناد هــكذا صــحيحاً فكأنـك تقـول خـذ عــنوان المتغيـر x وقـم
بوضعـــه فــي عـــنوان المؤشــر pAge* .. الآن حينمــا تريــد طباعـــة القيمــة
الموجودة في المؤشر فإنك تكتب هذا السطر
cout << *pAge;
هذا يعـني أنك تقول للمترجم أيها المترجم هل ترى العـنوان الذي يشـير إليـه
المؤشر ؛ قم بطباعـة القيمة الموجودة في العـنوان الذي يشير إليه المؤشر.
بالنسبة لمثالنا السابق (أقصد هـنا مثـال صـناديق البريـد) فـإن المؤشـر هــو
رقم صندوق البريـد المكتــوب علـى الرسـالة ؛ والـذي تسـتطيع أنـت شطبــه
وتغييره متى ما أردت بل وحتـى تعـديل ما هـو مكتـوب فـي الرسـالة وجعلهـا
تحـوي بيانـات أكثر وما إلى ذلك أما بالنسبـة للإشارة والتـي تقابـل هــنا رقـم
-ندوق البريد فهي ثابتـة ولن تتغير
الآن دعـنا من هذا الكلام ؛ ودعـنا نلقي نظرة فاحصـة على هذا الكود:
CODE
#include <iostream.h>
void main ( )
{
int p,g;
int *x;
p=5;g=7;
cout << p << "\t" << g << "\n " << &p << "\t" << &g
<< endl;
x=&p;
cout << *x << "\t" << x << endl;
x=&g;
cout << "\n\n" << *x << "\t" << x;
}
حسناً كما ترى قمنا بتعريف متغيرين p و g ومؤشر واحـد هــو x قمنـا بإسـناد
القيم للمتغيرين في السطر السادس ثم قمنـا فـي السـطر السـابع بطباعــة
قيم المتغيرين وأسـفل كـل قيمــة طبعــنا عــنوانها فـي الـذاكرة (أو بـالاحرى
عـنوان المتغير الذي يحويها) قمنا في السطر التاسع بجعل المؤشر x يحمـل
عـنوان المتغير p وقمنـا بطباعــة قيمـة المؤشـر وعــنوان هـذا المؤشـر فـي
السطر العاشر ؛ الآن لو كنت شديد الملاحظـة فسـتلاحظ أن عــنوان المؤشـر
x هـو نفسه عـنوان المتغير p ؛ قمنا بعد ذلك في السطر الحادي عشر بجعل
المؤشــر يحمــل عـــنوان المتغيــر g وقمنــا بطباعـــة قيمــة المؤشــر وعـــنوانه
وستلاحظ أيضاً أن عـنوان المؤشر هـو نفسه عـنوان المتغير g.
الآن أعـتقد أنك عرفت فائدة المثال السابق .. للمؤشـر ميـزة عـظيمــة وهـي
أنـه دائماً يقوم بتغيير عـنوانـه في الذاكرة (ستتعلم أنـه يسـتطيع تغييـر عـدد
البيانات التي يحويها) بعكس المتغيرات والإشارات .. المتغيرات قيمها متغيـرة
إلا أن عـناوينها ثابتـة أما الإشارات فعـنوانها ثابـت وقيمتهـا ثابتــة ولـن يمكنـك
تغيير قيمة الإشارة بـل يجـب عليـك تهيئتهـا عــند الإعـلان عــنها أمـا المؤشـر
فبإمكـانك تغيير عـنوانـه وقيمتـه.
لنعـد إلى المثال الكـودي الأخير لنفرض أني قمـت بإضافــة هـذين السـطرين
في الكـود بين السطر العاشر والحادي عشر