الفئات الأصلية والمشتقة في ++C

التوارث

يمكن أن نعتبر التوارث أقوي خصائص البرمجة الكائنية , فيه يمكن توليد فئات من فئات أخري تسمي "الفئات المشتقة derived classes , وتجوز الفئة المشتقة كافة خصائص الفئة الأصلية , مضافة إليها خصائص أخري (أنظر الشكل)

Object-Oriented Programming in C   _Page_0398_Image_0001

شكل التوارث

ومن أهم مزايا التوارث إمكانية إعادة الاستخدام بمعني أنه عندما تصاغ فئة معينة وتجتاز الاختبار , فإنه يعاد استخدام نفس الصياغة كما هي في مواقف جديدة مما يوفر من الذاكرة ومن عبء إعادة البرمجة علي حد سواء .

ومن جهة , فإن إعادة الاستخدام تعني سهولة توزيع الفئات المكتبية , فالمبرمج بإمكانه استخدام فئة صاغها مبرمج أخر , ويشقق منها ما يشاء من فئات .

وسوف نعرض لهذه الإمكانيات بالتفصيل بعد أن نعرض لخصائص معينة للتوارث في صورة عملية .

الفئات الأصلية و المشتقة

لنعد إلي الفصل التاسع وإلي البرنامج countpp3.cpp الذي استخدمنا فيه الفئة counter كمتغير عام الاستخدام كعداد , يستهل بالصفر أو بأية قيمة أخري , ويتزايد مع الحدث المراد تتبعه , وتقرأ قيمته بدالة المؤثر get_count() .

لنفرض أننا نريد أن نضيف للعداد إمكانية التناقص أيضا . لو حاولنا العبث بصياغة البرنامج السابق بعد كل بعد ما بذلناه في وضعه واختياره طبعا هذه مبالغة بعض الشئ ولكنها حقيقية في الحياة العملية ) , لكان معني ذلك إضاعة للجهد . ومن جهة أخري فقد لا يكون الكود المصدري تحت أيدينا للتغيير فيه كأن يكون فئة مكتبية ( سوف نعرض لهذا الموضوع بالتفصيل في الفصل الخامس عشر ) .

وسوف نتلاقي هذه الاحتمالات بإنشاء فئة مشتقة من الفئة counter , نضيف إليها الخاصية الجديدة .

counten.cpp


 


// counten.cpp


// inheritance with Counter class


#include <iostream.h>


#include <conio.h>


 


class Counter                   // base class


   {


   protected:                            // NOTE: not private


      unsigned int count;                // count


   public:


      Counter()        { count = 0; }    // constructor, no args


      Counter(int c)   { count = c; }    // constructor, one arg


      int get_count()  { return count; } // return count


      Counter operator ++ ()         // incr count (prefix)


    { return Counter(++count); }


   };


 


class CountDn : public Counter         // derived class


   {


   public:


      Counter operator -- ()         // decr count (prefix)


    { return Counter(--count); }


   };


 


void main()


   {


   CountDn c1;                          // c1 of class CountDn


 


   cout << "\nc1=" << c1.get_count();   // display c1


 


   ++c1; ++c1; ++c1;                // increment c1


   cout << "\nc1=" << c1.get_count();   // display it


 


   --c1; --c1;                    // decrement c1


   cout << "\nc1=" << c1.get_count();   // display it


   getche();


   }




في البرنامج أعدنا صياغة الفئة counter (عدا تغيير طفيف سنبحثه فيما بعد ) وقد استخدمنا مؤثر التزايد علي الصورة المبينة بالبرنامج postfix.cpp .



توصيف الدالة المشتقة



يتبع صياغة الفئة الأصلية صياغة الفئة المشتقة المسماة countDn , تضم دالة منتمية جديدة هي مؤثر التناقص . هذه الفئة تتمتع بخدمات الدوال الأخري في الفئة الأصلية , مؤثر التزايد ودالة get_count().



