Menu
flag Ar iconالعربيةdown icon

enum في لغة C: ثوابت مسمّاة وترقيم تلقائي وتحويل enum إلى نصّ

كيفية تعريف enum في C - الترقيم التلقائي، والقيم الصريحة، واستخدام التعدادات في switch ليلتقط المترجم الحالات الناقصة، وتحويل enum إلى سلسلة نصية، وأعراف التسمية التي تُبقيها مقروءة.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

الشيفرة المليئة بالأعداد المجرّدة شيفرة لا يستطيع أحد قراءتها. فـ if (state == 2) لا تخبرك شيئًا؛ وif (state == STATE_RUNNING) تخبرك كل شيء. وenum هي طريقة C لمنح مجموعة صغيرة ثابتة من الأعداد الصحيحة أسماءً لائقة - وخلافًا لكومة من #define، تأتي مجمّعة في نوع واحد يعرفه المترجم.

تعريف enum

يسمّي التصريح وسمًا ويسرد المعدودات بين قوسين معقوفين:

أمران يجدر ملاحظتهما فورًا. أولًا، يبدأ الترقيم من 0 ويزيد بواحد، فتكون RED صفرًا وGREEN واحدًا وBLUE اثنين. ثانيًا، أسماء المعدودات مرئية في كل مكان في النطاق المحيط - فتكتب GREEN، لا Color.GREEN. وهي تعيش في فضاء الأسماء نفسه الذي تعيش فيه متغيّراتك، ولهذا تصرخ بها شيفرة C عرفًا أو تسبقها بالنوع (COLOR_RED) تجنّبًا للتصادمات.

وطباعة enum تستخدم %d، لأن قيمة التعداد هي قيمة عددية صحيحة.

القيم الصريحة

أسنِد لأي معدود ثابتًا صريحًا، فتواصل التي بعده من هناك:

ولا يلزم أن تكون القيم فريدة ولا مرتّبة - فقد يتشارك معدودان عددًا واحدًا، وهو أمر مفيد أحيانًا للأسماء البديلة (COLOR_DEFAULT = COLOR_BLACK). ويجب أن تكون ثوابت عددية صحيحة وقت الترجمة، فلا يمكنك حساب واحدة من متغيّر.

وثمّة صيغة شائعة تستخدم هذا لتسجيل العدد:

enum Suit {
    CLUBS, DIAMONDS, HEARTS, SPADES,
    SUIT_COUNT           /* 4 تلقائيًا - ويبقى صحيحًا إن أضفت نوعًا */
};

SUIT_COUNT ليست نوعًا حقيقيًا من الورق؛ بل عددها، ويصونه الترقيم التلقائي مجانًا. ويمكن للحلقات وأحجام المصفوفات استخدامه دون أن يتقادم أبدًا.

typedef enum

كما هي الحال مع البنى، اسم النوع الكامل في C هو enum Color، وtypedef يُسقط الكلمة المفتاحية:

ولأن المعدودات أعداد صحيحة عادية تبدأ من 0، فإنها تصلح فهارس مصفوفات كذلك - فـ dx[DIR_EAST] تعمل دون أي تحويل. وتلك أنفع خاصية في تعداد يبدأ من الصفر.

التعدادات في switch

اقتران enum بـ switch هو حيث يبدأ النوع بردّ ثمنه. ترجم بـ -Wall فيخبرك المترجم حين تنسى حالة.

أضف STATE_FAILED إلى التعداد فيحذّر GCC: enumeration value 'STATE_FAILED' not handled in switch. وذلك التحذير أقرب ما لدى C إلى فحص الشمول، وهو سبب قوي لكتابة هذه الجمل بلا حالة default: - فـ default تُسكت التحذير وتدع الحالات الجديدة تمرّ دون معالجة. ضع الاحتياطي بعد switch بدلًا من ذلك، كما تفعل describe.

تحويل enum إلى سلسلة نصية

لا تحتفظ C بأي أسماء أثناء التشغيل؛ فـ printf("%s", GREEN) لا يمكن أن تعمل لأن GREEN ليست سوى 1. ونمطان يحلّان ذلك.

نسخة switch، التي يفحصها المترجم:

ونسخة جدول البحث، أقصر لكنها غير مفحوصة - فإن انحرفت المصفوفة والتعداد عن بعضهما، لا شيء يحذّر:

وفهارس المصفوفة المعيَّنة ([RED] = "RED") تُبقي الأسماء على الأقل مرتبطة بمواضعها، فلا يمكن لإعادة ترتيب التعداد أن تخلط السلاسل بصمت. وفحص الحدود مهمّ لأن لا شيء يمنع مستدعيًا من تمرير قيمة خارج التعداد - انظر القسم التالي.

التعدادات ليست محكمة الإغلاق

متغيّر التعداد عدد صحيح في العمق، ولا تراقب C المدى:

