C语言枚举类型全解析:定义、运用、底层及转换要点

恰如其分地撰写出枚举类型,能够促使你所编写的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语言开发者得以瞧见。

相关推荐
m0_488633324 小时前
C语言结构体成员定义方式与变量定义啥区别?a++啥意思?
c语言·程序设计·结构体·运算符·变量定义
C++ 老炮儿的技术栈4 小时前
Tcp客户端报错原因分析
linux·c语言·网络·c++·网络协议·tcp/ip
co_wait4 小时前
【C语言】Linux系统文件操作函数基本使用
linux·c语言·microsoft
计算机安禾4 小时前
【数据结构与算法】第8篇:线性表(四):双向链表与循环链表
c语言·开发语言·数据结构·c++·算法·链表·visual studio
WalterJau13 小时前
C 内存分区
c语言
csdn_aspnet15 小时前
C/C++ 两个凸多边形之间的切线(Tangents between two Convex Polygons)
c语言·c++·算法
剑心诀17 小时前
02 数据结构(C) | 线性表——顺序表的基本操作
c语言·开发语言·数据结构
m0_4886333218 小时前
Windows环境下编译运行C语言程序,合适工具与方法很关键
c语言·windows·git·开发工具·编译器
m0_4886333218 小时前
C语言变量命名规则、入门自学、运算符优先级及数据结构介绍
c语言·数据结构·运算符优先级·变量命名·入门自学