מחלקות Enum וטיפוסיות חזקה
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 80 מתוך 104.
C++11 הציגה מחלקות enum (המכונות גם מִנְיוֹנִים תחומים) כדי להתמודד עם החסרונות של מִנְיוֹנִים מסורתיים בסגנון C. הן מספקות בטיחות טיפוסים חזקה יותר ומונעות שימוש שגוי מקרי בערכי מִנְיוֹן.
ל-enum מסורתיים יש בעיות: הערכים שלהם דולפים אל תחום השמות שמסביב ומומרים באופן מרומז למספרים שלמים, דבר שמוביל לבאגים חמקמקים:
// enum מסורתי - בעייתי
enum Color { Red, Green, Blue };
enum TrafficLight { Red, Yellow, Green }; // שגיאה! Red ו-Green כבר הוגדרו
int x = Red; // מומר אוטומטית ל-int (0)
if (Red == 0) { } // עובר הידור - השוואה בין enum ל-intמחלקות enum פותרות את הבעיות האלה באמצעות תחימה של ערכים ומניעת המרות משתמעות:
enum class Color { Red, Green, Blue };
enum class TrafficLight { Red, Yellow, Green }; // תקין - אין התנגשות
Color c = Color::Red; // חובה להשתמש באופרטור תחום
// int x = c; // שגיאה! אין המרה מרומזת
int x = static_cast<int>(c); // נדרשת המרה מפורשת
// if (c == 0) { } // שגיאה! אי אפשר להשוות ל-int
if (c == Color::Red) { } // תקין - השוואה בין ערכים מאותו סוגאפשר גם לציין את הטיפוס הבסיסי לצורך שליטה בזיכרון:
enum class Status : uint8_t {
Inactive = 0,
Active = 1,
Pending = 2
};מחלקות enum משתלבות היטב בתכנוני OOP, הופכות את הקוד לקריא יותר ומאפשרות לזהות שגיאות טיפוסים בזמן הידור במקום בזמן ריצה.
אתגר
קלבואו נבנה מערכת לתעדוף משימות שמשתמשת במחלקות enum כדי לייצג רמות עדיפות שונות וסטטוסים של משימות. כך נראה כיצד מחלקות enum מספקות בטיחות טיפוסים ומונעות סוגי באגים שמתרחשים עם enums מסורתיים.
תארגנו את הקוד בשלושה קבצים:
TaskTypes.h: הגדירו את מחלקות ה-enum עבור מערכת המשימות.צרו מחלקת enum בשם
Priorityעם הערכים:Low,Medium,Highו-Critical. ציינו אתintכטיפוס הבסיס, עם הערכים 1, 2, 3 ו-4, בהתאמה.צרו מחלקת enum בשם
Statusעם הערכים:Pending,InProgressו-Completed.ספקו שתי פונקציות עזר:
priorityToString(Priority p): מחזירה את העדיפות כמחרוזת ("Low", "Medium", "High", or "Critical")statusToString(Status s): מחזירה את הסטטוס כמחרוזת ("Pending", "InProgress", or "Completed")
Task.h: הגדירו מחלקתTaskשמשתמשת במחלקות ה-enum שלכם.ה-
Taskשלכם צריכה לאחסן שם (מחרוזת),Priorityו-Status. הבנאי צריך לקבל שם ועדיפות, כאשר הסטטוס מוגדר כברירת מחדל ל-Status::Pending.ממשו את המתודות הבאות:
setStatus(Status s): מעדכנת את הסטטוס של המשימהgetPriorityValue(): מחזירה את הערך השלם הבסיסי של העדיפות באמצעותstatic_castdisplay(): מדפיסה את המשימה בתבנית:[name] - Priority: [priority] (Value: [value]) - Status: [status]
main.cpp: קראו שלושה קלטים:- שם המשימה (מחרוזת)
- רמת העדיפות כמספר שלם (1=Low, 2=Medium, 3=High, 4=Critical)
- הסטטוס כמספר שלם (0=Pending, 1=InProgress, 2=Completed)
צרו
Taskעם השם והעדיפות הנתונים (המירו את המספר השלם לערך המתאים של ה-enumPriorityבאמצעותstatic_cast). לאחר מכן הגדירו את הסטטוס שלה על סמך הקלט השלישי. לבסוף, קראו ל-display()כדי להציג את פרטי המשימה.
לדוגמה, עם הקלטים Fix bug, 3 ו-1:
Fix bug - Priority: High (Value: 3) - Status: InProgressעם הקלטים Write docs, 1 ו-2:
Write docs - Priority: Low (Value: 1) - Status: Completedעם הקלטים Deploy app, 4 ו-0:
Deploy app - Priority: Critical (Value: 4) - Status: Pendingשימו לב כיצד מחלקות ה-enum שומרות על ערכי העדיפות והסטטוס נפרדים לחלוטין. אי אפשר להשוות בטעות בין Priority ל-Status או להקצות אחד לשני. השימוש המפורש ב-static_cast, שנדרש להמרה בין מספרים שלמים לערכי enum, מבהיר את כוונת הקוד ומונע באגים חמקמקים.
נסו בעצמכם
#include <iostream>
#include <string>
#include "Task.h"
using namespace std;
int main() {
// קרא את הקלטים
string taskName;
int priorityInt;
int statusInt;
getline(cin, taskName);
cin >> priorityInt;
cin >> statusInt;
// TODO: צור Task עם השם והעדיפות שניתנו
// השתמש ב-static_cast כדי להמיר את priorityInt לערך enum מסוג Priority
// TODO: הגדר את מצב המשימה על סמך statusInt
// השתמש ב-static_cast כדי להמיר את statusInt לערך enum מסוג Status
// TODO: קרא ל-display() כדי להציג את פרטי המשימה
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סיכום — מחשבון צורות11מושגי OOP מתקדמים
הרכבה לעומת הורשהMixins באמצעות CRTPאידיום Pimplמחיקת טיפוסיםמחלקות Enum וטיפוסיות חזקהטיפול בחריגות ב-OOPהיררכיות חריגות מותאמות אישית3בנאים והורסים
בנאי ברירת מחדלבנאי עם פרמטריםבנאי העתקהבנאי העברהרשימות אתחול בבנאיםבנאים מאציליםמעמיקים בנושא הורסיםכלל השלושה / החמישה / האפססיכום – מחלקת String6העמסת אופרטורים
מבוא להעמסת אופרטוריםהעמסת אופרטורים אריתמטייםהעמסת אופרטורי השוואהאופרטורי זרםהעמסת אופרטור השמההעמסת האופרטורים [] ו־()אופרטורים להמרת טיפוסיםסיכום – מחלקת Matrix9תבניות
תבניות פונקציהתבניות מחלקההתמחות תבניותתבניות ארגומנטים משתנותיסודות SFINAE ותכונות טיפוסיםסיכום – מכל גנריתרגלו בעצמכם: קומפיילר C++ אונליין