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

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

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

ليست هناك تعليقات:

إرسال تعليق