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) 所有成员共享同一块内存 大小 = 最大成员的大小 同一时刻只能存一个成员 应用:节省内存、数据拆包 枚举 (enum) 给整数常量起名字 值默认从0开始自动递增 可手动赋值(如状态码) 应用:状态机、标志位组合 vs enum 优于 #define:类型安全、可调试

五、避坑指南与经验总结

最后,我把自己这些年踩过的坑总结一下:

  • 共用体赋值后立即使用:给union的某个成员赋值后,其他成员的值就变了。别指望还能读到旧值。
  • 枚举不要依赖具体数值:除非你手动赋值,否则别假设枚举值是多少。不同编译器可能有差异。
  • 共用体与结构体嵌套:这是高级用法,但容易把人绕晕。我建议先画内存布局图再写代码。
  • 枚举做函数参数:用枚举做参数,函数签名一目了然。比传int强太多了。

我曾经犯过的错:在一个通信协议里,用union解析报文,结果没注意字节对齐,解析出来的数据全是错的。后来加了#pragma pack(1)才搞定。嗯,字节对齐这个事,用union的时候一定要心里有数。

共用体和枚举,一个管内存,一个管可读性。用好了,代码既省空间又清晰。别小看这两个小东西,很多C语言高手就是靠这些细节拉开差距的。


专注资料整理