65.9K
CodeProject 正在变化。 阅读更多。
Home

枚举迭代和字符串转换

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.40/5 (9投票s)

2008年10月7日

CPOL

6分钟阅读

viewsIcon

45333

downloadIcon

256

枚举迭代和字符串转换

引言

是否曾想过遍历一系列枚举值?我曾遇到过这样的情况:给出一个枚举序列,需要执行特定任务。几分钟后,我生成了以下代码片段。它简单而有效。

enum MyEnum { enum1, firstEnum=enum1, enum2, enum3, enum4, enum5,
              totalEnums, errorEnum = 0xFF };
        
const MyEnum& operator++(MyEnum& eValue)    { return (eValue = (MyEnum)(eValue+1)); } 
    
for(MyEnum eValue = firstEnum ; eValue < totalEnums; ++eValue)
    DoSomething(eValue);

然而,这种实现存在一些根本性问题

  • 它只适用于简单的顺序枚举。
  • 它需要有关枚举序列的额外知识,即起始点和结束点。

除了遍历之外,`string` 转换功能也很有用,即能够将 `string` 转换为枚举值,反之亦然。
以下短文旨在提供一种实现此功能的简单机制。

EnumClass

EnumClass<> 提供了介绍中确定的功能。它为 C++ 风格的枚举提供了遍历和 `string` 转换功能。该类需要两个辅助宏来声明和实现枚举以及辅助类本身,但只需以下三行简单的代码,即可定义和实现 `enum` 和辅助类。

// Place in the header file for public access to enum MyEnum 
// and the helper class MyEnumClass
#define MYENUM { enum1, firstEnum=enum1, enum2, enum3, enum4, enum5, 
			totalEnums, errorEnum = 0xFF }
DECLARE_ENUM(MyEnumClass, MyEnum, MYENUM);
    
// Place in source file for implementation of MyEnumClass helper class
IMPLEMENT_ENUM(MyEnumClass);

上面的代码生成以下内容(为清晰起见,已删除 `MyEnumClass` 的外部声明)

enum MyEnum { enum1, firstEnum= enum1, enum2, enum3, 
	enum4, enum5,totalEnums, errorEnum = 0xFF }
MyEnum__EnumClass MyEnumClass;

枚举 `MyEnum` 可如下使用

// conversion functionality
// convert enum to string - outputs "totalEnums"
std::cout << MyEnumClass(totalEnums) << std::endl; 
// convert string to enum - outputs 5
std::cout << MyEnumClass("totalEnums") << std::endl; 

// simple looping
for(MyEnums eValue = firstEnum; eValue <= totalEnums; ++eValue)
    DoSomething(eValue);

更复杂的枚举

`for` 循环实现将正确地遍历顺序枚举,它还将处理非连续枚举,即。

enum MyEnum { firstEnum = 0, enum1 = firstEnum, enum2=1, enum3=2, enum3=4,
              enum4=8, enum5 = 16, totalEnums = enum5, errorEnum = 0xFF }

然而,简单的 `for` 循环无法保证处理非连续枚举(请参阅后面的注意事项),例如。

enum MyEnum { firstEnum = 0, enum1 = firstEnum, enum2=16, enum3=1, enum3=2,
              enum4=4, enum5 = 8, totalEnums = enum5, errorEnum = 0xFF }

为了解决这个问题,EnumClass<> 提供了可以正确遍历这些更复杂枚举序列的迭代器。以下代码片段展示了如何使用 EnumClass<>::const_iterator 来遍历更复杂的枚举序列。

// iteration
EnumClass<MyEnum>::const_iterator iter = MyEnumsEnumClass.begin();
EnumClass<MyEnum>::const_iterator end = MyEnumsEnumClass.end();
while(iter != end)
{
    DoSomething(*iter);
    iter++;
};

实现

以下是对 EnumClass 和辅助宏 DECLARE_ENUM & IMPLEMENT_ENUM 的简要描述。

