C: 联合与枚举

联合体就像多功能充电线——同一时刻只能用一种接口,但节省空间;枚举就像菜单编号——用名字代替数字,代码更可读。

1. union 定义与共享内存

union 的所有成员共享同一块内存,大小等于最大成员的大小。同一时刻只能使用一个成员。

C
union Data {
    int i;
    float f;
    char str[4];
};

printf("%zu\n", sizeof(union Data));
TEXT 📖 仅展示
4

int 成员赋值后再读取 float 成员,得到的是同一块内存的不同解释:

C
union Data d;
d.i = 42;
printf("i = %d\n", d.i);
printf("f = %f\n", d.f);
TEXT 📖 仅展示
i = 42
f = 0.000000
⚠️ 写入一个成员后读取另一个成员,结果是实现定义的(取决于平台和类型)。实际编程中应通过标记字段区分当前有效成员。


2. union 与 struct 对比

特性 struct union
内存 每个成员独立空间 所有成员共享空间
大小 各成员大小之和(含对齐填充) 最大成员的大小
同时访问 可同时访问所有成员 同一时刻只用一个成员
修改 改一个不影响其他 改一个影响所有
C
struct SData {
    int i;
    float f;
    char str[4];
};

union UData {
    int i;
    float f;
    char str[4];
};

printf("struct: %zu\n", sizeof(struct SData));
printf("union:  %zu\n", sizeof(union UData));
TEXT 📖 仅展示
struct: 12
union:  4

3. 带标记的联合体

实际使用中,联合体通常搭配一个标记字段,指示当前存储的是哪种类型:

C
typedef struct {
    int type;
    union {
        int int_val;
        float float_val;
        char str_val[32];
    } value;
} Variant;

void print_variant(const Variant *v) {
    switch (v->type) {
        case 0: printf("int: %d\n", v->value.int_val); break;
        case 1: printf("float: %f\n", v->value.float_val); break;
        case 2: printf("str: %s\n", v->value.str_val); break;
    }
}

▶ 示例

C
#include <stdio.h>

typedef struct {
    int kind;
    union {
        int ival;
        double dval;
    } u;
} Number;

void print_number(const Number *n) {
    if (n->kind == 0) {
        printf("整数: %d\n", n->u.ival);
    } else {
        printf("浮点: %.2f\n", n->u.dval);
    }
}

int main(void) {
    Number a = {0, .u.ival = 42};
    Number b = {1, .u.dval = 3.14};
    print_number(&a);
    print_number(&b);
    return 0;
}
▶ 试一试
TEXT 📖 仅展示
整数: 42
浮点: 3.14

4. enum 定义与使用

枚举用名字代替整数常量,提高代码可读性。

C
enum Color {
    RED,
    GREEN,
    BLUE
};

enum Color favorite = GREEN;
if (favorite == GREEN) {
    printf("选择绿色\n");
}
TEXT 📖 仅展示
选择绿色

(1) 指定枚举值

C
enum Weekday {
    MON = 1,
    TUE,
    WED,
    THU,
    FRI,
    SAT = 100,
    SUN
};

printf("MON=%d SAT=%d SUN=%d\n", MON, SAT, SUN);
TEXT 📖 仅展示
MON=1 SAT=100 SUN=101
💡 未指定值的枚举常量自动取前一个值 +1。第一个默认为 0。

(2) 枚举与 switch 搭配

C
enum Direction { UP, DOWN, LEFT, RIGHT };

void move(enum Direction d) {
    switch (d) {
        case UP:    printf("向上移动\n"); break;
        case DOWN:  printf("向下移动\n"); break;
        case LEFT:  printf("向左移动\n"); break;
        case RIGHT: printf("向右移动\n"); break;
    }
}
⚠️ 枚举底层是 int,可以赋任意整数值,编译器不一定警告。不要滥用这个特性。


5. 枚举底层 int 值

枚举常量本质上就是 int 类型的命名常量。

C
enum Status { OK = 200, NOT_FOUND = 404, ERROR = 500 };
printf("%d %d %d\n", OK, NOT_FOUND, ERROR);
TEXT 📖 仅展示
200 404 500

枚举变量可以和整数互相赋值(C 语言中),但建议用枚举值保持类型一致性:

C
enum Status s = OK;
int code = s;
💡 用枚举代替 #define 定义常量:枚举有类型信息、调试时显示名字、作用域更可控。


6. 用 union 判断大小端

大小端是指多字节数据在内存中的存放顺序。利用 union 可以方便判断:

C
int is_little_endian(void) {
    union {
        int i;
        char c;
    } u;
    u.i = 1;
    return u.c == 1;
}

如果最低字节存放在低地址(u.c == 1),就是小端;否则是大端。

▶ 示例

C
#include <stdio.h>

typedef union {
    unsigned int i;
    unsigned char bytes[4];
} EndianTest;

void print_byte_order(void) {
    EndianTest t;
    t.i = 0x12345678;
    printf("内存字节顺序: ");
    for (int i = 0; i < 4; i++) {
        printf("%02X ", t.bytes[i]);
    }
    printf("\n");
    if (t.bytes[0] == 0x78) {
        printf("小端序 (Little Endian)\n");
    } else {
        printf("大端序 (Big Endian)\n");
    }
}

int main(void) {
    print_byte_order();
    return 0;
}
▶ 试一试
TEXT 📖 仅展示
内存字节顺序: 78 56 34 12
小端序 (Little Endian)
🔥 x86/ARM 通常是 小端序,网络传输用大端序(网络字节序)。理解大小端对协议解析、文件格式处理至关重要。


7. union 节省内存的实际场景

当结构体中有多个互斥字段时,用 union 节省空间:

C
typedef struct {
    int type;
    union {
        struct { int width, height; } rect;
        struct { int radius; } circle;
        struct { int side; } square;
    } shape;
} Figure;

Figure f;
f.type = 1;
f.shape.circle.radius = 10;

三种形状同一时刻只用一种,用 union 共享内存,结构体大小由最大形状决定。

❓ 常见问题

Q union 能同时使用多个成员吗?
A 不能。union 成员共享内存,写入一个会覆盖其他成员的内容。同一时刻只应读取最后写入的成员。
Q 枚举能用 %s 直接打印名字吗?
A 不能。枚举值是整数,要打印名字需自己写映射函数或数组,如 const char *names[] = {"RED","GREEN","BLUE"}
Q enum 和 #define 定义常量有什么区别?
A enum 有类型(int)、调试器可显示符号名、作用域更规范;#define 是预处理器替换,无类型信息。优先用 enum。
Q 为什么用 union 判断大小端比指针强转更好?
A 两者本质一样,但 union 写法更符合 C 语言类型安全的精神,避免了严格的别名规则问题,可读性也更好。

📖 小节

📝 作业

  1. 定义 union IPAddressunsigned int addrunsigned char bytes[4],输入一个 IP 整数值,分别以整数和四个字节输出
  2. 定义枚举 enum Season { SPRING, SUMMER, AUTUMN, WINTER },编写函数根据枚举值返回对应季节的中文名字符串
  3. 用 tagged union 实现一个简单图形结构体,包含类型标记和 union(矩形/圆形),编写计算面积的函数
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