التكرار Iteration
do
تقدِّم تعليمة do
حلقة do-while
، انظر المثال التالي حيث نحصل على المحرف التالي غير الفارغ من مجرى الدخل القياسي:
char read_char() { char c; do { c = getchar(); } while (isspace(c)); return c; }
while
تقدّم تعليمة while
حلقة while.
int i = 0; // اطبع عشر نجمات while (i < 10) { putchar('*'); i++; }
حلقة for النطاقية range-based
std::vector primes = {2, 3, 5, 7, 11, 13}; for (auto prime: primes) { std::cout << prime << std::endl; }
for
تقدّم تعليمة for
حلقة for
، يمكن استخدامها في C++ 11 والإصدارات الأحدث لتقديم حلقة for النطاقية (range-based for loop).
// اطبع عشر نجمات for (int i = 0; i < 10; i++) { putchar('*'); }
التكرار على تعداد
ليس هناك حلٌّ مضمَّن مسبقًا للتكرار (iterate) على كائن تعداد (enumeration)، لكن هناك عدة طرق أخرى لذلك:
-
بالنسبة للتعدادات (
enum
) ذات القيم المتتالية:
enum E { Begin, E1 = Begin, E2, // .. En, End }; for (E e = E::Begin; e != E::End; ++e) { // e افعل شيئا ما بـ }
الإصدار ≥ C++ 11
يجب تنفيذ العامل operator ++
مع enum class
:
E& operator ++ (E& e) if (e == E::End) { throw std::out_of_range("for E& operator ++ (E&)"); } e = E(static_cast < std::underlying_type < E > ::type > (e) + 1); return e; }
-
استخدام حاوية كمتّجه (
std::vector
):
enum E { E1 = 4, E2 = 8, // .. En }; std::vector<E> build_all_E() { const E all[] = {E1, E2, /*..*/ En}; return std::vector<E>(all, all + sizeof(all) / sizeof(E)); } std::vector < E > all_E = build_all_E();
ثمّ:
for (std::vector<E>::const_iterator it = all_E.begin(); it != all_E.end(); ++it) { E e = *it; // e افعل شيئا ما بـ }
الإصدار ≥ C++ 11
-
أو يمكن استخدام
std::initializer_list
:
enum E { E1 = 4, E2 = 8, // .. En }; constexpr std::initializer_list < E > all_E = {
ثم بعد ذلك:
for (auto e: all_E) { // e افعل شيئا ما بـ }
التعدادات النطاقية (Scoped enums)
قدّمت C++ 11 ما يعرف باسم التعداد النطاقي، وهي تعدادات يلزَم تأهيل أعضائها عبر enumname::membername
. ويُصرَّح عن التعدادات النطاقية باستخدام الصيغة enum class
، فمثلًا لتخزين ألوان قوس قزح، نكتب ما يلي:
enum class rainbow { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET };
وللوصول إلى لون معين:
rainbow r = rainbow::INDIGO;
لا يمكن تحويل أصناف التعدادات (enum class
) ضمنيًا إلى أعداد صحيحة (int
) بدون تحويل النوع (cast). لذلك فإنّ التعبير int x = rainbow::RED
غير صالح.
تتيح لك التعدادات النطاقية أيضًا تحديد النوع الأساسي (underlying type)، أي النوع المستخدم لتمثيل العضو. والذي يساوي افتراضيًا النوع int
. مثلًا، في لعبة تيك تاك تو (Tic-Tac-Toe)، يمكنك تخزين القطع كما يلي:
enum class piece: char { EMPTY = '\0', X = 'X', O = 'O', };
لعلك انتبهت إلى أن التعداد enum
يمكن أن يحتوي فاصلة زائدة بعد العضو الأخير.
التصريح المسبق عن التعدادات في C++ 11
يمكن التصريح المسبق (forward declaration) عن التعدادات النطاقية على النحو التالي:
... enum class Status; // تصريح مسبق Status doWork(); // استخدام التصريح المسبق ... enum class Status { Invalid, Success, Fail }; Status doWork() // يلزم تصريح كامل لأجل التقديم { return Status::Success; }
أمّا التعدادات غير النطاقية، فيمكن التصريح عنها مُسبقًا عبر:
... enum Status: int; // تصريح مسبق، يلزم نوع صريح Status doWork(); // استخدام التصريح المسبق ... enum Status: int { Invalid = 0, Success, Fail }; // يلزم مطابقة نوع التصريح المسبق static_assert(Success == 1);
انظر إن شئت مثال "تاجر الفاكهة الأعمى" الأكثر تعقيدًا.
التصريح بالتعداد Enumeration Declaration
تسمح التعدادات القياسية للمستخدمين بالتصريح عن اسم يمثّل مجموعة من الأعداد الصحيحة، وتسمى هذه الأسماء مجتمعةً عدّادات (enumerators)، وتُعرّف التِّعدادات والعَدّادات المرتبطة بها على النحو التالي:
enum myEnum { enumName1, enumName2, };
يعدّ التعداد نوعًا متميزًا عن جميع الأنواع الأخرى، ويكون اسم هذا النوع في المثال أعلاه هو myEnum
، ويُتوقّع أن تُخمِّن كائنات هذا النوع قيمة العدّاد داخل التعداد.
العدّادات المُصرّح عنها ضمن التعداد تكون قيمًا ثابتة من نوع ذلك التعداد، ورغم أنّ العدّادات تُعلَن ضمن النوع، إلا أنّ عامل النطاق - scope operator - ::
ليس ضروريًا للوصول إلى الاسم. لذلك، فاسم العدّاد الأول سيكون enumName1
.
الإصدار ≥ C++ 11
يمكن استخدام عامل النطاق -اختياري- للوصول إلى عدّاد داخل التعداد. لذلك، يمكن كتابة enumName1
على النحو التالي myEnum::enumName1
.
وتُسند إلى العدّادات قيم عددية صحيحة تبدأ من 0 وتزداد بـ 1 لكل عدّاد إضافي في التعداد. لذلك في المثال أعلاه، فإنّ قيمة enumName1
تساوي 0، وقيمة enumName2
تساوي 1. كذلك يمكن إسناد قيمة معيّنة إلى العدّادات من قبل المستخدم؛ وعندئذ يجب أن تكون تلك القيمة تعبيرًا عدديًا صحيحًا ثابتًا. أيضًا، سيُسند إلى العدّادات التي لم تُوفّر قيمها صراحة "قيمة العداد السابق + 1".
enum myEnum { enumName1 = 1, // 1 القيمة ستكون enumName2 = 2, // 2 القيمة ستكون enumName3, // القيمة ستكون 5، أي القيمة السابقة + 1 enumName4 = 7, // 7 القيمة ستكون enumName5, // 8 القيمة ستكون enumName6 = 5, // القيمة ستكون 5، يجوز العودة إلى الوراء enumName7 = 3, // القيمة ستكون 3، يجوز إعادة استخدام الأعداد enumName8 = enumName4 + 2, // القيمة ستكون 9، يجوز أخذ العدادات السابقة وتعديلها };
التعدادات في تعليمات switch
يشيع استخدام العدادات مع عبارات switch، ومن ثمّ فهي تظهر عادة في أجهزة الحالة (state machines). من الميزات المفيدة التي تقدّمها التعدادات في عبارات switch
هو أنّه في حال لم تُضمَّن أيّ تعليمة افتراضية في switch
، ولم تُستخدم جميع قيم التعداد، فسيُطلِق المصرّف تحذيرًا.
enum State { start, middle, end }; ... switch (myState) { case start: ... case middle: ... } // warning: enumeration value 'end' not handled in switch [-Wswitch]
هذا الدرس جزء من سلسلة دروس عن C++.
ترجمة -وبتصرّف- للفصل Chapter 68: Enumeration من الكتاب C++ Notes for Professionals
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.