我如何遍历一个枚举?


302

我只是注意到您不能在++或+ =等枚举上使用标准数学运算符

那么,迭代C ++枚举中的所有值的最佳方法是什么?


2
许多方法之一:当枚举还不够时:C ++的枚举类。而且,如果您想封装更多东西,请尝试使用James Kanze的这种方法
唐·韦克菲尔德

链接项有一些有趣的响应。
托尼

这些答案似乎并未涵盖int可能还不够大的问题!([C++03: 7.2/5]
轻轨比赛在

有趣的是,您可以定义operator++枚举。但是,您可以这样做for(Enum_E e = (Enum_E)0; e < ENUM_COUNT; e++)。请注意,您必须强制转换0Enum_EC ++禁止对枚举进行赋值运算符。
weberc2 2014年

如果有一个类似于sizeof的工作方式的编译时运算符,可以发出包含枚举值的std :: initializer_list文字,那么我们将有一个解决方案,并且不会涉及任何运行时开销。
jbruni

Answers:


263

典型的方法如下:

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

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

请注意,该枚举Last应被迭代跳过。利用此“伪” Last枚举,您不必每次想添加新的枚举时都将for循环中的终止条件更新为最后一个“真实”枚举。如果要在以后添加更多枚举,只需在Last之前添加即可。此示例中的循环仍将起作用。

当然,如果指定了枚举值,则此操作会失败:

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;
}

如果您真的想枚举,请将枚举值填充到向量中并对其进行迭代。这也将正确处理指定的枚举值。


13
请注意,在示例的第一部分中,如果要使用“ i”作为Foo枚举而不是int,则需要将其静态转换为:static_cast <Foo>(i)
Clayton 2009年

5
同样,您在循环中跳过了Last。应该<= Last
Tony

20
@Tony Last被跳过。如果要稍后添加更多枚举,请在“ Last ...”之前添加它们。第一个示例中的循环仍将起作用。通过使用“伪”最后一个枚举,您不必每次想添加新的枚举时都将for循环中的终止条件更新为最后一个“实际”枚举。
timidpueo

除了现在,您实际上已经分配了一个枚举(只要它是零索引的并且严格连续的)就可以在不分配内存的情况下完成该任务。
2014年

1
请注意,为了使此枚举定义对于更新是安全的,应该定义一个值UNKNOWN = 0。另外,我建议default在切换枚举值时只删除这种情况,因为它可能隐藏在运行时忘记处理值的情况。相反,应该对所有值进行硬编码,并使用该UNKNOWN字段检测不兼容性。
本杰明·班尼尔

53
#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;
}

谢谢!请注意,如果你不过路口的文件/类,如果MS兼容性让你头宣布的非整数常量的问题,它在我的下编译有助于明确提出规模在标题类型:static const Type All[3];然后我就能在源中初始化:const MyEnum::Type MyEnum::All[3] = { a, b, c }; 在此之前,我遇到了令人讨厌的Error in range-based for...错误(因为数组的大小未知)。有了一个相关的答案,才
Sage

1
阵列版本对复制粘贴非常友好。最满意的答案是“否”或“仅用于顺序”。可能甚至宏友好。
Paulo Neves

1
对于数量较少的枚举,这可能是一个很好的解决方案,但对于数量较多的枚举,它不一定适合。
kato2

20

如果您的枚举从0开始且增量始终为1。

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

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

如果不是,我想唯一的原因就是要创建一个像

vector<enumType> vEnums;

添加项目,并使用普通的迭代器。


19

对于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;

然后您可以使用range-for

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

枚举中没有空白的假设仍然成立。没有关于存储枚举值实际需要的位数的假设(感谢std :: underlying_type)


1
@lepe?您只需为不同的枚举创建不同的typedef。
安德鲁·拉扎鲁斯

2
@lepe就像是说那std::vector不是通用的,因为std::vector<foo>它与绑在一起foo
Kyle Strand

1
typedef Iterator<color, color::green, color::red> colorIterator;确保您了解模板实例化的工作方式。
安德鲁·拉撒路

