24、共用体与枚举:union共用体的定义与特点、enum枚举类型的使用、与#define宏常量的对比
共用体和枚举,这两个东西在C语言里属于“看着简单,用好了很香”的类型。我刚开始学的时候,觉得它们就是语法糖,可有可无。直到后来做嵌入式开发,内存抠得紧,才真正体会到union的价值。至于枚举,说白了就是给数字起个名字,但起得好,代码可读性直接上一个台阶。
一、共用体(union)的定义与特点
共用体,也叫联合体。它的核心特点是:所有成员共享同一块内存空间。你想想看,这跟结构体完全相反——结构体是每个成员都有自己的房间,共用体是所有成员挤在一个房间里。
关键点:共用体的大小等于最大成员的大小。同一时刻,只能存储一个成员的值。
1. 定义语法
union Data {
int i;
float f;
char str[20];
};
定义变量:
union Data data;
2. 内存布局
我用一个例子来说明:
#include <stdio.h>
union Test {
int a;
char b;
};
int main() {
union Test t;
t.a = 0x12345678;
printf("a = %x\n", t.a);
printf("b = %x\n", t.b); // 小端模式下输出 78
return 0;
}
为什么会这样?因为int占4字节,char占1字节,它们共用起始地址。在小端模式下,int的低字节在低地址,所以char读到的是低8位。我在项目中遇到过用union做字节序转换的场景,省了不少代码量。
我的习惯:用union做协议解析时,一定要清楚当前存的是哪个成员。写错了,数据就全乱了。
3. 共用体的典型应用场景
- 节省内存:多个变量不会同时使用,共用一块空间
- 数据拆包:比如把int拆成4个字节,方便网络传输
- 变体记录:一个字段表示类型,另一个字段存具体值
// 变体记录示例
union Value {
int i;
float f;
char *s;
};
struct Variant {
int type; // 0:int, 1:float, 2:string
union Value v;
};
我曾经踩过的坑:用union存字符串,结果忘记当前存的是int,直接打印字符串,程序崩溃了。嗯,从那以后我每次访问union成员前都会检查类型标志。
二、枚举(enum)类型的使用
枚举,说白了就是给整数常量起个名字。你写RED肯定比写0好理解,对吧?
1. 定义语法
enum Color {
RED, // 默认0
GREEN, // 默认1
BLUE // 默认2
};
也可以手动赋值:
enum Status {
OK = 200,
NOT_FOUND = 404,
ERROR = 500
};
2. 使用枚举
enum Color c = RED;
if (c == RED) {
printf("红色\n");
}
枚举变量本质上就是int,所以你可以把它当整数用。但我建议你别这么干——枚举的意义就在于可读性,你把它当int用,还不如直接用#define。
注意:枚举值在编译时就确定了,是常量。你不能在运行时修改枚举定义。
3. 枚举的进阶用法
枚举可以用在位域中,也可以做标志位组合:
enum Flags {
FLAG_A = 1 << 0, // 1
FLAG_B = 1 << 1, // 2
FLAG_C = 1 << 2 // 4
};
int flags = FLAG_A | FLAG_C; // 同时设置A和C
我在项目中用这种方式管理多个开关状态,比用多个bool变量清爽多了。
三、枚举与#define宏常量的对比
很多人喜欢用#define定义常量,比如:
#define RED 0
#define GREEN 1
#define BLUE 2
那枚举和#define到底选哪个?我列个表给你看:
| 对比项 | enum | #define |
|---|---|---|
| 类型检查 | 有(编译器会检查) | 无(纯文本替换) |
| 调试信息 | 有(调试器能显示名字) | 无(只看到数字) |
| 作用域 | 遵循块作用域 | 全局有效(直到#undef) |
| 自增特性 | 自动递增 | 需要手动赋值 |
| 占用内存 | 占用(枚举变量) | 不占用(编译期替换) |
我个人习惯:能用枚举的地方,尽量不用#define。原因很简单——调试的时候,枚举能显示名字,而#define只能看到数字。你想想看,调试时看到RED和看到0,哪个更直观?
我的建议:如果只是定义单个常量,比如#define PI 3.14,用#define没问题。但如果是定义一组相关的常量,比如颜色、状态码、错误码,用枚举更合适。
四、知识体系结构图
下面这张图帮你理清共用体和枚举的核心脉络:
五、避坑指南与经验总结
最后,我把自己这些年踩过的坑总结一下:
- 共用体赋值后立即使用:给union的某个成员赋值后,其他成员的值就变了。别指望还能读到旧值。
- 枚举不要依赖具体数值:除非你手动赋值,否则别假设枚举值是多少。不同编译器可能有差异。
- 共用体与结构体嵌套:这是高级用法,但容易把人绕晕。我建议先画内存布局图再写代码。
- 枚举做函数参数:用枚举做参数,函数签名一目了然。比传int强太多了。
我曾经犯过的错:在一个通信协议里,用union解析报文,结果没注意字节对齐,解析出来的数据全是错的。后来加了#pragma pack(1)才搞定。嗯,字节对齐这个事,用union的时候一定要心里有数。
共用体和枚举,一个管内存,一个管可读性。用好了,代码既省空间又清晰。别小看这两个小东西,很多C语言高手就是靠这些细节拉开差距的。