Menu
Coddy logo textTech

פונקציות וירטואליות — מבט נוסף

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

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

כשמסמנים פונקציה כ־virtual במחלקת בסיס, מחלקות נגזרות יכולות לספק מימוש משלהן. המציין override מציין במפורש למהדר שבכוונתך לדרוס פונקציה וירטואלית:

class Animal {
public:
    virtual void speak() {
        std::cout << "Some sound" << std::endl;
    }
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void speak() override {
        std::cout << "Woof!" << std::endl;
    }
};

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

class Cat : public Animal {
public:
    void speek() override {  // שגיאת מהדר: אין פונקציה שניתן לדרוס
        std::cout << "Meow!" << std::endl;
    }
};

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

class Bulldog : public Dog {
public:
    void speak() override final {
        std::cout << "Gruff woof!" << std::endl;
    }
};

class TinyBulldog : public Bulldog {
    void speak() override {}  // שגיאה: לא ניתן לדרוס פונקציה שהוגדרה כ-final
};

השתמש תמיד ב־override כשאתה דורס פונקציות וירטואליות. כך הכוונה שלך מתועדת, והמהדר יכול לוודא שאתה אכן דורס פונקציה וירטואלית קיימת.

challenge icon

אתגר

קל

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

תארגנו את הקוד שלכם בשלושה קבצים:

  • Notifier.h: הגדירו מחלקת בסיס Notifier שמייצגת כל שולח התראות:
    • חבר std::string recipient מוגן
    • בנאי שמקבל ושומר את שם הנמען
    • שיטה וירטואלית send(const std::string& message) שמדפיסה: Notifying <recipient>: <message>
    • שיטה וירטואלית getType() שמחזירה את המחרוזת "Generic"
    • הורס וירטואלי
  • EmailNotifier.h: הגדירו מחלקת EmailNotifier שיורשת מ-Notifier:
    • חבר std::string domain פרטי
    • בנאי שמקבל נמען ודומיין, ומעביר את הנמען למחלקת הבסיס
    • דרסו את send() כדי להדפיס: Emailing <recipient>@<domain>: <message>
    • דרסו את getType() וסמנו אותה ב-final. היא אמורה להחזיר "Email"

    לאחר מכן הגדירו מחלקת UrgentEmailNotifier שיורשת מ-EmailNotifier:

    • בנאי שמקבל נמען ודומיין, ומעביר את שניהם ל-EmailNotifier
    • דרסו את send() כדי להדפיס: [URGENT] Emailing <recipient>@<domain>: <message>
    • הערה: אי אפשר לדרוס כאן את getType(), מכיוון שהיא סומנה ב-final בתוך EmailNotifier
  • main.cpp: קראו שלושה קלטים (כל אחד בשורה נפרדת):
    1. שם הנמען
    2. דומיין האימייל
    3. תוכן ההודעה

    צרו באופן דינמי שלושה אובייקטים של שולחי התראות: Notifier בסיסי, EmailNotifier ו-UrgentEmailNotifier: כולם משתמשים באותו נמען (ובאותו דומיין, לפי העניין). שמרו אותם במערך של מצביעי Notifier*.

    עברו בלולאה על המערך ועבור כל שולח התראות, הדפיסו את הסוג שלו באמצעות getType(), ולאחר מכן קראו ל-send() עם ההודעה שלכם. עצבו כל רשומה כך:

    Type: <type>
    <send output>

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

לדוגמה, עם הקלטים Alice, company.com ו-Meeting at 3pm:

Type: Generic
Notifying Alice: Meeting at 3pm

Type: Email
Emailing Alice@company.com: Meeting at 3pm

Type: Email
[URGENT] Emailing Alice@company.com: Meeting at 3pm

שימו לב איך UrgentEmailNotifier יכולה לדרוס את send() כדי להתאים אישית את פורמט ההודעה, אבל היא יורשת את הסוג "Email" מ-EmailNotifier, מכיוון ש-getType() סומנה ב-final. השתמשו במילת המפתח override בכל השיטות שנדרסו כדי לזהות אי-התאמות בחתימות בזמן הקומפילציה.

נסו בעצמכם

#include <iostream>
#include <string>
#include "Notifier.h"
#include "EmailNotifier.h"

using namespace std;

int main() {
    // קראו את הקלטים
    string recipient;
    string domain;
    string message;
    
    getline(cin, recipient);
    getline(cin, domain);
    getline(cin, message);
    
    // TODO: צרו מערך של מצביעי Notifier* עם 3 איברים
    
    // TODO: צרו בהקצאה דינמית:
    // - אובייקט Notifier בסיסי עם recipient
    // - אובייקט EmailNotifier עם recipient ו-domain
    // - אובייקט UrgentEmailNotifier עם recipient ו-domain
    
    // TODO: עברו על המערך ועבור כל אובייקט מסוג Notifier:
    // - הדפיסו "Type: " ואחריו את התוצאה של getType()
    // - קראו ל-send() עם message
    // - הדפיסו שורה ריקה בין האובייקטים (אך לא אחרי האחרון)
    
    // TODO: שחררו את האובייקטים שהוקצו דינמית
    
    return 0;
}
quiz iconבחנו את עצמכם

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

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

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