الخميس، 26 مارس 2015

التحـميل الزائد للتوابع:

التحـميل الزائد للتوابع:
يعتبر هذا الموضوع هـو أول نـوع مـن أنــواع تعـدد الاوجــه والتـي هـي ثلاثـة
أنـواع وتعدد الأوجـه أحد أساسـيات البرمجـة الكائنيـة ، وهـذا يعــني أنــه لـن
يمكنك تطبيق هذا الموضوع على لغـة السي (إن وجد مترجمات للغة السي
مستقلة عـن مترجمات السي بلس بلس).
كما قلت أن من أحد أهـم أهداف البرمجة الكائنية هـو الوصول إلى استقلالية
الكود الذي تكتبه وإمكانية إعادة استخدامـه وسـهولة فعـل ذلـك ، والتحــميل
الزائد يعـد أحد الأساليب القوية لفعل ذلك.
التحـميل الزائد للتوابع يعـني وجود نسخ أخرى تحمل نفس اسم التابع الزائد
التحميل ولكنها تختلف إما في عدد الوسائط أو نـوع الوسـائط أو حتـى ترتيـب
هذه الوسائط.
والفائدة من ذلك تظهر فيما لو فهـمت موضـوع الوسـائط الافتراضـية ، فوجـود
الوســائط الافتراضــية فــي التوابــع يمكنــك مــن اســتدعاء الدالــة بطــريقتين
مختلفتين إحـداها بـدون ذكـر قـيم الوسـائط الافتراضـية والأخـرى بتغييـر قـيم
الوسائط الافتراضية ، لنفرض أنك قررت كتابة أحد التوابـع وهــو التـابع Find ،
وتريد من هذا التابع أن يقوم بالبحث في أي مصفوفة يطلبهـا المسـتخدم مـع
العلم أن هـناك مشكلة كبيرة وهي كيفية جعل هذا التابع يتعامـل مـع جميـع
أنـواع المصـفوفات int و char و float ... وغيرهـا الحـل الوحيـد هــو أن تقـوم
بزيادة تحـميل التابع find ، أي ستصبح النمـاذج المصـغرة لنسـخ التـابع find،
هكذا:
int find (int [] , int );
char find (char [] , char);
float find (float [] , float );
وحينما تصل لمرحلة تعريف هذه التوابع ، فيجب عليك تعريف كل نموذج علـى
حدة ولن يكفيك تعريف تابع واحد فحسب.
عليك أن تعلم أن التحميل الزائد لأي تابع يعـني أن هـناك إصدارات أو نسـخ أو
توابع أخرى تحمل نفس اسم هذا التـابع ولكنهـا تختلـف فـي الوسـائط سـواء
في العدد أو النوع.
سنقوم الآن بتقليد التابع Abs الذي يعيد القيمة المطلقة لأي عدد تدخلـه مـن
المكتبة stdio في لغـة C ، ولربما تقوم أنت بتطـويره حتـى يصـبح أفضـل مـن
التابع الموجود في لغـة C :
CODE
1. #include <iostream>
2. using namespace std;
3.
4. int Abs (int );
5. float Abs(float );
6. double Abs (double );
7.
8. int main()
9. {
10. int Int=0;
11. float Float=0;
12. double Double=0;
13.
14. cout << "Int:\t" ; cin >> Int;
15. cout << "Float:\t"; cin >> Float;
16. cout << "Double:\t";cin >> Double;
17. cout << endl << endl;
18.
19. cout << "Int:\t" << Abs(Int) << endl;
20. cout << "Float:\t" << Abs (Float) << endl;
21. cout << "Double:\t" << Abs(Double) << endl;
22. cout << endl;
23.
24. return 0;
25. }
26. int Abs(int X)
27. {
28. return X<0 ? -X : X;
29. }
30.
31. float Abs(float X)
32. {
33. return X<0 ? -X : X;
34. }
35.
36. double Abs (double X)
37. {
38. return X<0 ? -X :X;
39. }
· انظر إلى النمـاذج المصـغرة للتوابـع ( )Abs ، جميعهـا تأحـذ أنــواعاً
مختلفة وسيقوم المترجم حينما تقوم باستدعاء هذه التوابع بالبحث
عـن التابع المناسب ، النماذج موجودة في الأسطر 3 و 5 و 6.
· في الأسطر 10 و 11 و 13 تم الإعلان عن ثلاث متغيرات من الأنـواع
int و float و double وتسمية كل متغير بنفس مسمى نـوعه ولكن
بجعل الحرف الأول كبيراً والسبب فـي هـذا الإجـراء حتـى تسـتطيع
التفريق بينها في البرنامج
· تطلب الأسطر 14 و 15 و 16 منك إدخال قيم هذه المتغيرات ، حتى
تستطيع فيما بعـد إيجاد القيمة المطلقة لكل عـدد.
· السطر 19 يقوم بطباعة القيمـة المطلقـة للمتغيـر مـن النــوع int ،
وكما ترى فهـو يقوم بطباعة القيمة العائدة للتابع ( )Abs int ، وكما
ترى فإن التنفيذ سينتقل إلى البحث عـن التابع المناسب لمثل هـذا
النوع من الوسائط والتابع الأفضل هـو في السطر 26 .
· في السطر 28 ، يقوم البرنـامج بمقارنـة العــدد الممـرر (الـذي نـود
إيجاد القيمة المطلقة له) مع الصفر وفي حال كان أصغر فإننا نعيـد
العدد ولكـن بقيمـة سـالبة وبالتـالي فعــندما تـدخل العــدد 2- فـإن
المقارنة ستنجح وبالتالي سيقوم التابع بإرجاع القيمة بعــد إضـافة
السالب إليها أي ستصبح القيمة العائدة هـكذا 2 - - ، والتي رياضياً
تساوي 2 ، أما في حـال لـم تـنجح المقارنـة أي أن العـدد أكبـر مـن
الصـفر أو مسـاوي لـه فسـيعيد التـابع نفـس القيمـة ويقـوم التـابع
( )main بطباعتها في السطر 19 .
· نفس الأمر سيحدث في السطرين 20 و 21 .
بالرغـم من سهولة هذا الموضوع إلا أنه يعتبـر أحـد أهــم الإمكانـات فـي لغـة
السي بلس بلس وفي البرمجة الكائنية بشكل عام ، وخاصة حينما تبـدأ فـي
التعامل مع الكائنـات.
محاذير عـند التحـميل الزائد للتوابع:
هـناك بعض الأخطاء عـندما تقوم بالتحميل الزائد للتوابع ، والتـي يغفـل عــنها
الكثيرون ، وهذه هـي أهـمها:
1- لن يكون بإمكانك زيادة تحـميل أي تابع اعتماداً على القيمة العائـدة فقـط ،
تعتبر هذه الإعلانات عـن التوابع خاطئـة:
int Abs(int , int );
float Abs( int , int );
والسبب بسيط وهـو أن المترجم لـن يعلـم أبـداً مـا هــو التـابع الـذي سـيقوم
باستدعاءه بالضبط ، لأن الوسائط هـي نفسها.
2- لن يكون بإمكانك زيادة تحــميل أي تـابع فـي حـال كانـت لـه نفـس قائمـة
الوسائط حتى وإن كانت بعض وسائطة افتراضية ، أنظر إلى هذا المثال:
int function(int a ,int b);
int function(int a,int b ,int c=100);
والسبب أنه حين استدعاء هذا التابع بواسطـة وسيطين ولـيس ثلاثـة فحينهـا
لن يعرف المترجم أي تابع يستدعي.
3-أيضاً لن يكون بإمكانك زيادة تحـميل تابع على هذا الشكل:
int function(int a);
int function(const int a);
تذكر لكي ينجح التحـميل الزائد للتوابع ، فعلى التوابع التي تحمل نفس اسم
التابع أن تختلف في قائمة الوسائط سواء في العدد أو الترتيب أو النوع أو أي
شيء آخر مع الأخذ بعين الاعتبار المحاذير السابقة.
التوابع السطرية Function Inline :
حينما تقوم بكتابة تابع ما ، وستقوم في الكــود باسـتدعاء هـذا التـابع خمـس
مرات فسيقوم المترجم بجعل هذا التابع في مكان خاص له بالذاكرة ، ثم مـع
كل استدعاء لهذا التابع ينتقل التنفيذ إلى تلك المنطقة من الذاكرة ، وبالتـالي
فالذي يوجد في الذاكرة هـو نسخـة واحدة من التابع ، ولو قمت باسـتدعاءها
مليون مرة.
يقلل هذا الإجراء من السرعة كثيراً بسبب هذه الاستدعاءات ، وخاصة إذا كان
التابع عبارة عـن سطر أو سطرين فربما يكون من الأفضـل الـتخلص مـن هـذا
الاستدعاء عبر التخلص من التابع وكتابة الأوامر التي نريـد كتابتهـا فـي التـابع
الرئيسي ولكن هذا الشيء غير مفضل ولا ينصح به لأننا سنفقد القدرة علـى
الاستفادة من هذا التـابع مسـتقبلاً ، لـذلك فسـيكون مـن الأفضـل جعـل هـذا
التابع تابعاً سطرياً وفي حال قمت بجعله سطرياً فإن المترجم سيقوم بنفس
الإجراء السابق الـذي كنـا نـود إضـافته لحـل المشـكلة أي نسـخ الأوامـر إلـى
التابع الرئيسي ، ولكنك ستتعامل معها على أنها تابع حقيقي.
الفائدة الحقيقية للتوابـع ليسـت علـى مسـتوى التصـميم بـل علـى مسـتوى
كفاءة البرنامج فالتابع المكون من سطرين سيكون من الأفضـل الـتخلص مـن
استدعائها لأن ذلك سيؤثر على سرعة البرنامج وجعله ضمن التابع الرئيسي
بجعله تابعاً سطرياً .
لا تقـم بجعـل جميـع توابعــك سـطرية، لأنـك حينمـا تقــم بـذلك سـيزيد حجـم
الذاكرة بشكل كبير جداً وستزداد السرعـة (ولكـن لـن تسـتفيد مـن السرعــة
بسبب زيادة حجم البرنام) التوابع التي قد تجعلهـا سـطرية هـي تلـك التوابـع
الصغيرة التي لا تزيد عـن سطرين أو سطر.
الإعلان عـن تابع سطري يكون بكتابة الكلمة inline قبل نوع التابع انظـر إلـى
هذا المثال:
inline int function( ) ;
تعريف قوالب التوابع:
سنبدأ بداية من البرمجـة الهيكلية ؛أقصد هــنا مـن موضـوع التوابـع ، سـنقوم
بالتخلص مـن عقـدة التحــميل الزائـد للـدوال عبـر دالـة واحـدة وعبـر موضـوع
القوالـب ثـم سـنتقدم أكثـر إلـى موضــوع الأصــناف والكائنــات فـي الوحـدات
القادمة.
لنفرض أنك تريد كتابة دالة تقوم بإيجاد القيمـة المطلقة لرقم معـين ، بالرغـم
من أن هذه الدالة موجودة في المكتبات القياسية للغـة السي إلا أننا
سنقوم بكتابتها من جـديد ، فإنك لن تجـد أفضل من المعامل الشرطي
الثلاثي ليقوم بالمهـمـة على هذا النحـو:
int Abs(int X)
{
return X<0 ? -X : X;
}
وكما ترى فإن هـذه الدالـة لا تتعامـل إلا مـع الأعــداد إلا مـن النــوع int ، وقـد
تقوم بزيـادة تحــميل هـذه الدالـة حتـى تتعامـل مـع بقيــة الأنــواع (كمـا فـي
الامثلة السابقة في هذه الوحدة) ؛ وقد تجـد هذا العـمل مملاً كما أنــه يضـيع
المزيد من الوقت والجهـد في أمـور كان من الأفضل للحاسب أن يتعامل معها
هـو بنفسـه دون أن يترك للمبرمج التعامـل مـع هـذه التفاصـيل الصـغيرة وقـد
تجـد الأمر متعباً للغاية حينما تتعامـل مـع دوال أخــرى أكثـر تعقيـداً مـن حيـث
عـدد الوسائط وأنـواعها مما يلزمك بكتابة جميع تلك الإحتمالات.
توفر لك السي بلس بلس طريقـة أفضل مـن ذلـك بكثيـر ألا وهــي القوالـب ،
دعـنا الآن نقوم بقولبـة الدالة السابقــة حتـى تصـبح قـادة علـى التعامـل مـع
جميع الاحتمالات:
CODE
1- template <class T> T Abs(T X)
2- {
3- return X<0 ? -X : X;
4- }
التغيير الحاصل هـو في أول سطر من التابع حيث تغير من (X int) Abs int
إلى:
. template <class T> T Abs(T X)
قارن بين السطرين الاولـين فـي التـابعين ؛ تجــد أنــه لا وجـود للنــوع int بـل
الحرف T ؛ والحرف T في الحقيقـة هـو نـوع الوسـائط ونــوع القيمــة المعـادة
كما هـو في تعريف التابع ؛ وليس الأمر في أن هـناك نـوع بيانات جـديد هـو T
بل لأننا قمنا بقولبـة الدالة ففي السطر الأول قمنا بكتابة الكلمـة الأساسيــة
وهـي template ومعـناها أننا نخبر المترجم بأن التابع القـادم نريـد قولبتــه ،
ثم يأتي بعض ذلـك وبـين قوسـين حـادين الكلمــة الأساسيــة <T class>
لاحـظ أنه بإمكـانك تغيير الحرف T إلى ما تريد لكن الكلمـة الأساسية class لا
تستطيع تغييرها إلى ما تريد وهذه الكلمـة بمفهـوم عام أنك تخبر المترجم أن
الحرف T هـو نوع بيانات على المترجم تحــديده بنفســه ولا يجـب ذلـك علـى
مبرمج أو مستخدم التابع وبالمعـنى فإنـك إذا قمـت بتمريـر إحـدى القـيم مـن
النـوع int فإن المترجم يقـوم بإصـدار دالـة تستخــدم النــوع int ويستخــدمها
وهـكذا بالنسبـة لجميع الأنـواع وحتى النـوع c.