DECLARE_ENUM

此宏声明了枚举、递增运算符和辅助类的 `extern` 引用。

#define DECLARE_ENUM(EnumClassName, EnumName, ENUM) \
enum EnumName ENUM; \
class EnumClassName##__EnumClass: public EnumClass<MyEnum> \
{ \
public: \
	EnumClassName##__EnumClass(): EnumClass<MyEnum>(DEFINE_AS_STR(ENUM)) {}; \
}; \
extern EnumClassName##__EnumClass EnumClassName; \
inline const EnumName& operator++(EnumName& eValue)         
	{ return (eValue = EnumClassName.GetNext(eValue)); } \
inline const EnumName& operator++(EnumName& eValue, int)    
	{ return (eValue = EnumClassName.GetNext(eValue)); }
  • 第一个参数是辅助类对象的名称 - EnumClassName
    extern EnumName##__EnumClass EnumClassName;
  • 第二个参数是枚举序列的名称 - EnumName
    enum EnumName ENUM
  • 第三个参数是枚举集 - ENUM
    enum EnumName ENUM

这将生成以下代码(使用上面的示例)

extern MyEnum__EnumClass MyEnumClass;
enum MyEnum { enum1, firstEnum= enum1, enum2, enum3, enum4, enum5,
	totalEnums, errorEnum = 0xFF }

MyEnum__EnumClass 也是作为宏的一部分定义的。这个类是 EnumClass<> 的一个包装器,它只是用作向 EnumClass<> 提供字符串化枚举的机制。它不需要用于访问任何 `enum` 功能。有关字符串化枚举的更多信息,请参阅 DEFINE_AS_STR 部分。

可以通过类对象名称访问 `enum` 转换功能,即对于上面的示例

// convert enum to string - outputs "totalEnums"
std::cout << MyEnumClass(totalEnums) << std::endl;	
// convert string to enum - outputs 5
std::cout << MyEnumClass("totalEnums") << std::endl;	

可以通过基类名称和类对象访问 `enum` 迭代器,即。

EnumClass<MyEnum>::const_iterator iter = MyEnumClass.begin();
EnumClass<MyEnum>::const_iterator end = MyEnumClass.end();

IMPLEMENT_ENUM

此宏实例化辅助类。

#define IMPLEMENT_ENUM(EnumClassName) \
	EnumClassName##__EnumClass EnumClassName;
  • 第一个参数是辅助类对象的名称 - EnumClassName
    EnumName##__EnumClass EnumClassName;

EnumClass

该类为任何枚举序列提供了转换和遍历功能。
它是一个模板类,提供基于唯一枚举标识符的命名空间解析,即为每个枚举生成一个不同且唯一的 EnumClass<>,从而防止命名空间冲突。
EnumClass<> 的接口很简单。

// Class construction
EnumClass(const std::string& eString)
	:undefined("undefined")                    { ParseEnumString(eString); }

// Conversion 
const std::string operator()(T eValue) const        { return GetEnumString(eValue); }
const T operator()(const std::string& sStr) const   { return GetEnumValue(sStr); }

// iteration
const_iterator begin() const              { return const_iterator(enumList.begin()); }
const_iterator end() const                { return const_iterator(enumList.end()); }
const_reverse_iterator rbegin() const     
	{ return const_reverse_iterator(enumList.rbegin()); }
const_reverse_iterator rend() const       
	{ return const_reverse_iterator(enumList.rend()); }
	
// first / last accessors
const T first() const                               { return enumList.front(); }
const T last() const                                { return enumList.last(); }
size_t size() const                                 { return enumList.size(); }

EnumClass<> 持有一个 EnumInfo 对象的向量。这些对象包含枚举值与 `string` 等价物之间的映射。EnumInfo 对象列表在类构造期间生成。ParseEnumString() 方法将枚举 `string` 分割成单独的名称、值对。名称存储为 `enum` 的名称,而值被评估以生成枚举的整数值。然后将这些名称值对添加到 EnumInfo 对象列表中。如果 EnumInfo 对象已存在相同的值,则将名称添加到现有的 EnumInfo 对象中,并丢弃新的 EnumInfo 对象。

