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 语言类型安全的精神,避免了严格的别名规则问题,可读性也更好。
📖 小节
- union 所有成员共享内存,大小等于最大成员,同一时刻只用一个
- 实际使用中联合体搭配标记字段(tagged union)区分当前有效类型
- enum 用命名常量代替魔数,提高可读性,底层是 int
- 枚举值默认从 0 递增,可手动指定任意 int 值
- 利用 union 可判断系统大小端——多字节数据的字节存放顺序
📝 作业
- 定义
union IPAddress含unsigned int addr和unsigned char bytes[4],输入一个 IP 整数值,分别以整数和四个字节输出 - 定义枚举
enum Season { SPRING, SUMMER, AUTUMN, WINTER },编写函数根据枚举值返回对应季节的中文名字符串 - 用 tagged union 实现一个简单图形结构体,包含类型标记和 union(矩形/圆形),编写计算面积的函数