التمرير بالمرجع أفضل من التمرير بواسطة القيمة:

التمرير بالمرجع أفضل من التمرير بواسطة القيمة:
كما تلاحظ فإنـه عــند إرسـال أي قيمــة لأي إجـراء فإنـه فـي الحقيقــة يقـوم
بنسخ تلك القيم ووضعها في قائمـة الوسائط الموجودة في إعـلان الإجـراء...
بالتالي فإنـك عـندما تمرر عشر قيم إلى أحـد الإجـراءات فكأنـك قمـت بإنشـاء
عشرين متغير وليس عشرة ... أما عـندما تمرر عـناوين تلـك القـيم فإنـك فـي
الحقيقـة تمرر المتغيرات الأصلية وليس نسخاً عـنها وهذا مـا يـوفر الكثيـر مـن
ناحية السرعـة والأداء وبقية ميزات برنامجـك.
التوابع والمصفوفات:
تعرفنا في الفقرة السابقـة على الفائـدة المرجـوة بـين التوابـع والمؤشـرات ،
والآن سنتعرف على كيفية تعامل المصفوفات أو التوابع مع الأخرى.
في الحقيقـة فإنه ليس بامكانك إرسـال مصـفوفة دفعــة واحـدة إلا إن كانـت
تحتوي على متغير واحد وليس بإمكانك أيضاً جعل التابع يعيد مصفوفة كاملة.
أما عـن كيفيـة انتقال المصفوفات إلى التوابـع فهــي تكـون بـالمرجع حصـراً ،
والمترجم هـو بنفسه سـيقوم بـذلك ، تسـتطيعها إرسـالها بالقيمــة كوسـائط
للتوابع ولكن لن يكون بإمكانك سـوى إسـتدعاء التـابع أكثـر مـن مـرة (حسـب
عدد عناصر المصـفوفة) أمـا إذا قمـت بإرسـال المصـفوفة فسـيكون بإمكانـك
إستدعاء التابع مرة واحدة فقط لتغيير جميع المصفوفة.
حتى تستطيع جعل تابع من التوابع يستطيع استقبال مصـفوفة كبـارامتر لـه،
فعليك أولاً بـإبلاغ التـابع أنــه سيسـتقبل مصـفوفة ، انظـر إلـى أحـد النمـوذج
المصغر لتابع يستقبل مصفوفة:
void arraysFunction (int [] );
لم نقـم في قائمـة الوسائط إلا بذكر نـوع المصـفوفة وكتابـة علامتـي فهـرس
المصفوفات ، ثم بعـد ذلك نستطيع التعامل مع المصفوفة وكأنها عــنصر فـي
التابع ( )main ، ولا يجب علينا أن نتـدخل فـي أمــور المؤشـرات والمرجعيـات
المعقدة ، سنقوم الآن بكتابة كـود يقوم بعـكس عــناصر إحـدى المصـفوفات ،
انظر إلى هذا الكـود وحاول فهـمـه قبل قراءة الشرح الموجود تحتـه:
CODE
1. #include <iostream>
2. using namespace std;
3.
4. void arraysf (int [] );
5.
6. int main()
7. {
8. int array[5]={1,2,3,4,5};
9. for (int i=0;i<5;i++)
10. cout << array[i] << endl;
11. arraysf(array );
12. for ( i=0;i<5;i++)
13. cout << array[i] << endl;
14.
15. return 0;
16. }
17.
18. void arraysf(int m[])
19. {
20. for (int i=0,int j=5;i<5;i++,j--)
21. m[i]= j;
22. }
· انظـر إلـى النمـوذج المصـغر للتـابع arraysf ، وهــكذا نكـون أعلمنـا
التابع أنـه سيستقبل مصفوفة.
· الإعــلان عـــن المصــفوفة كــان فــي الســطر 8 وهــي مكونـــة مــن
خمسة أرقام من الرقم 1 إلى الرقم 5 .
· السطران 9 و 10 تقوم بطباعة عـناصر المصفوفة.
· يقوم السـطر 11 باسـتدعاء التـابع arraysf وهــو مـن النــوع void ،
وسيقوم بمعالجـة عـناصر المصفوفة بواسطـة عـناوين الذاكرة، قـد
تستغرب من هـذا الشـيء خاصــة وأن الكــود لـم يكتـب لـيس فيـه
علامة مرجع ولا مؤشر ولكن هذه الأمـور يقوم بها المترجم بنفسه.
· ينتقل التنفيذ إلى السطر 20 ، حيث تقوم الحلقة for بتغيير عـناصر
المصفوفـة عـكسياً وحينما ينتهي التنفيذ ينتهي التابع ، لاحـظ أنــه
يرجع قيمـة void .
· يعـود التنفيذ إلى التابع ( )main ويقوم السطران 12 و 13 بطباعــة
عـناصر المصفوفة بعـد تغييرها ، هذه هـي نتيجـة تنفيذ هذا الكـود:
123454
321
نقل المصفوفة ذات بعـدين إلى التوابع:
بقي أن أشير هـنا إلى كيفية نقل مصفوفة ذات بعـدين إلى تابع معــين، فـي
الحالــة الأولــى (المصــفوفة ذات البعـــد الأول) لــم يكــن يشــترط ذكــر حجــم
المصفوفة ولكن في هذه الحالة يجب عليك ذكر حجم البعد الثاني للمصفوفة
، وبالتــالي فســيكون النمــوذج المصــغر لأي تــابع يعــالج هــذا النـــوع مــن
المصفوفات هكذا:
void arrayFunction (int [ ] [ 6 ] );
تذكر أن المصفوفات شديدة الشبه جداً بالمؤشرات حتى تفهـم عـملها وحتى
تفهــم مـا يـأتي منهـا كـالقوائم المترابطــة والأشــجار خاصـــة فـي المواضــيع
المتقدمـة، وقد نتناول موضوع القوائم المترابطـة و جـزءاً مـن بنـى المعطيـات
في هذا الكتاب.
العـودية:
هــــناك نــــوع مـــن الخوارزميـــات يدعــــي الخــــوارزميات العــــودية ، وهـــذه
الخوارزميات لا تعـمل إلا بوجـود التوابـع وربمـا فـي بعـض الحـالات المتغيـرات
الساكنة ، وحتى تفهـمها فهي قريبة جداً من حلقات التكرار إلا أنها أخطر منها
حيث أنها في بعض الأحايين تكون غامضة أو شـرط توقفهـا غامضـة كحلقـات
for الأبدية .
لا يمكن فهـم العـودية إلا من خلال الأمثلة ، لنفرض أن لديك هذا التابع :
void Function()
{
Function( );
}
يعتبر هذا المثال مضـحكاً للغايـة وقـد يـدمر مشـروعك البرمجـي حينمـا تقـوم
باستدعاء هذا التـابع مـن التـابع ( )main فإنـه حينمـا يصـل لأول أمـر سـيقوم
باستداعاء نفس التابع وهذا التابع المستدعى سيقوم باستدعاء نفـس التـابع
وستقوم جميع التوابع المستدعاة باستداعاء نفسها إلى مالانهاية ، وقد ينهار
برنامجك بسبب ذلك.
إذاً العـودية هـي أن تقوم الدوال باستدعاء نفسها ، ولكن كما فـي التكـرارات
فلا بد لهذا الاستدعاء من نهاية ، وكما يحدث في التكرارات من وجـود شـرط ،
فلا بد في التابع أن يكون هـنا مـن شـرط وكمـا رأيـت فـي الحلقـة for والتـي
تقوم بالعـد حتى تصل إلى نقطـة معينة ثم تنتهي فإنـه بإمكانك إحداث الأمـر
هـنا نفسه في العـودية عـن طريق المتغيرات الساكنـة ، سنقوم الآن بكتابـة
مثال شبيه بالحلقة for ، وسـيقوم هـذا التـابع الموجـود فـي الكــود بطباعــة
نفسه حسبما تريد من المرات (مثل حلقـة for ):
CODE
1. #include <iostream>
2. using namespace std;
3.
4. void function (int x);
5.
6. int main()
7. {
8. int n=0;
9. cout << "Enter The Number:\t" ;
10. cin >> n;
11. function (n);
12.
13. return 0;
14. }
15.
16. void function (int x )
17. {
18. static int i=0;
19. i++;
20. cout << "Number i=\t" << i << endl;;
21. if (i==x)
22. return ;
23. function(x);
24. }
بالرغـم من طول هذا المثـال ، إلا أن فهــمك لـك سيسـهل الكثيـر مـن الأمـور
عليك في موضوع العـودية (بعض الأشـخاص يعتبـر صـعوبة موضـوع العوديـة
مثل صعوبة موضوع المؤشرات ) :
· كما تـرى فـي التـابع ( )main فإنـه طلـب البرنـامج مـن المسـتخدم
طباعـة الرقم الذي يريد تكراره في السطر 10.
· في السطر 11 تم إستدعاء التابع function وتم تمرير العــدد الـذي
أدخله المستخدم إليه.
· ينتقل التنفيذ إلى السطر 18، حيث تـم الإعـلان عــن متغيـر سـاكن
وتمت تهيئته بالعدد 0 (وهذا شبيه بالجزء الأول من حلقة for ).
· في السـطر 19 تمـت زيـادة المتغيـر السـاكن i (والـذي يعتبـر مثـل
الجزء الثالث من حلقة for ).
· في السـطر 20 تمـت طباعــة الـرقم الـذي وصـل إليـه التـابع (مثـل
التكرار) .
· في السطر 21 تتم مقارنـة الرقم الذي وصل إليه التابع بالرقم الذي
أدخله المستخدم في التابع ( )main وفـي حالـة المسـاواة تنتهـي
هــذه العـــودية بالجملــة return ، والتــي تخرجــك نهائيــاً مــن هــذه
العـودية (تشبه الجملة break ) في حلقات التكرار.
· في حال عـدم نجاح المقارنـة يتم إستدعاء التـابع مـرة أخـرى حتـى
تنجح هذه المقارنـة.
قليلـة جـداً هــي الامثلـة التـي تسـتخدم المتغيـرات الساكنــة فـي موضــوع
العــودية لإنهـاء الاسـتدعاء الـذاتي للتـابع ، هــناك شــروط أخـرى أكثـر تقنيـة
وابتكـاراً مـن مجـرد تشـبيه العــودية بحلقـة for ، سـنتعرض لهـا فـي المثـال
التالي .
وبالرغـم من أن حلقات التكرار أفضل بكثير من العـودية والسبب فـي ذلـك أن
العـودية تستهلك كثيراً من الطاقة فالأفضل هــو أن تتـرك هـذا الموضـوع (أي
موضوع العـودية) لمهاراتك البرمجية وألا تستخدمـه إلا في حـالات اسـتثنائية
حينما لا تجد حـلاً إلا بهـذا الموضـوع ، وهــناك بالفعـل بعـض الأشـياء التـي لا
يمكن حلها إلا بموضوع العـودية.
مثال عـملي:
هذا هـو المثال الوحيد الذي سأتناوله عــن موضـوع العــودية للأسـباب التـي
ذكرتها سابقاً.
سنقوم بكتابة كـود يحسب مضـروب أي عــدد مـا ، وسـنحله بطريقــة التكـرار
وليس بطريقة العـودية.
إليك أمثلة على مضروب أي عـدد إن كنت لا تفهـم ما هـو:
2! = 2 * 1;
5! = 5 * 4 * 3 * 2 * 1 ;
أول ما يجب علينا التفكير فيه هـو معرفة متى سيتوقف التـابع عــن اسـتدعاء
نفسـه، كمـا تعلـم أن مضـروب الصـفر يسـاوي الواحـد الصـحيح ( 1 = !0 ) .
بالتالي فحينما يصل التابع إلى الرقم 0 فسيتوقف عـن استدعاء نفسه.
أما عـن كيفية سيصـل هـذا التـابع إلـى الصـفر فـالجواب بسـيط حينمـا يقـوم
بمقارنـة العـدد الممرر بالصفر وفي حال لـم يجـده كـذلك فإنـه يقـوم بإنقـاص
العدد الممرر رقماً واحداً ثم يمرره إلى التابع المستدعى الآخر وهـكذا:
CODE
1. #include <iostream>
2. using namespace std;
3.
4. int fact(int );
5.
6. int main()
7. {
8. int i=0;
9. cout << "Enter the Number:\t";
10. cin >> i;
11.
12. int x=fact (i);
13. cout << x << endl;
14.
15. return 0;
16. }
17.
18. int fact (int x)
19. {
20. if (x==0) return 1;
21. else return x*fact(x-1);
22. }
· يطلــب البرنــامج مــن المســتخدم إدخــال العـــدد الــذي يريــد إيجــاد
مضروبه في السطر 10.
· يتم إنشاء المتغير x والذي سيتم تحزين نتيجـة حـل هـذا المضـروب
فيه ، وسيتم تهيئته بالقيمة العائدة للتابع fact ، والذي ستتم تمرير
العدد الذي أدخله المستخدم لحساب مضروبه.
· ينتقل التنفيذ إلى السطر 20 ، وفيها يقارن البرنـامج العــدد الممـرر
بالعدد 0 وفي حال كان كذلك يقـوم بإعـادة القيمـة 1 ، لأن مضـروب
الصفر هـو العـدد الصحيح.
· في حال لم يكن كذلك فإن التـابع يعيـد قيمــة ضـرب العـدد الممـرر
في مضروب العـدد الذي قبله فلو كان العـدد الممرر هــو 5 فـيمكن
تشبيه قيمـة الإعادة رياضياً هـكذا ( !4 * 5 ) أما عــن كيـف كتابتهـا
برمجياً فهـو بتمرير الرقم 4 إلى تابع من نفس التابع fact مرة أخرى
وهـكذا تكـون العــملية متتاليـة حتـى يجـد البرنـامج الـرقم 0 حينهـا
سيعيد القيمة 1 وبالتالي ينتهي كل شيء.
في حال ما لم تفهـم ما سبق فقم بإعادة قراءتـه مـن جــديد لأنـه مهــم فـي
بعض الامـور والتي نادراً ما ستواجهها .
أما إذا فهـمت ما سبق فسأترك لـك هـذا المثـال الآخـر والـذي يقـوم بطباعــة
السلسة fibbianci .
ملاحظـة :هذه السلسلة الحسـابية عبـارة يكـون العــدد عبـارة عــن مجمــوع
العـددين الذين قبلاه في السلسلة مع العلم أن العدد الاول والثـاني هــما 1،
انظر:
1 1 2 3 5 8 13 21 34 55 ……… etc
CODE
1. #include <iostream>
2. using namespace std;
3.
4. int fib(int );
5.
6. int main()
7. {
8.
9. int i=0;
10. cout << "Enter the Number:\t";
11. cin >> i;
12.
13. i=fib (i);
14. cout << i << endl;
15.
16. return 0;
17. }
18.
19. int fib (int x)
20. {
21. if ( x<3)
22. return 1;
23. else return (fib (x-2) + fib (x-1) );
24.
25. }
يقوم هذا البرنامج بطباعـة رقم السلسلة الذي أدخلت موقعـه منها.

