如何在枚举中进行迭代?

17 浏览
0 Comments

如何在枚举中进行迭代?

我刚刚注意到不能对枚举类型(enum)使用标准的数学运算符,如++或+=。

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

0
0 Comments

如何迭代枚举类型?

在C++11中,有一个替代方法:编写一个模板化的自定义迭代器。

假设你的枚举类型是:

enum class foo {
  one,
  two,
  three
};

下面的通用代码可以高效地实现迭代 - 放在一个通用的头文件中,它将为你需要迭代的任何枚举类型提供服务:

#include 
template < typename C, C beginVal, C endVal>
class Iterator {
  typedef typename std::underlying_type::type val_t;
  int val;
public:
  Iterator(const C & f) : val(static_cast(f)) {}
  Iterator() : val(static_cast(beginVal)) {}
  Iterator operator++() {
    ++val;
    return *this;
  }
  C operator*() { return static_cast(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 fooIterator;

然后你可以使用范围for循环来迭代:

for (foo i : fooIterator() ) { //注意括号!
   do_stuff(i);
}

假设你的枚举类型没有间隔,仍然是正确的;对于存储枚举值所需的位数没有假设(借助std::underlying_type)。

请注意,“通用代码”与“foo”相关(见代码中间的部分),因此你实际上不能将其与其他枚举定义一起使用。你可以为不同的枚举定义创建不同的typedef。

这就像说,std::vector不是泛型的,因为std::vector与foo相关。

如果我有另一个枚举类型,比如:

enum color { green = 20, white = 30, red = 40 }

如何在不更改foo operator*() { ...部分的情况下使用上述代码?有人能用示例代码解释一下吗?(对不起,我的C++技能还不那么高级)。

由于跳过,你必须特化operator ++,使用val+=10代替++val。

对不起,我的错误...我设置了这些值使其与foo不同,但这不是我关心的。请从我的以前的评论中更改,并将enum设置为:

enum color { green, black, white, blue, red }

我如何使用Francesco提出的代码才能迭代foo或color而无需修改他的代码?如果可能的话,我想看到一个示例代码。

typedef Iterator colorIterator;

确保你理解模板实例化是如何工作的。每个枚举将有自己(模板特化的)迭代器类型,就像你需要一个不同的std::vector类型来保存不同类型的对象一样。这通过模板特化来实现,就像前面的评论中所示。关键是你不需要为每个独特的枚举类型重新定义Iterator模板,就像你不需要为每种对象类型重新定义std::vector模板一样;你只需要特化它,可以用一行typedef来完成。

为了以一种有限但相对通用的方式解决“跳过”问题,请参考下面的答案。另请注意,你只能在注释中标记一个人(所以当你试图标记我时,我没有收到通知)。

哦,我明白了 - foo operator*() { ... 应该是 C operator*() { ...。

你懂了!现在它完全有意义。代码需要更新吗?谢谢大家的解释。

我实际上在这里创建了一个更加通用的版本:https://github.com/BatmanAoD/PublicPaste/blob/master/GenericCppCode/EnumIterator.cpp

通过“更加通用”,我是指它依赖于名为IsEnumValid的重载全局函数(可以轻松地移入命名空间)来确定特定的int值是否是有效的枚举值,从而解决了“跳过”问题。一个替代方法是将验证函数作为模板参数之一。另一个方法是要求存在一个用于迭代的静态数组,就像http://stackoverflow.com/a/26910769/1858225中那样。

当枚举是一个连续的序数数字枚举时,这个方法是有效的。那么当你有一组不连续的位标志时,包括它们的组合,该方法还能起作用吗?

肯定加1,但是a)为什么val成员数据的类型是int而不是val_t?b)重写==运算符是否有用?

0
0 Comments

如何对枚举进行迭代?

枚举类型在C++中被广泛使用,但是在迭代过程中,很多开发者会遇到一些问题。本文将介绍枚举迭代的问题以及解决方法。

在给出解决方法之前,我们先来看一段代码示例:

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

以上代码示例展示了三种迭代枚举的方法。

第一种是使用范围for循环迭代整个枚举,通过遍历`MyEnum::All`数组中的元素,调用`fun`函数进行处理。

第二种是使用范围for循环迭代部分枚举,通过遍历初始化列表中的元素,调用`fun`函数进行处理。

第三种是使用`std::for_each`算法迭代整个枚举,通过传递`MyEnum::All`数组的起始和结束迭代器,调用`fun`函数进行处理。

在这段代码中,我们使用了一个静态数组`MyEnum::All`来存储枚举的所有元素。

然而,在实际开发中,有些开发者可能会遇到一些问题。例如,在跨文件/类时,如果使用头文件声明的非整数常量与MS兼容性存在问题,编译器可能会报错。因此,可以在头文件中显式声明数组的大小,然后在源文件中进行初始化,这样可以避免数组大小未知的问题。

以下是解决方法的代码示例:

// 头文件
static const Type All[3];
// 源文件
const MyEnum::Type MyEnum::All[3] = { a, b, c };

通过在头文件中声明数组的大小,并在源文件中进行初始化,我们可以解决数组大小未知的问题。

除了上述解决方法外,还有一种更友好的解决方案,即使用宏。使用宏可以更方便地复制和粘贴代码。但是,需要注意的是,这种方法可能不适用于枚举元素数量较多的情况。

本文介绍了如何对枚举进行迭代的问题以及解决方法。通过使用范围for循环和`std::for_each`算法,我们可以对枚举进行迭代操作。另外,还介绍了解决数组大小未知问题的方法,以及使用宏的解决方案。希望本文对你理解如何对枚举进行迭代有所帮助。

0
0 Comments

如何迭代枚举?

枚举的典型迭代方式如下所示:

enum Foo {
  One,
  Two,
  Three,
  Last
};
for (int fooInt = One; fooInt != Last; fooInt++)
{
   Foo foo = static_cast(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;
}

如果你真的想要枚举,将枚举值存储在一个vector中,并在其上进行迭代。这样还可以正确处理指定的枚举值。

注意,在示例的第一部分中,如果你想要将`i`作为一个Foo枚举而不是一个int使用,你需要像这样进行静态转换:`static_cast(i)`。同时,你在循环中跳过了`Last`。应该是`<= Last`。

`Last`是用来跳过的。如果以后想要添加更多的枚举,在`Last`之前添加它们,示例中的循环仍然有效。通过使用“虚假”的`Last`枚举,您不必每次添加新的枚举时都更新for循环中的终止条件到最后一个“真实”的枚举。

除非枚举的值被指定,否则这种方法会失效。在这种情况下,枚举的安全处理方式是将枚举值存储在一个vector中,并在其上进行迭代。

注意,如果想要使用宏,那就意味着你需要通过宏来定义枚举。这样你实际上避免了类型转换,也不需要使用vector。

实际上,它的操作逻辑与迭代STL容器相同。就像`std::end()`返回一个“超出末尾”的迭代器,所以你可以使用`i < std::end(whatever)`或`it != std::end(whatever)`,在枚举中添加一个虚拟值,你可以使用`i < Last`。类似地,如果枚举用于迭代,添加一个虚拟的`First`元素也是有用的,它的底层值与真正的第一个元素的值相同。例如,在这个答案中,可以是`First = One`。

要注意的是,为了安全更新这个枚举定义,应该定义一个值`UNKNOWN = 0`。此外,建议在枚举值切换时省略`default`情况,因为这可能会隐藏值处理被遗忘的情况,直到运行时。相反,应该硬编码所有的值,并使用`UNKNOWN`字段来检测不兼容性。

这就是为什么我更喜欢将最后一个条目称为`Count`。这样会更明显一些。

没错。而且,当优化代码时,消除澄清代码的临时变量并不是一种好的方式,因为(a)它通常并不实际优化,而是(b)降低了代码的清晰度。

0