枚举类型enum,即enumeration的前四个字母,从字面上就可以看是它定义的值就是某一种值的穷举,例如一年中的12个月,一周中的七天、性别等。假设在一个应用程序中要实现几种数据传输方式,协议可能支持HTTP、FTP几种:
enum TransProtocol{
HTTP = 0,
FTP
};
TransProtocol prot = HTTP;
switch(prot){
case HTTP:
......
break;
case FTP:
......;
break;
default: ......
}
在C++98中enum变量的实际大小由编译器决定,只要能够保存enum的成员即可,而在将要发布的新的C++0x中,可以指定enum的实际实现类型,如实现为int类型。
enum Month : int { Jan, Feb, …, Dec }
其实,enum的作用就是定义一些整数型的常量,其数值默认从0开始,下面的值依次加1;而且在定义时可以为每一个常量定义一个特殊的值,而不仅限于用默认值,如[??],如果前面定义了特殊值,后面的将会依次在此值上递增。例如[??]中,如果Hide没有指定0x04,其值将会是0x03。
enum FileAttribute {
Read = 0x01,
Write = 0x02,
Hide = 0x04,
System = 0x08
};
用枚举值定义常量不仅可以像宏那样提高程序的可读性,而且还具有像用const定义的常量一样具备强类型检查。此外,在遍历一组值时,使用枚举就比使用常量方便得多,如[??]。以后如果支持了新的传输协议,只需要在PROTOCOL_COUNT增加一个变量即可,而后面用于遍历的for循环代码无需修改,使程序具有较好的扩展性,便于维护与升级,这也是一个在C/C++中惯用的技巧。
enum TransProtocol{ HTTP = 0, FTP, PROTOCOL_COUNT};
int main( void )
{
for(int p= 0; p < PROTOCOL_COUNT; ++p){
cout<<"Protocol:"<
}
return 0;
}
在C++中使用枚举类型不需要像C#中那样需要类型名作为标识符的一部分,如HTTP在C#中需要用TransProtocol::HTTP。这样在大型项目中将可能会出现命名冲突的问题,通常主要有几种解决方法。
1) 加前缀。例如,为TransProtocol的第一个成员都增加一个前缀ETP_,即:
enum TransProtocol { ETP_HTTP, ETP_FTP };
2) 使用命名空间,例如
namespace TransProtocol { enum TransProtocol { HTTP, FTP }; }
这样,在用到HTTP或FTP时就需要在前面加上域标识符TransProtocol::HTTP。
3) 将enum定义为类的嵌套类型,如[??]。
class DataTransfer
{
public:
enum TransProtocol { HTTP, FTP };
......
};
DataTransfer::TransProtocol prot = DataTransfer::HTTP;
其实,后两种方案用法基本相同,可以根据引用所定义枚举的范围选择使用,不推荐使用第1种方法。
虽然使用枚举比使用宏和常量都有优点,但还有一个问题,那就是每次将整数强制转换为枚举类型之前,客户端代码都必须检查枚举值范围的合法性,否则软件就可能存在未知的BUG。
enum Weekday { Sun, Mon, Tue, Wed, Thu, Fri, Sat };
void PrintWeekday(Weekday day) { … }
int main( void )
{
PrintWeekday((Weekday)8); // 潜在BUG
}
要保证传给PrintWeekday的实参合法,则必须在PrintWeekday函数中对参数进行检验,如果有另外一个函数也使用了Weekday,则也需要实现对参数值的检验,这样程序里将可能出向很多重复代码,难以维护。
除了上节中谈到枚举类型由于取值范围问题,可能使客户端代码存在不安全隐患;我们也不能像在C#或Java那样每个枚举变量都是一个对象,都有一个从object类继承而来的ToString方法,把对象转换为可以打印的字符串便于调试。而枚举值在C++中只是一种POD类型,其实质与int类型并无本质区别,不同于类类型,没有成员函数,故而不可能具有ToString方法,如果我们想实现:
std::cout<
则也必须将HTTP实现为某个类的实例,下面定义一个枚举类的实例。
#include
#include
#include
using namespace std;
class ScriptLanguage
{
public:
static const ScriptLanguage Shell;
static const ScriptLanguage Lua;
static const ScriptLanguage Python;
static const ScriptLanguage Perl;
inline const string& ToString( void ) const
{ return _langNames[_langId]; }
inline int Id( void ) const { return (int)_langId; }
inline bool operator==(const ScriptLanguage& other) const
{ return _langId == other._langId; }
inline bool operator!=(const ScriptLanguage& other) const
{ return _langId != other._langId; }
inline operator int( ) const { return _langId; }
inline operator string( ) const
{ return _langNames[_langId]; }
static ScriptLanguage FromString( const string& name);
public:
enum _ScriptLanguageEnum{
EShell , ELua, EPython, EPerl,
EScriptLanguageCount
};
explicit ScriptLanguage( const _ScriptLanguageEnum langId );
private:
_ScriptLanguageEnum _langId;
static const string _langNames[];
};
const string ScriptLanguage::_langNames[]
= {"shell", "lua", "python", "perl"};
const ScriptLanguage ScriptLanguage::Shell(ScriptLanguage::EShell);
const ScriptLanguage ScriptLanguage::Lua(ScriptLanguage::ELua);
const ScriptLanguage ScriptLanguage::Python(ScriptLanguage::EPython);
const ScriptLanguage ScriptLanguage::Perl(ScriptLanguage::EPerl);
ScriptLanguage ScriptLanguage::FromString( const string& name )
{
for(int i=0; i
if(name == _langNames[i]){
return ScriptLanguage((_ScriptLanguageEnum)i);
}
}
throw std::invalid_argument("Invalid ScriptLanguage name:" + name);
}
ScriptLanguage::ScriptLanguage( const _ScriptLanguageEnum langId ) :_langId(langId)
{
}
// 测试代码
int main( void )
{
ScriptLanguage shell = ScriptLanguage::Shell;
ScriptLanguage lua = ScriptLanguage::Lua;
ScriptLanguage python=ScriptLanguage::FromString("python");
ScriptLanguage perl = ScriptLanguage::FromString("perl");
assert(shell != lua && shell != python && python != perl);
assert(shell==ScriptLanguage::Shell
&&lua== ScriptLanguage::Lua);
assert(shell.ToString()==ScriptLanguage::Shell.ToString());
assert((int)shell==shell.Id());
assert( (string)lua == ScriptLanguage::Lua.ToString() );
assert( (string)lua == "lua");
switch(shell){
case ScriptLanguage::EShell: // do something
break;
case ScriptLanguage::ELua: // do something
break;
default: // do something
break;
}
return 0;
}
在实现枚举类时使用了一些C++的特性:
l 虽然使用类封装了_ScriptLanguageEnum,但其效率并不比直接使用枚举类型低多少,因为数据成员也只有一个枚举型的成员变量;
l ToString方法直接返回的是静态变量的引用,虽然是函数,但通过inline关键字标识,编译器可以很容易地优化为对数组的直接引用,并没有函数调用对效率的影响,而且这些常量的构造是在main函数执行前已经初始化完毕了;
l 通过对类型转换函数的定义,可以方便地将 ScriptLanguage类型转换为string类型或int类型,使用起来非常方便;并且通过对int类型转换函数的定义,使得ScriptLanguage类型变量可以直接用于switch语句,而不必使用多个if-else分支语句。
l 将构造函数定义为private,保证了客户端代码不会使用非常的int型类型强制转换为ScriptLanguage类型,这比使用enum类型更为安全。
通过使用类替代枚举类型,基本模拟了C#中的枚举类型,增加了ToString函数和FromString函数,方便用于人机界面,而且并没有对应用程序的性能有明显影响(虽然静态变量多占用了一点内存)。