المعـامل (::) :

المعـامل (::) :هـناك معـامل آخر لم نتعرض له وهـو معـامل الوصول إلى المتغيرات العامـة
وهـو :: ، انظر إلى هذا المثال:
int a=10;
void function( )
{ int a= 5 }
كما تلاحـظ فـإن هــناك متغيـر خـاص أو محلـي لـه اسـم a للتـابع function ،
وهــناك أيضـاً متغيـر عـام ، السـي بلـس بلـس تسـمح لـك بفعـل ذلـك ولكـن
المتغيــر العــام ســيتم إســتبعاده أو إخفــاءه وســتكون الأولويـــة فــي التــابع
function للمتغيــرات المحليــة ولــيس للمتغيــرات العامـــة ، وحتــى تســتطيع
الوصول إلى المتغير العام ضمن كتلة التابع function فعليـك أن تقـوم بكتابـة
المعامل :: حتى تصل إليه أنظر لهذا الأمر الذي نفترض أنـه ضمن كتلـة التـابع
: function
cout << ::a ;
لن يقـوم هـذا الأمـر بطباعــة القيمــة الخاصـة بـالمتغير الخـاص بـل بالقيمــة
الخاصـة بالمتغير العام لأننا قمنا بكتابة المعامل :: قبل اسم المتغير.
الوسائط الإفتراضيـة:
أحــد أهــم أهــداف أي برمجــة هــو إعـادة الاسـتخدام ، أي إعـادة اسـتخدام
الاكواد السابقـة وحتى نصل إلى هذا الهـدف فلا بد علينا من جعـل اسـتخدام
هذه الأكواد السابقة بسيطاً للغايـة وبـدون أي تعقيـد ، انظـر مـثلاً للكـائن cin
وكيف أن إستخدامه بسـيط وميسـر وأيضـاً للدالـة ( )printf فـي لغـة السـي
ومدى سهولتها وهذا أيضاً ما نحـاول الوصول إليه من خلال هذا الكتاب.
بإمكاننا تسهيل استخدام أي دالة بواسطـة الوسائط الافتراضيـة (البارامترات
الافتراضية) وهذه الأداة تمكننا من تسـهيل الكــود لدرجـة كبيـرة ، هـل تتـذكر
التابع ( )getline ، هذا التابع يحتـوي علـى ثـلاث بـارمترات ، ولكنـك تسـتطيع
التعامل معـه على أنه يستقبل بارامترين اثنـين وتسـتطيع إذا أردت اسـتخدام
ثلاث بارامترات ، نفس الأمر ينطبق هـنا ، بإمكاننا إنشاء توابـع بتلـك الطريقــة
ووسيلتنا لذلك هـي الوسائط الافتراضية.
سـنقوم الآن بكتابـة مثـال كــودي وهـذه المـرة سـيقوم هـذا المثـال بحسـاب
النسـبة المئويــة ، حيـث أنــه سـيقوم بحسـاب النسـبة مـن 100 افتراضــياً ،
وبإمكان المستخدم حساب النسبة من 100 أو أي رقم آخر يريده.
1. float rate(float a,float b ,float c=100)
2. {
3. float j=0;
4. j= (a*c)/b;
5. return j;
6. }
انظر إلى السطر الأول تجـد أن البارامتر الثالث غريب بعض الشيء حيث قمنا
باسناد البارامتر إلـى القيمــة 100 ، وبـذلك سـيكون بإمكانـك اسـتخدام هـذه
القيمـة افتراضياً ، بإمكانك استدعاء هذا التابع بهذا الشكل:
rate ( 50 , 100)
أو بهذا الشكل إن أردت:
rate ( 20, 100 , 1000)
والفـرق بـين الاسـتدعائين أن البـارامتر الثالـث للتـابع المسـتدعى الأول هــو
سيكون افتراضياً بقيمـة 100 ، أمـا التـابع الثالـث فلقـد أراد المسـتخدم تغييـر
هذه القيمـة وبالتالي فلقد قام البرنـامج باسـتبعاد القيمـة الافتراضـية ووضـع
القيمة التي قام المستخدم بوضعها.
سنرى الآن كيف سيكون استخدامنا لهذا التابع فـي وسـط برنـامج حقيقـي ،
عليـك أن تعلـم أن القيمــة الافتراضــية لا تكتـب أبـداً فـي رأس التـابع إلا فـي
النموذج المصغر فقط ، أما تعريف التابع فلا تقم بكتابة القيمـة الافتراضـية وإلا
فـإن المتـرجم سيصـدر خــطأ ، انظــر لهـذا المثـال ، وكيـف تـم تطبيـق الكـلام
الحالي:
CODE
1. #include <iostream>
2. using namespace std;
3.
4.
5. float rate (float a,float b,float c=100);
6.
7. void main()
8. {
9. float i,j,k,avg;
10. cout << "Please Enter the number?\n";
11. cin >> i;
12. cout << "from:\t";
13. cin >> j;
14. cout << "the Avrege:";
15. cin >> avg;
16.
17. k=rate (i ,j,avg);
18. cout << endl << k << endl;
19.
20. }
21.
22. float rate(float a,float b ,float c)
23. {
24. float j=0;
25. j= (a*c)/b;
26. return j;
27. }
قارن بين رأس التابع في السطر 22 والنموذج المصـغر للتـابع فـي السـطر 5
تستنتج أن النمـوذج المصـغر بإمكانـه الاحتـواء علـى قـيم افتراضـية أمـا رأس
التابع أو تعريف التابع فليس بإمكانـه الاحتـواء على أي قيمـة افتراضية.
إعادة أكثر من قيمـة بواسطـة المؤشرات أو المرجعيات:
الآن سنأتي إلى التطبيق الفعلي للمؤشرات ؛ هل تتـذكر التوابـع ألـيس فـي
نهايـة كل تابع مالم يكـن void العبارة التاليـة: return (Value);
حيث value القيمة المعادة.
كما ترى فإن جميع الدوال أو الإجراءات لا تعـود إلا بقيمـة واحـدة ولا تسـتطيع
العـودة بأكثر من قيـمـة ، الآن سنفكر بطريقـة تمكننا من جعـل التوابـع تعــود
بأكثر من قيمـة.
ما رأيك الآن بدلاً من أن نمرر للتوابع القـيم أن نمـرر لهـا عــناوين تلـك القيــم ؛
سنكتب برنامج هذا البرنامج يحــوي تابعـان التـابع main وتـابع آخـر سـنطلق
عليه plus سيعــيد هـذا الإجـراء قيمتـين وسـيقوم الإجـراء main بطباعتهــما
وليس التابع plus .
1 #include < iostream.h>
2 void plus (int num1,int num2,int *plus1,int *plus2)
3 {
4 *plus1=num1 + num2;
5 *plus2=num1*num2;
6 }
78
void mian ( )
9 {
10 int num1,num2,plus1,plus2;
11 plus (num1,num2, &plus1 , & plus2);
12 cout << plus1 << endl;
13 cout << plus2 << endl;
14 }
الآن وكما ترى فإن قيم plus1 و plus2 ستؤدي المطلـوب منهـا حيـث plus1
يجمع عـددان و plus2 يضـرب عـددان بالرغــم مـن أن المعالجــة لا تتــم فـي
التابع ()main بل فـي التـابع plus وكمـا تلاحـظ فـإن التـابع plus لا يعــود أي
قيمـة لأنه void ؛ كما تلاحظ أعـلنا عـن عددان مهيئان مسبقاً وعددان لم يهيئا
في السطر العاشر ؛ بعـد ذلـك قمنـا بتمريـر قيمــة العــددان num1 و num2
إلى الإجراء plus أما بالنسبة للعــددان الآخـران فلـم نمـرر قيمهــما بـل مررنـا
عـناوين تلك القيـم كما هـو واضح من السطر الحادي عشر ؛ كما درسـنا فـي
هذا الموضوع (موضوع التوابع) أنهـا تنشأ نسخ مـن المتغيـرات الممـررة إليهـا
أما في هذه الحالة فهي لم تقوم بإنشاء نسخــة بـل أخـذت النسـخ الأصليــة
من تلـك المتغيـرات وهــي عــناوينها ... الآن يتفـرع البرنـامج إلـى التـابع plus
والذي عرفناه في السطر الثاني وكما تلاحظ فهــو يحتــوي عـددان مـن نــوع
int ومتغيران آخران لكن مؤشرات هـذه المـرة وليسـا متغيـرات عاديــة .. هـل
تعرف لماذا .. كما تلاحـظ فلقـد مررنـا عــناوين تلـك المتغيـرات ؛ البرنـامج الآن
بحـاجـة إلى متغير ليحمل تلـك لعــناوين وكنـا تعلـم فـإن المؤشـر هــو متغيـر
يحمل عـنوان .. ثم في السطر الرابع والخامس تمت معالجـة القيم حيث في
السطر الأول جمعـنا العددان وفي السطر الخـامس ضـربنا العـددان ثـم فـي
السطر السادس عــدنا مـرة أخــرى إلـى الإجـراء ()main ثـم فـي السـطران
الثاني عشر والثالث عشر قمنا بطباعـة النتائج .... وهـكذا انتهـى البرنامج.
خلاصـة هذا الشرح ؛ أنـه لكي تجعل التابع يعـود بأكثر من قيمـة عليك أولاً أن
تمرر عـناوين أو مرجعيـات تلـك القـيم ولـيس القـيم بحـد ذاتهـا ؛ حينمـا تقـوم
بتعريــف التــابع فإنــك تضــع فــي قائمـــة الوســائط مؤشــرات لتلــك العـــناوين
المرسلة حتى تستطيع حـملها
كما تلاحظ فلقد استخـدمنا في المثال السابق المؤشرات ... ما رأيـك الآن أن
نستخـدم بـدلاً عــن المؤشـرات المرجعيـات... انظـر لهـذا المثـال وهــو نفـس
المثال السابق لكن هذه المرة نستخـدم المرجعيات بدلاً من المؤشرات:
1 #include < iostream.h>
2 void plus (int num1,int num2,int &plus1,int &plus2)
3 {
4 plus1=num1 + num2;
5 plus2=num1*num2;
6 }
78
void mian ( )
9 {
10 int num1,num2,plus1,plus2;
11 plus (num1,num2, plus1 , plus2);
12 cout << plus1 << endl;
13 cout << plus2 << endl;
14 }
المثال نفس مثال المؤشرات عـدا في السطر الحادي عشر فلقـد تـم إرسـال
القيم بدون أي تغيير لها أما تعريف التابع plus في السطر الثاني فلقـد جعلنـا
تلك القيم إشارات .
بالرغـــم مـن أن المثـالين السـابقين سيعــملان بـنفس الجــودة إلا أن المثـال
الأخير بإستخدام المرجعيات أقوى مـن المثـال السـابق فهــو لا يجعلـك تفكـر
عـند إرسال القيم للإجـراء ؛ فـلا يجعلـك تقـول هـل أرسـل عــنوان القيمــة أم
القيمـة ؛ وهذا مـا تحــاول ++C الوصـول إليـه ؛ خاصــة فـي أمــور البرمجــة
الكائنيـة .. عـموماً سنصل إلى جميع نقاط هذه الفوائـد فـي وقـت لاحـق مـن
الكتاب

