القائمة الرئيسية

الصفحات

مفهوم ال Overriding وOverloading في السي بلاس بلاس

C++ Overriding

مفهوم إعادة التعريف في C++

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

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

الهدف الحقيقي من إعادة التعريف أو التعريف من جديد هو إتاحة الفرصة للكلاس الإبن ليعرّف الدوال حسب حاجته.
في دروس متقدمة سنرث من كلاسات جاهزة في C++, و نفعل Override للدوال الموجودة فيها لكي تناسب التطبيقات التي سنقوم ببنائها.


شروط إعادة تعريف الدوال الموروثة

  • يجب أن يكون الـ Modifier المستخدم للدالة الجديدة هو نفسه المستخدم للدالة القديمة, و يجب أن يكون نوعه public أو protected.

  • عدد و نوع باراميترات الدالة الجديدة يجب أن يطابق عدد و نوع باراميترات الدالة القديمة.

  • نوع الإرجاع للدالة الجديدة يجب أن يكون نفس نوع الإرجاع للدالة القديمة.

  • الدالة المعرفة كـ private لا يمكن أن نفعل لها Override, لأن كلمة private تمنع إمكانية الوصول المباشر للدالة من الكلاس الإبن.

  • الدالة المعرفة كـ final لا يمكن أن نفعل لها Override, لأن كلمة final تمنع تغير محتوى الدالة بعد تعريفها.

  • الدالة المعرفة كـ static لا يمكن أن نفعل لها Override و لكن يمكن تعريفها من جديد في أي مكان, لأن كلمة static تجعل الدالة مشتركة بين جميع الكلاسات.

  • لا يمكن أن نفعل Override للكونستركتور.

مثال يوضح فائدة إعادة تعريف الدوال في C++

في المثال التالي قمنا بتعريف كلاس إسمه Country يعتبر الكلاس الأساسي لأي بلد.
في هذا الكلاس قمنا بتعريف دالة إسمها language() فكرتها طباعة لغة البلد و جعلناها تطبع اللغة الإنجليزية بشكل إفتراضي كلغة أي بلد.

بعدها قمنا بتعريف ثلاث كلاسات تمثل ثلاث بلدان مختلفة (أستراليا, لبنان, إسبانيا) لذلك جعلناها ترث من الكلاس Country الذي يعتبر أساس أي بلد.
إذاً الكلاسات الثلاثة سترث الدالة language().

هنا الفكرة التي نريد إيصالها هي أن أي كلاس يرث من Country قد يضطر إلى تعريف الدالة language() من جديد حتى تناسبه.
فمثلاً لبنان لغته هي العربية, و إسبانيا لغتها هي الإسبانية.

بعد إنشاء هذه الكلاسات, قمنا بإنشاء كائنات منها و استدعاء الدالة language() من كل واحد فيهم لطباعة اللغة التي يتكلمها الناس فبه.

مثال

