C 高阶系列导航
1. 枚举简介
枚举是 C 中的一种基本数据类型,常作为数据标识符的集合使用,能够明显地提高程序的可读性与安全性。
2. 枚举的定义
枚举的定义格式为:
enum 枚举名
{
枚举成员 1,
枚举成员 2,
...
};
如果需要定义枚举变量时,可以使用 typedef
对枚举类型的名称进行简化:
typedef enum 枚举名
{
枚举成员 1,
枚举成员 2,
...
} 枚举类型别称;
甚至省略枚举名:
typedef enum
{
枚举成员 1,
枚举成员 2,
...
} 枚举类型别称;
在使用时可以省略 enum
直接使用枚举类型别称:
枚举类型别称 value;
在定义枚举类型时,可以直接对枚举成员设定对应的数值,例如:
enum 枚举名
{
枚举成员 1 = 1,
枚举成员 2 = 2,
...
};
当然,设定的数值必须为整型,其他类型的数值会发生报错。
如果没有设定数值时,缺省地,第一个枚举成员为 0,第二个枚举成员为 1,依次类推。准确来说,当当前枚举成员没有被设定数值时,则缺省值为前一成员的数值加一。例如:
enum 枚举名
{
枚举成员 1 = 1,
枚举成员 2 = 0,
枚举成员 3,
...
};
此时。枚举成员 3 的缺省值为 1。
当枚举应用于 switch-case 或 if 语句中时,由于枚举成员的判别条件实际为成员的值,此时需确保枚举成员的值的唯一性。故在定义枚举类型过程中,应避免随意乱定义枚举成员的值。
3. 枚举的使用方法:
枚举有两种使用方法。一是把它当做数据类型看待,定义枚举变量;二是直接使用枚举成员,把枚举成员当做宏使用。
3.1 枚举变量
在定义枚举变量时,枚举变量可以当做整型变量对待,支持整型相关的运算。且对象缺省值为 0。
- 示例:
typedef enum test
{
element1 = 1,
element2,
element3,
} eTest;
int main()
{
eTest obj;
printf("obj = %d, sizeof(obj) = %d\n", obj, sizeof(obj)); // ptintf: obj = 0, sizeof(obj) = 4
return 0;
}
可以看到,枚举变量的缺省值为 0,且占据 4 字节内存。实际上,枚举类型的内存大小与枚举成员的大小有关,当最大的枚举成员的值在整型范围内,该枚举类型大小为 4 字节;当超出整型范围而小于长整型范围时,该枚举类型大小为 8 字节。可以理解为枚举类型的大小会自适应地根据枚举成员的值的大小而改变。
3.2 类宏方法
枚举类型与其他数据类型最大的区别是,可以不通过枚举变量直接使用枚举类型中的成员。在程序中直接使用枚举成员等价于使用枚举成员对应的值,效果与宏替换类似。
- 示例:
typedef enum test
{
element1 = 1,
element2,
element3,
} eTest;
int main()
{
int i = element1;
if (i < element3)
{
printf("i = %d\n", i); // ptintf: i = 1
}
return 0;
}
4. 枚举的应用
在简介中有提到,枚举常作为数据标识符的集合使用,那么何为数据标识符呢?简单来说就是使用某个数据代表每一具体的事物。
例如,需要设计一个接口函数,该函数的传参代表着七色调,传参数值的不同对应着不同的颜色,函数中会根据不同的颜色完成不同的动作。假设该函数声明为:
int process(int colour);
假设约定为使用数值 1 ~ 7 对应着红橙黄绿蓝靛紫七种颜色,程序设计如下:
int process(int colour)
{
if (colour < 1 || colour > 7)
{
return -1;
}
...
switch(colour)
{
case 1:
{
...
break;
}
case 2:
{
...
break;
}
...
case 7:
{
...
break;
}
default:
{
...
}
}
...
}
乍一看该程序没什么问题,但过一段时间再回头来看这段程序,里面的 1、2 等数值便可能区分不清具体对应着哪一种颜色了。可能有人说,注释好 1 ~ 7 对应的颜色不就可以了吗?但如果颜色不止 7 种,而是 70 种、700 种,那么一个一个查阅注释并不是多高明的选择。而且,假设在设计以上程序的过程中,把 case 7
错写为 case 8
,那么程序的编译并不会产生任何的报错,但毫无疑问该程序将产生一个 BUG,该 BUG 会直接影响程序的正常功能,如果程序的复杂度较高的话,定位该 BUG 将会花费不少的时间。
那么有什么办法可以完善以上程序呢?答案就是使用枚举。把枚举作为颜色标识符的集合,在程序中把对颜色的判别全部替换为对应的枚举即可使程序更易读、安全。
设计枚举类型如下:
enum colour
{
RED = 1,
ORANGE = 2,
YELLOW = 3,
GREEN = 4,
BLUE = 5,
INDIGO = 6,
PURPLE = 7
};
把以上函数 process()
完善为:
int process(int colour)
{
if (colour < Red || colour > Purple)
{
return -1;
}
...
switch(colour)
{
case RED:
{
...
break;
}
case ORANGE:
{
...
break;
}
...
case PURPLE:
{
...
break;
}
default:
{
...
}
}
...
}
在函数调用时,直接使用枚举 color 中的成员即可避免传参错误的问题,例如:
if (res = process(RED) < 0)
{
...
}
可见,使用枚举后,无论是程序的可读性还是安全性都得到了明显的提升。
5. 枚举与宏
在工程中,常见使用宏作为数据标识符,例如:
#define MaxLen 1024
可见,枚举与宏同样能够完成数据标识符功能。那么枚举与宏表现为数据标识符时是否相同呢?
首先需要把枚举与宏区别开来,枚举与宏是两种完全不同的事物。枚举为 C 的基础数据类型,应用周期为程序的整个运行期;而宏为 C 的一种语法特性,仅为预处理期有效,预处理时把所有宏进行替换。
当程序中需要使用数据标识符时,可以使用宏来实现,例如:
#define RED 1;
#define ORANGE 2;
#define YELLOW 3;
#define GREEN 4;
#define BLUE 5;
#define INDIGO 6;
#define PURPLE 7;
同样可以使用枚举,实现如下所示:
enum colour
{
RED = 1,
ORANGE = 2,
YELLOW = 3,
GREEN = 4,
BLUE = 5,
INDIGO = 6,
PURPLE = 7
};
那么在工程中应该使用枚举还是宏呢?其实是没有标准答案的。而本人认为枚举应该是更理想的选择,适用于大多数场景,理由如下:
- 使用枚举可以更好地把数据标识符进行归类管理。
- 使用枚举可以更好地避免定义错误的数据类型的宏,因为枚举成员只能为整型。
- 当需要大量的数据标识符时,使用枚举可以利用其自推导特性,只设定少量甚至不需要设定枚举成员的数值,降低重复性编程。
当然,当需要浮点型数据标识符时枚举也无法胜任,只能使用宏来实现,例如:
#define PI 3.14