النماذج المصغرة Prototype :

النماذج المصغرة Prototype :
لننظر إلى بداية البرنامج وبالتحـديد في هذا الجزء من الكـود:
النمــاذج المصغرة// ;(x int)HowTimes void .8
9. void pointer();
10. void TheMain(int x);
كما ترى فلقد قمنا بكتابة رؤوس التوابـع فقـط وقمنـا بـالتفريق بينهـا بعلامــة
الفاصلة المنقوطـة ( ;) وينصـح دائمـاً فـي أي بـرامج تقـوم بكتابتهـا أن تكتـب
النماذج المصغرة لها كما هـو في هذا المثال وللنماذج المصغرة فوائد كثيرة:
1- لنفــرض أن لــديك تــابعين اثنــين ولنفــرض أن التــابع الأول احتــاج إلــى
إستدعاء التابع الثاني وفي نفس الوقت فقد يحتاج التـابع الثـاني إلـى
إستدعاء التابع الأول أي أن التابعين الاثنين يحتاجان إلى إسـتدعاء كـل
واحد منهـما فحينها لن تستطيع كتابة تعريف أحـد التـابعين قبـل الآخـر
والنماذج المصغرة تحل هذه المشكلـة.
2- لـن تحتــاج إذا قمـت باستعــمال النمـاذج المصـغرة إلـى كتابـة أسـماء
الوسائط والمتـرجم سـيتجاهل الأسـماء فـي الأسـاس الـذي تحتــاجه
فقــط هـــو كتابــة نـــوع المتغيــرات فلنفــرض أن لــديك تــابع يســتقبل
وسيطين اثنين من النـوع int و float ؛ في حال إذا أردت كتابة النموذج
المصغر فإنـه يستحسن أن تكتبه هـكذا:
int test(int , float);
3- أيضاً فإن هـناك فائدة أخرى وهـي أنه عـند تعريف التوابـع تحـت التـابع
main فلن تضطر إلى كتابة القيم المعادة للتوابع كمـا هــو ظـاهر لـدى
التابع TheMain في السطر 35.
هذه هـي أهـم فوائد النماذج المصغرة.
مشاكل المتغيرات العامـة:
يميل أكثر المبرمجيم المبتدئين إلى إستخدام المتغيرات العامـة فهي تبعــدك
كثيراً عـن مشاكل القيم المعادة وتبـادل المعلومـات بـين التوابـع إلا أن هــناك
مشاكل كثيرة لها وهــي أنهـا سـتبقى مرئيــة فـي المنـاطق التـي لا تريـدها
أيضاً فهي تجعل من عـملية تتبع البرنامج عـملية تكاد تكـون مستحيلــة نظـراً
للتعقيد ، ربما تعتبر هـذه المشـاكل هــي التـي جعلـت أول مبـدأ مـن مبـادئ
البرمجــة الشيئيــة يظهـر وهــو الكبسلــة الـذي سـنتعرض لـه فـي الفصـول
اللاحقـة.
تمرير الوسائط بواسطـة القيمـة:
بالرغـم من أننا أشرنا إلى هـذا الموضـوع إلا أنــه لا بـد مـن تناولـه فـي فقـرة
كاملـة ؛ على العـموم يوجد نـوعـان من التمرير بالوسائط إلى التوابع الأخرى:
1- التمرير بواسطـة القيمـة
2- التمرير بواسطـة المرجع
ويعتبر النـوع الثاني هـو الأفضل والأكثر أماناً إلا أن هـناك بعض الحالات التـي
تضطرك إلى إستخـدام النـوع الأول.
عـموماً تعتبر البارامترات (الوسائط الممررة ) متغيرات محليـة بالنسـبة للتـابع
الذي مررت إليه ويتم تدميرها عـند إنتهاء تنفيذ التـابع ، وعــموماً فيجـب عليـك
عـند كتابة النموذج المصغر للتابع أن تـذكر معــه نــوع البـارامترات ولا يشـترط
ذكر اسمها ، ولكن تذكر أن هذا الأمر خاطيء :
int function (int x, z);
والسبب في ذلك يعـود إلى أنك لم تذكر نـوع الوسيط الثاني ، صحيح أنـه قـد
يفهـم من الامر السـابق أنـك تقصـد أن البـارامتر الثـاني مـن النــوع int إلا أن
المترجم لن يفهـم هذا الأمر.
في حال ما إذا كان لديك أكثر من بارامتر فإنك تقوم بالفصـل بينهــا بواسطــة
الفاصلة العادية وليس الفاصلة المنقوطـة ؛ هـكذا (, ).
القيمـة العائدة Value return :
في نهاية كـل تـابع نجــد هـذه الكلمــة return والتـي تحــدد مـا هـي قيمــة
الإعادة ، انظر إلى هذا الأمر:
int function (int x,int z);
تجـد أنـه لا بد لهذا التابع أن يعيد قيمـة من النـوع int ، قد تكون هذه القيمـة
رقماً أو متغيراً من النـوع int ، انظر لهذا الأمر:
return ( 2) ;
لا يشـترط أن تضـع القيمــة المعــادة بـين قوسـين ولكـن يفضـل حتـى يصـبح
الكـود أكثر تنظيمـاً وفهــماً ، لربمـا أنـه تعلـم أنــه بإمكــانك إسـتبدال الـرقم 2
بمتغير آخر من النـوع int .
ليس ذلك فحسب بل بإمكـانك جعل القيمـة المعادة تابعاً كاملاً بحد ذاتـه انظر
لهذا المثال:
return (function(4));
إنـه يقوم بإستدعاء تابع إسمـه function وسيقوم هذا التابع بإستدعاء نفس
التابع وسيستدعي نفسـه إلى مالا نهاية مالم تضع للأمر حداً بواسطة
القرارات. وسنتناول هذا الإستدعاء المتكرر في موضوع آخر من هذه الوحدة.
أيضاً فإن للقيمـة المعادة فائدة كبيرة أخرى وهـي أنها تسمح لك بطباعـتها
دون الحاجـة إلى تخزين قيمتها في متغير ما ، فبدلاً من كتابة هذه الأوامر:
int number=function (4) ;
cout << number ;
كما ترى فلقد قمت بتخـزين القيمـة المعادة للتـابع function فـي متغيـر آخـر
حتى تقوم بطباعتها ، بإمكانك إختصار هذا الأمر إلى هذا السطر:
cout << function ( 4 ) ;
وسيقوم المتـرجم بطباعــة القيمــة المعـادة للتـابع function ، قـد تجـد هـذه
المواضيع سخيفـة أو ليس لها من داع ولكن ستسفيد منها كثيراً حينمـا تصـل
لمواضيع الكائنـات.

