C++中枚举与字符串相互转换
前言
有的时候我们喜欢使用一些外部的文件保存管理一些配置信息,这些配置文件大多都是文本格式例如ini,xml等,这样方便编辑和管理。因此在使用的过程中必然会遇到各种字符串转换问题。
最常见的便是将字符串的数字转换为对应的整形(integer)或者浮点(float),如果遇到枚举类型,可能便会想当做是整形来处理,但觉得不是特别理想。如果能有办法直接转换为枚举会方便很多。
案例
一个简单的配置文件:
1 |
[config] |
2 |
fooInt = 10 |
3 |
fooFloat = 2.5 |
4 |
fooEnum = cpp |
在C++里面有枚举:
1 |
enum ProgLang
|
2 |
{ |
3 |
e_cpp,
|
4 |
e_java,
|
5 |
e_csharp
|
6 |
}; |
如果对于前面两个数值,可以很简单的读取,例如:
1 |
Config cfg;//Config 类见<<C++编写Config类读取配置文件>> |
2 |
cfg.Load( "config.ini" );
|
3 |
4 |
int fooInt = cfg.GetValue< int >( "fooInt" );
|
5 |
float fooFloat = cfg.GetValue< float >( "fooFloat" );
|
对于后面那个枚举,可能就稍微麻烦一点:
1 |
ProgLang fooEnum; |
2 |
const char * rawData = cfg.GetValue< const char *>( "fooEnum" );
|
3 |
if ( strcmp (rawData, "cpp" )) fooEnum = e_cpp;
|
4 |
else if ( strcmp (rawData, "java" )) fooEnum = e_java;
|
5 |
else if ( strcmp (rawData, "csharp" )) fooEnum = e_csharp;
|
如果直接在配置文件里面不保存”cpp“字符串而直接保存枚举值,这里便可以当整形取出来再做类型转换。不过这样不安全,也不好维护,如果枚举内部的定义发生了变化,外部保存的数据自然也就会失效出错。
解决方案
C++没有(也不可能会有)反射机制,枚举成员在编译以后也已经变成了纯粹的数值,失去了名字。所以想使用字符串作为搜索依据,必须为枚举保留一份名字信息,例如:
1 |
enum ProgLang
|
2 |
{ |
3 |
e_cpp,
|
4 |
e_java,
|
5 |
e_csharp
|
6 |
}; |
7 |
const char * ProgLangNames[] = { "cpp" , "java" , "csharp" };
|
这个字符串数组只能预先写好,无论是手动还是通过工具自动生成。然后可以在这个字符串数组里面搜索目标字符串,将找到的结果下标转换为对应的枚举值即可,例如:
1 |
for ( int i = 0; i < sizeof (ProgLangNames) / sizeof (ProgLangNames[0]); ++i)
|
2 |
{ |
3 |
if ( strcmp (rawData, ProgLangNames[i]))
|
4 |
{
|
5 |
fooEnum = static_cast <ProgLang>(i);
|
6 |
break ;
|
7 |
}
|
8 |
} |
枚举关联字符串
解决方案找到了,随之而来的问题就是如何将枚举与所需要的字符串查找表联系起来。如有多个枚举:
01 |
enum ProgLang
|
02 |
{ |
03 |
e_cpp,
|
04 |
e_java,
|
05 |
e_csharp
|
06 |
}; |
07 |
const char * ProgLangNames[] = { "cpp" , "java" , "csharp" };
|
08 |
09 |
enum ScriptLang
|
10 |
{ |
11 |
e_lua,
|
12 |
e_actionscript,
|
13 |
e_javascript
|
14 |
}; |
15 |
const char * ScriptLangNames[] = { "lua" , "actionscript" , "javascript" };
|
枚举不像类或结构体可以定义自己的成员变量,所以查找表只能在外部定义,通过实例化模板类来将他们相互联系起来。例如:
1 |
< template EnumType>
|
2 |
struct SEnumName
|
3 |
{ |
4 |
static const char * List[];
|
5 |
} |
这定义了一个查找表的模板结构体,然后将之前的代码改造为:
01 |
enum ProgLang
|
02 |
{ |
03 |
e_cpp,
|
04 |
e_java,
|
05 |
e_csharp
|
06 |
}; |
07 |
const char * SEnumName<ProgLang>::List[] =
|
08 |
{ |
09 |
"cpp" ,
|
10 |
"java" ,
|
11 |
"csharp"
|
12 |
}; |
13 |
14 |
enum ScriptLang
|
15 |
{ |
16 |
e_lua,
|
17 |
e_actionscript,
|
18 |
e_javascript
|
19 |
}; |
20 |
const char * SEnumName<ScriptLang>::List[] =
|
21 |
{ |
22 |
"lua" ,
|
23 |
"actionscript" ,
|
24 |
"javascript"
|
25 |
}; |
然后便可以实现字符串转枚举的功能:
01 |
template < typename EnumType>
|
02 |
EnumType ConvertStringToEnum( const char * pStr)
|
03 |
{ |
04 |
EnumType fooEnum = static_cast <EnumType>(-1);
|
05 |
int count = sizeof (SEnumName<EnumType>::List) /
|
06 |
sizeof (SEnumName<EnumType>::List[0]);
|
07 |
for ( int i = 0; i < count; ++i)
|
08 |
{
|
09 |
if ( strcmp (rawData, SEnumName<EnumType>::List[i]))
|
10 |
{
|
11 |
fooEnum = static_cast <ProgLang>(i);
|
12 |
break ;
|
13 |
}
|
14 |
}
|
15 |
return fooEnum;
|
16 |
} |
这样一来,最开始的问题就可以简化为:
1 |
ProgLang fooEnum = cfg.GetValue<ProgLang>( "fooEnum" );
|
有了这种方式,自然枚举反转回字符串也很容易了,就不再赘述了。
转载出处: http://a.vifix.us/blog/cpp-convert-string-enum