ירושה מרובה
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 53 מתוך 104.
ב-C++ מחלקה יכולה לרשת בו-זמנית ממחלקות בסיס מרובות. הדבר נקרא ירושה מרובה ומאפשר לשלב תכונות מהיררכיות שונות של מחלקות.
כדי לרשת מכמה מחלקות, הפרידו ביניהן באמצעות פסיקים:
class Printer {
public:
void print() {
std::cout << "Printing document" << std::endl;
}
};
class Scanner {
public:
void scan() {
std::cout << "Scanning document" << std::endl;
}
};
class MultiFunctionDevice : public Printer, public Scanner {
public:
void copy() {
scan();
print();
}
};למחלקה MultiFunctionDevice יש כעת גישה למתודות הן של Printer והן של Scanner:
MultiFunctionDevice mfd;
mfd.print(); // מתוך Printer
mfd.scan(); // מתוך Scanner
mfd.copy(); // המתודה שלו עצמובנאים נקראים לפי סדר הופעת מחלקות הבסיס בהצהרת הירושה (משמאל לימין), והורסים בסדר הפוך. אפשר להעביר ארגומנטים לכל בנאי בסיס ברשימת האתחול:
class MultiFunctionDevice : public Printer, public Scanner {
public:
MultiFunctionDevice() : Printer(), Scanner() {}
};בעיה אפשרית מתעוררת כאשר לשתי מחלקות הבסיס יש איבר בעל אותו שם. הדבר יוצר עמימות, ועליך להשתמש באופרטור פתרון התחום כדי לציין לאיזה מהם אתה מתכוון:
class A { public: void show() {} };
class B { public: void show() {} };
class C : public A, public B {};
C obj;
obj.A::show(); // קרא לגרסה של A
obj.B::show(); // קרא לגרסה של Bירושה מרובה היא עוצמתית, אך עלולה להוביל למצבים מורכבים, במיוחד כשמחלקות בסיס חולקות אב קדמון משותף. נבחן את האתגר הזה בשיעור הבא.
אתגר
קלבואו נבנה מערכת למכשירי בית חכם שמשלבת יכולות מכמה מחלקות בסיס באמצעות ירושה מרובה. תיצרו מכשירים שיכולים גם להתחבר ל-WiFi וגם להיות נשלטים באמצעות קול, ואז תשלבו את התכונות האלה לרמקול חכם מאוחד.
תארגנו את הקוד בשלושה קבצים:
WiFiDevice.h: הגדירו מחלקה בשםWiFiDeviceשמייצגת כל מכשיר שמסוגל להתחבר לרשת:- איבר מוגן
std::string networkName - בנאי שמקבל שם רשת ושומר אותו
- מתודה ציבורית
connect()שמדפיסה:Connecting to WiFi: <networkName> - מתודה ציבורית
status()שמדפיסה:WiFi Status: Connected
- איבר מוגן
VoiceControl.h: הגדירו מחלקה בשםVoiceControlשמייצגת כל מכשיר עם יכולות קוליות:- איבר מוגן
std::string assistantName - בנאי שמקבל שם של עוזר קולי ושומר אותו
- מתודה ציבורית
listen()שמדפיסה:<assistantName> is listening... - מתודה ציבורית
status()שמדפיסה:Voice Status: Active
- איבר מוגן
main.cpp: קראו שלוש קלטים (כל אחד בשורה נפרדת):- שם המכשיר (מחרוזת)
- שם רשת ה-WiFi (מחרוזת)
- שם העוזר הקולי (מחרוזת)
הגדירו מחלקה בשם
SmartSpeakerשיורשת באופן ציבורי גם מ-WiFiDeviceוגם מ-VoiceControl:- איבר פרטי
std::string deviceName - בנאי שמקבל את כל שלושת הפרמטרים, מעביר את שם הרשת אל
WiFiDeviceואת שם העוזר הקולי אלVoiceControlבאמצעות רשימת האתחול, ושומר את שם המכשיר - מתודה
powerOn()שמדפיסה:<deviceName> powering on..., ואז קוראת ל-connect()ול-listen() - מתודה
showAllStatus()שמדפיסה את שם המכשיר, ואז משתמשת באופרטור פתרון התחום כדי לקרוא לשתי מתודותstatus()ולפתור את העמימות
צרו אובייקט מסוג
SmartSpeakerעם ערכי הקלט. קראו ל-powerOn(), הדפיסו שורה ריקה, ואז קראו ל-showAllStatus().
לדוגמה, עם הקלטים Echo, HomeNetwork ו-Alexa:
Echo powering on...
Connecting to WiFi: HomeNetwork
Alexa is listening...
Echo Status:
WiFi Status: Connected
Voice Status: Activeהמתודה showAllStatus() צריכה להדפיס את שם המכשיר ואחריו " Status:" בשורה אחת, ואז לקרוא למתודת status() של כל מחלקת בסיס באמצעות WiFiDevice::status() ו-VoiceControl::status(), כדי למנוע עמימות מכיוון שלשתי מחלקות הבסיס יש מתודה באותו שם.
נסו בעצמכם
#include <iostream>
#include <string>
#include "WiFiDevice.h"
#include "VoiceControl.h"
using namespace std;
// TODO: להגדיר מחלקה SmartSpeaker שיורשת באופן ציבורי גם מ-WiFiDevice וגם מ-VoiceControl
// - שדה פרטי: deviceName (string)
// - בנאי: מקבל את deviceName, networkName, assistantName
// יש להשתמש ברשימת אתחול כדי להעביר את networkName ל-WiFiDevice ואת assistantName ל-VoiceControl
// - המתודה powerOn(): מדפיסה "<deviceName> powering on...", ואז קוראת ל-connect() ול-listen()
// - המתודה showAllStatus(): מדפיסה "<deviceName> Status:", ואז קוראת לשתי המתודות status()
// באמצעות אופרטור זיהוי התחום (WiFiDevice::status() ו-VoiceControl::status())
class SmartSpeaker {
// TODO: לממש כאן את המחלקה SmartSpeaker
};
int main() {
string deviceName, networkName, assistantName;
getline(cin, deviceName);
getline(cin, networkName);
getline(cin, assistantName);
// TODO: ליצור אובייקט SmartSpeaker עם ערכי הקלט
// TODO: לקרוא ל-powerOn()
// TODO: להדפיס שורה ריקה
// TODO: לקרוא ל-showAllStatus()
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++ אונליין