قواعد مجالات الرؤيـة:

قواعد مجالات الرؤيـة:
بعــد أن انتهينـا مـن أساسـيات التوابـع فسنمضــي قـدماً فـي الحـديث عــن
المتغيرات ولكن هذه المرة من وجهـة نظر التوابع ؛ لسنا هــنا بصـدد الحـديث
عـن الانـواع الداخلية لأنمـاط البيانـات مثـل int .. وغيرهـا ، بـل حسـب قواعـد
مجالات الرؤيـة لدى هذه الدالة ؛ عـموماً فهــناك ثلاثــة أنــواع للمتغيـرات مـن
وجهـة نظر التوابع هـي كالتالي:
1- المتغيرات الخاصـة:
2- المتغيرات العامـة:
3- المتغيرات الساكنـة:
وسنأتي على كل منها.
المتغيرات الخاصـة Variables Local :
هل تتذكر كلامنا السابق في الفصول الأولى من الكتاب حــول الكتـل ، التـابع
في الحقيقـة ليس إلا كتلة وبالتالي فحينما تقوم بكتابة هـذا القـوس { فـذلك
يعـني أنك قمت بتدمير جميع المتغيـرات التـي تـم الإعـلان عــنها بعــد قـوس
الفتح } ، وكما أن الأمر ينطبق علـى توابـع ودوال التكـرار فـالأمر نفســه هــنا
بالنسبــة للتوابــع ، إذا قمـت بالتصــريح عــن أي متغيــر فـي أي تــابع فحينمــا
ينتهي تنفيذ هذا التابع فإن جميع متغيراتـه تكون انتهـت معــه أيضـاً وبالتـالي
فحينمــا تقــوم بإســتدعاء نفــس التــابع مــرة أخـــرى فســيتعامل البرنــامج مــع
المتغيــرات وكأنهــا متغيــرات جديــدة لــم تــتم ترجمتهــا ســابقاً ، ومثــال الكـــود
السابق هـو مثال نموذجي لما نتكلم عـنه.
المتغيرات العامة Variables Global :
بعـكس النوع السابق فإن المتغيرات العامـة هـي متغيرات يتم الإعـلان عــنها
خارج أي تابع آخر ، وجميع توابع البرنامج بإمكانها إستخدامها والتعامل معها ،
وحتى نفهـم هذا النـوع بشكل أفضـل ، فدعــنا نفـرض أن لـدينا تـابعين اثنـين
هـما:
Void test1( )
{
int g=1,k;
}
void test2()
{
int b, g=2;
}
كما ترى فإن التـابعين الاثنـين test1 و test2 يقومـان بـالإعلان عــن متغيـرين
اثنين إلا أن الأمر الذي نـود التأكيد عليه هـو أن أحد التابعين لا يستطيع رؤيــة
متغيـرات التـابع الآخـر وبالتـالي فـلا يسـتطيع التعامـل معهـا لأنـه لا يسـتطيع
رؤيتها ، وكما نرى فإن للتابعين الاثنين متغيرين اثنين لهـما نفس الاسم وهــو
g ولكن ليس لهـما نفس مكان الذاكرة وليس لهـما نفـس القيمــة فـالمتغير g
له نسختين ، كل تابع له نسخـة منهــما ، الآن لـو قمنـا بكتابـة تعريـف لمتغيـر
جـــديد خــارج أي كتلــة ســواء for أو while أو أي تــابع آخــر فحينهــا ســتكون
متغيرات عامـة أي أن جميع الكتـل تسـتطيع رؤيتهـا ، وبالتـالي التعامـل معهـا
وكأنها متغيرات خاصـة ، إلا أن الفـرق هــنا هــو أن أي تغييـر فـي قيمــة هـذا
المتغير من أي تابع في البرنـامج فـإن التغييـر سـيبقى حتـى انتهـاء البرنـامج
بشكل نهائي.
المتغيرات الساكنـة Variables Static:
المتغيرات الساكنـة تأخذ مزايا النـوعين السابقين فهـي أولاً نفـس المتغيـرات
الخاصـة أي أن هـناك تابع وحيد يستطيع رؤيتها هــو التـابع الـذي تـم الإعـلان
عـنها داخله وثانياً أنها لا تنتهـي أو تمـوت حينمـا يـتم انتهـاء تنفيـذ التـابع فـي
المرة الواحدة فمثلاً لو قمنا بكتابة متغير ساكن ضمن تعريف تابع ما ، فحينمـا
يتم تنفيذ هـذا التـابع وقـام فرضـاً بزيـادة قيمــة المتغيـر السـاكن إلـى 2 ، ثـم
انتهى تنفيـذ هـذا التـابع فـإن هـذا المتغيـر السـاكن لا ينتهـي معــه وسـيظل
محتفظاً بالقيمـة 2 حتى يتم استدعاء التـابع مـرة أخــرى وسـيجد أن المتغيـر
الساكن أصبح كما هـو عليه في المرة السابقــة ولـن يعــود إلـى القيمــة 0 ؛
باختصار بإمكان تشبيه المتغيرات الساكنـة على أنها متغيرات خاصـة لا تموت
حتى بانتهاء تنفيذ التابع.
وعـموماً فإن التصريح عـن هذه المتغيرات يتم بالكلمـة المفتاحيـة static كمـا
يرى من هذا السطر:
static int number;
مثال عـملي:
سنقوم الآن بكتابـة تـابع يقـوم بمضاعفــة العــدد الوسـيط إلـى ضعفــه ومـن
الممكن أن يكون هذا التابع بدايـة لك لكي تقوم بإنشاء برنامج حاسبـة آليـة:
CODE
1. #include <iostream>
2. using namespace std;
3.
4. double binate (float b)
5. {
6. return b*b;
7. }
8.
9. void main()
10. {
11. float a;
12. cin>> a;
13. double m=binate(a);
14. cout << m << endl;
15. }
تـم تعريـف التـابع ( )binate فـي الأسـطر مـن 4 إلـى 7 حيـث يسـتقبل عــدد
وسيط واحد وهـو b من النـوع float ويقوم بضربه في نفسـه وإعـادة القيمــة
إلى التابع main .
سنتقدم الآن أكثر وسنقوم بكتابة برنــامج أكثـر عـملانيــة وأكثـر فائـدة وهـذه
المرة فسنستخدم المؤشرات والمتغيرات العامـة كذلك.
البرنامج الذي نحن بصدده عبارة عـن قواسم عـدد ، المستخــدم يـدخل عــدد
ما ثم يقوم البرنامج بإنشاء مصفوفـة ثم إسناد كل قاسم مـن هـذه القواسـم
إلى عـنصر من عـناصر المصفوفـة ؛ إليك كـود البرنامج:
CODE
كـود يقوم بحساب قواسم أي عـدد // .1
2. #include <iostream>
3. using namespace std;
4.
المتغيرات العــامـة //;divides* float .5
6. int times;
7. //////////////////////////////
النمــاذج المصغرة// ;(x int)HowTimes void .8
9. void pointer();
10. void TheMain(int x);
11. ////////////////////////////
التابع الرئيسي // ()main void .12
13. {
14. int a;
15. cin>> a;
16. TheMain(a);
17. for(int i=0;i<times;i++)
18. cout <<divides[i]<< endl;
19. cout << "The Many Of How Number Divides Is:\t"
20. <<times
21. <<endl;
22. }
23. ///////////////////////
24. void pointer( )
25. {
26. divides=new float[times];
27. }
28. /////////////////////////
29. void HowTimes(int x)
30. {
31. for (int i=1;i<=x;i++)
32. if(x%i==0) ++times;
33. }
34. /////////////////////
35. TheMain(int x)
36. {
37. HowTimes(x);
38. pointer();
39. for (int i=1,int j=0;j<times,i<=x;i++)
40. if(x%i==0){
41. divides[j]=i;
42. j++;}
43. }
لقد احتـوى هذا المثال على مواضيع كثيرة سنقوم بمناقشتها حالاً.
فكرة البرنامج:
لهذا البرنامج متغيران عامين رئيسين هـما:
- المتغير العام times : وهذا المتغير يحسب عـدد الأعــداد التـي تقسـم
العـدد المراد إيجاد قواسمـه.
- المؤشر divides: بعـد أن يحسب البرنـامج عــدد قواسـم العــدد فإنــه
يقوم بحجز مصفوفة عدد عـناصرها يساوي قيمــة المتغيـر times ، ثـم
يقوم البرنامج يتخـزين قواسم العـدد في المصفوفـة divides.
أيضاً فإن لهذا البرنامج ثلاث توابع وهـي كالتالي:
1- التـابع (x int)HowTimes : يسـتقبل هــذا العــدد الــرقم الـذي أدخلــه
المستخـدم ويقوم بحساب عـدد قواسمـه ويخزنهـا فـي المتغيـر العـام
.times
2- التـابع ( )pointer : يقـوم هـذا التـابع بحجـز الـذاكرة للمؤشـر divides
وهـو يحجز له مصفوفـة حتى يخـزن فيهـا جميـع قواسـم العــدد الـذي
أدخله المستخـدم.
3- التابع (x int)TheMain : يعتبر هـذا التـابع هــو أهــم تـابع حيـث يقـوم
بإســتقبال الــرقم الــذي أدخلــه المستخـــدم ويــتحكم فــي التــابعين
السابقين ويحسب قواسم العـدد ويخزنها في مصفوفـة divides.
هذه هـي فكرة هذا البرنامج بشكل عام ولكن هــناك بعـض النقـاط الجــديدة
التي يجب التوقف عـندها وشرحها للقارئ العـزيز.