דריסת מתודות
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 51 מתוך 104.
דריסת מתודה מתרחשת כאשר מחלקה נגזרת מספקת מימוש משלה למתודה שכבר קיימת במחלקת הבסיס. כך מחלקות נגזרות יכולות להתאים אישית התנהגות שהן ירשו, תוך שמירה על אותה חתימת מתודה.
כדי לדרוס מתודה, פשוט הגדירו מתודה במחלקה הנגזרת עם אותו שם בדיוק, אותו טיפוס החזרה ואותם פרמטרים:
class Animal {
public:
void speak() {
std::cout << "Some sound" << std::endl;
}
};
class Dog : public Animal {
public:
void speak() {
std::cout << "Woof!" << std::endl;
}
};
Dog d;
d.speak(); // פלט: Woof!הגרסה של המחלקה הנגזרת מסתירה את הגרסה של מחלקת הבסיס. עם זאת, עדיין אפשר לגשת למתודה של מחלקת הבסיס באמצעות אופרטור פתרון תחום השמות:
class Dog : public Animal {
public:
void speak() {
Animal::speak(); // קרא לגרסה של מחלקת הבסיס
std::cout << "Woof!" << std::endl;
}
};יש מגבלה חשובה בצורת הבסיסית הזו של דריסת מתודות. כשניגשים לאובייקט נגזר דרך מצביע או הפניה למחלקת בסיס, נקראת הגרסה של מחלקת הבסיס במקום זאת:
Dog d;
Animal* ptr = &d;
ptr->speak(); // פלט: צליל כלשהו (לא Woof!)זה קורה משום שהמהדר קובע איזו מתודה לקרוא לפי סוג המצביע, ולא לפי סוג האובייקט בפועל. כדי להשיג פולימורפיזם אמיתי בזמן ריצה, שבו המתודה הנכונה נקראת לפי סוג האובייקט בפועל, תצטרך פונקציות וירטואליות, שבהן נעסוק בשיעור הבא.
אתגר
קלבואו נבנה מערכת התראות שמדגימה החלפת מימוש של מתודות בפעולה. תיצרו מחלקת בסיס Notifier ושתי מחלקות התראות ייעודיות שיתאימו אישית את אופן מסירת ההודעות.
תארגנו את הקוד בשלושה קבצים:
Notifier.h: הגדירו מחלקת בסיסNotifierעם:- שדה
std::string recipientמוגן - בנאי שמקבל את שם הנמען ושומר אותו
- מתודת
send(const std::string& message)ציבורית שמדפיסה:Sending to <recipient>: <message>
- שדה
EmailNotifier.h: הגדירו מחלקתEmailNotifierשיורשת באופן ציבורי מ־Notifier:- בנאי שמקבל נמען ומעביר אותו למחלקת הבסיס
- החליפו את מימוש המתודה
send()כך שקודם תקרא לגרסה של מחלקת הבסיס באמצעותNotifier::send(), ואז תדפיס:[Email sent successfully]
main.cpp: קראו שני קלטים (כל אחד בשורה נפרדת):- שם הנמען (מחרוזת)
- תוכן ההודעה (מחרוזת)
הגדירו מחלקת
SMSNotifierישירות בתוך main.cpp, שיורשת באופן ציבורי מ־Notifier:- בנאי שמקבל נמען ומעביר אותו למחלקת הבסיס
- החליפו את מימוש המתודה
send()כך שתדפיס:SMS to <recipient>: <message>(ותחליף לחלוטין את ההתנהגות של מחלקת הבסיס בלי לקרוא לה)
צרו אובייקט אחד מכל סוג של התראה (מחלקת הבסיס
Notifier, המחלקהEmailNotifierוהמחלקהSMSNotifier) באמצעות אותו נמען. קראו ל־send()בכל אחד מהם עם הודעת הקלט, והדפיסו שורת הפרדה בין כל אחד:--- Base Notifier --- <output> --- Email Notifier --- <output> --- SMS Notifier --- <output>
לדוגמה, עבור הקלטים Alice ו־Hello there!:
--- Base Notifier ---
Sending to Alice: Hello there!
--- Email Notifier ---
Sending to Alice: Hello there!
[Email sent successfully]
--- SMS Notifier ---
SMS to Alice: Hello there!שימו לב איך EmailNotifier מרחיבה את ההתנהגות של מחלקת הבסיס באמצעות קריאה תחילה למתודת האב, ואילו SMSNotifier מחליפה אותה לחלוטין במימוש משלה. אל תשכחו שומרי כותרת ולכלול את הכותרות המתאימות בכל קובץ.
נסו בעצמכם
#include <iostream>
#include <string>
#include "Notifier.h"
#include "EmailNotifier.h"
using namespace std;
// TODO: הגדירו כאן מחלקה SMSNotifier שיורשת באופן ציבורי מ-Notifier
// - בנאי שמעביר את recipient למחלקת הבסיס
// - דרסו את send() כך שתדפיס: SMS to <recipient>: <message>
// (החליפו לחלוטין את התנהגות הבסיס, אל תקראו ל-send() של מחלקת האב)
int main() {
string recipient;
string message;
getline(cin, recipient);
getline(cin, message);
// TODO: צרו אובייקט Notifier עם recipient
// הדפיסו "--- Base Notifier ---"
// קראו ל-send() עם message
// TODO: צרו אובייקט EmailNotifier עם recipient
// הדפיסו "--- Email Notifier ---"
// קראו ל-send() עם message
// TODO: צרו אובייקט SMSNotifier עם recipient
// הדפיסו "--- SMS Notifier ---"
// קראו ל-send() עם message
return 0;
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP
קבצים חיצונייםבנייה וקומפילציה של C++קובצי Header וקובצי מקורמרחבי שמות והיקףמבוא ל-OOP ב-C++מחלקות לעומת אובייקטיםמצביע thisמתודות (פונקציות איבר)תכונות (חברי נתונים)יסודות הבנאים וההורסיםסיכום – מחשבון פשוט4מאפייני מחלקה
חברי מופע לעומת חברים סטטייםגטרים וסטריםפונקציות איבר קבועותמילת המפתח mutableמתודות ומשתנים סטטייםפונקציות ומחלקות ידידותסיכום – מנהל חשבונות בנק7ירושה
ירושה בסיסיתרמות גישה בירושהסדר הקריאות לבנאי ולמפרקדריסת מתודותפונקציות וירטואליות ו־VTableירושה מרובהירושה וירטואליתסיכום – היררכיית עובדים2ניהול זיכרון
זיכרון Stack לעומת Heapמצביעים והפניותזיכרון דינמי (new/delete)מצביעים חכמים ב-C++RAII ב-C++סיכום – מנהל מערכים דינמיים5כימוס
מצייני גישה ב-C++מצייני גישה לעומקהסתרת מידעמבנה לעומת מחלקהמחלקות מקוננות ופנימיותסיכום – מערכת רשומות תלמידים8רב־צורתיות
רב־צורתיות בזמן קומפילציה לעומת בזמן ריצההעמסת פונקציותפונקציות וירטואליות — מבט נוסףפונקציות וירטואליות טהורותמחלקות מופשטותתכנון ממשקים ב־C++המרה דינמית ו־RTTIסיכום — מחשבון צורות3בנאים והורסים
בנאי ברירת מחדלבנאי עם פרמטריםבנאי העתקהבנאי העברהרשימות אתחול בבנאיםבנאים מאציליםמעמיקים בנושא הורסיםכלל השלושה / החמישה / האפססיכום – מחלקת String6העמסת אופרטורים
מבוא להעמסת אופרטוריםהעמסת אופרטורים אריתמטייםהעמסת אופרטורי השוואהאופרטורי זרםהעמסת אופרטור השמההעמסת האופרטורים [] ו־()אופרטורים להמרת טיפוסיםסיכום – מחלקת Matrix9תבניות
תבניות פונקציהתבניות מחלקההתמחות תבניותתבניות ארגומנטים משתנותיסודות SFINAE ותכונות טיפוסיםסיכום – מכל גנריתרגלו בעצמכם: קומפיילר C++ אונליין