الخميس، 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.

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

إرسال تعليق