C语言关键字---枚举
文章目录
- enmu(枚举)
- 命名枚举(`enum Color {...}`)
- 匿名枚举(`enum {...} color`)
- 带typedef的别名(`typedef enum {...} Color`)
- 枚举应用与实战
enmu(枚举)
针对枚举,我们需要做的是把他当成一组有意义的常量,或者说是一个常量的集合。
切记这是一个常量,一个常量,一个常量。
关于枚举定义的二三事:
命名枚举(enum Color {...}
)
enum Color{RED, GREEN, BLUE};
enum Color c;
此时我们可以把enum Color
当成一个标识符,就类似于一个int
,然后我们还需要实例化
enum Color c;
enum Color c1; // 声明变量c1
enum Color c2; // 声明变量c2(类型可复用)
这个过程可以理解为:原本我们C语言库里面有一些int
关键字,我们只要使用这个区定义某个变量,我们就在知道这个范围是多少,同理我们最开始做的这一部分代码的目的就是告诉编译器我现在需要一个枚举变量,需要的是一个常量集合,我需要给这个类型划定一个范围,但是编译器并没有给我们现成的我们想要的,因为编译器不知道我们想要的范围是多少,因此就需要我们自己去定义一个范围,毕竟自己知道自己需要什么。因此此时就相当于让编译器弄一个和int
让人一看就知道范围是多少的常量集合。我们给这个行为叫做枚举类型的创建。说白了就是相当于是创建一个和int
关键字一样功能的枚举关键字。(这里面有一些用词是不准确的,但是是为了方便个人理解。)
enum Color{RED, GREEN, BLUE};
这也就是我们为啥需要一个实例化的原因,如果我们不实例化,其实就是只是相当于是一个弄了一个int
,那怎么用,毕竟我们在编程的时候,没有见过独立将int
单独使用的,都是结合变量一起使用的。
因此才有了最后一步:
enum Color c;
搞了一个枚举变量c
,这个变量c
的取值范围只能是我们在枚举定义内的,最关键的是虽然c
表示的是0、1、2,但是在表现方面他是 RED、GREEN、BLUE,只不过是换了一个身份使用,人还是这么个人。
匿名枚举(enum {...} color
)
匿名枚举的定义形式通常为:
enum { 常量1, 常量2
} 变量名; // 直接声明变量
无类型名
枚举未命名(如 enum Color
),因此无法通过类型名声明新变量。
允许:enum { RED, GREEN } color1;
(定义时声明变量)
编译器将匿名枚举视为一次性整型常量集合,仅为其绑定的变量分配内存,不生成可复用的类型符号。
匿名枚举常量本质是整型,编译器无法检测越界赋值。
// 定义枚举类型,放在函数外部或内部(如果只在内部使用,可以放在内部)
int process(int event) {// 定义枚举类型和状态变量enum { IDLE, BUSY, ERROR };static int state = IDLE; // 静态变量,初始化为IDLEswitch (state) {case IDLE:if (event == 1) { // 事件1state = BUSY;}// 注意:这里没有else返回,因为我们要执行到最后的返回break;case BUSY:if (event == 2) {state = IDLE;} else if (event == 3) {state = ERROR;}break;case ERROR:// 在ERROR状态,无论什么事件,都转回IDLEstate = IDLE;break;}return state; // 返回当前状态
}
注意我们返回的类型,为什么可以使用int定义返回类型?
枚举本质是整数常量 C语言标准明确规定:枚举类型(enum
)的成员本质上是int
类型的常量。
这些枚举常量在编译时会被直接替换为对应的整数值(如IDLE
替换为0
),因此枚举变量在内存中实际存储的是整数。
枚举与int
的隐式互操作性
C语言允许枚举类型与int
类型之间进行隐式转换,无需强制类型转换。
目前能想到用到的地方就是需要返回多个不用状态,表示不一样的事件,也就是工作中的那个难缠的代码。
带typedef的别名(typedef enum {...} Color
)
typedef enum { RED, GREEN, BLUE } Color; // 定义匿名枚举并用typedef起别名
Color c ; // 直接使用别名声明
Color c1 ; // 直接使用别名声明
Color c2 ; // 直接使用别名声明
省略enum
关键字:后续可直接用 Color c;
声明变量。
主要的作用就是省略第一种的声明过程,在引入关键字type
后看起来更便捷,更方便。
枚举应用与实战
#include <stdio.h>// 1. 定义枚举类型
enum Color {RED = 1, // 显式赋值为1GREEN, // 自动递增为2BLUE = 5, // 显式赋值为5YELLOW // 自动递增为6
};int main() {// 2. 声明枚举变量enum Color c;// 3. 将整数2强制转换为枚举类型c = (enum Color)2; // 整数2转为枚举类型,对应GREEN// 4. 验证转换结果printf("枚举值: %d\n", c); // 输出: 2 (GREEN的值)// 5. 实际使用:通过switch判断枚举值switch (c) {case RED:printf("红色\n");break;case GREEN: // 命中此分支printf("绿色\n"); // 输出: 绿色break;case BLUE:printf("蓝色\n");break;case YELLOW:printf("黄色\n");break;default:printf("未知颜色\n");}return 0;
}
我们主要关心的是:
c = (enum Color)2; // 整数2转为枚举类型,对应GREEN
这一步是能顺利执行switch
语句的核心。
这是因为如果不强制转换,直接赋值整数给枚举变量通常会导致编译警告或错误,具体取决于编译器的严格程度。
枚举变量只能直接接受同类型枚举常量(如 GREEN
)。若要将整数赋给枚举变量,必须显式强制转换,否则违反类型安全规则。
直接赋值整数也存在问题:
- 类型安全破坏:
枚举类型的设计是为了限制取值范围(如Color
只允许0,1,2
)。直接赋整数可能引入无效值(如c = 10
),导致逻辑错误。 - 可读性降低:
c = GREEN
明确表达意图,而c = 2
需开发者记忆映射关系,增加维护成本。 - 跨平台隐患:
枚举的底层类型由编译器决定(通常为int
),直接赋整数可能因平台差异引发值截断(如枚举底层为char
时)。
因此既然是也一个常量,如果我们给他一个不属于里面内容的常量,那么就是一个逻辑错误。
另外就是我们一般使用枚举的时候,其实就是为了避免魔鬼数字,在编程中,我们总是使用1、2、3表示特定的意义,有时候我们使用注释,来表示1、2、3分别表示什么,但是这样做总是麻烦的,有时候还容易注释错误。因此在这里我们可以使用枚举来替代上述的思想,我们直接定义一个类似于1、2、3集合,但是有趣的是我们不需要使用1、2、3我们可以换个我们需要表示的真实意思的单词即可,一般是大写。例如上述的例子
enum Color {RED = 1, // 显式赋值为1GREEN, // 自动递增为2BLUE = 5, // 显式赋值为5YELLOW // 自动递增为6
};
我们现在直接就规定了RED=1,就是说原本我们用1表示是红色, 用来做判断或者怎么。但是我们现在可以直接使用RED这个单词来使用,其实际以及还是表示的是1
。只是角度一样了,但是这样缺大大提升了阅读量。
场景 | 操作 | 示例 |
---|---|---|
赋值枚举常量 | 直接赋值 | c = GREEN; |
赋值有效整数 | 强制转换 | c = (enum Color)2; |
初始化全局/静态变量 | 可直接用整数 | enum Color c = 2; |
枚举与整数比较 | 直接比较 | if (c == 2) |
函数参数(形参为整数) | 直接传递枚举 | func(c); |
直接赋整数(无转换) | 避免使用 | c = 2; |
方案 | 匿名枚举 | 命名枚举 (enum State ) | #define 宏 |
---|---|---|---|
类型检查 | ✅ 有 | ✅ 有 | ❌ 无(文本替换) |
作用域控制 | 函数内 | 文件/全局 | 全局(易冲突) |
内存占用 | 栈变量(低) | 同左 | 编译期替换(无内存占用) |
复用性 | ❌ 仅限单函数 | ✅ 可跨函数/结构体 | ❌ 无类型 |
可读性 | 高(语义化状态名) | 高 | 低(魔数) |
typedef enum {BTN_PRESS_DOWN = 0, // button pressed downBTN_PRESS_UP, // button releasedBTN_PRESS_REPEAT, // repeated press detectedBTN_SINGLE_CLICK, // single click completedBTN_DOUBLE_CLICK, // double click completedBTN_LONG_PRESS_START, // long press startedBTN_LONG_PRESS_HOLD, // long press holdingBTN_EVENT_COUNT, // total number of eventsBTN_NONE_PRESS // no event} ButtonEvent;写法1:ButtonEvent button_get_event(Button* handle){if (!handle) return BTN_NONE_PRESS;return (ButtonEvent)(handle->event);}
ButtonEvent event = button_get_event(&buttons[i]);写法2:int button_get_event(Button* handle){if (!handle) return BTN_NONE_PRESS;return (handle->event);}
ButtonEvent event = (ButtonEvent)button_get_event(&buttons[i]);
在C语言中,这两种写法在运行时效果是等价的,但存在类型安全性和代码可读性的差异。
特性 | 第一种写法(返回枚举类型) | 第二种写法(返回int +强制转换) |
---|---|---|
函数返回类型 | ButtonEvent (枚举类型) | int (基础整型) |
类型安全性 | ✅ 高(编译器检查返回值合法性) | ❌ 低(依赖手动转换,可能忽略无效值) |
可读性 | ✅ 清晰(函数意图明确) | ⚠️ 较差(需额外转换,隐藏设计意图) |
编译器警告 | 无(类型匹配) | 可能触发警告(枚举与整型混用) |
这个地方就是我们前面说的,ButtonEvent event 如果我们想给枚举变量赋值,一定是要赋值我们人工定义的枚举变量的范围,不然就相当于是给int
类型变量赋值float
变量。但是如果我们只是想要枚举里面的值,我们只需要返回int
就行。就类似于方法2的前半段代码。
文章源码获取方式:
如果您对本文的源码感兴趣,欢迎在评论区留下您的邮箱地址。我会在空闲时间整理相关代码,并通过邮件发送给您。由于个人时间有限,发送可能会有一定延迟,请您耐心等待。同时,建议您在评论时注明具体的需求或问题,以便我更好地为您提供针对性的帮助。
【版权声明】
本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议。这意味着您可以自由地共享(复制、分发)和改编(修改、转换)本文内容,但必须遵守以下条件:
署名:您必须注明原作者(即本文博主)的姓名,并提供指向原文的链接。
相同方式共享:如果您基于本文创作了新的内容,必须使用相同的 CC 4.0 BY-SA 协议进行发布。
感谢您的理解与支持!如果您有任何疑问或需要进一步协助,请随时在评论区留言。