Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

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

جزء من قسم البرمجة كائنية التوجه في رحلة C++ على Coddy. الدرس 56 من 104.

تعني Polymorphism «أشكالًا متعددة»، وهي مفهوم أساسي في البرمجة كائنية التوجه (OOP) يتيح التعامل مع الكائنات بشكل موحّد مع بقائها تتصرف بطرق مختلفة. يدعم C++ نوعين متميزين من تعدد الأشكال، ويُحسم كل منهما في مرحلة مختلفة من تنفيذ البرنامج.

تعدد الأشكال وقت الترجمة (ويُسمّى أيضًا تعدد الأشكال الساكن) يُحدَّد بواسطة المترجم قبل تشغيل البرنامج. يحدّد المترجم بالضبط أي دالة سيتم استدعاؤها استنادًا إلى توقيع الدالة. ويشمل ذلك التحميل الزائد للدوال والقوالب:

void print(int x) { std::cout << "Integer: " << x << std::endl; }
void print(double x) { std::cout << "Double: " << x << std::endl; }

print(5);      // المترجم يختار print(int)
print(3.14);   // المترجم يختار print(double)

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

class Shape {
public:
    virtual void draw() { std::cout << "Drawing shape" << std::endl; }
};

class Circle : public Shape {
public:
    void draw() override { std::cout << "Drawing circle" << std::endl; }
};

Shape* s = new Circle();
s->draw();  // يُقرر في وقت التشغيل: "Drawing circle"

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

challenge icon

التحدي

سهل

لنبنِ نظام آلة حاسبة يوضّح كلا النوعين من تعدد الأشكال جنبًا إلى جنب. ستنشئ نظامًا يتعامل فيه تعدد الأشكال وقت Compile مع أنواع inputs المختلفة من خلال تحميل زائد لـ functions، بينما يتيح تعدد الأشكال وقت التشغيل تبديل استراتيجيات calculation المختلفة dynamically.

ستنظّم التعليمات البرمجية عبر ثلاثة ملفات:

  • Calculator.h: عرّف فئة Calculator أساسية تمثّل أي استراتيجية calculation:
    • دالة calculate(int a, int b) افتراضية تُرجع int وتطبع: Base calculation: <a> ? <b> (مع إرجاع 0)
    • مدمّر افتراضي
  • Operations.h: عرّف فئتين مشتقتين لآلة حاسبة تتجاوزان سلوك calculation:
    • Adder: تجاوز calculate() لطباعة Adding: <a> + <b> وإرجاع المجموع
    • Multiplier: تجاوز calculate() لطباعة Multiplying: <a> * <b> وإرجاع حاصل الضرب
    يجب أن تستخدم كلتا الفئتين الكلمة المفتاحية override.
  • main.cpp: أنشئ نظامًا يعرض كلا نوعي تعدد الأشكال. اقرأ قيمتي inputs من النوع integer (كل قيمة في سطر منفصل).

    أولًا، اعرض تعدد الأشكال وقت Compile من خلال إنشاء ثلاث دوال display() محمّلة تحميلًا زائدًا:

    • display(int x) تطبع: Integer value: <x>
    • display(double x) تطبع: Double value: <x>
    • display(const std::string& x) تطبع: String value: <x>

    ثم اعرض تعدد الأشكال وقت التشغيل من خلال إنشاء مصفوفة من مؤشرات Calculator* تحتوي على Calculator أساسية، وAdder، وMultiplier. نفّذ Loop عبرها واستدعِ calculate() على كل عنصر باستخدام قيم inputs الخاصة بك، مع طباعة النتيجة بعد كل calculation.

    نظّم مخرجاتك كما يلي:

    === Compile-Time Polymorphism ===
    <display outputs for int, double, string>
    
    === Runtime Polymorphism ===
    <calculate outputs with results>

    في قسم Compile، استدعِ display() باستخدام input الأول كعدد صحيح، ثم كعدد عشري (القيمة نفسها مع إضافة .5)، ثم كسلسلة النص "Result". نفّذ Clean لمثيلات calculators التي جرى تخصيصها dynamically عند الانتهاء.

على سبيل المثال، مع القيمتين 10 و3:

=== Compile-Time Polymorphism ===
Integer value: 10
Double value: 10.5
String value: Result

=== Runtime Polymorphism ===
Base calculation: 10 ? 3
Result: 0
Adding: 10 + 3
Result: 13
Multiplying: 10 * 3
Result: 30

لاحظ كيف يختار Compiler التحميل الزائد الصحيح لـ display() استنادًا إلى نوع الوسيط (قرار وقت Compile)، بينما تُحدَّد دالة calculate() الصحيحة بواسطة نوع الكائن الفعلي وقت التشغيل من خلال آلية vtable.

جرّب بنفسك

#include <iostream>
#include <string>
#include "Calculator.h"
#include "Operations.h"

// TODO: أنشئ ثلاث دوال display() مثقلة:
// 1. display(int x) - prints "Integer value: <x>"
// 2. display(double x) - تطبع "Double value: <x>"
// 3. display(const std::string& x) - تطبع "String value: <x>"



int main() {
    // اقرأ مدخلين صحيحين
    int a, b;
    std::cin >> a;
    std::cin >> b;
    
    // === Compile-Time Polymorphism ===
    std::cout << "=== Compile-Time Polymorphism ===" << std::endl;
    // TODO: استدعِ display() مع:
    // - a كعدد صحيح
    // - a كـ double (أضف 0.5 إليه)
    // - السلسلة "Result"
    
    
    std::cout << std::endl;
    
    // === Runtime Polymorphism ===
    std::cout << "=== Runtime Polymorphism ===" << std::endl;
    // TODO: أنشئ مصفوفة من مؤشرات Calculator* بـ 3 عناصر:
    // - a base Calculator
    // - Adder
    // - Multiplier
    
    // TODO: مرّ عبر المصفوفة، واستدعِ calculate(a, b) على كل منها،
    // واطبع "Result: <return_value>" بعد كل عملية حساب
    
    
    // TODO: نظّف الذاكرة المخصصة ديناميكيًا
    
    
    return 0;
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم C++ عبر الإنترنت