الدوال الافتراضية في C++: لماذا يحتاجها المبتدئ؟

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

الدوال الافتراضية في C++: لماذا لا يمكن الاستغناء عنها؟

\n\n

تخيل أنك مخرج سينمائي تقوم بتصوير فيلم عن الحيوانات. لديك سيناريو: "جميع الحيوانات تصدر صوتًا". تقوم بتصوير مشهد مع كلب - ينبح. مشهد مع قطة - تموء. كل شيء منطقي. والآن تخيل أن مساعدك (المترجم) يحضر لك صندوقًا من الحيوانات الألعاب ويقول: "صور!". تضغط على الزر، واللعبة... تصمت. لماذا؟ لأنك نسيت الدوال الافتراضية.

\n\n

في البرمجة بلغة C++، هذه المشكلة تتكرر باستمرار. تكتب فئة "حيوان"، بداخلها دالة sound(). ثم تنشئ فئتي "كلب" و"قطة" ترثان من "حيوان". ويبدو كل شيء على ما يرام، حتى تحاول العمل مع هذه الكائنات من خلال مؤشر على الفئة الأساسية. هنا يتبين أن C++ بشكل افتراضي يستدعي دالة الفئة الأساسية، وليس الدالة المعاد تعريفها في الفئة المشتقة. الدوال الافتراضية تحل هذه المشكلة نهائيًا.

\n\n

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

\n\n

ما هي الدالة الافتراضية بكلمات بسيطة

\n\n

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

\n\n

يبدو الأمر معقدًا؟ دعنا نأخذ مثالًا من الحياة. تخيل جهاز تحكم عن بعد للروبوتات. لديك فئة أساسية "روبوت" بدالة doWork(). هناك روبوت مكنسة كهربائية وروبوت توصيل. إذا ضغطت على زر "اعمل"، يجب أن تبدأ الخوارزميات المناسبة: المكنسة تبدأ بالتنظيف، وروبوت التوصيل يبدأ بتوصيل الطرد. بدون الدوال الافتراضية، كنت ستضغط على الزر والروبوت سيقف مكانه (سيتم استدعاء دالة الفئة الأساسية).

\n\n

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

\n\n

المشكلة بدون الدوال الافتراضية: مثال توضيحي

\n\n

دعنا نرى ما يحدث إذا لم نستخدم virtual.

\n\n
#include <iostream>\n#include <vector>\n\nclass Animal {\npublic:\n    void sound() {\n        std::cout << "Some generic animal sound" << std::endl;\n    }\n};\n\nclass Dog : public Animal {\npublic:\n    void sound() {\n        std::cout << "Woof! Woof!" << std::endl;\n    }\n};\n\nclass Cat : public Animal {\npublic:\n    void sound() {\n        std::cout << "Meow!" << std::endl;\n    }\n};\n\nint main() {\n    std::vector<Animal*> animals;\n    animals.push_back(new Dog());\n    animals.push_back(new Cat());\n\n    for (Animal* a : animals) {\n        a->sound(); // ماذا سيتم طباعته؟\n    }\n\n    // تنظيف الذاكرة\n    for (Animal* a : animals) delete a;\n\n    return 0;\n}\n
\n\n

قم بتشغيل هذا الكود. سترى:

\n
Some generic animal sound\nSome generic animal sound\n
\n\n

لماذا؟ لأن المترجم ينظر إلى نوع المؤشر (Animal*)، وليس إلى الكائن الفعلي. يستدعي دالة الفئة الأساسية. هذا يسمى الربط المبكر (early binding) - يتم اتخاذ قرار أي دالة سيتم استدعاؤها في مرحلة الترجمة.

\n\n

ونحن نريد الربط المتأخر (late binding) - بحيث يتم اتخاذ القرار أثناء تنفيذ البرنامج، اعتمادًا على نوع الكائن. وهذا بالضبط ما توفره الدوال الافتراضية.

\n\n

الحل: إضافة virtual

\n\n

لنصلح المثال بإضافة الكلمة المفتاحية virtual.

\n\n
class Animal {\npublic:\n    virtual void sound() {  // أضفنا virtual\n        std::cout << "Some generic animal sound" <<                

المدونات

توصيات الكتب