ما الغرض من enum
تجمع البنية عدة قيم مترابطة في كائن واحد. أما enum فيحل مشكلة مختلفة: فهو يمنح أسماءً لمجموعة صغيرة وثابتة من الخيارات. فبدلًا من تذكّر أن 0 تعني "أحمر" و1 تعني "أخضر" و2 تعني "أزرق"، تكتب Color::Red ويبقيك المُصرِّف على الصواب.
اللجوء إلى enum كلما كان المتغير لا يمكن أن يكون إلا واحدًا من حفنة من الحالات المسمّاة — لون إشارة المرور، أو نوع ورقة اللعب، أو حالة الاتصال — يجعل الشيفرة موثِّقة لنفسها ويتيح للمُصرِّف اكتشاف الأخطاء المطبعية والحالات الناقصة التي لن تكتشفها الأعداد الصحيحة المجردة أبدًا.
تعريف enum
تتوفر في ++C الحديثة صيغتان. ابدأ بالصيغة التي ينبغي أن تلجأ إليها في معظم الأحيان: enum class ذو النطاق. تُعدّد الأسماء، ويُوصَل إلى كل عنصر تعداد عبر اسم التعداد مع :::
لاحظ أنك تكتب 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!
}
ولأن عناصر التعداد العادية أسماء عامة، فقد يتعارض تعدادان لمجرد أنهما يتشاركان تسمية واحدة. ولأنها تتدنّى إلى int، يقارن المُصرِّف بكل أريحية بين قيم من تعدادين لا علاقة بينهما البتة. ويُصلح enum class ذو النطاق كلا الأمرين: فالأسماء تعيش داخل النوع، والنوع لن يتحول بصمت إلى int:
القاعدة العملية: استخدم enum class افتراضيًا. ولا تعُد إلى enum العادي إلا حين تريد تحديدًا التحويل الضمني إلى int، كما في ثوابت الرايات القديمة على نمط لغة C.
القيم المخصصة والنوع الأساسي
يمكنك إسناد أرقام صريحة لعناصر التعداد. وأي عنصر تتركه دون إسناد يواصل العدّ تصاعديًا من العنصر السابق، وهو أمر مفيد لأشياء مثل رموز حالة HTTP أو رايات البتات:
كل تعداد مدعوم بنوع عددي صحيح — وهو int افتراضيًا. ويمكنك تثبيته على نوع أصغر حين يهمّك الحجم، مثلًا عند تخزين عدد كبير من التعدادات في بنية مضغوطة أو لمطابقة صيغة نقل بيانات:
اختيار النوع الأساسي يضمن أيضًا المدى الذي يجب أن تتسع له قيمك: فتعداد من نوع uint8_t لا يمكنه استيعاب قيمة تتجاوز 255.
التحويل بين enum و int
لا يتحول enum class ذو النطاق ضمنيًا أبدًا، وهذا هو جوهر الفكرة. وحين تحتاج فعلًا إلى الرقم — لطباعته، أو لفهرسة مصفوفة، أو لقراءته من ملف — فالجأ إلى static_cast. والانتقال من enum إلى int آمن دائمًا:
أما تحويل int عائدًا إلى تعداد فهو الاتجاه الخطير. فالتحويل لا يتحقق من أن الرقم يقابل عنصر تعداد حقيقيًا — بل سيسلّمك قيمة تقع تقنيًا خارج مجموعة أسماء التعداد:
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 على الإطلاق — وهو مصدر خفي للسلوك غير المُعرَّف لاحقًا.
استخدام التعدادات مع switch
ولأن التعداد هو "واحد من مجموعة ثابتة"، فإنه يتلاءم تمامًا مع switch. وحين تغطّي كل عناصر التعداد، تحذّرك مُصرِّفات كثيرة إن أضفت لاحقًا قيمة جديدة ونسيت معالجتها — أمان مجاني لا تحصل عليه من الأعداد الصحيحة الخام:
ثمة مزلق واحد: لا توجد طريقة مدمجة لطباعة اسم عنصر التعداد. فالتعبير cout << TrafficLight::Red لن يُصرَّف لتعداد ذي نطاق، وحتى مع تعداد عادي فإنه يطبع الرقم لا "Red". وتُعدّ كتلة switch صغيرة أو جدول بحث كالذي بالأعلى الطريقةَ المعتادة لتحويل تعداد إلى سلسلة نصية يقرؤها البشر.
التالي: الاستثناءات
تتيح لك التعدادات والبنى نمذجة كيف تبدو بياناتك. لكن البرامج الحقيقية عليها أيضًا أن تتعامل مع ما يسير على نحو خاطئ — ملف يأبى أن يُفتح، ورقم يتعذّر تحليله، وقيمة خارج المدى. تعالج ++C مسارات الإخفاق هذه عبر الاستثناءات، وذلك هو موضوع الصفحة التالية.
الأسئلة الشائعة
ما الفرق بين enum و enum class في ++C؟
يُسرّب enum العادي أسماءه إلى النطاق المحيط ويتحول ضمنيًا إلى int، مما يسبب تعارضات في الأسماء ومقارنات غير مقصودة. أما enum class ذو النطاق فيُبقي أسماءه داخل التعداد (Color::Red) ويرفض التحول إلى int دون تحويل صريح. فضّل enum class في ++C الحديثة: فهو آمن من حيث الأنواع ويتجنب المزالق الكلاسيكية.
كيف تحوّل enum في ++C إلى int؟
يتحول enum العادي ضمنيًا، لذا فإن int n = Red; يعمل مباشرةً. أما enum class ذو النطاق فيتطلب تحويلًا صريحًا: int n = static_cast<int>(Color::Red);. وللاتجاه المعاكس، حوّل الـ int مرة أخرى: Color c = static_cast<Color>(2); — لكن انتبه، فوقت التشغيل لا يتحقق من أن القيمة عنصر تعداد صالح.
من أي قيمة يبدأ أول enum في ++C؟
افتراضيًا يكون أول عنصر تعداد 0، وكل عنصر بعده أكبر بواحد من سابقه. لذا في enum class Level { Low, Mid, High }; تكون Low هي 0 وMid هي 1 وHigh هي 2. يمكنك إسناد قيم صريحة لأيٍّ منها لتجاوز هذا السلوك.