C++ تعدد الأشكال مع المثال

⚡ ملخص ذكي

تعدد الأشكال في C++ يسمح هذا الأسلوب لدالة عضو واحدة باتخاذ أشكال متعددة، حيث يختلف سلوكها باختلاف الكائن الذي يستدعيها. وهو يعتمد على الوراثة ويظهر بنوعين، يتم تحديدهما إما في وقت الترجمة أو وقت التشغيل.

  • 🔷 فريف: تعني تعدد الأشكال "أشكالًا متعددة"، مما يسمح لوظيفة عضو واحدة بالتصرف بشكل مختلف بناءً على الكائن الذي يتم استدعاؤها.
  • 🧭 نوعين: C++ يوفر تعدد الأشكال في وقت الترجمة (الثابت) وتعدد الأشكال في وقت التشغيل (الديناميكي).
  • 🧩 وقت التجميع: يؤدي التحميل الزائد للدوال والتحميل الزائد للمعاملات إلى تحديد الدالة الصحيحة أثناء عملية الترجمة.
  • 🔁 وقت التشغيل: تعمل وظائف التجاوز والوظائف الافتراضية على حل الاستدعاء أثناء تنفيذ البرنامج.
  • ⚙️ الوظائف الافتراضية: تتيح الكلمة المفتاحية الافتراضية في الفئة الأساسية الربط المتأخر من خلال مؤشر الفئة الأساسية.
  • 🤖 مساعدة الذكاء الاصطناعي: يقوم برنامج GitHub Copilot والمساعدون المماثلون بإنشاء وظائف زائدة التحميل، ووظائف تم تجاوزها، ووظائف افتراضية من تعليق قصير.

C++ تعدد الأشكال

ما هو تعدد الأشكال في C++?

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

على سبيل المثال، لنفترض أن لدينا الدالة makeSound(). عندما تستدعي القطة هذه الوظيفة، فإنها ستنتج صوت المواء. عندما تستدعي البقرة نفس الوظيفة، فإنها ستصدر صوت المواء.

تعدد الأشكال في C++ مثال على makeSound

على الرغم من أن لدينا وظيفة واحدة، إلا أنها تتصرف بشكل مختلف في ظل ظروف مختلفة. الدالة لها أشكال عديدة؛ وبالتالي، حققنا تعدد الأشكال.

مع وجود الفكرة الأساسية، C++ يصنف هذا السلوك إلى فئتين متميزتين.

أنواع تعدد الأشكال

C++ يدعم نوعين من تعدد الأشكال:

  • تعدد الأشكال في وقت الترجمة، و
  • تعدد الأشكال في وقت التشغيل.

أنواع تعدد الأشكال في C++

تقوم الفئة الأولى بحل كل استدعاء دالة قبل تشغيل البرنامج.

تجميع تعدد الأشكال الوقت

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

يتم تحقيق تعدد الأشكال في وقت التجميع من خلال التحميل الزائد للوظيفة والتحميل الزائد للمشغل.

وظيفة التحميل الزائد

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

مثال 1

#include <iostream> 
using namespace std;

void test(int i) {
	cout << " The int is " << i << endl;
}
void test(double  f) {
	cout << " The float is " << f << endl;
}
void test(char const *ch) {
	cout << " The char* is " << ch << endl;
}

int main() {
	test(5);
	test(5.5);
	test("five");
	return 0;
}

الإخراج:

C++ مثال على تحميل الدوال الزائد 1 - المخرجات

فيما يلي لقطة شاشة للكود:

C++ مثال على تحميل الدوال الزائد (الرمز 1)

