C++枚举类型讨论

Technorati 标签: C++, enum

枚举类型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;

}

1.1.1. 枚举类型的作用域

在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种方法。

1.1.2. 枚举值的范围

虽然使用枚举比使用宏和常量都有优点,但还有一个问题,那就是每次将整数强制转换为枚举类型之前,客户端代码都必须检查枚举值范围的合法性,否则软件就可能存在未知的BUG。

enum Weekday { Sun, Mon, Tue, Wed, Thu, Fri, Sat };

void PrintWeekday(Weekday day) { … }

int main( void )

{

PrintWeekday((Weekday)8); // 潜在BUG

}

要保证传给PrintWeekday的实参合法,则必须在PrintWeekday函数中对参数进行检验,如果有另外一个函数也使用了Weekday,则也需要实现对参数值的检验,这样程序里将可能出向很多重复代码,难以维护。

1.1.3. 使用枚举类

除了上节中谈到枚举类型由于取值范围问题,可能使客户端代码存在不安全隐患;我们也不能像在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函数,方便用于人机界面,而且并没有对应用程序的性能有明显影响(虽然静态变量多占用了一点内存)。

你可能感兴趣的:(C++,String,shell,python,perl,lua)