2
哦,我知道了- foo operator*() { ...应该是C operator*() { ...
Kyle Strand

1
@KyleStrand:知道了!现在完全有意义。代码应该更新吗?感谢大家的解释。
lepe 2015年

16

这些解决方案太复杂了,我这样做:

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

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

for (NodePosition pos : NodePositionVector) {
...
}

我不知道为什么这被否决了。这是一个合理的解决方案。
Paul Brannan

11
我希望这是因为条目需要在两个地方维护。
蚂蚁

C ++是否允许for (NodePosition pos : NodePositionVector)语法?据我所知,这是Java语法,您将需要C ++中的迭代器来执行等效操作。
thegreatjedi 2015年

3
@thegreatjedi自C ++ 11起,您甚至可以使用以下命令:for(auto pos:NodePositionVector){..}
Enzojz 2016年

@thegreatjedi搜索,甚至编译测试程序,都比提出这个问题要快。但是,是的,因为C ++ 11是完全有效的C ++语法,所以编译器通常通过迭代器将其转换为等效的代码(并且更冗长/更少抽象)。参见cppreference。而且,正如Enzojz所说,C ++ 11还添加了auto,因此您不必显式声明元素的类型,除非您(A)需要使用转换运算符或(B)auto由于某种原因不喜欢。大多数范围for用户使用autoAFAICT
underscore_d

9

我经常那样做

    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 EAnimalCaps
    {
        E_First,
        E_None    = E_First,
        E_CanFly  = 0x1,
        E_CanWalk = 0x2
        E_CanSwim = 0x4,
        E_Last
    }

    class MyAnimal
    {
       EAnimalCaps m_Caps;
    }

    class Frog
    {
        Frog() : 
            m_Caps(EAnimalCaps(E_CanWalk | E_CanSwim))
        {}
    }

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

但是,按位打印值有什么用?
阿努

1
使用枚举创建位掩码。例如,将多个选项组合到单个变量中,然后使用FOR测试每个选项。用更好的例子修复了我的帖子。
Niki

我仍然无法使用它(您的帖子仍然显示了旧示例)!使用枚举作为位掩码确实很有帮助,但无法连接各个点!您能否在示例中详细说明一下,也可以添加其他代码。
阿努

@anu对不起,没有看到您的评论。添加了Frog类作为位掩码示例
Niki,

8

你不能用枚举。枚举可能不是最适合您的情况。

常见的约定是命名最后一个枚举值(如MAX),并使用该值来控制使用int的循环。


这里有几个例子说明了相反的情况。我自己的说法是你在矛盾自己(第二行)。
Niki

6

其他答案中未涉及的内容=如果您使用的是强类型的C ++ 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)

}

3
...但是C ++ 11引入了基于该范围的范围,其他答案中对此进行了说明。:-)
Sage

5

您可以尝试定义以下宏:

#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 ++中的反汇编程序。该代码非常有效。不要拖延,而是三个for语句:优化后,编译器只会产生一个循环!您甚至可以定义封闭的循环:

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;
    }

您显然无法遍历带有间隙的枚举类型。


1
这是一个很棒的技巧!也许有人会说,尽管C比C ++更适合C语言。
einpoklum 2013年

3
_A1不是允许使用的名称,它是带下划线的前导下划线。
Martin Ueding '02


3

假定枚举顺序编号是容易出错的。此外,您可能只想迭代选定的枚举数。如果该子集很小,则明确地对其进行循环可能是一个不错的选择:

enum Item { Man, Wolf, Goat, Cabbage }; // or enum class

for (auto item : {Wolf, Goat, Cabbage}) { // or Item::Wolf, ...
    // ...
}

我认为这是一个不错的选择。当我问我正在猜测的问题时,一定是比我使用的更新的C ++规范的一部分吗?
亚当

是。遍历std :: initializer_list <Item>。链接
marski

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);
}

2

这是另一个仅适用于连续枚举的解决方案。它提供了预期的迭代,但增量是丑陋的,因为增量是它所属的,因为这是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))
{
    // ...
}

1
增量可以缩短为foo = Bar(foo + 1)
HolyBlackCat

谢谢,HolyBlackCat,我已经把您的出色建议纳入其中!我还注意到Riot具有几乎相同的解决方案,但符合强类型(因此更为冗长)。
伊桑·布拉德福德

2
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);
}

A constexpr std::array甚至可以迭代非顺序枚举,而无需由编译器实例化数组。这取决于诸如编译器的优化试探法以及是否采用数组的地址之类的问题。

在我的实验中,我发现如果存在2个非顺序值或相当多的顺序值(我测试了最多6个),则g++9.1 with -O3将优化上述数组。但这仅在您有if发言权的情况下执行。(我尝试了一个比较整数值的语句,该整数值比顺序数组中的所有元素都大,并且尽管没有排除任何变量,但它内联了迭代,但是当我省略if语句时,这些值已放入内存中。)它也内联了5 [一种情况|]中来自非顺序枚举的值 https://godbolt.org/z/XuGtoc]。我怀疑这种奇怪的行为是由于深度启发法与缓存和分支预测有关。

这是一个指向Godbolt的简单测试迭代链接,该迭代演示了数组并不总是被实例化。

这种技术的代价是两次编写枚举元素并使两个列表保持同步。


我喜欢简单的类似于范围的for循环语义,并且我认为它会进一步发展,这就是为什么我喜欢这种解决方案。
rtischer8277

2

在Bjarne Stroustrup的C ++编程语言书中,您可以了解到他提议重载operator++您的enumenum是针对这些特定情况的用户定义类型和语言中存在的重载运算符。

您将能够编写以下代码:

#include <iostream>
enum class Colors{red, green, blue};
Colors& operator++(Colors &c, int)
{
     switch(c)
     {
           case Colors::red:
               return c=Colors::green;
           case Colors::green:
               return c=Colors::blue;
           case Colors::blue:
               return c=Colors::red; // managing overflow
           default:
               throw std::exception(); // or do anything else to manage the error...
     }
}

int main()
{
    Colors c = Colors::red;
    // casting in int just for convenience of output. 
    std::cout << (int)c++ << std::endl;
    std::cout << (int)c++ << std::endl;
    std::cout << (int)c++ << std::endl;
    std::cout << (int)c++ << std::endl;
    std::cout << (int)c++ << std::endl;
    return 0;
}

测试代码:http : //cpp.sh/357gb

记住我正在使用enum class。代码也可以正常工作enum。但是我更喜欢,enum class因为它们是强类型的,可以防止我们在编译时犯错误。


此职位投了反对票。有什么理由为什么不能回答这个问题?
拉尔

原因可能是因为从结构上来讲,这是一个糟糕的解决方案:它迫使您编写绑定到特定组件(您的枚举)的global-meant-to逻辑,此外,如果您的枚举确实由于任何原因而改变,那么您就不得不编辑+ +运营商也一样,作为一种方法它是不可持续的任何中大型项目,它是不是来自一个Bjarne的Stroustrup的推荐一个惊喜,在天回软件架构是像科幻小说
满稼

本来的问题是关于一个运算符enum。这不是体系结构问题。我不相信2013年C ++是科幻小说。
LAL

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); 
}

注意:这比简单的模板化定制迭代器答案要少得多的代码。

您可以使用typeof代替decltype来使它与GCC一起使用,但是目前我没有那个编译器可以确保它可以编译。


这是在decltype成为标准C ++ 大约5年后编写的,因此您不应该推荐typeof古老的GCC 过时的代码。大概最近的海湾合作委员会处理decltype得很好。还有其他问题:不鼓励使用C型强制转换,并且宏程序更糟。适当的C ++功能可以提供相同的通用功能。最好重写为使用static_cast&模板函数:template <typename T> auto inc_enum(T const t) { return static_cast<T>(static cast<int>(t) + 1); }。而且,非不需要强制转换enum class。另外,运算符可以按enum类型(TIL)重载
underscore_d

1
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]
}

此解决方案将在内存中创建不必要的静态变量,而枚举的目的仅仅是为内联的常量创建“掩码”
TheArquitect

除非更改为constexpr static const int enumItems[]
Mohammad Kanan

0

C ++没有内省,因此您无法在运行时确定这种事情。


1
您能否向我解释为什么要对一个枚举进行迭代需要“自省”?
乔纳森·梅

也许这个词是反射
坎普ͩ

2
我想说两件事:1)C ++可以完成许多其他答案,因此,如果您要说不能,则需要链接或进一步说明。2)以目前的形式,这充其量只是评论,当然不是答案。
乔纳森·梅

Downvote我的答案,然后-我想你已经超过合理的它
坎普ͩ

1
我再次发表2条评论:1)我不投票,因为我发现投票不足会削弱网站的参与度,我发现结果适得其反2)我仍然不明白您要说的是什么,但这听起来像您了解我不了解的内容,在这种情况下,我希望您详细说明,而不要删除不满意的答案。
乔纳森·梅

0

如果您知道枚举值是连续的,例如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);
    }
}

它按预期工作。


-1

只需创建一个int数组并在该数组上循环即可,但将最后一个元素设置为-1并将其用于退出条件。

如果枚举是:

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 check不与其中一个元素冲突,这就不会破坏表示形式中的整数。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.