מחלקות מופשטות
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 60 מתוך 104.
מחלקה מופשטת היא כל מחלקה שמכילה לפחות פונקציה וירטואלית טהורה אחת. מכיוון שהממשק שלה אינו שלם, אי אפשר ליצור ישירות אובייקטים של מחלקה מופשטת — היא קיימת אך ורק כדי שמחלקות אחרות יירשו ממנה.
class Shape {
public:
virtual double area() = 0; // וירטואלית טהורה - הופכת את Shape למחלקה מופשטת
virtual double perimeter() = 0; // פונקציה וירטואלית טהורה נוספת
void printInfo() { // מתודה רגילה - יש לה מימוש
std::cout << "Area: " << area() << std::endl;
}
virtual ~Shape() = default;
};
Shape s; // שגיאה: אי אפשר ליצור מופע של מחלקה מופשטתמחלקות מופשטות יכולות להכיל שילוב של פונקציות וירטואליות טהורות (ללא מימוש), פונקציות וירטואליות רגילות (עם מימוש ברירת מחדל) ופונקציות שאינן וירטואליות. כך אפשר להגדיר התנהגות משותפת ובו בזמן לדרוש ממחלקות נגזרות לממש פונקציונליות ספציפית.
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() override { return 3.14159 * radius * radius; }
double perimeter() override { return 2 * 3.14159 * radius; }
};
Circle c(5.0); // OK: Circle מממשת את כל הפונקציות הווירטואליות הטהורות
c.printInfo(); // משתמשת ב-printInfo() שעברה בירושה, וקוראת ל-area() של Circleמחלקה נגזרת חייבת לממש את כל הפונקציות הווירטואליות הטהורות כדי להפוך לממשית (ניתנת ליצירה). אם היא משאירה פונקציות כלשהן ללא מימוש, היא עצמה נשארת מופשטת. מנגנון זה מבטיח שכל מחלקה ממשית בהיררכיה שלך מספקת את הפונקציונליות הנדרשת.
אתגר
קלבואו נבנה מערכת לניגון מדיה שמדגימה כיצד מחלקות מופשטות מגדירות חוזים עבור סוגי מדיה שונים. תיצרו מחלקת בסיס מופשטת שמגדירה מה כל נגן מדיה חייב לעשות, ואז תממשו נגנים קונקרטיים עבור תוכן אודיו ווידאו.
תארגנו את הקוד בשלושה קבצים:
MediaPlayer.h: הגדירו מחלקה מופשטת בשםMediaPlayerשמשמשת כתבנית לכל סוגי המדיה. המחלקה המופשטת שלכם צריכה לכלול:- חבר מוגן
std::string title - חבר מוגן
int duration(בשניות) - בנאי שמאתחל את שני הערכים
- מתודה וירטואלית טהורה
play(): כל סוג מדיה יתנגן באופן שונה - מתודה וירטואלית טהורה
getMediaType()שמחזירהstd::string - מתודה רגילה (לא וירטואלית)
showInfo()שמדפיסה:<title> (<duration>s) - Type: <mediaType>. המתודה הזאת צריכה לקרוא ל־getMediaType()כדי לקבל את הסוג - הורס וירטואלי
- חבר מוגן
Players.h: ממשו שני נגני מדיה קונקרטיים שיורשים מ־MediaPlayer:AudioPlayer:- חבר פרטי
std::string artist - בנאי שמקבל כותרת, משך זמן ואמן
- ממשו את
play()כך שתדפיס:Playing audio: <title> by <artist> - ממשו את
getMediaType()כך שתחזיר"Audio"
VideoPlayer:- חבר פרטי
std::string resolution - בנאי שמקבל כותרת, משך זמן ורזולוציה
- ממשו את
play()כך שתדפיס:Playing video: <title> in <resolution> - ממשו את
getMediaType()כך שתחזיר"Video"
- חבר פרטי
main.cpp: קראו ארבעה קלטים (כל אחד בשורה נפרדת):- כותרת של רצועת אודיו
- משך האודיו (מספר שלם)
- כותרת של וידאו
- משך הווידאו (מספר שלם)
צרו
AudioPlayerעם האמן"Unknown Artist"ו־VideoPlayerעם הרזולוציה"1080p". שמרו את שניהם במערך של מצביעיMediaPlayer*.עברו בלולאה על המערך, ועבור כל נגן, קראו תחילה ל־
showInfo()ואז ל־play(). הדפיסו שורה ריקה בין כל פריט מדיה. בסיום, שחררו את האובייקטים שהוקצו דינמית.
לדוגמה, עבור הקלטים Summer Vibes, 180, Nature Documentary ו־3600:
Summer Vibes (180s) - Type: Audio
Playing audio: Summer Vibes by Unknown Artist
Nature Documentary (3600s) - Type: Video
Playing video: Nature Documentary in 1080pשימו לב שמתודת showInfo() מוגדרת פעם אחת במחלקת הבסיס המופשטת, אבל קוראת למתודה הווירטואלית הטהורה getMediaType(). כך מודגם כיצד מחלקות מופשטות יכולות לשלב מימושים קונקרטיים עם מתודות שהמחלקות הנגזרות חייבות לספק. זכרו להשתמש במילת המפתח override בכל המתודות שדורכות על מתודות קיימות.
נסו בעצמכם
#include <iostream>
#include <string>
#include "MediaPlayer.h"
#include "Players.h"
int main() {
// קרא את הקלט
std::string audioTitle;
int audioDuration;
std::string videoTitle;
int videoDuration;
std::getline(std::cin, audioTitle);
std::cin >> audioDuration;
std::cin.ignore();
std::getline(std::cin, videoTitle);
std::cin >> videoDuration;
// TODO: צור AudioPlayer עם האמן "Unknown Artist"
// TODO: צור VideoPlayer עם רזולוציה "1080p"
// TODO: אחסן את שניהם במערך של מצביעי MediaPlayer*
// TODO: עבור על המערך ועבור כל נגן:
// 1. קרא ל-showInfo()
// 2. קרא ל-play()
// 3. הדפס שורה ריקה בין כל פריט מדיה
// TODO: שחרר את האובייקטים שהוקצו דינמית
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++ אונליין