Menu
Coddy logo textTech

רב־צורתיות בזמן קומפילציה לעומת בזמן ריצה

חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 56 מתוך 104.

פולימורפיזם פירושו "צורות רבות", והוא מושג מרכזי בתכנות מונחה עצמים (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

אתגר

קל

בוא נבנה מערכת מחשבון שמדגימה את שני סוגי הפולימורפיזם זה לצד זה. תיצור מערכת שבה פולימורפיזם בזמן קומפילציה מטפל בסוגי קלט שונים באמצעות העמסת פונקציות, ואילו פולימורפיזם בזמן ריצה מאפשר להחליף אסטרטגיות חישוב שונות באופן דינמי.

תארגן את הקוד שלך בשלושה קבצים:

  • Calculator.h: הגדר מחלקת בסיס Calculator שמייצגת כל אסטרטגיית חישוב:
    • מתודה וירטואלית calculate(int a, int b) שמחזירה int ומדפיסה: Base calculation: <a> ? <b> (מחזירה 0)
    • הורס וירטואלי
  • Operations.h: הגדר שתי מחלקות מחשבון נגזרות שדורסות את התנהגות החישוב:
    • Adder: דרוס את calculate() כך שתדפיס Adding: <a> + <b> ותחזיר את הסכום
    • Multiplier: דרוס את calculate() כך שתדפיס Multiplying: <a> * <b> ותחזיר את המכפלה
    על שתי המחלקות להשתמש במילת המפתח override.
  • main.cpp: צור מערכת שמדגימה את שני סוגי הפולימורפיזם. קרא שני קלטים של מספרים שלמים (כל אחד בשורה נפרדת).

    תחילה, הדגם פולימורפיזם בזמן קומפילציה על ידי יצירת שלוש פונקציות 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. עבור על המערך וקרא ל-calculate() עם ערכי הקלט שלך, והדפס את התוצאה אחרי כל חישוב.

    בנה את הפלט כך:

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

    בחלק של זמן הקומפילציה, קרא ל-display() עם הקלט הראשון כמספר שלם, לאחר מכן כמספר עשרוני (אותו ערך בתוספת ‎.5), ולבסוף עם המחרוזת "Result". בסיום, שחרר את המחשבונים שהוקצו באופן דינמי.

לדוגמה, עם הקלטים 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

שים לב כיצד המהדר בוחר את העמסת display() הנכונה על סמך סוג הארגומנט (החלטה בזמן קומפילציה), בעוד שהמתודה calculate() הנכונה נקבעת לפי סוג האובייקט בפועל בזמן ריצה, באמצעות מנגנון ה-vtable.

נסו בעצמכם

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

// TODO: צרו שלוש פונקציות display() בעלות עומס יתר:
// 1. display(int x) - מדפיסה "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 איברים:
    // - אובייקט בסיסי מסוג Calculator
    // - אובייקט מסוג Adder
    // - אובייקט מסוג Multiplier
    
    // TODO: עברו על המערך וקראו ל־calculate(a, b) על כל אחד מהאיברים,
    // והדפיסו "Result: <return_value>" אחרי כל חישוב
    
    
    // TODO: שחררו את הזיכרון שהוקצה באופן דינמי
    
    
    return 0;
}
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר C++ אונליין