main.cpp
		#include <iostream>

		using namespace std;

		// الذي يعتبر الكلاس الأساسي لأي دولة في العالم, إذاً يجب أن يرثه كل كلاس يمثل دولة Country هنا قمنا بتعريف الكلاس
		class Country {

		// هنا قمنا بتعريف دالة تقوم بطباعة لغة البلد و جعلناها تطبع اللغة الإنجليزية كاللغة الإفتراضية لأي بلد
		public:
        void language()
        {
		cout << "English \n";
        }

		};


		// Country و يرث من الكلاس Australia هنا قمنا بتعريف كلاس يمثل دولة أستراليا و إسمه
		class Australia : public Country {

		// من جديد لأن اللغة الإنجليزية هي لغة أستراليا أصلاً language() هنا لا داعي لتعريف الدالة

		};


		// Country و يرث من الكلاس Lebanon هنا قمنا بتعريف كلاس يمثل دولة لبنان و إسمه
		class Lebanon : public Country {

		// من جديد لأن اللغة الإنجليزية ليست لغة لبنان language() هنا يجب تعريف الدالة
		public:
        void language()
        {
		cout << "Arabic \n";
        }

		};


		// Country و يرث من الكلاس Spain هنا قمنا بتعريف كلاس يمثل دولة إسبانيا و إسمه
		class Spain : public Country {

		// من جديد لأن اللغة الإنجليزية ليست لغة إسبانيا language() هنا يجب تعريف الدالة
		public:
        void language()
        {
		cout << "Spanish \n";
        }

		};


		// main() هنا قمنا بتعريف الدالة
		int main()
		{
		// هنا قمنا بإنشاء كائنات من البلدان الثلاثة
		Australia au;
		Lebanon lb;
		Spain sp;

		// لعرض لغة كل بلد language() هنا قمنا باستدعاء الدالة
		au.language();
		lb.language();
		sp.language();

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

		English
		Arabic
		Spanish 
	  


أنت الآن فهمت لما قد تحتاج أن تفعل Override.
سنقوم الآن بشرح طريقة عمل المترجم عندما فعلنا Override للدالة language().

إذا عدت للكلاس Lebanon, ستجد أننا فعلنا Override للدالة language().
إذاً هنا أصبح الكلاس Lebanon فعلياً يملك دالتين إٍسمهما language(), الأولى هي التي ورثها من الكلاس Country و الثانية هي التي قمنا بتعريفها فيه.


بما أن الكلاس Lebanon يملك دالتين لهما نفس الإسم, النوع و عدد البارامتيرات, كيف عرف المترجم أي دالة يختار؟

عندما قمنا بتعريف الدالة language() من جديد في الكلاس Lebanon (أي الكلاس الإبن) قام المترجم بإخفاء الدالة الأصلية التي ورثها من الكلاس Country (أي الكلاس الأب) و أظهر الدالة الجديدة فقط.


ملاحظة

داخل الكلاس Lebanon, لا يزال بإمكانك إستخدام الدالة language() الموجودة في الكلاس Country و التي قام المترجم بإخفائها عندما قمنا بتعريفها من جديد و هذا ما سنتعرف عليه في المثال التالي.

مفاهيم مهمة حول إعادة تعريف الدوال في C++


المفهوم الأول

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

في المثال التالي قمنا بتعريف كلاس إسمه Base يحتوي على دالة إسمها printMsg.
بعدها قمنا بتعريف كلاس إسمه Derived يرث من الكلاس Base و قمنا فيه بإعادة تعريف الدالة printMsg و دالة أخرى إسمها printBoth.

في الدالة printBoth() قمنا باستدعاء الدالة printMsg() التي ورثها الكلاس Derived و الدالة printMsg() التي تم إعادة تعريفها فيه.

في الأخير قمنا بإنشاء كائن من الكلاس Derived و من ثم استدعاء كل الدوال التي يملكها.

مثال

main.cpp
		#include <iostream>

		using namespace std;

		// print يحتوي على دالة واحدة إسمها Base هنا قمنا بتعريف كلاس إسمه
		class Base
		{
		public:
		void print() {
		cout << "Base::print() \n";
		}
		};

		// printBoth أيضاً و دالة أخرى إسمها print و فيه قمنا بتعريف دالة إسمها Base يرث من الكلاس Derived هنا قمنا بتعريف الكلاس
		class Derived : public Base
		{
		public:
		void print()
		{
		cout << "Derived::print() \n";
		}

		void printBoth()
		{                    // printBoth() عند استدعاء الدالة
		Base::print();   // Base الموجودة في الكلاس print() سيتم استدعاء الدالة
		print();         // الموجودة في نفس الكلاس print() ثم استدعاء الدالة
		}
		};

		// main() هنا قمنا بتعريف الدالة
		int main()
		{
		// d إسمه Derived هنا قمنا بإنشاء كائن من الكلاس
		Derived d;

		// Derived و التي تم تعريفها في الكلاس d من الكائن print() هنا قمنا باستدعاء الدالة
		d.print();

		// Base و التي تم تعريفها في الكلاس d من الكائن print() هنا قمنا باستدعاء الدالة
		d.Base::print();

		// و التي ستستدعي كلتا الدالتين السابقتين Derived التي تم تعريفها في الكلاس d من الكائن print() هنا قمنا باستدعاء الدالة
		d.printBoth();

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

Derived::print()     -->   نتيجة إستدعاء الدالة في السطر 37
Base::print()        -->   نتيجة إستدعاء الدالة في السطر 40
Base::print()        -->   نتيجة إستدعاء الدالة في السطر 43
Derived::print()     -->   نتيجة إستدعاء الدالة في السطر 43


المفهوم الثاني

إذا كنت تريد كتابة الكود بشكل يكون سهل التعامل معه و مراجعته لاحقاً فمن الأفضل إضافة الكلمة virtual لأي دالة تريد من الكلاس الذي يرثها أن يقوم بإعادة تعريفها حتى تتناسب معه, و في الكلاس الذي يرثها من الأفضل أيضاً أن تضيف الكلمة override للدالة التي تعيد تعريفها من جديد في الكلاس الإبن.

إذا كنت تريد كتابة الكود بشكل يكون سهل التعامل معه و مراجعته لاحقاً فمن الأفضل إضافة الكلمة virtual لأي دالة تريد من الكلاس الذي يرثها أن يقوم بإعادة تعريفها حتى تتناسب معه, و في الكلاس الذي يرثها من الأفضل أيضاً أن تضيف الكلمة override للدالة التي تعيد تعريفها من جديد في الكلاس الإبن.

ملاحظة: تم إضافة هاتين الكلمتين إبتداءاً من الإصدار C++ 11 و الإصدارات اللاحقة.


في المثال التالي قمنا بتعريف كلاس إسمه Base يحتوي على دالة إسمها func و نوعها virtual void.
بعدها قمنا بتعريف كلاس إسمه Derived يرث من الكلاس Base و قمنا فيه بإعادة تعريف الدالة func مع إضافة الكلمة override عند تعريفها.

في الأخير قمنا بإنشاء كائن من الكلاس Derived و من ثم استدعاء الدالة func() الموجودة فيه.

مثال

main.cpp
		#include <iostream>

		using namespace std;

		// func يحتوي على دالة واحدة إسمها Base هنا قمنا بتعريف كلاس إسمه
		class Base
		{
		public:
		virtual void func()
		{
		cout << "Base class default behavior \n";
		}
		};

		// أيضاً func و فيه قمنا بتعريف دالة إسمها Base يرث من الكلاس Derived هنا قمنا بتعريف الكلاس
		class Derived : public Base
		{
		public:
		void func() override
		{
		cout << "Derived class overridden behaviour \n";
		}
		};

		// main() هنا قمنا بتعريف الدالة
		int main()
		{
		// d إسمه Derived هنا قمنا بإنشاء كائن من الكلاس
		Derived d;

		// Derived و التي تم تعريفها في الكلاس d من الكائن func() هنا قمنا باستدعاء الدالة
		d.func();

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

		Derived class overridden behaviour
	  

معلومة تقنية

في المثال السابق إذا قمت بإزالة الكلمتين virtual و override و لن يسبب ذلك أي مشكلة أو إختلاف لأنه كما سبق و قلنا أننا نضعهما فقط بهدف كتابة الكود بأسلوب متعارف عليه.

بالنسبة للمترجم, عند وضع الكلمة virtual فإنك مخير على وضع الكلمة override عند إعادة تعريف الدالة. و لكنه لا يسمح لك بوضع الكلمة override إذا لم تكن الدالة في الأصل نوعها virtual.


بالعودة للدرس ستتعلم المزيد حول فائدة الكلمة virtual التي يمكن استخدامها لتحقيق مبدئ التجريد ( Abstraction ).



المفهوم الثالث

إذا كنت تريد تعريف شكل الدالة ( Prototype ) فقط في الكلاس و إجبار أي كلاس يرثها على أن يقوم بتعريفها فيمكنك إستخدام الكلمة virtual لتحقيق ذلك.
أسلوب كتابة الكود المتبع في هذا المثال يقال له التجريد ( Abstraction ).

مفهوم التجريد في C++

التجريد ( Abstraction ) هو أسلوب مهم جداً يستخدم لتسهيل كتابة الأوامر على المبرمجين, فهو يجعلك قادراً على تنفيذ ما تريد دون الحاجة إلى معرفة كافة التفاصيل التي تم فيها تنفيذ ذلك.

كمثال بسيط, إذا كنت تنوي بناء برنامج يتيح لمستخدميه إرسال إقتراحات حول التطبيق من خلال البريد الإلكتروني, في هذه الحالة لن يكون عليك القلق بتاتاً حول طريقة تعامل أوامر C++ مع البروتوكولات التي تعمل عندما يتم إرسال رسائل إلكترونية من خلال هذا التطبيق لأنك لست مسؤولاً عنها. فعلياً هذه وظيفة الشركة التي تعطيك كود جاهز يمكن استخدامه لإرسال بريد إلكتروني و سيكون عليك فقط تحديد المعلومات الأساسية مثل البريد الإلكتروني للمرسل, البريد الإلكتروني للمرسل إليه, نص الرسالة إلخ..

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

تطبيق مبدأ التجريد في C++

إذا كنت تريد تعريف شكل الدالة ( Prototype ) فقط في الكلاس و إجبار أي كلاس يرثها على أن يقوم بتعريفها فيمكنك اتباع أسلوب التجريد لتحقيق ذلك.

كل ما عليك فعله لإعلام المترجم بأنك تريد إجبار الكلاس الإبن على تعريف الدالة التي يرثها من الكلاس الأب بنفسه هو تعريف الدالة في الكلاس الأب بالأساس كدالة مجرّدة و هنا سيكون عليك جعل نوعها virtual و جعلها تساوي 0 فقط.


مصطلحات تقنية

  • الدالة المجرّدة هي أي دالة تطبق أسلوب التجريد و يقال لها Abstract Function أو Pure Virtual Function.

  • الكلاس المجرّد هو أي كلاس يحتوي على دالة مجرّدة أو أكثر و يقال له Abstract Class.



شروط التجريد

عند تطبيق أسلوب التجريد فإنه عليك الإنتباه للنقاط التالية:

  • الكلاس الذي يحتوي على دوال مجردة لا يمكن إنشاء كائنات منه لأنه في حال استدعاء الدالة لم يتم تعريفها منه فإن هذا سيسبب مشكلة.

  • الكلاس الذي يرث دوال مجردة و لا يقوم بإعادة كتابة محتواها أيضاً لا يمكن إنشاء كائنات منه لذات السبب.

  • الدوال الثابتة التي نوعها static لا يمكن تعريفها كدوال مجردة لأنها أصلاً تستخدم من الكلاس الأساسي.

  • أسلوب التجريد يتطلب استخدام الكلمتين virtual و override اللتين تم إضافتهما ابتداءاً من الإصدار C++ 11 لهذا تأكد أنك تستخدم هذا الإصدار أو الإصدارات الأحدث حتى تستطيع تطبيقه.



في المثال التالي قمنا بتعريف كلاس إسمه Base يحتوي على دالة مجردة إسمها func.
بعدها قمنا بتعريف كلاس إسمه Derived يرث من الكلاس Base و قمنا فيه بإعادة تعريف الدالة func.

في الأخير قمنا بإنشاء كائن من الكلاس Derived و من ثم استدعاء الدالة func() الموجودة فيه.

المثال الأول

main.cpp
        #include <iostream>

        using namespace std;

        // func يحتوي على دالة مجردة إسمها Base هنا قمنا بتعريف كلاس إسمه
        class Base
        {
        public:
        virtual void func() = 0;
        };

        // func و فيه قمنا بتعريف الدالة المجردة Base يرث من الكلاس Derived هنا قمنا بتعريف محتوى الكلاس
        class Derived : public Base
        {
        public:
        void func() override
        {
        cout << "Derived class overridden behaviour \n";
        }
        };

        // main() هنا قمنا بتعريف الدالة
        int main()
        {
        // d إسمه Derived هنا قمنا بإنشاء كائن من الكلاس
        Derived d;

        // Derived و التي تم تعريفها في الكلاس d من الكائن func() هنا قمنا باستدعاء الدالة
        d.func();

        return 0;
        }
      

سنحصل على النتيجة التالية عند التشغيل.

        Derived class overridden behaviour
      


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

المثال الثاني

main.cpp
        #include <iostream>

        using namespace std;

        // func يحتوي على دالة مجردة إسمها Base هنا قمنا بتعريف كلاس إسمه
        class Base
        {
        public:
        virtual void func() = 0;
        };

        // func و فيه قمنا بتعريف الدالة المجردة Base يرث من الكلاس Derived هنا قمنا بتعريف محتوى الكلاس
        class Derived : public Base
        {
        public:
        void func() override
        {
        cout << "Derived class overridden behaviour \n";
        }
        };

        // main() هنا قمنا بتعريف الدالة
        int main()
        {
        // b إسمه Base هنا قمنا بإنشاء كائن من الكلاس
        Base b;

        // Base و التي تم تعريفها في الكلاس b من الكائن func() هنا قمنا باستدعاء الدالة
        b.func();

        return 0;
        }
      

سنحصل على النتيجة التالية عند التشغيل.

error: cannot declare variable 'b' to be of abstract type 'Base'
    because the following virtual functions are pure within 'Base':
        'virtual void Base::func()'

لاحظ أن الخطأ فعلياً حدث بسبب السطر 26 حيث أن المترجم قال بأنه لا يمكن إنشاء كائن من الكلاس Base لأنه يحتوي على دالة مجردة ( Pure Virtual Function ) ثم كتب لنا إسم الدالة المجردة أيضاً.



المفهوم الرابع

إذا كنت تريد منع الكلاس الإبن من إعادة تعريف الدالة التي ورثها من الكلاس فيمكنك جعل نوع الدالة virtual final حتى أنه يمكنك جعل نوع الكلاس بأكمله final لا يمكن إعادة تعريف أي دالة موجودة فيه بهدف أن يتم استخدام الدوال الموجودة فيه كما هي.

الكلمة final في C++

إذا كنت تريد منع الكلاس الإبن من إعادة تعريف الدالة التي ورثها من الكلاس فيمكنك جعل نوع الدالة virtual final حتى أنه يمكنك جعل نوع الكلاس بأكمله final لا يمكن إعادة تعريف أي دالة موجودة فيه بهدف أن يتم استخدام الدوال الموجودة فيه كما هي.

ملاحظة: تم إضافة هذه الكلمة إبتداءاً من الإصدار C++ 11 و الإصدارات اللاحقة.


مصطلحات تقنية

  • الدالة التي لا يمكن إعادة تعريفها يقال لها في العادة دالة ثابتة.

  • الكلاس الذي لا يمكن إعادة تعريف أي شيء موجود فيه يقال له كلاس ثابت.



الكلاس الثابت لا يسمح لك بالوراثة منه لأن الوراثة بالأساس فكرتها أنك ترث الشيء بهدف تطويره أو الزيادة عليه.
بمعنى آخر يمكنك إنشاء كائنات منه فقط و استخدام ما هو موجود فيه.

الكلمة final يمكن استخدامها كإسم لمتغير, دالة, مصفوفة, كائن إلخ.. لأنها لا تعتبر من الكلمات المحجوزة ( Keywords ) في اللغة.

أمثلة شاملة على الكلمة final في C++


في المثال التالي قمنا بتعريف كلاس إسمه Base يحتوي على دالة ثابتة إسمها func().
بعدها قمنا بتعريف كلاس إسمه Derived يرث من الكلاس Base و حاولنا فيه إعادة تعريف الدالة func() لتنبيهك من الخطأ الذي قد يظهر في حال حاولت إعادة تعريف دالة ثابتة في الكلاس الذي يرثها.

المثال الأول

main.cpp
		#include <iostream>

		using namespace std;

		// func يحتوي على دالة ثابتة إسمها Base هنا قمنا بتعريف كلاس إسمه
		class Base
		{
		public:
		virtual void func() final
		{
		cout << "Base class default behaviour \n";
		}
		};

		// func و فيه قمنا بإعادة تعريف الدالة الثابتة Base يرث من الكلاس Derived هنا قمنا بتعريف محتوى الكلاس
		class Derived : public Base
		{
		public:
		void func() override     // هذا السطر الذي سيسبب المشكلة عند التشغيل
		{
		cout << "Derived class overridden behaviour \n";
		}
		};

		// main() هنا قمنا بتعريف الدالة
		int main()
		{
		return 0;
		}
	  

سيظهر الخطأ التالي عند التشغيل.

error: virtual function 'virtual void Derived::func()' overriding final function
    note: overridden function is 'virtual void Base::func()'
        In member function 'virtual void Derived::func()'

في المثال السابق حدث الخطأ فعلياً بسبب السطر 19 حيث أن المترجم قال بأنه لا يمكن إعادة تعريف الدالة func() في الكلاس Derived الذي ورثها لأنها في الكلاس الأب Base معرّفة كدالة ثابتة.



في المثال التالي قمنا بتعريف كلاس ثابت إسمه Base يحتوي على دالة إسمها func().
بعدها قمنا بتعريف كلاس إسمه Derived يرث من الكلاس Base فقط لتنبيهك من الخطأ الذي قد يظهر في حال حاولت الوراثة من كلاس ثابت.

المثال الثاني

main.cpp
		#include <iostream>

		using namespace std;

		// func يحتوي على دالة إسمها Base هنا قمنا بتعريف كلاس ثابت إسمه
		class Base final
		{
		public:
		void func()
		{
		cout << "Base class default behaviour \n";
		}
		};

		// و طبعاً هذا الأمر سيسبب مشكلة عند التشغيل Base يرث من الكلاس Derived هنا قمنا بتعريف محتوى الكلاس
		class Derived : public Base
		{

		};

		// main() هنا قمنا بتعريف الدالة
		int main()
		{
		return 0;
		}
	  

سيظهر الخطأ التالي عند التشغيل.

error: cannot derive from 'final' base 'Base' in derived type 'Derived'

في المثال السابق حدث الخطأ فعلياً بسبب السطر 16 حيث أن المترجم قال بأنه لا يمكن الوراثة من الكلاس Base لأنه كلاس ثابت.



في المثال التالي قمنا بتعريف كلاس ثابت إسمه Base يحتوي على دالة إسمها func().
بعدها قمنا بإنشاء كائن منه و استدعاء الدالة الموجودة فيه.

المثال الثالث

main.cpp
		#include <iostream>

		using namespace std;

		// func يحتوي على دالة إسمها Base هنا قمنا بتعريف كلاس ثابت إسمه
		class Base final
		{
		public:
		void func()
		{
		cout << "Base class default behaviour \n";
		}
		};

		// main() هنا قمنا بتعريف الدالة
		int main()
		{
		// b إسمه Base هنا قمنا بإنشاء كائن من الكلاس
		Base b;

		// b من الكائن func() هنا قمنا باستدعاء الدالة
		b.func();

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

		Base class default behaviour
	  

C++ Overloading

مفهوم الـ Overloading في C++

Overloading تعني تعريف أكثر من عامل, دالة أو كونستركتور لهم نفس الإسم و لكنهم يختلفون في عدد أو نوع الباراميترات.

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

شروط الـ Overloading في C++

  • يطبق فقط على العوامل, الدوال و الكونستركتورات.

  • يجب أن يملكوا نفس الإسم.

  • يجب أن يختلفوا في نوع أو عدد الباراميترات.

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


العوامل التالية يمكن أن نفعل لها Overload.

+
-
*
/
%
^
&
|
~
!
,
=
<>
<=
>=
++
--
<<
>>
==
!=
&&
||
+=
-=
/=
%=
^=
&=
|=
*=
<<=
>>=
[]
()
->
->*
new
new []
delete
delete []

العوامل التالية لا يمكن أن نفعل لها Overload.

    .       ::       ?:       sizeof      

أمثلة شاملة على الـ Overloading في C++


المثال الأول

تعريف دوال لها نفس الإسم و تختلف في نوع الباراميترات.

في المثال التالي قمنا بتعريف دالتين إسمهما sum و نوعهما void و لكنهما مختلفتان عن بعضهما بأنواع الباراميترات.
الدالة الأولى مهمتها جمع أي عددين نوعهما int نمررهما لها عند استدعاءها و من ثم طباعة الناتج.
الدالة الثانية مهمتها جمع أي عددين نوعهما float نمررهما لها عند استدعاءها و من ثم طباعة الناتج.

مثال

main.cpp
	  #include <iostream>

	  using namespace std;

	  // فتقوم بطباعة ناتج جمعهما int عند استدعاءها نمرر لها قيمتين من النوع sum هنا قمنا بتعريف دالة إسمها
	  void sum(int a, int b)
	  {
	  cout << "First method is called ====> " << a << " + " << b << " = " << (a+b) << endl;
	  }

	  // فتقوم بطباعة ناتج جمعهما double عند استدعاءها نمرر لها قيمتين من النوع sum هنا قمنا بتعريف دالة إسمها
	  void sum(double a, double b)
	  {
	  cout << "Second method is called ===> " << a << " + " << b << " = " << (a+b) << endl;
	  }

	  // main() هنا قمنا بتعريف الدالة
	  int main()
	  {
	  sum(4, 6);            // int هنا سيتم إستدعاء الدالة التي تأخذ 2 باراميتر نوعهم
	  sum(2.3, 5.4);        // double هنا سيتم إستدعاء الدالة التي تأخذ 2 باراميتر نوعهم

	  return 0;
	  }
	

سنحصل على النتيجة التالية عند التشغيل.

	  First method is called ====> 4 + 6 = 10
	  Second method is called ===> 2.3 + 5.4 = 7.7
	

كما لاحظت هنا, في كل مرة قمنا فيها باستدعاء الدالة sum() وجدنا أن المترجم قام باستدعاء الدالة sum() المناسبة لنوع الباراميترات التي كنا نمرره لها.



المثال الثاني

تعريف دوال لها نفس الإسم و تختلف في عدد الباراميترات.

في المثال التالي قمنا بتعريف ثلاث دوال إسمهم sum و نوعهما void و لكنهم مختلفين عن بعضهم بعدد الباراميترات.
الدالة الأولى مهمتها جمع أي عددين نمررهما لها عند استدعاءها و من ثم طباعة الناتج.
الدالة الثانية مهمتها جمع أي ثلاث أعداد نمررها لها عند استدعاءها و من ثم طباعة الناتج.

مثال

main.cpp
	  #include <iostream>

	  using namespace std;

	  // عند استدعاءها نمرر لها عددين فتقوم بطباعة ناتج جمعهما sum هنا قمنا بتعريف دالة إسمها
	  void sum(float a, float b)
	  {
	  cout << a << " + " << b << " = " << (a + b) << endl;
	  }

	  // عند استدعاءها نمرر لها ثلاث أعداد فتقوم بطباعة ناتج جمعهم sum هنا قمنا بتعريف دالة إسمها
	  void sum(float a, float b, float c)
	  {
	  cout << a << " + " << b << " + " << c << " = " << (a + b + c) << endl;
	  }

	  // main() هنا قمنا بتعريف الدالة
	  int main()
	  {
	  sum(1, 3);        // التي تأخذ قيمتين sum() هنا سيتم إستدعاء الدالة
	  sum(1, 3, 7);     // التي تأخذ ثلاث قيم sum() هنا سيتم إستدعاء الدالة

	  return 0;
	  }
	

سنحصل على النتيجة التالية عند التشغيل.

	  1 + 3 = 4
	  1 + 3 + 7 = 11
	

كما لاحظت هنا, في كل مرة قمنا فيها باستدعاء الدالة sum() وجدنا أن المترجم قام باستدعاء الدالة sum() التي تحتوي على نفس عدد الباراميترات الذي كنا نمرره لها.



المثال الثالث

تعريف دوال تعتمد على دوال عندها نفس الإسم.

في المثال التالي قمنا بتعريف ثلاث دوال إسمهم maximum() و نوعهم double.
الدالة الأولى تأخذ 2 باراميتر عبارة عن أرقام, و هي تعطينا العدد الأكبر بينهما.
الدالة الثانية تأخذ 3 باراميترات عبارة عن أرقام, و هي تعطينا العدد الأكبر بينهم.
الدالة الثالثة تأخذ 4 باراميترات عبارة عن أرقام, و هي تعطينا العدد الأكبر بينهم.

مثال

main.cpp
	  #include <iostream>

	  using namespace std;

	  // هذه الدالة تعطيها رقمين فترجع لك العدد الأكبر بينهما
	  double maximum(double a, double b)
	  {
	  if(a>b)
	  return a;
	  else
	  return b;
	  }

	  // هذه الدالة تعطيها ثلاثة أرقام فترجع لك العدد الأكبر بينهم
	  // و هي تعتمد على الدالة السابقة لمقارنة أول عددين مع العدد الثالث
	  double maximum(double a, double b, double c)
	  {
	  if(maximum(a,b)>c)
	  return maximum(a,b);
	  else
	  return c;
	  }

	  // هذه الدالة تعطيها ثلاثة أرقام فترجع لك العدد الأكبر بينهم
	  // و هي تعتمد على الدالتين السابقتين لمقارنة أول ثلاث أعداد مع العدد الرابع
	  double maximum(double a, double b, double c, double d)
	  {
	  if(maximum(a,b,c)>d)
	  return maximum(a,b,c);
	  else
	  return d;
	  }

	  // main() هنا قمنا بتعريف الدالة
	  int main()
	  {

	  cout << "The maximum number is: " << maximum(5, 20) << endl;       // هنا سيتم إستدعاء الدالة التي تأخذ 2 باراميتر
	  cout << "The maximum number is: " << maximum(5, 20, 15) << endl;   // هنا سيتم إستدعاء الدالة التي تأخذ 3 باراميترات
	  cout << "The maximum number is: " << maximum(5, 20, 15, 30);       // هنا سيتم إستدعاء الدالة التي تأخذ 4 باراميترات

	  return 0;
	  }
	

سنحصل على النتيجة التالية عند التشغيل.

	  The maximum number is: 20
	  The maximum number is: 20
	  The maximum number is: 30
	

كما لاحظت هنا, في كل مرة قمنا فيها باستدعاء الدالة maximum() وجدنا أن المترجم قام باستدعاء الدالة maximum() التي تحتوي على نفس عدد الباراميترات الذي كنا نمرره لها, و داخلياً ربطنا الدوال ببعضها.



المثال الرابع

تعريف أكثر من كونستركتور في الكلاس.

في المثال التالي قمنا بتعريف كلاس إسمه Country يمثل المعلومات التي يمكن أن يتضمنها أي بلد, و هو يحتوي على أربعة كونستركتورات.
الكونستركتور الأول لا يأخذ أي باراميتر.
الكونستركتور الثاني يأخذ باراميتر واحد عبارة عن إسم البلد.
الكونستركتور الثالث يأخذ إثنين باراميتر عبارة عن إسم و مساحة البلد.
الكونستركتور الرابع تأخذ ثلاثة باراميترات عبارة عن إسم و مساحة و عدد سكان البلد.

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

مثال

main.cpp
	  #include <iostream>

	  using namespace std;

	  // الذي يمثل الأشياء التي يمتلكها أي بلد Country هنا قمنا بتعريف الكلاس
	  class Country
	  {
	  public:
	  // سيملك هذه الخصائص, أي كل بلد سيملك هذه الخصائص Country كل كائن من الكلاس
	  string name;
	  double area;
	  long population;

	  // الكونستركتور الأول لا يأخذ باراميترات
	  Country()
	  {
	  name = "";
	  area = 0;
	  population = 0;
	  }

	  // الكونستركتور الثاني يتيح لك تحديد إسم البلد مباشرةً عند إنشاء الكائن
	  Country(string n)
	  {
	  name = n;
	  area = 0;
	  population = 0;
	  }

	  // الكونستركتور الثالث يتيح لك تحديد إسم البلد و مساحته مباشرةً عند إنشاء الكائن
	  Country(string n, double a)
	  {
	  name = n;
	  area = a;
	  population = 0;
	  }

	  // الكونستركتور الرابع يتيح لك تحديد إسم البلد و مساحته و عدد السكان مباشرةً عند إنشاء الكائن
	  Country(string n, double a, long p)
	  {
	  name = n;
	  area = a;
	  population = p;
	  }

	  // هذه الدالة تعرض جميع المعلومات التي تم إدخالها في الكائن
	  void printInfo()
	  {
	  cout << "name: " << name << endl;
	  cout << "area: " << area << endl;
	  cout << "population: " << population << endl;
	  cout << "-------------------" << endl;
	  }
	  };


	  // main() هنا قمنا بتعريف الدالة
	  int main()
	  {
	  // هنا كل كائن يمثل بلد ,Country هنا قمنا بإنشاء 4 كائنات من الكلاس
	  Country c1;                               // c1 هنا سيتم إستدعاء الكونستركور الأول, أي لن يتم تمرير أي معلومة حول البلد الذي يمثله الكائن
	  Country c2("KSA");                        // c2 هنا سيتم إستدعاء الكونستركور الثاني, أي سيتم تحديد إسم البلد الذي يمثله الكائن
	  Country c3("Turkey", 780580);             // c3 هنا سيتم إستدعاء الكونستركور الثالث, أي سيتم تحديد إسم و مساحة البلد الذي يمثله الكائن
	  Country c4("Lebanon", 10452, 4467000);    // c4 هنا سيتم إستدعاء الكونستركور الربع, أي سيتم تحديد إسم و مساحة و عدد سكان البلد الذي يمثله الكائن

	  // لعرض خصائص جميع الكائنات printInfo() هنا قمنا باستدعاء الدالة
	  c1.printInfo();
	  c2.printInfo();
	  c3.printInfo();
	  c4.printInfo();

	  return 0;
	  }
	

سنحصل على النتيجة التالية عند التشغيل.

	  name:
	  area: 0
	  population: 0
	  -------------------
	  name: KSA
	  area: 0
	  population: 0
	  -------------------
	  name: Turkey
	  area: 780580
	  population: 0
	  -------------------
	  name: Lebanon
	  area: 10452
	  population: 4467000
	  -------------------
	

كما لاحظت هنا أن الكلاس Country يملك أربعة كونستركتورات إسمهم Country() و كل كونستركتور منهم يتيح لك تمرير قيم معينة مباشرةً عند إنشاء كائن من الكلاس Country.
في كل مرة قمنا فيها باستدعاء الكونستركتور Country() وجدنا أن المترجم قام باستدعاء الكونستركتور Country() الذي يحتوي على نفس عدد الباراميترات الذي كنا نمرره له.



المثال الخامس

كيف يمكنك أن تفعل Overload للعامل ++ لجعله يقوم بزيادة قيم الكائن الذي يستدعى منه.

في المثال التالي قمنا بتعريف كلاس إسمه Player يحتوي على ثلاث متغيرات تمثل المعلومات العامة التي يمكن أن يمتلكها اللاعب مثل رتبته grade في اللعبة و سرعته speed و كمية المال money التي جمعها, بالإضافة إلى دالة إسمها printInfo تعرض قيم هذه المتغيرات بشكل مرتب.

هنا افترضنا أن أي لاعب جديد ستكون رتبته تساوي 1 و سرعته تساوي 1 و لا يملك أي مال.
عند ترقية اللاعب سيتم زيادة رتبته و سرعته بمقدار 1 و سيتم إعطاؤه مبلغ 1000 أيضاً.

لنريك كيف يمكن أن تفعل Overload لعامل, قمنا بتخصيص العامل ++ لكي نستخدمه كلما أردنا ترقية اللاعب.

في الأخير بإنشاء كائن من الكلاس Player (أي كأننا قمنا بإنشاء لاعب) و من ثم تجربة ترقيته بواسطة العامل ++.

مثال

main.cpp
	  #include <iostream>

	  using namespace std;

	  // الذي يمثل اللاعب Player هنا قمنا بتعريف الكلاس
	  class Player {

	  public:
	  // هنا قمنا بتعريف خصائص الكلاس
	  int grade;
	  int speed;
	  int money;

	  // هنا قمنا بتعريف كونستركتور الكلاس بهدف إعطاء قيم أولية لخصائص الكائن الذي يتم إنشاؤه من الكلاس
	  Player()
	  {
	  grade = 1;
	  speed = 1;
	  money = 0;
	  }

	  // هنا قمنا بتعريف دالة تقوم بطباعة قيم خصائص الكائن بشكل مرتب عندما يتم استدعاؤه
	  void printInfo()
	  {
	  cout << "Grade = " << grade << endl;
	  cout << "Speed = " << speed << endl;
	  cout << "Money = " << money << endl;
	  cout << "----------------\n";
	  }

	  // هنا قمنا بتعريف ما سيحدث عند استدعاء الرمز ++ من أي كائن ننشئه من الكلاس
	  void operator++ (int)
	  {
	  // سيضاف عليها 1000 money سيضاف عليها 1 و قيمة speed سيضاف عليها 1 و قيمة grade قيمة
	  grade += 1;
	  speed += 1;
	  money += 1000;
	  }
	  };

	  // main() هنا قمنا بتعريف الدالة
	  int main()
	  {
	  // أي لاعب جديد ,Player هنا قمنا بإنشاء كائن من الكلاس
	  Player player;

	  // player لعرض القيم الإفتراضية التي يملكها الكائن printInfo() هنا قمنا باستدعاء الدالة
	  player.printInfo();

	  // حتى يتم ترقيته player هنا قمنا باستدعاء الرمز ++ من الكائن
	  player++;

	  // player من جديد لعرض القيم التي أصبحها يملكها الكائن printInfo() هنا قمنا باستدعاء الدالة
	  player.printInfo();

	  return 0;
	  }
	

سنحصل على النتيجة التالية عند التشغيل.

	  Grade = 1
	  Speed = 1
	  Money = 0
	  ----------------
	  Grade = 2
	  Speed = 2
	  Money = 1000
	  ----------------
	


المثال السادس

كيف يمكنك أن تفعل Overload للعامل + لجعله يقوم بإضافة قيم كائن على قيم كائن آخر.

في المثال التالي سنقوم بتطبيق إحدى قواعد الرياضيات التي نستخدمها لقياس المسافة بين نقطتين.
بالمبدأ كل نقطة سيكون لديها قيمة أفقية نرمز لها بالحرف x و قيمة عامودية نرمز لها بالحرف y.

الآن بما أننا سنحتاج لنقطتين و كل نقطة عندها x و y فمن المنطقي أن ننشئ كلاس إسمه Point و نضع فيه متغيرين إسمهمها x و y.
عندها كلما أردنا إنشاء نقطة نقوم بإنشاء كائن من الكلاس Point و نعطيه قيم.

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

مثال

main.cpp
	  #include <iostream>
	  #include <cmath>

	  using namespace std;

	  // الذي يمثل النقطة Point هنا قمنا بتعريف الكلاس
	  class Point {

	  public:
	  // هنا قمنا بتعريف خصائص الكلاس
	  int x;
	  int y;

	  // هنا قمنا بتعريف كونستركتور الكلاس بهدف إعطاء قيم أولية لخصائص الكائن الذي يتم إنشاؤه من الكلاس
	  Point(int x, int y)
	  {
	  this->x = x;
	  this->y = y;
	  }

	  // و الحصول على المسافة بينهما Point على كائن من الكلاس Point للرمز + حتى نستطيع إضافة كائن من الكلاس overload هنا فعلنا
	  double operator+ (Point point)
	  {
	  return sqrt(pow(point.x - this->x, 2) + pow(point.y - this->y, 2));
	  }
	  };

	  // main() هنا قمنا بتعريف الدالة
	  int main()
	  {
	  // أي نقطتين ,Point هنا قمنا بإنشاء كائنين من الكلاس
	  Point p1(1, 5);
	  Point p2(4, 8);

	  // distance و من ثم قمنا بتخزين الناتج في المتغير p2 و النقطة p1 هنا قمنا باستخدام العامل + للحصول على المسافة الموجودة بين النقطة
	  double distance = p1 + p2;

	  // distance هنا قمنا بعرض المسافة بين النقطتين و التي قمنا بتخزينها في المتغير
	  cout << "Distance between p1 and p2 = " << distance;

	  return 0;
	  }
	

سنحصل على النتيجة التالية عند التشغيل.

	  Distance between p1 and p2 = 4.24264