سلسلة ++c للمحترفين الدرس 35: التكرار Iteration والتعداد Enumeration في Cpp


محمد الميداوي

التكرار 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





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن