经常遇到这样的问题:为某些属性定义一组可选择的值。例如,文件的打开状态可能会有3种:输入、输出和追加。记录这些状态值的一种方式是定义每种状态都与一个唯一的常量数值相关联。我们可以定义下面这些状态码:
#define INPUT_MODE 0// 输入模式
#define OUTPUT_MODE 1 // 输出模式
#defineAPPEND_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, //输出模式
APPENDMODE, //追加模式
}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,POLYGONFORM初始化为 4。
接着再看POINTSTYPE枚举类型的定义:
//POINTS2D is 2,POINTS2W is3,POINTS3D is 3, POINTS3W is4
enum POINTS TYPE //点类型枚举
{
POINTS2D =2, //平面二维点
POINTS2W,
POINTS3D =3, //立体三维点
POINTS3W,
};
在 POINTSTYPE枚举类型中,枚举成员 POINTS2D显式初始化为 2;POINTS2W默认初始化,它的值比前一枚举成员的值大1,所以POINTS2W初始化为 3;枚举成员POINTS3D显式初始化为3;POINTS3W默认初始化,结果为 4。
注意:枚举成员值可以不唯一,两个枚举成员可以具有相同的值;不能改变枚举成员的值;枚举成员是一个常量表达式,可用于需要常量表达式的任何地方。
每个enum都定义唯一的类型。每个enum都定义了一种新的类型。和其他类型一样,可以定义和初始化POINTSTYPE类型的对象,也可以不同的方式使用这些对象。枚举类型对象的初始化或赋值,只能通过其枚举成员或同一枚举类型的其他对象来进行:
POINTS_TYPE pt3d= POINTS3D; // ok: POINTS3D is a POINTS_TYPE enumerator
POINTS_TYPE pt2w =3; // error: pt2w initialized with int
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 // 输入模式打开
#defineOUTPUT_MODE 1 // 输出模式打开
#defineAPPEND_MODE 2 // 追加模式打开
FILE *File_Open(const char *pszFileName, int nModes); // 文件打开函数声明
int main()
{
FILE *hFile =File_Open("a.txt",3);// File_Open 可正常编译通过
retum 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无法编译通过,参数错误
returm 0;
}
第一种实现方式,虽然File_Open 传入了非法参数,但是依然可以编译通过,不会发出任何报警信息,但是这时函数行为已经无法确定了。
第二种实现方式,在向函数传入非枚举类型值时,编译器将报出错误“参数不合法”。可以看出,通过枚举类型实现时,实参传入时确实进行了参数检查。
最后,我们对枚举类型和#define 使用优劣情况进行总结:
(1) enum 枚举值属于常量,#define 宏值不是常量。
(2) enum 枚举具有类型,#define 宏没有类型。枚举变量具有与普通变量相同的诸如作用域、值等性质,但宏没有,宏不是语言的一部分,它是一种预处理替换符。枚举类型主要用于限制性输入,例如,某个函数的某参数只接受某种类型中的有限个数值,除此之外的其他数值都不接受,这时候枚举能很好地解决这个问题。因此,能用枚举尽量用枚举。
(3) 宏没有作用域,宏定义后的代码都可使用这个宏。宏可以被重复定义,这可能导致宏的值被修改,所以不要用宏定义整型变量,建议用枚举或const。
请谨记
● 枚举用于某些限制性输入环境,可限制某个参数接受有限个数组,而#define定义的系列宏无法实现这些功能。
● #define宏可以重复定义导致宏值可被修改,所以整型变量的宏尽量用枚举或const 替代。