الحساب على المعدودات يعمل، والمقارنات مع أعداد صحيحة عادية تعمل، والتحويل إلى قيمة خارج المدى مقبول. فعامِل enum على أنه توثيق ممتاز وتلميح جيد للمترجم، لا ضمانة. وتحقّق من أي قيمة تصل من خارج برنامجك - ملف أو وسيط أو رسالة شبكة - قبل أن توزّعها بـ switch.

الحجم والتخزين

في C17، يجب أن يتّسع كل معدود في int - فـ enum Huge { BIG = 3000000000 }; مخالفة قيد يرفضها مترجم صارم (-pedantic-errors)، وإن قبلها gcc وclang توسعةً في وضعهما الافتراضي. والمعدودات نفسها من النوع int، بينما حجم نوع التعداد يحدّده التطبيق (وعمليًا هو int):

(وترفع C23 هذا القيد: فقد يتجاوز المعدود مدى int، بل يمكنك اختيار النوع الأساسي بـ enum E : unsigned long { ... }. وإلى أن تستهدف أدواتك C23، أبقِ المعدودات داخل int.)

ولأن الاختيار المضبوط يحدّده التطبيق، لا تكتب أبدًا enum مباشرةً في ملف ثنائي أو حزمة شبكة وتتوقّع أن يقرأها جهاز آخر. حوّل إلى نوع ثابت العرض مثل uint8_t أو uint32_t من <stdint.h> عند الحدّ.

enum مقابل ‎#define

كلاهما يمنح اسمًا لعدد. والتعداد أفضل عمومًا:

enum#define
يعالجهالمترجمالمعالج الأولي
التجميعالقيم المترابطة تتشارك نوعًاكل ثابت مستقلّ
الترقيم التلقائينعملا، ترقّم يدويًا
مرئي في المنقّحغالبًا، بالاسملا، العدد فقط
تحذير شمول switchنعملا
النطاقيحترم نطاق الكتلةنصّي، حتى #undef

الجأ إلى #define حين لا يكون الثابت عددًا صحيحًا (سلسلة نصية أو عدد عشري) أو حين يجب أن يكون صالحًا لاستخدام المعالج الأولي نفسه، كما في الترجمة الشرطية. أما لمجموعة من الحالات أو الرايات أو الأصناف الصحيحة المترابطة، فاستخدم enum.

أعراف التسمية

تتصادم التعدادات بسهولة لأن أسماءها تقع في النطاق العادي. والأعراف التي تُبقي قواعد شيفرة C الكبيرة عاقلة:

  • اسبق كل معدود باسم النوع: COLOR_RED وSTATE_IDLE وHTTP_OK. فـ RED بلا بادئة في برنامج رسوميات ستلتقي في النهاية بـ RED شخص آخر.
  • اكتب المعدودات بأحرف كبيرة، مطابقةً لشكل الثوابت الأخرى.
  • سمِّ النوع بصيغة المفرد بـ PascalCase أو snake_case_t: Color أو State أو color_t، باتّساق عبر المشروع.
  • أضف حارس _COUNT أخيرًا حين تنوي المرور أو الفهرسة بالتعداد.

الأسئلة الشائعة

كيف تعرّف enum في لغة C؟

enum Color { RED, GREEN, BLUE }; يعرّف نوعًا قيمه هي الثوابت الثلاثة المسمّاة. وافتراضيًا تُرقَّم من 0، فتكون RED صفرًا وGREEN واحدًا وBLUE اثنين. عرّف متغيّرًا بـ enum Color c = GREEN;، أو أضف typedef لتكتب Color c فقط.

هل يمكن إسناد قيم محدّدة إلى enum في لغة C؟

نعم: enum Status { OK = 200, NOT_FOUND = 404, ERROR = 500 };. ويمكنك ضبط بعضها وترك البقية تتبع - فأي اسم بلا قيمة صريحة يواصل من الذي قبله، فـ enum { A = 10, B, C }; يعطي B = 11 وC = 12.

كيف تحوّل enum إلى سلسلة نصية في لغة C؟

لا تملك C طريقة مدمجة؛ فأسماء المعدودات غير موجودة أثناء التشغيل. والنهج المعتاد دالة صغيرة فيها switch تُعيد قيمة نصية حرفية لكل قيمة، أو مصفوفة من السلاسل مفهرسة بالتعداد. ونسخة switch أأمن لأن -Wall تحذّر حين لا يكون لمعدود جديد حالة.

ما حجم enum في لغة C؟

يحدّده التطبيق، لكنه عمليًا sizeof(int) - أي 4 بايتات على معظم الأنظمة. فالمترجم يختار نوعًا عدديًا قادرًا على حمل كل معدود. لا تفترض حجمًا معيّنًا عند ترتيب ملف ثنائي أو حزمة شبكة؛ استخدم نوعًا ثابت العرض من <stdint.h> هناك.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن