Menu

Enum ב-C++: enum class מול enum רגיל

למדו enums ב-C++: איך מצהירים עליהם, למה enum class עם טווח בטוח יותר מ-enum רגיל, ערכים מותאמים אישית, switch על ערכי enum, והמרה אל מספרים שלמים וחזרה.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

בשביל מה יש enum

struct מקבץ כמה ערכים קשורים לאובייקט אחד. enum פותר בעיה אחרת: הוא נותן שמות לקבוצה קטנה וקבועה של אפשרויות. במקום לזכור ש-0 אומר "אדום", 1 אומר "ירוק" ו-2 אומר "כחול", כותבים Color::Red והמהדר שומר עליכם.

שימוש ב-enum בכל פעם שמשתנה יכול להיות רק אחד מתוך כמה מצבים בעלי שם, צבע של רמזור, צורה של קלף, סטטוס של חיבור, הופך את הקוד למתעד את עצמו ומאפשר למהדר לתפוס שגיאות הקלדה ומקרים חסרים שמספרים שלמים חשופים לעולם לא היו תופסים.

הצהרה על enum

ב-C++ מודרנית יש שני סוגים. התחילו בזה שכדאי להשתמש בו כמעט תמיד: enum class עם טווח. רושמים את השמות, וניגשים לכל ערך דרך השם של ה-enum עם :::

שימו לב שכותבים Color::Green, אף פעם לא Green לבד. הערכים עצמם הם רק תוויות: משווים אותם, משימים אותם ומעבירים אותם, אבל רק לעיתים רחוקות אכפת לכם מהמספר שמתחתם. כברירת מחדל Red הוא 0, Green הוא 1 ו-Blue הוא 2, בספירה עולה מאפס.

enum רגיל מול enum class

ה-enum הישן בלי טווח (בלי מילת המפתח class) מפיל את השמות שלו ישירות לטווח הסובב וממיר את עצמו ל-int לבד. זה נשמע נוח, אבל זה גורם לשתי בעיות אמיתיות:

enum Color { Red, Green, Blue };
enum Fruit { Apple, Banana, Red };  // error: 'Red' already declared

enum Status { Active, Inactive };
int x = Active;          // compiles silently - is this what you meant?
if (Active == Banana) {  // compares unrelated enums via int - allowed!
}

מכיוון שערכים של enum רגיל הם שמות גלובליים, שני enums יכולים להתנגש רק בגלל תווית משותפת. ומכיוון שהם הופכים ל-int, המהדר משווה בשמחה ערכים מ-enums שאין ביניהם שום קשר. enum class עם טווח מתקן את שתי הבעיות: השמות חיים בתוך הטיפוס, והטיפוס לא יהפוך בשקט ל-int:

כלל האצבע: השתמשו ב-enum class כברירת מחדל. חזרו ל-enum רגיל רק כשאתם רוצים במפורש את ההמרה המרומזת ל-int, למשל בקבועי דגלים ישנים בסגנון C.

ערכים מותאמים אישית והטיפוס שמתחת

אפשר לתת מספרים מפורשים לערכי ה-enum. אלה שלא מקבלים ערך ממשיכים לספור מהקודם, וזה שימושי לדברים כמו קודי סטטוס של HTTP או דגלי ביטים:

כל enum מגובה בטיפוס שלם, int כברירת מחדל. אפשר לקבע אותו לטיפוס קטן יותר כשהגודל חשוב, למשל כשמאחסנים הרבה enums במבנה דחוס או כשצריך להתאים לפורמט תקשורת:

בחירת הטיפוס שמתחת גם מבטיחה את הטווח שהערכים שלכם חייבים להיכנס אליו: enum מסוג uint8_t לא יכול להחזיק ערך מעל 255.

המרה בין enum ל-int

enum class עם טווח אף פעם לא מומר באופן מרומז, וזו כל הנקודה. כשבאמת צריך את המספר, כדי להדפיס אותו, לגשת לאינדקס במערך או לקרוא אותו מקובץ, השתמשו ב-static_cast. המרה מ-enum ל-int תמיד בטוחה:

המרה של int בחזרה ל-enum היא הכיוון המסוכן. ההמרה לא בודקת שהמספר מתאים לערך enum אמיתי: היא תיתן לכם ערך שמבחינה טכנית נמצא מחוץ לקבוצת השמות של ה-enum:

Suit s = static_cast<Suit>(2);   // fine - that's Clubs
Suit bad = static_cast<Suit>(99); // compiles, but 99 is not a valid Suit
// using `bad` in a switch or as an array index is a lurking bug

אם המספר מגיע מקלט של משתמש או מקובץ, בדקו את הטווח בעצמכם לפני ההמרה, אחרת תיצרו ערך שאף case לא מטפל בו, מקור עדין ל-undefined behavior בהמשך הדרך.

שימוש ב-enums עם switch

מכיוון ש-enum הוא "אחד מתוך קבוצה קבועה", הוא משתלב מושלם עם switch. כשמכסים כל ערך, מהדרים רבים יזהירו אתכם אם בהמשך תוסיפו ערך חדש ותשכחו לטפל בו: בטיחות בחינם שלא מקבלים ממספרים שלמים גולמיים:

מלכודת אחת: אין דרך מובנית להדפיס את השם של ערך enum. cout << TrafficLight::Red לא יתהדר עבור enum עם טווח, ואפילו עבור enum רגיל הוא מדפיס את המספר ולא "Red". switch קטן או טבלת חיפוש כמו זו שלמעלה הם הדרך המקובלת להפוך enum למחרוזת קריאה.

הבא בתור: חריגות

Enums ו-structs מאפשרים לכם למדל איך הנתונים שלכם נראים. אבל תוכניות אמיתיות צריכות להתמודד גם עם דברים שמשתבשים: קובץ שלא נפתח, מספר שלא מצליח להתפרש, ערך מחוץ לטווח. C++ מטפלת במסלולי הכישלון האלה עם חריגות (exceptions), וזה העמוד הבא.

שאלות נפוצות

מה ההבדל בין enum ל-enum class ב-C++?

enum רגיל מדליף את השמות שלו לטווח הסובב וממיר את עצמו באופן מרומז ל-int, מה שגורם להתנגשויות שמות ולהשוואות בטעות. enum class עם טווח שומר את השמות שלו בתוך ה-enum (Color::Red) ומסרב להפוך ל-int בלי המרה מפורשת. העדיפו enum class ב-C++ מודרנית: הוא בטוח מבחינת טיפוסים ונמנע מהמלכודות הקלאסיות.

איך ממירים enum ל-int ב-C++?

enum רגיל מומר באופן מרומז, כך ש-int n = Red; פשוט עובד. enum class עם טווח דורש המרה מפורשת: int n = static_cast<int>(Color::Red);. כדי ללכת בכיוון ההפוך, ממירים את ה-int בחזרה: Color c = static_cast<Color>(2);, אבל היזהרו: בזמן ריצה לא נבדק שהערך הוא ערך enum תקין.

מאיזה ערך מתחיל ה-enum הראשון ב-C++?

כברירת מחדל הערך הראשון הוא 0, וכל אחד אחריו גדול באחד מהקודם. כך שב-enum class Level { Low, Mid, High };, Low הוא 0, Mid הוא 1 ו-High הוא 2. אפשר לתת ערכים מפורשים לכל אחד מהם כדי לעקוף את זה.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל