379

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

那么遍历 C++ 枚举中所有值的最佳方法是什么?

4

27 回答 27

309

典型的方式如下:

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

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

于 2008-11-04T14:10:51.643 回答
72
#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;
}
于 2014-11-13T14:07:29.427 回答
29

使用 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)

于 2015-08-05T15:13:41.457 回答
23

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

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

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

for (NodePosition pos : NodePositionVector) {
...
}
于 2014-07-27T15:56:21.857 回答
17

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

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

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

如果不是,我猜唯一的原因是创建类似

vector<enumType> vEnums;

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

于 2008-11-04T14:20:35.900 回答
15

我经常这样做

    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_None    = 0,
        E_First   = 0x1,
        E_CanFly  = E_First,
        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))
    {}
于 2017-01-18T11:54:09.263 回答
7

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

一个常见的约定是将最后一个枚举值命名为 MAX,并使用它来控制使用 int 的循环。

于 2008-11-04T14:10:23.283 回答
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)

}
于 2013-06-13T01:15:40.163 回答
6

假设枚举按顺序编号很容易出错。此外,您可能只想迭代选定的枚举数。如果该子集很小,则显式循环它可能是一个优雅的选择:

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

for (auto item : {Wolf, Goat, Cabbage}) { // or Item::Wolf, ...
    // ...
}
于 2019-07-14T00:17:02.550 回答
5
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);
}

Aconstexpr std::array甚至可以迭代非顺序枚举,而无需编译器实例化数组。这取决于编译器的优化启发式方法以及您是否采用数组的地址。

在我的实验中,我发现如果有 2 个非连续值或相当多的连续值(我测试了最多 6 个) , g++9.1 with将优化上述数组。-O3但只有在您有if声明时才会这样做。(我尝试了一个语句,它比较了一个大于顺序数组中所有元素的整数值,尽管没有排除任何元素,但它内联了迭代,但是当我省略 if 语句时,这些值被放入内存中。)它还内联了 5 [one case| 中非顺序枚举的值] https://godbolt.org/z/XuGtoc]。我怀疑这种奇怪的行为是由于深度启发式与缓存和分支预测有关。

这是godbolt 上一个简单测试迭代的链接,它演示了数组并不总是被实例化。

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

于 2019-05-27T21:17:07.970 回答
4

您可以尝试定义以下宏:

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

您显然不能遍历有间隙的枚举类型。

于 2011-12-09T22:04:07.217 回答
3

您还可以为枚举类型重载递增/递减运算符。

于 2008-11-04T17:35:39.203 回答
3

这是另一种仅适用于连续枚举的解决方案。它给出了预期的迭代,除了增量中的丑陋,这是它所属的地方,因为这在 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))
{
    // ...
}
于 2018-11-08T19:00:28.137 回答
2

如果你不喜欢用最终的 COUNT 项污染你的枚举(因为也许如果你也在开关中使用枚举,那么编译器会警告你缺少 case 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);
}
于 2016-05-30T07:32:46.487 回答
2

评论中已经讨论了 std::initializer_list (C++11)。我提到了迭代枚举的示例。

或 std::initializer_list 和更简单的语法:

enum E {
    E1 = 4,
    E2 = 8,
    // ..
    En
};

constexpr std::initializer_list<E> all_E = {E1, E2, /*..*/ En};

进而

for (auto e : all_E) {
    // Do job with e
}

参考链接

于 2021-05-23T11:33:10.573 回答
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而不是使用 GCC 来使其与 GCC 一起工作decltype,但我目前没有方便的编译器来确保它可以编译。

于 2016-02-13T23:35:07.137 回答
1

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

您将能够编写以下代码:

#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它们,因为它们是强类型的,可以防止我们在编译时出错。

于 2019-11-21T20:39:13.363 回答
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);
    }
}

它按预期工作。

于 2016-07-31T13:58:29.893 回答
0
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]
}
于 2019-07-17T04:37:30.190 回答
0

扩展@Eponymous 的答案:很好,但不提供通用语法。这是我想出的:

// Common/EnumTools.h
#pragma once

#include <array>

namespace Common {

// Here we forward-declare metafunction for mapping enums to their values.
// Since C++<23 doesn't have reflection, you have to populate it yourself :-(
// Usage: After declaring enum class E, add this overload in the namespace of E:
// inline constexpr auto allValuesArray(const E&, Commob::EnumAllValuesTag) { return std::array{E::foo, E::bar}; }
// Then `AllValues<NS::E>` will call `allValuesArray(NS::E{}, EnumAllValuesTag)` which will resolve
// by ADL.
// Just be sure to keep it sync'd with your enum!

// Here's what you want to use in, e.g., loops: "for (auto val : Common::AllValues<MyEnum>) {"

struct EnumAllValuesTag {}; // So your allValuesArray function is clearly associated with this header.

template <typename Enum>
static inline constexpr auto AllValues = allValuesArray(Enum{}, EnumAllValuesTag{});
// ^ Just "constexpr auto" or "constexpr std::array<Enum, allValuesArray(Enum{}, EnumAllValuesTag{}).size()>" didn't work on all compilers I'm using, but this did.

} // namespace Common

然后在您的命名空间中:

#include "Common/EnumTools.h"

namespace MyNamespace {

enum class MyEnum {
    foo,
    bar = 4,
    baz = 42,
};

// Making this not have to be in the `Common` namespace took some thinking,
// but is a critical feature since otherwise there's no hope in keeping it sync'd with the enum.
inline constexpr auto allValuesArray(const MyEnum&, Common::EnumAllValuesTag) {
    return std::array{ MyEnum::foo, MyEnum::bar, MyEnum::baz };
}

} // namespace MyNamespace

然后在任何需要使用它的地方:

for (const auto& e : Common::AllValues<MyNamespace::MyEnum>) { ... }

所以即使你已经 typedef 了:

namespace YourNS {
using E = MyNamespace::MyEnum;
} // namespace YourNS

for (const auto& e : Common::AllValues<YourNS::E>) { ... }

除了查看此页面的每个人都想要的实际语言功能之外,我想不出比这更好的了。

未来的工作:

  1. 您应该能够添加一个constexpr函数(以及一个元函数)来过滤Common::AllValues<E>,以便Common::AllDistinctValues<E>为具有重复数值(如enum { foo = 0, bar = 0 };.
  2. 我敢打赌,有一种方法可以使用编译器的switch-covers-all- -valuesenum来编写allValuesArray,这样如果枚举添加了一个值,它就会出错。
于 2020-07-20T17:40:09.723 回答
0

优点:枚举可以按您喜欢的任何顺序具有您喜欢的任何值,并且迭代它们仍然很容易。名称和值在第一个#define 中定义一次。

缺点:如果你在工作中使用它,你需要一个完整的段落来向你的同事解释它。而且,必须声明内存以给循环提供一些可以迭代的内容很烦人,但我不知道有一种解决方法不会将您限制在具有相邻值的枚举(如果枚举总是具有相邻值,则无论如何,枚举可能不会给你买那么多东西。)

//create a, b, c, d as 0, 5, 6, 7
#define LIST x(a) x(b,=5) x(c) x(d)
#define x(n, ...) n __VA_ARGS__,
enum MyEnum {LIST}; //define the enum
#undef x //needed
#define x(n,...) n ,
MyEnum myWalkableEnum[] {LIST}; //define an iterable list of enum values
#undef x //neatness

int main()
{
  std::cout << d;
  for (auto z : myWalkableEnum)
    std::cout << z;
}
//outputs 70567

用未定义的宏包装器声明一个列表,然后在各种情况下以不同的方式定义包装器的技巧,除此之外还有很多应用。

于 2020-09-30T23:51:59.670 回答
0

这里有一些非常易读和易于理解的方法,适用于弱类型C 和 C++ 正则enums,以及 强类型C++ enum classes。

我建议用-Wall -Wextra -Werror. switch这为您提供了额外的安全性,如果您在编译器抛出编译时错误的情况下忘记覆盖任何枚举值!这迫使您保持枚举定义和切换案例同步,这是您的代码的额外安全措施。只要您:

  1. 涵盖您案例中的所有枚举值,并且switch
  2. 没有default开关盒。
  3. -Wall -Wextra -Werror用旗帜建造。

我建议您遵循所有这 3 点,因为这是一种很好的做法并且可以创建更好的代码。

1. 对于标准的弱类型C 或 C++ enum

C 定义(这也是有效的 C++):

typedef enum my_error_type_e 
{
    MY_ERROR_TYPE_SOMETHING_1 = 0,
    MY_ERROR_TYPE_SOMETHING_2,
    MY_ERROR_TYPE_SOMETHING_3,
    MY_ERROR_TYPE_SOMETHING_4,
    MY_ERROR_TYPE_SOMETHING_5,
    /// Not a valid value; this is the number of members in this enum
    MY_ERROR_TYPE_count,
    // helpers for iterating over the enum
    MY_ERROR_TYPE_begin = 0,
    MY_ERROR_TYPE_end = MY_ERROR_TYPE_count,
} my_error_type_t;

C++ 定义:

enum my_error_type_t 
{
    MY_ERROR_TYPE_SOMETHING_1 = 0,
    MY_ERROR_TYPE_SOMETHING_2,
    MY_ERROR_TYPE_SOMETHING_3,
    MY_ERROR_TYPE_SOMETHING_4,
    MY_ERROR_TYPE_SOMETHING_5,
    /// Not a valid value; this is the number of members in this enum
    MY_ERROR_TYPE_count,
    // helpers for iterating over the enum
    MY_ERROR_TYPE_begin = 0,
    MY_ERROR_TYPE_end = MY_ERROR_TYPE_count,
};

在这个弱类型枚举上的C 或 C++ 迭代:

注意:不允许通过做增加枚举——甚至在 C 风格的枚举上也不行,所以我们必须这样my_error_type++做:但是请注意,这允许的,因为这个弱枚举会自动隐式转换为here 以使此添加成为可能,而无需手动将其转换为 int ,如下所示:my_error_type = (my_error_type_t)(my_error_type + 1)my_error_type + 1 intmy_error_type = (my_error_type_t)((int)my_error_type + 1)

for (my_error_type_t my_error_type = MY_ERROR_TYPE_begin; 
        my_error_type < MY_ERROR_TYPE_end;
        my_error_type = (my_error_type_t)(my_error_type + 1)) 
{
    switch (my_error_type) 
    {
        case MY_ERROR_TYPE_SOMETHING_1:
            break;
        case MY_ERROR_TYPE_SOMETHING_2:
            break;
        case MY_ERROR_TYPE_SOMETHING_3:
            break;
        case MY_ERROR_TYPE_SOMETHING_4:
            break;
        case MY_ERROR_TYPE_SOMETHING_5:
            break;
        case MY_ERROR_TYPE_count:
            // This case will never be reached.
            break;
    }
}

2. 对于作用域强类型C++ enum class

C++ 定义:

enum class my_error_type_t
{
    SOMETHING_1 = 0,
    SOMETHING_2,
    SOMETHING_3,
    SOMETHING_4,
    SOMETHING_5,
    /// Not a valid value; this is the number of members in this enum
    count,
    // helpers for iterating over the enum
    begin = 0,
    end = count,
};

在这个强类型枚举上的C++ 迭代:

请注意强制增加变量所需的额外强制转换(size_t)(或者也可以接受)!我也选择在这里使用 C++ 风格的演员表,但是如上所述的 C 风格演员表也可以。(int)enum classstatic_cast<my_error_type_t>(my_error_type_t)

for (my_error_type_t my_error_type = my_error_type_t::begin; 
        my_error_type < my_error_type_t::end;
        my_error_type = static_cast<my_error_type_t>((size_t)my_error_type + 1)) 
{
    switch (my_error_type) 
    {
        case my_error_type_t::SOMETHING_1:
            break;
        case my_error_type_t::SOMETHING_2:
            break;
        case my_error_type_t::SOMETHING_3:
            break;
        case my_error_type_t::SOMETHING_4:
            break;
        case my_error_type_t::SOMETHING_5:
            break;
        case my_error_type_t::count:
            // This case will never be reached.
            break;
    }
}

还要注意范围。在 C++强类型中 enum class,我曾经my_error_type_t::访问每个作用域enum class成员。但是,在 C 风格的弱类型正则enum中,可以实现非常相似的作用域,正如我所演示的,只需在每个enum成员名称前加上MY_ERROR_TYPE_. 因此,C++强类型 enum class添加作用域这一事实并没有真正增加太多价值——这实际上只是个人偏好。C++强类型 具有额外的类型安全性这一事实enum class也有利有弊。在某些情况下它可能会对您有所帮助,但它肯定会使增加枚举并对其进行迭代变得很痛苦,老实说,这意味着它正在完成它的工作。通过使其更难为了像整数一样增加作用域enum class变量,C++强类型 完全按照它的设计目的enum class进行。您是否想要这种行为取决于您。就个人而言,我经常希望这种行为,因此即使在 C++ 中,我也更喜欢使用 C 风格的枚举并不少见。

也可以看看:

  1. [我的回答]有没有办法在 c++11 中按索引初始化向量?
  2. [我的问答]在 C++ 中迭代枚举类的常用方法是什么?
  3. 我对C++ 中enum classes(强类型枚举)和常规enums(弱类型枚举)之间的一些差异的回答:如何自动将强类型枚举转换为 int?
  4. -Wall -Wextra -Werror来自我的eRCaGuy_hello_world 存储库的关于和其他构建选项的一些个人笔记
于 2021-10-29T20:03:45.287 回答
0

使用 lambda,我发现这是循环枚举的最佳(现代)方式。这极大地提高了抽象性。甚至可以使其成为模板,因此它适用于任何枚举。此代码既不会给您带来 clang(-tidy) 的问题。

#include <functional>

/// @brief Loop over all enum values where the last enum value is the invalid one
void forEachAction(std::function<void(Enum)> &&doThis) {
    for (int value = 0; value = static_cast<int>(Enum::LastValue); ++value ) {
        doThis(static_cast<Enum>(value ));
    }
}

...

forEachAction([this](Enum value) {
    ...  // what you want to execute for every enum
});
于 2021-12-22T13:22:24.963 回答
0

将变量转换为 anint&可以让您在保持类型可读的同时递增。

#include <iostream>

enum MyEnum
{
    ONE,
    TWO,
    THREE,
    FOUR,
};

int main()
{
    for (MyEnum v = MyEnum::ONE; v <= MyEnum::FOUR; ++(int&)v)
    {
        std::cout<<v<<std::endl;
    }

    return 0;
}
0
1
2
3
于 2021-12-27T07:38:15.560 回答
-1

大多数解决方案都基于 (MIN, MAX) 范围内的循环,但忽略了枚举中可能存在漏洞的事实。

我的建议是:

        for (int i = MYTYPE_MIN; i <= MYTYPE_MAX; i++) {
            if (MYTYPE_IsValid(i)) {
                MYTYPE value = (MYTYPE)i;
                // DoStuff(value)
            }   
        }   
        
于 2021-01-21T19:30:02.677 回答
-2

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

于 2008-11-04T14:12:42.343 回答
-3

只需创建一个整数数组并遍历该数组,但将最后一个元素设为 -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 检查当然不与其中一个元素发生冲突。

于 2018-03-15T09:51:07.407 回答