التمرير بالمرجع أفضل من التمرير بواسطة القيمة:
كما تلاحظ فإنـه عــند إرسـال أي قيمــة لأي إجـراء فإنـه فـي الحقيقــة يقـوم
بنسخ تلك القيم ووضعها في قائمـة الوسائط الموجودة في إعـلان الإجـراء...
بالتالي فإنـك عـندما تمرر عشر قيم إلى أحـد الإجـراءات فكأنـك قمـت بإنشـاء
عشرين متغير وليس عشرة ... أما عـندما تمرر عـناوين تلـك القـيم فإنـك فـي
الحقيقـة تمرر المتغيرات الأصلية وليس نسخاً عـنها وهذا مـا يـوفر الكثيـر مـن
ناحية السرعـة والأداء وبقية ميزات برنامجـك.
التوابع والمصفوفات:
تعرفنا في الفقرة السابقـة على الفائـدة المرجـوة بـين التوابـع والمؤشـرات ،
والآن سنتعرف على كيفية تعامل المصفوفات أو التوابع مع الأخرى.
في الحقيقـة فإنه ليس بامكانك إرسـال مصـفوفة دفعــة واحـدة إلا إن كانـت
تحتوي على متغير واحد وليس بإمكانك أيضاً جعل التابع يعيد مصفوفة كاملة.
أما عـن كيفيـة انتقال المصفوفات إلى التوابـع فهــي تكـون بـالمرجع حصـراً ،
والمترجم هـو بنفسه سـيقوم بـذلك ، تسـتطيعها إرسـالها بالقيمــة كوسـائط
للتوابع ولكن لن يكون بإمكانك سـوى إسـتدعاء التـابع أكثـر مـن مـرة (حسـب
عدد عناصر المصـفوفة) أمـا إذا قمـت بإرسـال المصـفوفة فسـيكون بإمكانـك
إستدعاء التابع مرة واحدة فقط لتغيير جميع المصفوفة.
حتى تستطيع جعل تابع من التوابع يستطيع استقبال مصـفوفة كبـارامتر لـه،
فعليك أولاً بـإبلاغ التـابع أنــه سيسـتقبل مصـفوفة ، انظـر إلـى أحـد النمـوذج
المصغر لتابع يستقبل مصفوفة:
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 تمـت طباعــة الـرقم الـذي وصـل إليـه التـابع (مثـل
التكرار) .
· في السطر
كما تلاحظ فإنـه عــند إرسـال أي قيمــة لأي إجـراء فإنـه فـي الحقيقــة يقـوم
بنسخ تلك القيم ووضعها في قائمـة الوسائط الموجودة في إعـلان الإجـراء...
بالتالي فإنـك عـندما تمرر عشر قيم إلى أحـد الإجـراءات فكأنـك قمـت بإنشـاء
عشرين متغير وليس عشرة ... أما عـندما تمرر عـناوين تلـك القـيم فإنـك فـي
الحقيقـة تمرر المتغيرات الأصلية وليس نسخاً عـنها وهذا مـا يـوفر الكثيـر مـن
ناحية السرعـة والأداء وبقية ميزات برنامجـك.
التوابع والمصفوفات:
تعرفنا في الفقرة السابقـة على الفائـدة المرجـوة بـين التوابـع والمؤشـرات ،
والآن سنتعرف على كيفية تعامل المصفوفات أو التوابع مع الأخرى.
في الحقيقـة فإنه ليس بامكانك إرسـال مصـفوفة دفعــة واحـدة إلا إن كانـت
تحتوي على متغير واحد وليس بإمكانك أيضاً جعل التابع يعيد مصفوفة كاملة.
أما عـن كيفيـة انتقال المصفوفات إلى التوابـع فهــي تكـون بـالمرجع حصـراً ،
والمترجم هـو بنفسه سـيقوم بـذلك ، تسـتطيعها إرسـالها بالقيمــة كوسـائط
للتوابع ولكن لن يكون بإمكانك سـوى إسـتدعاء التـابع أكثـر مـن مـرة (حسـب
عدد عناصر المصـفوفة) أمـا إذا قمـت بإرسـال المصـفوفة فسـيكون بإمكانـك
إستدعاء التابع مرة واحدة فقط لتغيير جميع المصفوفة.
حتى تستطيع جعل تابع من التوابع يستطيع استقبال مصـفوفة كبـارامتر لـه،
فعليك أولاً بـإبلاغ التـابع أنــه سيسـتقبل مصـفوفة ، انظـر إلـى أحـد النمـوذج
المصغر لتابع يستقبل مصفوفة:
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 تمـت طباعــة الـرقم الـذي وصـل إليـه التـابع (مثـل
التكرار) .
· في السطر