附加信息

枚举求值

如果未定义值,则赋给 EnumInfo 对象的 `value` 是顺序的,从零开始。如果定义了值,则对其进行求值并赋给 EnumInfo 对象。求值逻辑允许简单的代数表达式(包括使用大括号)。可以处理以下运算符
  • + 加
  • - 减
  • * 乘
  • / 除
  • ^ 异或
  • & 算术与
  • | 算术或
  • % 模

求值应该能处理 99% 的表达式,但是求值严格按照从左到右进行,即不执行优先级,有关详细信息请参阅注意事项。

枚举值

#define ENUM

为了确保 `enum` 序列(多个逗号,因此多个参数)使用标准宏正常工作,需要固定数量的宏参数。这是通过将 `enum` 序列定义为宏来实现的。然后,此 `enum` 宏可以在任何使用它的地方被视为单个宏参数。宏的主体中可以包含任意数量的逗号来定义枚举序列,并仍然将其视为单个宏参数。

DEFINE_AS_STR

在尝试字符串化 ENUM #define 时遇到了一个问题。编译器似乎不执行预期的 #define 替换,而是对 #define 名称进行字符串化,而不是其内容。

#define QAZ { 1,2,3,4,5 }
#define ZAQ(P1) char* myString = #P1;

生成以下内容

char* myString = "QAZ";

DEFINE_AS_STR 允许编译器执行正确的替换,即。

#define QAZ { 1,2,3,4,5 }
#define ZAQ(P1) char* myString = DEFINE_AS_STR(P1);

生成以下内容

char* myString = "{ 1,2,3,4,5 }";

我不能声称发现了这个宝藏,这个想法是从 boost 库中借鉴的。所以我不太确定它是如何工作的,但它确实确保了正确的替换。

注意事项

for 循环

前置和后置递增运算符可以正常工作,但是应该注意的是,如果将它们与非顺序枚举一起使用,可能会出现意外结果。考虑以下情况

enum MyEnums { firstEnum = 0, enum1 = firstEnum, enum2=1, enum3=16, enum3=2,
               enum4=4, enum5 = 8, totalEnums = enum5, errorEnum = 0xFF }
for(MyEnums eValue = firstEnum; eValue < totalEnums; ++eValue)
    DoSomething(eValue)

递增运算符将按以下顺序遍历 `enum`:`enum1`、`enum2`、`enum3`。但是,当 `eValue` 变为 `enum3`(`eValue = 2`)时,它小于 `totalEnums`,即 16 > 8,`for` 循环将退出。有两种解决方案:首先,不要使用 `<` 运算符,而是使用 `!=` 运算符,或者使用 EnumClass 提供的 const_iterator。注意:使用 `!=` 运算符需要结束值是唯一的。在遍历非顺序 `enum` 时,应谨慎确保 `for` 循环按预期工作。

表达式求值

EnumClass 使用一个非常简单的求值机制,该机制按从左到右的顺序进行求值。它不对值表达式执行任何优先级,因此
A+B*C 将不会正确求值,请使用大括号确保正确求值,即 (A+B)*C

结论

我曾尝试在此实现中使用标准的 C++,但仅使用 Microsoft 编译器进行了编译,因此,如果它能成功与其他编译器/平台编译,我会很有兴趣知道。我怀疑字符串化宏会有一个问题,但比我更聪明的人应该能够解决这个问题。

上面确定的两个注意事项是为将来的版本准备的;我不确定 `for` 循环问题是否有简单的答案,但是如果需求足够,表达式求值应该是可以解决的。

希望这个类能有所帮助。祝您使用愉快!

历史

  • 2008年10月06日:原始文章
© . All rights reserved.