2008-11-04 7 views
214

Я просто заметил, что вы не можете использовать стандартные математические операторы для перечисления, такие как ++ или + =Как я могу перебирать перечисление?

Так что же лучший способ перебора всех значений в перечислении C++?

+0

Связанные предметы имеют некоторые интересные ответы. – Tony 2013-06-13 01:27:31

+0

Эти ответы, похоже, не охватывают проблему, что `int` может быть недостаточно большой! (`[C++ 03: 7.2/5]`) – 2013-06-21 10:03:56

+0

Интересно, что вы можете определить `operator ++` на enums; однако, вы можете сделать `for (Enum_E e = (Enum_E) 0; e weberc2 2014-06-25 22:52:54

ответ

199

Типичный способ заключается в следующем:

enum Foo { 
    One, 
    Two, 
    Three, 
    Last 
}; 

for (int fooInt = One; fooInt != Last; fooInt++) 
{ 
    Foo foo = static_cast<Foo>(fooInt); 
    // ... 
} 

Конечно, это ломается, если заданы значения перечислений:

enum Foo { 
    One = 1, 
    Two = 9, 
    Three = 4, 
    Last 
}; 

Это свидетельствует о том, что перечисление не предназначен для перебора , Типичным способом обработки перечисления является использование его в инструкции switch.

switch (foo) 
{ 
    case One: 
     // .. 
     break; 
    case Two: // intentional fall-through 
    case Three: 
     // .. 
     break; 
    case Four: 
     // .. 
     break; 
    default: 
     assert(! "Invalid Foo enum value"); 
     break; 
} 

Если вы действительно хотите перечислить, введите значения enum в вектор и перейдете к этому. Это также будет иметь дело с указанными значениями перечисления.

+12

Обратите внимание, что в первой части примера, если вы хотите использовать «i» в качестве перечисления Foo, а не int, вам нужно будет статично отличить его: static_cast (i) – Clayton 2009-03-17 22:30:40

+4

Также вы пропустите Последнее в петля. Должно быть <= Last – Tony 2013-06-13 01:24:19

+11

@Tony Last предназначен для пропуска. Если вы хотите добавить больше перечислений позже, добавьте их до последнего ... цикл в первом примере все равно будет работать. Используя «поддельное» последнее перечисление, вам не нужно обновлять условие завершения в цикле for до последнего «реального» перечисления каждый раз, когда вы хотите добавить новое перечисление. – timidpueo 2013-08-03 21:06:08

8

Вы не можете с перечислением. Возможно, перечисление не подходит для вашей ситуации.

Общее соглашение - это имя последнего значения перечисления, что-то вроде MAX, и использовать его для управления циклом с использованием int.

0

У C++ нет интроспекции, поэтому вы не можете определить этот тип вещей во время выполнения.

17

Если перечисление начинается с 0 и приращение всегда 1.

enum enumType 
{ 
    A = 0, 
    B, 
    C, 
    enumTypeEnd 
}; 

for(int i=0; i<enumTypeEnd; i++) 
{ 
    enumType eCurrent = (enumType) i;    
} 

Если нет, я думаю, только почему, чтобы создать что-то вроде

vector<enumType> vEnums; 

добавить элементы, а также использование нормальные итераторы ....

4

Вы также можете перегрузить операторы increment/decment для вашего перечисленного типа.

6

Вы можете попытаться определить следующий макрос:

#define for_range(_type, _param, _A1, _B1) for (bool _ok = true; _ok;)\ 
for (_type _start = _A1, _finish = _B1; _ok;)\ 
    for (int _step = 2*(((int)_finish)>(int)_start)-1;_ok;)\ 
     for (_type _param = _start; _ok ; \ 
(_param != _finish ? \ 
      _param = static_cast<_type>(((int)_param)+_step) : _ok = false)) 

Теперь вы можете использовать его:

enum Count { zero, one, two, three }; 

    for_range (Count, c, zero, three) 
    { 
     cout << "forward: " << c << endl; 
    } 

Он может быть использован для итерации назад и вперед через неподписанные, целые числа, перечисления и голец :

for_range (unsigned, i, 10,0) 
{ 
    cout << "backwards i: " << i << endl; 
} 


for_range (char, c, 'z','a') 
{ 
    cout << c << endl; 
} 

Несмотря на свое неудобное определение, оно оптимизировано очень хорошо. Я посмотрел на дизассемблер в VC++. Код чрезвычайно эффективен. Не откладывайте, а три для операторов: компилятор будет производить только один цикл после оптимизации! Вы даже можете определить закрываемую петлю:

unsigned p[4][5]; 

for_range (Count, i, zero,three) 
    for_range(unsigned int, j, 4, 0) 
    { 
     p[i][j] = static_cast<unsigned>(i)+j; 
    } 

Вы, очевидно, не можете перебирать перечисленные тип с пробелами.

5

Что-то, что не было рассмотрено в других ответах = если вы используете строго типизированные C++ 11 перечислений, вы не можете использовать ++ или + int на них. В этом случае немного раствора Messier требуется:

enum class myenumtype { 
    MYENUM_FIRST, 
    MYENUM_OTHER, 
    MYENUM_LAST 
} 

for(myenumtype myenum = myenumtype::MYENUM_FIRST; 
    myenum != myenumtype::MYENUM_LAST; 
    myenum = static_cast<myenumtype>(static_cast<int>(myenum) + 1)) { 

    do_whatever(myenum) 

} 
8

слишком сложно эти решения, я делаю так:

enum NodePosition { Primary = 0, Secondary = 1, Tertiary = 2, Quaternary = 3}; 

const NodePosition NodePositionVector[] = { Primary, Secondary, Tertiary, Quaternary }; 

for (NodePosition pos : NodePositionVector) { 
... 
} 
16
#include <iostream> 
#include <algorithm> 

namespace MyEnum 
{ 
    enum Type 
    { 
    a = 100, 
    b = 220, 
    c = -1 
    }; 

    static const Type All[] = { a, b, c }; 
} 

void fun(const MyEnum::Type e) 
{ 
    std::cout << e << std::endl; 
} 

int main() 
{ 
    // all 
    for (const auto e : MyEnum::All) 
    fun(e); 

    // some 
    for (const auto e : { MyEnum::a, MyEnum::b }) 
    fun(e); 

    // all 
    std::for_each(std::begin(MyEnum::All), std::end(MyEnum::All), fun); 

    return 0; 
} 
9

В C++ 11, есть на самом деле альтернатива: создание простого шаблонизированного пользовательского итератора.

давайте предположим, что ваш перечисление является

enum class foo { 
    one, 
    two, 
    three 
}; 

Этот общий код будет делать трюк, достаточно эффективно - место в общий заголовок, он будет служить вам для любого перечислимого вам может понадобиться перебрать:

#include <type_traits> 
template < typename C, C beginVal, C endVal> 
class Iterator { 
    typedef typename std::underlying_type<C>::type val_t; 
    int val; 
public: 
    Iterator(const C & f) : val(static_cast<val_t>(f)) {} 
    Iterator() : val(static_cast<val_t>(beginVal)) {} 
    Iterator operator++() { 
    ++val; 
    return *this; 
    } 
    C operator*() { return static_cast<C>(val); } 
    Iterator begin() { return *this; } //default ctor is good 
    Iterator end() { 
     static const Iterator endIter=++Iterator(endVal); // cache it 
     return endIter; 
    } 
    bool operator!=(const Iterator& i) { return val != i.val; } 
}; 

Вам нужно специализироваться это

typedef Iterator<foo, foo::one, foo::three> fooIterator; 

И тогда вы можете перебирать с помощью ра nge-for

for (foo i : fooIterator()) { //notice the parenteses! 
    do_stuff(i); 
} 

Предполагается, что у вас нет пробелов в вашем перечислении, все еще верно; нет никаких предположений о количестве битов на самом деле необходимы для хранения значения перечисления (благодаря StD :: underlying_type)

1

Для компиляторов MS:

#define inc_enum(i) ((decltype(i)) ((int)i + 1)) 

enum enumtype { one, two, three, count}; 
for(enumtype i = one; i < count; i = inc_enum(i)) 
{ 
    dostuff(i); 
} 

Примечание: это намного меньше кода, чем просто templatized пользовательский ответ итератора.

Вы можете заставить это работать с GCC с помощью typeof вместо decltype, но на данный момент у меня нет такого компилятора, чтобы убедиться, что он скомпилирован.

2

Если вы не хотите загрязнять вас перечислением конечным пунктом COUNT (потому что, возможно, если вы также используете перечисление в коммутаторе, тогда компилятор предупредит вас о недостающем случае COUNT :), вы можете сделать это:

enum Colour {Red, Green, Blue}; 
const Colour LastColour = Blue; 

Colour co(0); 
while (true) { 
    // do stuff with co 
    // ... 
    if (co == LastColour) break; 
    co = Colour(co+1); 
} 
0

Если бы вы знали, что значения перечислений были последовательными, например, Qt: ключ перечисления, вы можете:

Qt::Key shortcut_key = Qt::Key_0; 
for (int idx = 0; etc...) { 
    .... 
    if (shortcut_key <= Qt::Key_9) { 
     fileMenu->addAction("abc", this, SLOT(onNewTab()), 
          QKeySequence(Qt::CTRL + shortcut_key)); 
     shortcut_key = (Qt::Key) (shortcut_key + 1); 
    } 
} 

Он работает, как ожидалось.

1

Я часто делаю это как то

enum EMyEnum 
    { 
     E_First, 
     E_Orange = E_First, 
     E_Green, 
     E_White, 
     E_Blue, 
     E_Last 
    } 

    for (EMyEnum i = E_First; i < E_Last; i = EMyEnum(i + 1)) 
    {} 

или если не последовательны, но с регулярным шагом (например, битовые флаги)

enum EMyEnum 
    { 
     E_First, 
     E_None = E_First, 
     E_Green = 0x1, 
     E_White = 0x2 
     E_Blue = 0x4, 
     E_Last 
    } 

    for (EMyEnum i = E_First; i < E_Last; i = EMyEnum(i << 1)) 
    {} 
1

Один из ответов говорит: «Если бы вы знали, что значения enum были последовательными, например, перечисление Qt: Key ".

Qt :: Значения ключей не являются последовательными.Некоторые сегменты перечислены.

Эта статья посвящена итерации по всем значениям в перечислении. На самом деле это возможно в Qt из-за его использования Meta System Object:

const QMetaObject *metaObject = qt_getQtMetaObject(); 
QMetaEnum keyEnum = metaObject->enumerator(metaObject->indexOfEnumerator("Key")); 
for (int i = 0; i < keyEnum.keyCount(); ++i) { 
    qDebug() << keyEnum.key(i); 
} 

Смотрите также QObject :: метаобъект() и Q_ENUM макро.

Я думаю, что этот материал станет проще с C++ 20? Но я не заглянул в нее.

Смежные вопросы