Code التفسير:

  1. قم بتضمين ملف رأس iostream في الكود الخاص بنا. سنكون قادرين على استخدام وظائفها.
  2. قم بتضمين مساحة الاسم std في الكود الخاص بنا. سنكون قادرين على استخدام فئاته دون الاتصال به.
  3. قم بإنشاء دالة باسم test تأخذ معلمة عدد صحيح i. يمثل { بداية نص اختبار الوظيفة.
  4. البيان الذي سيتم تنفيذه إذا تم استدعاء/استدعاء اختبار الوظيفة أعلاه.
  5. نهاية جسم اختبار الوظيفة أعلاه.
  6. قم بإنشاء دالة باسم test تأخذ معلمة عائمة f. يمثل { بداية نص اختبار الوظيفة.
  7. البيان الذي سيتم تنفيذه إذا تم استدعاء/استدعاء اختبار الوظيفة أعلاه.
  8. نهاية نص اختبار الوظيفة أعلاه.
  9. قم بإنشاء دالة باسم test تأخذ معلمة حرف ch. يمثل { بداية نص اختبار الوظيفة.
  10. البيان الذي سيتم تنفيذه إذا تم استدعاء/استدعاء اختبار الوظيفة أعلاه.
  11. نهاية نص اختبار الوظيفة أعلاه.
  12. استدعاء الدالة الرئيسية (). يمثل { بداية نص الوظيفة.
  13. استدعاء اختبار الدالة وتمرير 5 إليه كقيمة الوسيطة. يؤدي هذا إلى استدعاء دالة الاختبار التي تقبل وسيطة عدد صحيح، أي دالة الاختبار الأولى.
  14. استدعاء اختبار الدالة وتمرير 5.5 إليه كقيمة الوسيطة. سيؤدي هذا إلى استدعاء وظيفة الاختبار التي تقبل وسيطة عائمة، أي وظيفة الاختبار الثانية.
  15. استدعاء اختبار الدالة وتمرير خمسة إليها كقيمة الوسيطة. سيؤدي هذا إلى استدعاء وظيفة الاختبار التي تقبل وسيطة الحرف، وهي وظيفة الاختبار الثالثة.
  16. يجب أن يقوم البرنامج بإرجاع قيمة إذا تم تشغيله بنجاح.
  17. نهاية نص الدالة main().

لدينا ثلاث وظائف بنفس الاسم ولكن أنواع مختلفة من الوسائط. لقد حققنا تعدد الأشكال.

Operaالحمولة الزائدة

In Operaالتحميل الزائد لـ Tor، نحدد معنى جديدًا لـ a C++ عاملكما أنه يُغيّر طريقة عمل المعامل. على سبيل المثال، يُمكننا تعريف المعامل + لدمج سلسلتين نصيتين. نعرفه كمعامل جمع القيم العددية. بعد تعريفه، عند وضعه بين عددين صحيحين، سيجمعهما. وعند وضعه بين سلسلتين نصيتين، سيدمجهما.

مثال 2

#include<iostream> 
using namespace std;

class ComplexNum {
private:
	int real, over;
public:
	ComplexNum(int rl = 0, int ov = 0) {
		real = rl;   
		over = ov; 
	}

	ComplexNum operator + (ComplexNum const &obj) {
		ComplexNum result;
		result.real = real + obj.real;
		result.over = over + obj.over;
		return result;
	}
	void print() { 
		cout << real << " + i" << over << endl; 
	}
};
int main()
{
	ComplexNum c1(10, 2), c2(3, 7);
	ComplexNum c3 = c1+c2;
	c3.print();
}

الإخراج:

C++ مثال على تحميل المعاملات الزائدة 2 - المخرجات

فيما يلي لقطة شاشة للكود:

C++ مثال 2 على تحميل المعاملات الزائدة

C++ مثال 2 على تحميل المعاملات الزائدة - النتيجة

Code التفسير:

  1. قم بتضمين ملف رأس iostream في برنامجنا لاستخدام وظائفه.
  2. قم بتضمين مساحة الاسم std في برنامجنا لاستخدام فئاته دون استدعائها.
  3. قم بإنشاء فئة باسم ComplexNum. يشير الرمز { إلى بداية نص الفئة.
  4. استخدم معدل الوصول الخاص لوضع علامة على المتغيرات على أنها خاصة، مما يعني أنه لا يمكن الوصول إليها إلا من داخل الفصل الدراسي.
  5. حدد متغيرين صحيحين، حقيقي وأكثر.
  6. استخدم مُعدِّل الوصول العام لتمييز المُنشئ على أنه عام، مما يعني أنه سيكون متاحًا حتى من خارج الفئة.
  7. إنشاء منشئ الفئة وتهيئة المتغيرات.
  8. تهيئة قيمة المتغير الحقيقي.
  9. تهيئة قيمة المتغير انتهى.
  10. نهاية الجسم المنشئ.
  11. نحن بحاجة إلى تجاوز معنى عامل +.
  12. إنشاء نتيجة نوع البيانات من نوع ComplexNum.
  13. استخدم عامل + مع الأعداد المركبة. سيضيف هذا السطر الجزء الحقيقي من رقم إلى الجزء الحقيقي من رقم آخر.
  14. استخدم عامل + مع الأعداد المركبة. سيضيف هذا السطر الجزء التخيلي من رقم إلى الجزء التخيلي من رقم آخر.
  15. سيقوم البرنامج بإرجاع قيمة النتيجة المتغيرة عند التنفيذ الناجح.
  16. نهاية تعريف المعنى الجديد لعامل +، أي التحميل الزائد.
  17. استدعاء طريقة الطباعة ().
  18. اطبع العدد المركب الجديد بعد الجمع على وحدة التحكم.
  19. نهاية نص الدالة print().
  20. نهاية نص فئة ComplexNum.
  21. استدعاء الدالة الرئيسية ().
  22. مرر قيم الأجزاء الحقيقية والمركبة المراد إضافتها. سيتم إضافة الجزء الأول من c1 إلى الجزء الأول من c2، أي 10+3. سيتم إضافة الجزء الثاني من c1 إلى الجزء الثاني من c، أي 2+7.
  23. قم بإجراء عملية باستخدام عامل + المحمل وتخزين النتيجة في المتغير c3.
  24. اطبع قيمة المتغير c3 على وحدة التحكم.
  25. نهاية نص الدالة main().

في حين أن تعدد الأشكال في وقت الترجمة يحدد ذلك مبكراً، فإن الفئة الثانية تؤجل الاختيار حتى التنفيذ.

تعدد الأشكال وقت التشغيل

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

تجاوز الوظيفة

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

فمثلا:

#include <iostream>
using namespace std;
class Mammal {

public:
	void eat() {

		cout << "Mammals eat...";
	}

};

class Cow: public Mammal {

public:
	void eat() {

		cout << "Cows eat grass...";
	}
};
int main(void) {

	Cow c = Cow();

	c.eat();

	return 0;

}

الإخراج:

C++ مخرجات تجاوز الدالة

فيما يلي لقطة شاشة للكود:

C++ كود تجاوز الدالة

Code التفسير:

  1. قم باستيراد ملف رأس iostream إلى برنامجنا لاستخدام وظائفه.
  2. قم بتضمين مساحة الاسم std في برنامجنا لاستخدام فئاته دون استدعائها.
  3. قم بإنشاء فئة تسمى الثدييات. يمثل { بداية نص الفصل.
  4. استخدم معدّل الوصول العام لتعيين الوظيفة التي نحن على وشك إنشائها لتكون متاحة للعامة. سيكون متاحًا من خارج هذا الفصل.
  5. قم بإنشاء وظيفة عامة باسم Eat. يمثل { بداية نص الوظيفة.
  6. اطبع العبارة المضافة إلى الدالة cout عند استدعاء الدالة Eat().
  7. نهاية جسم الدالة Eat().
  8. نهاية الجسم من فئة الثدييات.
  9. قم بإنشاء فئة تسمى البقرة التي ترث فئة الثدييات. البقرة هي الفئة المشتقة، في حين أن الثدييات هي الفئة الأساسية. يمثل { بداية هذه الفئة.
  10. استخدم مُعدِّل الوصول العام لتحديد الوظيفة التي نحن على وشك إنشائها على أنها متاحة للعامة. سيكون متاحًا من خارج هذا الفصل.
  11. تجاوز الدالة Eat() التي تم تعريفها في الفئة الأساسية. يمثل { بداية نص الوظيفة.
  12. العبارة التي سيتم طباعتها على وحدة التحكم عند استدعاء هذه الوظيفة.
  13. نهاية نص الدالة Eat().
  14. نهاية جسد فئة البقرة.
  15. استدعاء الدالة الرئيسية (). يمثل { بداية نص هذه الوظيفة.
  16. قم بإنشاء مثيل لفئة Cow وإعطائه الاسم c.
  17. قم باستدعاء الدالة Eat() المحددة في فئة Cow.
  18. يجب أن يقوم البرنامج بإرجاع قيمة عند اكتماله بنجاح.
  19. نهاية الوظيفة الرئيسية ().

C++ الوظيفة الافتراضية

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

إذا تم توريث فئة دالة افتراضية، فإن الفئة الافتراضية تعيد تعريف الوظيفة الافتراضية لتناسب احتياجاتها. على سبيل المثال:

#include <iostream>  
using namespace std;
class ClassA {
		public:
		virtual void show() {
			cout << "The show() function in base class invoked..." << endl;
		}
	};
	class ClassB :public ClassA {
	public:
		void show() 	{
			cout << "The show() function in derived class invoked...";
		}
	};
	int main() {
		ClassA* a;   
		ClassB b;
		a = &b;
		a->show();      
	}

الإخراج:

C++ مخرجات الدالة الافتراضية

فيما يلي لقطة شاشة للكود:

C++ رمز الدالة الافتراضية

Code التفسير:

  1. قم بتضمين ملف رأس iostream في الكود لاستخدام وظائفه.
  2. قم بتضمين مساحة الاسم std في الكود الخاص بنا لاستخدام فئاتها دون استدعائها.
  3. قم بإنشاء فئة تسمى ClassA.
  4. استخدم معدّل الوصول العام لوضع علامة على عضو الفصل على أنه يمكن الوصول إليه بشكل عام.
  5. قم بإنشاء دالة افتراضية باسم show(). ستكون وظيفة عامة.
  6. النص الذي سيتم طباعته عند استدعاء الدالة show()‎. النهاية هي أ C++ الكلمة الأساسية، والتي تعني خط النهاية. يقوم بنقل مؤشر الماوس إلى السطر التالي.
  7. نهاية نص الدالة الافتراضية show().
  8. نهاية جسم الفئة ClassA.
  9. إنشاء فئة جديدة باسم ClassB ترث الفئة ClassA. تصبح ClassA هي الفئة الأساسية بينما تصبح ClassB هي الفئة المشتقة.
  10. استخدم معدّل الوصول العام لوضع علامة على عضو الفصل على أنه يمكن الوصول إليه بشكل عام.
  11. أعد تعريف الوظيفة الافتراضية show() المشتقة من الفئة الأساسية.
  12. النص الذي سيتم طباعته على وحدة التحكم عند استدعاء الدالة show() المحددة في الفئة المشتقة.
  13. نهاية نص الدالة show().
  14. نهاية نص الفئة المشتقة، ClassB.
  15. استدعاء الدالة الرئيسية (). يجب إضافة منطق البرنامج داخل جسمه.
  16. قم بإنشاء متغير مؤشر يسمى أ. يشير إلى الفئة المسماة ClassA.
  17. قم بإنشاء مثيل للفئة المسماة ClassB. تم إعطاء المثيل الاسم ب.
  18. قم بتعيين مخازن القيم في العنوان b في المتغير a.
  19. استدعاء الدالة show() المحددة في الفئة المشتقة. تم تنفيذ الربط المتأخر.
  20. نهاية نص الدالة main().

بعد تغطية كلا الآليتين، يقارن الجدول أدناه بينهما جنبًا إلى جنب.

ترجمة تعدد الأشكال في الوقت مقابل. تعدد الأشكال في وقت التشغيل

فيما يلي الاختلافات الرئيسية بين الاثنين:

تعدد الأشكال وقت الترجمة تعدد الأشكال في وقت التشغيل
ويُطلق عليه أيضاً اسم الارتباط المبكر أو التعدد الشكلي الثابت ويُطلق عليه أيضاً اسم الربط المتأخر/الديناميكي أو تعدد الأشكال الديناميكي
يتم استدعاء/استدعاء الطريقة أثناء وقت الترجمة يتم استدعاء/استدعاء الطريقة أثناء وقت التشغيل
تم التنفيذ من خلال التحميل الزائد للوظيفة والتحميل الزائد للمشغل يتم تنفيذها عبر تجاوز الطريقة والوظائف الافتراضية
مثال، طريقة التحميل الزائد. قد يكون للعديد من الأساليب أسماء متشابهة ولكن عدد أو أنواع مختلفة من الوسائط مثال، طريقة التجاوز. قد يكون للعديد من الطرق اسم مشابه ونفس النموذج الأولي.
تنفيذ أسرع لأن اكتشاف الأساليب يتم أثناء وقت الترجمة تنفيذ أبطأ لأن اكتشاف الأساليب يتم أثناء وقت التشغيل.
Less يتم توفير المرونة لحل المشكلات نظرًا لأن كل شيء معروف أثناء وقت الترجمة. يتم توفير قدر كبير من المرونة لحل المشكلات المعقدة حيث يتم اكتشاف الأساليب أثناء وقت التشغيل.

الأسئلة الشائعة

يُعرّف التحميل الزائد عدة دوال بنفس الاسم ولكن بمعاملات مختلفة، ويتم تحديدها في وقت الترجمة دون وراثة. أما التجاوز فيُعيد تعريف دالة موروثة من فئة أساسية بنفس التوقيع في فئة مشتقة، ويتم تحديدها في وقت التشغيل عندما تُعلن الدالة افتراضية.

تُعرَّف الدالة الافتراضية البحتة بقيمة 0 ولا تحتوي على جسم في الفئة الأساسية. أي فئة تحتوي على دالة افتراضية بحتة تصبح فئة مطلقة.tracالفئة t، التي لا يمكن إنشاء مثيل لها مباشرة وتجبر الفئات المشتقة على توفير تنفيذها الخاص.

عندما تُعلن فئة ما عن دوال افتراضية، يقوم المُصرّف بإنشاء جدول افتراضي (vtable) لمؤشرات الدوال. يخزن كل كائن مؤشرًا مخفيًا إلى جدول الدوال الافتراضي الخاص بفئته، بحيث يستدعي مؤشر الفئة الأساسية الدالة المُعاد تعريفها الصحيحة في وقت التشغيل من خلال الربط المتأخر.

نعم. تسمح القوالب لدالة أو فئة واحدة بالعمل على أنواع بيانات متعددة، حيث يقوم المترجم بتوليد شيفرة خاصة بكل نوع أثناء الترجمة. تُعد هذه البرمجة العامة شكلاً من أشكال تعدد الأشكال الثابت الذي يُكمل تحميل الدوال وتحميل المعاملات.

عند حذف كائن مشتق عبر مؤشر من الفئة الأساسية، يضمن وجود مُدمِّر افتراضي فقط تشغيل مُدمِّر الفئة المشتقة أيضًا. وبدونه، تكون عملية التنظيف غير مكتملة وتسبب تسربًا للذاكرة. إن تعريف مُدمِّر الفئة الأساسية على أنه افتراضي يجعل عملية التدمير متعددة الأشكال.

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

نعم. تُحوّل برامج الذكاء الاصطناعي المساعدة في كتابة التعليمات البرمجية التعليقات القصيرة إلى دوال مُحمّلة بشكل زائد، وفئات أساسية افتراضية، وتعديلات مشتقة. وهي تتعامل مع التعليمات البرمجية المتكررة بسرعة، مع ذلك، يُنصح بالتحقق من الكلمات المفتاحية الافتراضية، وتوقيعات الدوال، وسلامة المُدمّرات قبل تجميع التعليمات البرمجية المُولّدة.

نعم. مساعد الطيار جيثب يقترح هذا البرنامج تعريفات الدوال الافتراضية، وتجاوزات الفئات المشتقة، وتحميل المعاملات الزائد أثناء الكتابة. كما أنه يُسرّع كتابة التعليمات البرمجية الأساسية لتسلسلات الوراثة، مع ضرورة التأكد من الكلمة المفتاحية `virtual`، وتطابق التوقيعات، وسلوك الربط المتأخر الصحيح قبل البناء.

تلخيص هذه التدوينة بـ: