恰如其分地撰写出枚举类型,能够促使你所编写的C语言代码在可读性方面实现一个层次的提升。为数众多的初学者因嫌其繁琐而径直去书写数字,然而在历经三个月之后,连他们自身都无法明晰那段逻辑究竟是什么。而当进行调试之时,则更是犹如一场灾难。枚举实际上就是为整数赋予相应的名字,将那些宛如魔法般的数字转变成为具备实际意义的单词,如此这般的一种投入绝对是极为合算的。
从零开始认识枚举
plaintext
// 枚举定义
// enum_name: 枚举名称
// member_name: 成员名称
enum {
= ,
= ,
};
其实枚举的写法是挺简单的,用enum关键字去定义一个类型,在花括号里把所有可能的取值给列出来,并且每个取值都对应着一个名字,就像这样子,比如enum Weekday { MON, TUE, WED, THU, FRI, SAT, SUN }; 如此一来就创建了一个关于星期几的类型嗯,在定义完之后呢,就能够用Weekday作为类型去声明变量,就好比enum Weekday today = MON; 然后代码一下子就变得如同英文句子那般好读了的。
编译器对待枚举之际,会给每个名字赋予一个整数值,倘若不人工去指定,默认情况下依序从0启程递增,MON是0,TUE为1,依此类推,你也能够在中途定出某个值,像把SAT订为5,后续的SUN会自行变成6,这种自动分配机制省却了手动管理常量的繁杂,还规避了重复定义的状况。
枚举的底层存储原理
在C语言底层,枚举是整数,编译器会将枚举名字替换为对应的整数值,运行时全然不知有枚举类型,枚举变量占用内存大小与int相同,多为4个字节,这表明能把枚举变量直接当作整数使用,进行加减乘除均可,然而如此做会失去类型检查的保障。
plaintext
// 举例说明
enum COLOR {
COLOR_RED = 0,
COLOR_GREEN,
COLOR_BLUE,
};
COLOR color = COLOR_RED;
printf("color = %d\n", color); // color = 0
// 使用typedef关键字进行类型定义
typedef enum COLOR MY_COLOR;
MY_COLOR my_color = COLOR_GREEN;
printf("my_color = %d\n", my_color); // my_color = 1
因枚举本质上是整数,故而它能够与int类型相互进行赋值且不会报错,int a = MON,如此书写是全然合法的,a会获取到0,反之,enum Weekday day = 5,虽说能够编译通过,然而5不一定对应你所定义的任何枚举值,这种隐式转换是C语言为了具备灵活性而留存的特性,不过在实际项目当中应当尽可能予以规避。
枚举与整数的转换陷阱
存在这样一个状况,隐式转换所引发的最为严重的问题便是类型安全出现失效的情况,当你已然定义了一个枚举类型,其原本的意图是对变量进行限定,使其仅能够获取那几种特定的值,然而,由于存在能够随意赋予整数的情况,导致这个原本设定的限制变得毫无作用,就好像不存在一样,在代码审查过程当中,常常能够见到enum Color c = 100; 这样的书写方式,在后续使用c进行比较操作的时候,只要在相应的判断过程中,仅仅对RED、GREEN、BLUE进行了判断,却遗漏掉了100这个并不合法的值,那么便会继而产生那种很难进行排查的逻辑错误。
GCC编译器于碰到枚举与整数混合使用情形之际,一般会给出警告,像将整数赋予枚举变量,或者运用枚举变量跟整数作比较,编译器就会给出类型不匹配的提示,建议于编译之际加上-Wall -Wextra选项,将警告当作错误来处置,要是确实需要传入整数值,应当运用显式类型转换,写成(enum COLOR)0,以使代码阅读者清晰明了这里是什么操作。
plaintext
// 创建枚举变量并赋值
enum COLOR color = COLOR_RED;
// 比较枚举变量
if (color == COLOR_RED) {
printf("COLOR_RED\n");
} else {
printf("COLOR_GREEN or COLOR_BLUE\n");
}
枚举在状态机中的应用
用于嵌入式开发的状态机是常用模式,而枚举特别适宜用以表示状态,像一个能够售卖商品的自动售货机来说,它存在IDLE、SELECTING、PAYING、DISPENSING、OUT_OF_STOCK这些状态,当 employing枚举予以定义之后,在 switch语句里面能够直接运用状态名,如此代码逻辑清晰可见,相较于运用0、1、2、3去代表状态,使用枚举的版本几乎无需注释便可理解。
用于异常处理的情况,枚举也是有着很好的用途的。去定义一个错误码的枚举,其中涵盖ERR_SUCCESS、ERR_TIMEOUT、ERR_INVALID_PARAM、ERR_MEMORY等等。函数进行返回的,是这种枚举类型,调用的一方借助判断返回的值,便能够知晓发生了何种错误。相较于返回-1、-2这类数字而言,枚举使得错误处理的代码具备自解释性,在后续维护方面带来了极大的便利。
枚举在实际项目中的规范
实际项目里头,对于枚举的运用,存在着一些约定俗成的规范,命名常常采用全大写加下划线的样式,像是COLOR_RED这般,能够一眼就瞧出是枚举常量,枚举类型名一般加上后缀_T或者首字母大写,例如color_t或者Color,这些命名习惯虽说并非语法要求,然而在大型项目当中,能够极大地提升代码一致性。
plaintext
void func(enum COLOR color)
{
if (color == COLOR_RED) {
printf("COLOR_RED\n");
} else {
printf("COLOR_GREEN or COLOR_BLUE\n");
}
}
func(COLOR_RED); // COLOR_RED
// 隐式转换
func(0); // COLOR_RED
// 显示转换
func((enum COLOR)0);
存在一个容易被忽视掉的问题,即枚举值的扩展性,倘若枚举定义放在头文件里面,后续要是需要增加新的值,那么所有依赖这个头文件的源文件都得重新进行编译,所以在设计公共的接口之时,通常会预留一些值用以供未来扩展,或者定义最大值宏,就比如在枚举的最后添加一个MAX_XXX项,它既能够被用来当作数组大小,又方便后续去插入新的值。
枚举与宏定义的选择
众多的人会在何时运用枚举,何时运用#define上陷入纠结。要是存在一组相关的常量,像是星期、颜色、状态,那么枚举便是更佳的选择。枚举将这些常量归拢到一个类型之下,编译器能够给予更优的类型检查以及调试信息。在调试器当中,你所看到的是MON而非0,这对于定位问题有着极大的帮助。
常量之间若不存在关联,像是单个的缓冲区大小、超时时间这种情况,选用#define会更为适宜。枚举同样不适宜用于定义位掩码之举,虽说通过指定值能够达成,然而枚举变量无法直接开展位运算,得先转换为整数才行,如此操作会显得颇为别扭。此外,枚举的值在编译之际便已确定,无法如同宏那般参与条件编译。
要是枚举运用得恰到好处,那代码读起来就好似小说一般。你所编写的每一处枚举,皆是在给后续的维护人员讲述故事,告知他们此处为何是这般的值,此状态究竟代表着什么。那么随之而来的问题便是,你于项目里最为常用枚举去管理什么呢?是状态机、错误码,还是配置选项呢?欢迎在评论区域分享你的使用方式,同时也千万别忘记点赞,以便让更多的C语言开发者得以瞧见。