والسطر الأولي في توصيف الفئة المشتقة هو :



Class countDn : public counter



يقول هذا السطر إن الفئة countDn هي فئة مشتقة من الفئة الأصلية count , (لاحظ استخدام النقطتين مرة واحدة فقط (:) وليس مرتين ) , وسوف نعرف تأثير الكلمة الحاكمة public فيما بعد . ويبين الشكل العلاقة بين الفئتين , ونلاحظ فيه أن اتجاه السهم قد وضع ليؤكد حقيقة أن الفئة المشتقة لها الحق في الوصول لكافة عناصر الفئة الأصلية , وأن العكس غير صحيح . ويطلق علي هذا الشكل أحيانا شجرة التوارث inheritance tree ] كما في الأسر العريقة [ .



c   1



الإتاحة



يقصد بالإتاحة accessibility استخدام الفئة لدالة من الفئة الأصلية . فلنر كيف ينفذ المترجم هذه الإتاحة .



استعارة دوال من الدالة الأصلية



في الدالة الأصلية أنشأنا كائنا منتميا للدالة المشتقة , وهو c1 وتم إنشاؤه واستهلاله رغم أننا لم نصغ له بادئة في فئته , ويدلنا هذا أنه حين لا تصاغ بادئة في الفئة المشتقة فإن المترجم يبحث عن البادئة المناسبة في الفئة الأصلية , هذه المرونة في عمل المترجم بالبحث عن المتاح حين لا يجد المطلوب , هي بصورة عامة ما نطلبه من البرمجة الكائنية , ولكن أحيانا ما تكون مثيرة للأعصاب .



وبنفس المنطق أمكن تطبيق دالة الإظهار get_count() ومؤثر التزايد علي الكائن المذكور .



موصف الإتاحة protected



قلنا أننا سوف نستخدم صياغة الفئة الأصلية بلا تغيير , حسنا بلا تغيير تقريبا , فلعلك لاحظت أن توصيف الإتاحة للبيانات قد تغير من privat إلي proctected وهو توصيف جديد فعهدنا بمواصفات الإتاحة أنها علي نوعين خاص للعناصر التي لا يتصل بها سوي دوال فئتها وعام وهي العناصر المتاحة لكافة دوال البرنامج .



الموصف الثالث "محمي protected يجعل البيان متاحا لدوال فئته والفئات المشتقة منها وعلي ذلك لو أنك صغت فئة تشعر باحتمال أن يشتق منها فئات أخري فعليك توصيف بياناتها علي أنها محمية وليست خاصة .



علي أن إتاحة بيان لدوال من خارج فئته أمر محفوف بالمخاطر فهو يعرضها للتغيير الخاطئ وعليك تقدير ذلك .



ومن البديهي أن نقول إن كائنات الفئة الأصلية لا علاقة لها بدوال الفئة المشتقة فلن يمكن أن تستفيد من خاصية التناقص في مثالنا هذا .





بادئات الفئة المشتقة



لو أ{دنا ان تستهل كائنا من الفئة المشتقة وليكن C2 بقيمة غير صفرية فهل يمكننا استخدام البادئة وحيدة المعامل الموجودة في الفئة الأصلية ؟ الإجابة بالنفي فالمترجم لن يقبل أمرا كهذا :



CountDn c2(100);



سبق أن قلنا أن المترجم يستخدم بادئة الفئة الأصلية عديمة المعامل حين لا توجد في الفئة المشتقة ولكنه يستمر لأبعد من ذلك . علينا إذن في هذه الحالة صياغة بادئات الفئة المشتقة , وهو ما نفعله في البرنامج التالي :






counten2.cpp


 


// counten2.cpp


// constructors in derived class


// UCS Laboratories


#include <iostream.h>


#include <conio.h>


 


class Counter


   {


   protected:                            // NOTE: not private


      unsigned int count;                // count


   public:


      Counter()        { count = 0; }    // constructor, no args


      Counter(int c)   { count = c; }    // constructor, one arg


      int get_count()  { return count; } // return count


      Counter operator ++ ()         // incr count (prefix)


    { return Counter(++count); }


   };


 


class CountDn : public Counter


   {


   public:


      CountDn() : Counter()              // constructor, no args


    { }


      CountDn(int c) : Counter(c)        // constructor, 1 arg


    { }


      CountDn operator -- ()         // decr count (prefix)


    { return CountDn(--count); }


   };


 


void main()


   {


   CountDn c1;                         // class CountDn


   CountDn c2(100);


 


   cout << "\nc1=" << c1.get_count();  // display


   cout << "\nc2=" << c2.get_count();  // display


 


   ++c1; ++c1; ++c1;               // increment c1


   cout << "\nc1=" << c1.get_count();  // display it


 


   --c2; --c2;                   // decrement c2


   cout << "\nc2=" << c2.get_count();  // display it


 


   CountDn c3 = --c2;               // create c3 from c2


   cout << "\nc3=" << c3.get_count();  // display c3


   getche();


   }




يستخدم البرنامج بادئتين , عديمة المعامل منها علي الصورة :



CountDn() : Counter()



       {}



ومن هذه الصيغة تعرف أن استدعاء بادئة الدالة المشتقة يستتبع استدعاء بادئة الفئة الأصلية . ولهذا السبب لم تتطلب وضع عبارة لها اكتفاء بعبارة البادئة الأصلية . وبنفس المنطق فإن البادئة وحيدة المعامل في الدالة المشتقة لم تحتج لعبارات لها حيث إن استدعاءها بالأمر :



C2(200);



يستتبع استدعاء البادئة في الفئة الأصلية لتنفيذه



ويمكنك أن تعطي دوال الفئة المشتقة نفس أسماء الفئة الأصلية , إذا أردت مثلا الاحتفاظ بنفس أسلوب العمل . وفي البرنامج التالي سوف نعدل برنامج stackaray.cpp بحيث ننشئ فئة مشتقة , تقوم كائناتها بالتحذير عند محاولة وضع بيانات فيها بعد امتلائها أو سحب بيانات منها وهي فارغة .






staken.cpp


 


// staken.cpp


// overloading functions in base and derived classes


// UCS Laboratories


#include <iostream.h>


#include <process.h>            // for exit()


#include <conio.h>


 


const int MAX = 3;              // maximum size of stack


 


class Stack


   {


   protected:                   // NOTE: can't be private


      int st[MAX];              // stack: array of integers


      int top;                  // index to top of stack


   public:


      Stack()                   // constructor


     { top = -1; }


      void push(int var)        // put number on stack


     { st[++top] = var; }


      int pop()                 // take number off stack


     { return st[top--]; }


   };


 


class Stack2 : public Stack


   {


   public:


      void push(int var)           // put number on stack


     {


     if(top >= MAX-1)       // error if stack full


        { cout << "\nError: stack is full"; exit(1); }


     Stack::push(var);       // call push() in Stack class


     }


      int pop()                    // take number off stack


     {


     if(top < 0)           // error if stack empty


        { cout << "\nError: stack is empty"; exit(1); }


     return Stack::pop();       // call pop() in Stack class


     }


   };


 


void main()


   {


   Stack2 s1;


 


   s1.push(11);                   // push some values onto stack


   s1.push(22);


   s1.push(33);


 


   cout << endl << s1.pop();      // pop some values from stack


   cout << endl << s1.pop();


   cout << endl << s1.pop();


   cout << endl << s1.pop();      // woops, pops one too many...


   getche();


   }




وتعتمد الدالة المنفذة من أي من الدالتين المسميتين بنفس الاسم علي فئة الكائن المنتمية لها .

تعليقات

المشاركات الشائعة من هذه المدونة

المؤثرات الحسابية في C++

الرسم Graphics

دوال النمط الرسومي في ++C