实用经验 10 枚举和一组预处理的#define有何不同?

经常遇到这样的问题,为某些属性定义一组可选择的值。例如:文件的打开状态可能会有三种:输入、输出、追加。记录这些状态值的一种方式是定义每种状态都与一个唯一的常量数值相关联。我们可以定义下面这些状态码:

#define  INPUT_MODE    0  // 输入模式 
#define  OUTPUT_MODE  1  // 输出模式
#define  APPEND_MODE  2  // 追加模式

虽然这种方式也可奏效,但是它存在一个明显的缺点:没有指出这些值是相关联的。枚举(enumeration)提供了一种替代方法,不但可定义整数数量集,而且还把他们进行了分组。

枚举的定义包括关键字enum,其后可选的枚举类型名称,一个用花括号括起来、并用逗号分隔的枚举成员(enumerators)列表组成。我们可把文件打开模式按枚举形式从新定义一番。

// INPUT_MODE is 0, OUTPUT_MODE is 1, APPEND_MODE is 2
typedef enum  tagOPEN_MODES
{
    INPUT _MODE,   // 输入模式
    OUTPUT_MODE,  // 输出模式
    APPEND_MODE,  // 追加模式
}OPEN_MODES;

默认地,第一个枚举成员赋值为0,后面的每个枚举成员赋值比前一个大1。如OPEN_MODES枚举类型INPUT _MODE等于0,OUTPUT_MODE等于1,APPEND_MODE等于2。

每个枚举成员都是一个常量。在枚举定义时,可以为一个或多个枚举成员提供初始化值。用来初始化枚举成员的值必须是一个常量表达式。常量表达式是在编译时就能够计算出结果的整型表达式。字面值常量是常量表达式,正如一个通过常量表达式自我初始化的 const对象也是常量表达式一样。例如,FORMS枚举定义如下:

// SHAPE_FORM = 1, SPHERE_FORM = 2,CYLINDER_FORM = 3,POLYGON_FORM = 4
typedef enum tagFORMS  		// 形状每局
{
	SHAPE_FORM = 1// 三角形  
	SPHERE_FORM,	// 球形
	CYLINDER_FORM,	// 圆柱型
	POLYGON_FORM,	// 多边形型
} FORMS

在枚举类型FORMS中,显式地将SHAPE_FORM赋值为1。其他枚举成员隐式初始化:SPHERE_FORM初始化为2,CYLINDER_FORM初始化为3,POLYGON_FORM初始化为4。

接着再看POINTS_TYPE枚举类型的定义:

// POINTS2D is 2, POINTS2W is 3, POINTS3D is 3, POINTS3W is 4 
enum POINTS_TYPE  // 点类型枚举
{
    POINTS2D  = 2// 平面二维点
    POINTS2W,
    POINTS3D  = 3// 立体三维点
    POINTS3W,
};

POINTS_TYPE枚举类型中,枚举成员POINTS2D显示初始化为2。POINTS2W默认初始化,它的值比前一枚举成员的值大1。所以POINTS2W初始化为3。枚举成员POINTS3D显示初始化为3。 POINTS3W默认初始化,结果为4。

注意:枚举成员值可以不唯一,可以两个枚举成员具有相同的值;不能改变枚举成员的值,枚举成员是一个常量表达式,枚举成员可用于需要常量表达式的任何地方。

每个enum 都定义唯一的类型。每个 enum 都定义了一种新的类型。和其他类型一样,可以定义和初始化 POINTS_TYPE类型的对象,也可以以不同的方式使用这些对象。枚举类型对象的初始化或赋值,只能通过其枚举成员或同一枚举类型的其他对象来进行:

POINTS_TYPE pt3d = POINTS3D;      // ok: POINTS3D is a POINTS_TYPE enumerator 
POINTS_TYPE pt2w = 3;             // error: pt2w initialized with int
FORMS polygon = POLYGON_FORM;
pt2w = polygon;                   // error: polygon is not a Points enumerator
pt2w = pt3d;                      // ok: both are objects of Points enum type

注意:不能用int数值初始化枚举类型变量,即使是初始化的int数值与枚举类型相关联。

通过define宏也可以实现枚举类型的部分功能。但是有些功能define宏是无法实现的。像枚举类型具有的类型检查,枚举类型值具有的数据值关联属性。这些都是define宏无法匹敌的。

我们通过文件打开函数的define宏实现和枚举类型实现对比一下两者的优劣。
第一种实现:define宏实现

#define  INPUT_MODE    0   // 输入模式打开
#define  OUTPUT_MODE  1   // 输出模式打开
#define  APPEND_MODE  2   // 追加模式打开
FILE *File_Open(const char *pszFileName, int nModes);  // 文件打开函数声明
int main()
{
    FILE *hFile = File_Open(“a.txt”, 3);  // File_Open 可正常编译通过
    return 0;
}

第二种实现:枚举类型实现。

// INPUT_MODE is 0, OUTPUT_MODE is 1, APPEND_MODE is 2
typedef enum  tagOPEN_MODES
{
    INPUT _MODE,        // 输入模式打开
    OUTPUT_MODE,       // 输出模式打开
    APPEND_MODE,       // 追加模式打开
}OPEN_MODES;
FILE* File_Open(const char *pszFileName, OPEN_MODES nModes); // 文件打开函数声明
int main()
{
    FILE *hFile = File_Open(“a.txt”, 3);  // File_Open 无法编译通过,参数错误。
    return 0;
}

第一种实现方式,虽然File_Open传入了非法参数但是依然可以编译通过,不会向我们发出任何报警信息。但是这时函数行为已经无法确定了。

第二种实现方式,向函数传入非枚举类型值时,编译器将报出错误“参数不合法”错误,提醒我们。可以看出通过枚举类型实现时,实参传入时确实进行了参数检查。

最后,我们对枚举类型和#define使用优劣情况进行总结:

  1. enum枚举值属于常量,#define宏值不是常量。
  2. enum枚举具有类型,#define宏没有类型。枚举变量具有与普通变量相同的诸如作用域、值等性质,但宏没有,宏不是语言的一部分,它是一种预处理替换符。枚举类型主要用于限制性输入,例如,某个函数的某参数只接受某种类型中的有限个数值,除此之外的其它数值都不接受,这时候枚举能很好地解决这个问题。能用枚举尽量用枚举。
  3. 宏没有作用域,宏定义后的代码都可使用这个宏。宏可以被重复定义这可能导致宏的值被修改,所以不要用宏定义整型变量,建议用枚举或const。

请谨记

  • 枚举用于某些限制性输入环境,可限制某个参数接受有限个数组。而#define定义的系列宏无法实现这些功能。
  • #define宏可以重复定义导致宏值可被修改,所以整型变量的宏尽量用枚举或const替代。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值