结构体一个萝卜一个坑,每个成员都占自己的地。可有些时候,数据彼此不会同时用到——这时候还让它们各占各的,就显得有点浪费啦。于是 C 语言给了我们两位会省钱的小伙伴:联合体和枚举。

联合体:大家共用同一块地

联合体(也叫共用体)由多个成员组成,但编译器只为最大的那个成员分配空间,所有成员共用同一块内存。给其中一个赋值,其他成员的值会跟着变。

union Un {
    int i;      // 4 字节
    char c;     // 1 字节
};
printf("%zu\n", sizeof(union Un));   // 4 —— 只留最大的那个

而且这三个「地址」是同一个:

printf("%p\n", &(un.i));
printf("%p\n", &(un.c));   // 和上面一样
printf("%p\n", &un);        // 也是同一个

联合体大小怎么算

大小至少是最大成员的大小;若最大成员大小不是「最大对齐数」的整数倍,就要再加到对齐数的整数倍。

union Un1 { char c[5]; int i; };   // char[5]=5, 对齐到 4 的倍数 → 8
union Un2 { short c[7]; int i; };  // short[7]=14, 对齐到 4 的倍数 → 16

一个实实在在的省空间案例

假设要做一个礼品兑换单,商品有图书、杯子、衬衫三类,各自还带不同属性。如果用一个结构体装下所有属性,就会又大又浪费:

// 优化前:图书根本用不到 "design、colors、sizes",却很占地方
struct gift_list {
    int stock_number; double price; int item_type;
    char title[20]; char author[20]; int num_pages;   // 图书
    char design[30]; int colors; int sizes;           // 杯子/衬衫
};

优化思路:公共属性单独列出,剩余「不同商品自己才用」的属性放进联合体:

struct gift_list {
    int stock_number; double price; int item_type;
    union {
        struct { char title[20]; char author[20]; int num_pages; } book;
        struct { char design[30]; } mug;
        struct { char design[30]; int colors; int sizes; } shirt;
    } item;   // 三种商品共用这块最大的空间
};

这样内存一下就"瘦"下来了。

联合体的另一个妙用:判断大小端

int check_sys() {
    union { int i; char c; } un;
    un.i = 1;
    return un.c;   // 返回 1 是小端,返回 0 是大端
}

枚举:把可能的取值一个个列出来

枚举顾名思义就是「一一列举」可能的取值:

enum Color { RED, GREEN, BLUE };
enum Week  { Mon, Tue, Wed, Thu, Fri, Sat, Sun };

这些常量本质是整数,默认从 0 开始依次 +1(RED=0, GREEN=1, BLUE=2),也可以手动改,比如 RED = 5。

枚举的几大优点:

  • 可读性/可维护性好:switch 里写 ADD 比写 1 明白多了。
  • 比 #define 更严谨:有类型检查。
  • 便于调试:预处理会删掉 #define,但枚举常量会保留在调试信息里。
  • 一次可以定义多个常量,且遵循作用域规则。
枚举与联合体的可爱插画
让该共用的共用、该列举的列举,数据也能精打细算

✿ 摘要

  • 联合体所有成员共用同一块内存,大小为最大成员并对齐到最大对齐数。
  • 联合体适合「互斥使用」的场景,能明显节省内存。
  • 联合体还能优雅地判断机器大小端。
  • 枚举列举可能的取值,常量默认从 0 递增,可手动改值。
  • 枚举比宏多类型检查、易调试、可读性高等优点。

愿当下的花如你所见般美丽