Как я могу перебирать перечисление?
Я только заметил, что вы не можете использовать стандартные математические операторы для перечисления, такие как ++ или + =
Итак, каков наилучший способ перебора всех значений в перечислении С++?
Ответы
Ответ 1
Типичный способ заключается в следующем:
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 в вектор и перейдете к этому. Это также будет правильно обрабатывать указанные значения перечисления.
Ответ 2
#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;
}
Ответ 3
Один из многих подходов: Когда перечисление просто недостаточно: классы перечисления для С++.
И если вам нужно что-то более инкапсулированное, попробуйте этот подход от Джеймса Канзе.
Ответ 4
Если ваше перечисление начинается с 0, а инкремент всегда равен 1.
enum enumType
{
A = 0,
B,
C,
enumTypeEnd
};
for(int i=0; i<enumTypeEnd; i++)
{
enumType eCurrent = (enumType) i;
}
Если я не думаю, что только зачем создавать что-то вроде
vector<enumType> vEnums;
добавьте элементы и используйте обычные итераторы....
Ответ 5
С С++ 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;
И тогда вы можете итерировать с помощью диапазона для
for (foo i : fooIterator() ) { //notice the parentheses!
do_stuff(i);
}
Предположение, что у вас нет пробелов в вашем перечислении, все еще верно; нет никакого предположения о количестве битов, фактически необходимых для хранения значения перечисления (благодаря std :: basic_type)
Ответ 6
слишком усложняет это решение, я делаю так:
enum NodePosition { Primary = 0, Secondary = 1, Tertiary = 2, Quaternary = 3};
const NodePosition NodePositionVector[] = { Primary, Secondary, Tertiary, Quaternary };
for (NodePosition pos : NodePositionVector) {
...
}
Ответ 7
Вы не можете с перечислением. Возможно, перечисление не подходит для вашей ситуации.
Общим соглашением является имя последнего значения перечисления, что-то вроде MAX, и использовать его для управления циклом с использованием int.
Ответ 8
Я часто так делаю
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 EAnimal
{
E_First,
E_None = E_First,
E_CanFly = 0x1,
E_CanWalk = 0x2
E_CanSwim = 0x4,
E_Last
}
for (EAnimali = E_First; i < E_Last; i = EAnimal(i << 1))
{}
Ответ 9
Вы можете попробовать и определить следующий макрос:
#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;
}
Он может использоваться для итерации назад и вперед через unsigned, целые числа, перечисления и символы:
for_range (unsigned, i, 10,0)
{
cout << "backwards i: " << i << endl;
}
for_range (char, c, 'z','a')
{
cout << c << endl;
}
Несмотря на свое неудобное определение, он оптимизирован очень хорошо. Я посмотрел на дизассемблер в VС++.
Код чрезвычайно эффективен. Не откладывайте, а три для операторов: компилятор будет производить только один цикл после оптимизации! Вы даже можете определить замкнутые контуры:
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;
}
Вы, очевидно, не можете перебирать нумерованные типы с пробелами.
Ответ 10
Что-то, что не было рассмотрено в других ответах = если вы используете строго типизированные С++ 11 перечисления, вы не можете использовать ++
или + int
. В этом случае требуется немного беспорядочного решения:
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)
}
Ответ 11
Вы также можете перегрузить операторы increment/decment для вашего перечисляемого типа.
Ответ 12
Если вы не хотите загрязнять вас перечислением с конечным пунктом 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);
}
Ответ 13
Для компиляторов 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);
}
Примечание: это намного меньше кода, чем простой шаблонный пользовательский ответ итератора.
Вы можете заставить это работать с GCC, используя typeof
вместо decltype
, но на данный момент у меня нет такого компилятора, чтобы убедиться, что он компилируется.
Ответ 14
Здесь другое решение, которое работает только для смежных перечислений. Это дает ожидаемую итерацию, за исключением уродства в приращении, которое находится там, где оно принадлежит, начиная с того, что сломано в C++.
enum Bar {
One = 1,
Two,
Three,
End_Bar // Marker for end of enum;
};
for (Bar foo = One; foo < End_Bar; foo = Bar(foo + 1))
{
// ...
}
Ответ 15
Предполагая, что перечисление нумеруется последовательно, подвержено ошибкам. Кроме того, вы можете перебирать только выбранные перечислители. Если это подмножество маленькое, зацикливание на нем явно может быть элегантным выбором:
enum Item { Man, Wolf, Goat, Cabbage }; // or enum class
for (auto item : {Wolf, Goat, Cabbage}) { // or Item::Wolf, ...
// ...
}
Ответ 16
У С++ нет интроспекции, поэтому вы не можете определить эту вещь во время выполнения.
Ответ 17
Если вы знали, что значения перечисления были последовательными, например, перечисление Qt: Key, вы могли бы:
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);
}
}
Работает так, как ожидалось.
Ответ 18
enum class A {
a0=0, a3=3, a4=4
};
constexpr std::array<A, 3> ALL_A {A::a0, A::a3, A::a4}; // constexpr is important here
for(A a: ALL_A) {
if(a==A::a0 || a==A::a4) std::cout << static_cast<int>(a);
}
constexpr std::array
может выполнять итерации даже непоследовательных перечислений без создания экземпляра массива компилятором. Это зависит от таких вещей, как эвристика оптимизации компилятора и от того, берете ли вы адрес массива.
В своих экспериментах я обнаружил, что g++
9.1 с -O3
оптимизирует вышеприведенный массив, если есть 2 непоследовательных значения или довольно много последовательных значений (я проверял до 6). Но он делает это только в том случае, если у вас есть утверждение if
. (Я пробовал оператор, который сравнивал целочисленное значение больше, чем все элементы в последовательном массиве, и он включал итерацию, хотя ни один из них не был исключен, но когда я пропустил оператор if, значения были помещены в память.) Он также встроен 5 значения из непоследовательного перечисления в [одном случае | https://godbolt.org/z/XuGtoc]. Я подозреваю, что это странное поведение связано с глубокой эвристикой, связанной с кэшем и предсказанием ветвлений.
Вот ссылка на простую итерацию теста на Godbolt, которая демонстрирует, что массив не всегда создается.
Ценой этой техники является написание элементов enum дважды и синхронизация двух списков.
Ответ 19
typedef enum{
first = 2,
second = 6,
third = 17
}MyEnum;
static const int enumItems[] = {
first,
second,
third
}
static const int EnumLength = sizeof(enumItems) / sizeof(int);
for(int i = 0; i < EnumLength; i++){
//Do something with enumItems[i]
}
Ответ 20
Просто сделайте массив целых и сделайте цикл по массиву, но заставьте последний элемент сказать -1 и используйте его для условия выхода.
Если enum это:
enum MyEnumType{Hay=12,Grass=42,Beer=39};
затем создайте массив:
int Array[] = {Hay,Grass,Beer,-1};
for (int h = 0; Array[h] != -1; h++){
doStuff( (MyEnumType) Array[h] );
}
Это не ломается независимо от целых чисел в представлении, пока проверка -1 не сталкивается ни с одним из элементов, конечно.