

前言
在C语言编程中,结构体是我们常用的自定义类型,但面对内存优化、常量管理等场景,联合体和枚举能发挥更灵活的作用。本文将结合实例,详细拆解联合体和枚举的核心特性、使用场景及实战技巧,帮助大家高效运用这两种自定义类型。
一、联合体
1.1 联合体的核心定义与特性
联合体(又称共用体)是由一个或多个不同类型成员构成的自定义类型,所有成员共用同一块内存空间,编译器仅为最大成员分配足够内存。这一特性带来两个关键表现:
- 给任意成员赋值会覆盖其他成员的内容。
- 所有成员的内存起始地址完全相同。
c
#include <stdio.h>
// 联合体声明
union Un {
char c;
int i;
};
int main() {
union Un un = {0};
// 输出三个相同的地址
printf("%p\n", &(un.i));
printf("%p\n", &(un.c));
printf("%p\n", &un);
un.i = 0x11223344;
un.c = 0x55;
// 输出结果为11223355,c覆盖了i的第4个字节
printf("%x\n", un.i);
return 0;
}
1.2 联合体与结构体的内存布局对比
相同成员的结构体和联合体,内存占用差异显著:
- 结构体:成员按对齐规则依次排列,可能存在内存碎片(填充字节)。
- 联合体:成员重叠存储,内存大小仅与最大成员及对齐规则相关。
| 类型 | 成员(char c + int i) | 内存大小(32位系统) | 内存特性 |
|---|---|---|---|
| 结构体struct S | char c、int i | 8字节(含4字节填充) | 成员独立存储 |
| 联合体union Un | char c、int i | 4字节 | 成员重叠存储 |
1.3 联合体大小的计算规则
联合体的大小遵循"最小足够+对齐补充"原则:
- 基础规则:大小至少为最大成员的大小。
- 对齐规则:若最大成员大小不是最大对齐数的整数倍,需补充至最大对齐数的整数倍(对齐数通常为成员类型大小,如int为4,double为8)。
c
#include <stdio.h>
// 示例1:char[5](大小5)与int(对齐数4)
union Un1 {
char c[5];
int i;
};
// 示例2:short[7](大小14)与int(对齐数4)
union Un2 {
short c[7];
int i;
};
int main() {
// 输出8(5不足4的2倍,补充至8)
printf("%d\n", sizeof(union Un1));
// 输出16(14不足4的4倍,补充至16)
printf("%d\n", sizeof(union Un2));
return 0;
}
1.4 联合体的实战场景:优化内存占用
当数据存在"互斥属性"时,用联合体替代结构体可大幅节省内存。例如礼品兑换单设计:图书、杯子、衬衫的特殊属性互斥,仅需在联合体中存储当前商品的专属属性。
c
// 优化后的礼品兑换单结构
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;
};
1.5 联合体进阶用法:判断机器字节序
利用联合体成员内存重叠的特性,可快速判断当前机器是大端还是小端字节序:
c
// 返回1为小端,返回0为大端
int check_sys() {
union {
int i;
char c;
} un;
un.i = 1;
// 小端:低地址存低字节(c=1);大端:低地址存高字节(c=0)
return un.c;
}
二、枚举
2.1 枚举的核心定义与赋值规则
枚举是将可能取值一一列举的自定义类型,{}中的内容为枚举常量,默认从0开始依次递增1,也可在声明时显式赋值。
c
// 默认赋值:Mon=0, Tues=1, ..., Sun=6
enum Day {
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
// 显式赋值:RED=2, GREEN=4, BLUE=8
enum Color {
RED=2,
GREEN=4,
BLUE=8
};
2.2 枚举的核心优势(对比#define)
相比#define定义的无类型常量,枚举具有明显优势:
- 可读性更强 :枚举常量自带语义关联,便于代码理解(如
enum Sex {MALE, FEMALE, SECRET})。 - 类型安全:枚举变量有明确类型,编译器会进行类型检查,而#define无类型约束。
- 便于调试:枚举常量在编译阶段保留,#define在预处理阶段被替换,无法在调试中查看。
- 批量定义:一次可定义多个相关常量,语法简洁。
- 作用域约束:枚举声明在函数内时,仅在函数内有效,避免全局污染。
2.3 枚举的使用注意事项
- C语言中,枚举变量可直接赋值整数(不推荐,破坏类型一致性)。
- C++中,枚举变量仅能赋值枚举常量(类型检查更严格)。
- 枚举常量本质是整数,可参与算术运算(如
RED + GREEN = 6)。
c
enum Color {
RED=1,
GREEN=2,
BLUE=4
};
int main() {
// 正确用法:枚举常量赋值
enum Color clr = GREEN;
// C语言允许,C++报错:类型不匹配
enum Color clr2 = 3;
return 0;
}
三、联合体与枚举的适用场景
| 类型 | 核心特性 | 典型场景 |
|---|---|---|
| 联合体 | 成员共用内存、节省空间 | 1. 互斥属性数据存储;2. 字节序判断;3. 内存优化场景 |
| 枚举 | 类型化常量、规范取值 | 1. 状态标识(如登录状态、订单状态);2. 有限选项列举(如性别、颜色);3. 替代魔法数字 |
四、联合体额外实战案例
4.1 数据类型转换:无拷贝内存复用
用联合体实现char数组与int/float的快速转换(无内存拷贝):
c
#include <stdio.h>
union DataConvert {
int i;
float f;
char bytes[4];
};
int main() {
union DataConvert dc;
// float转字节数组
dc.f = 3.14f;
printf("3.14的字节表示:");
for (int i=0; i<4; i++) {
printf("%02x ", (unsigned char)dc.bytes[i]);
}
printf("\n");
// 字节数组转int
dc.bytes[0] = 0x44;
dc.bytes[1] = 0x33;
dc.bytes[2] = 0x22;
dc.bytes[3] = 0x11;
printf("字节数组转int:0x%x\n", dc.i);
return 0;
}
效果 :利用内存重叠直接转换数据类型,比memcpy更高效。
五、枚举额外实战案例
5.1 订单状态机管理
电商系统中用枚举规范订单状态流转,避免魔法数字:
c
#include <stdio.h>
// 定义订单状态枚举
enum OrderStatus {
ORDER_CREATED = 101, // 已创建
ORDER_PAID, // 已支付(102)
ORDER_SHIPPED, // 已发货(103)
ORDER_DELIVERED, // 已送达(104)
ORDER_CANCELLED // 已取消(105)
};
// 订单状态流转函数
void updateOrderStatus(enum OrderStatus* status, enum OrderStatus newStatus) {
// 模拟状态合法性检查(如已取消订单不能再支付)
if (*status == ORDER_CANCELLED && newStatus == ORDER_PAID) {
printf("错误:已取消订单无法支付!\n");
return;
}
*status = newStatus;
printf("订单状态更新为:%d\n", *status);
}
int main() {
enum OrderStatus order1 = ORDER_CREATED;
updateOrderStatus(&order1, ORDER_PAID); // 合法流转
updateOrderStatus(&order1, ORDER_SHIPPED); // 合法流转
updateOrderStatus(&order1, ORDER_CANCELLED); // 合法流转
updateOrderStatus(&order1, ORDER_PAID); // 非法流转(触发错误)
return 0;
}
效果:通过枚举约束状态取值,结合流转检查,避免非法状态变更。
5.2 菜单选项枚举
控制台程序中用枚举定义菜单选项,提升代码可读性:
c
#include <stdio.h>
enum MenuOption {
MENU_QUIT = 0, // 退出
MENU_ADD_USER, // 添加用户(1)
MENU_DEL_USER, // 删除用户(2)
MENU_QUERY_USER // 查询用户(3)
};
void showMenu() {
printf("===== 用户管理系统 =====\n");
printf("%d. 退出\n", MENU_QUIT);
printf("%d. 添加用户\n", MENU_ADD_USER);
printf("%d. 删除用户\n", MENU_DEL_USER);
printf("%d. 查询用户\n", MENU_QUERY_USER);
printf("请选择操作:");
}
int main() {
enum MenuOption choice;
while (1) {
showMenu();
scanf("%d", &choice);
switch (choice) {
case MENU_QUIT:
printf("退出系统...\n");
return 0;
case MENU_ADD_USER:
printf("执行添加用户逻辑...\n");
break;
case MENU_DEL_USER:
printf("执行删除用户逻辑...\n");
break;
case MENU_QUERY_USER:
printf("执行查询用户逻辑...\n");
break;
default:
printf("无效选项,请重新选择!\n");
}
}
}
效果:菜单选项与枚举常量一一对应,switch-case逻辑更清晰,后续扩展菜单只需修改枚举。
合理运用联合体和枚举,既能优化内存占用,又能提升代码的可读性、规范性和可维护性。在实际开发中,结合数据的存储特性和使用场景选择合适的自定义类型,是写出高效代码的关键。
至此,我们已梳理完"联合体与枚举"的全部内容了。最后我们在文末来进行一个投个票,告诉我你对哪部分内容最感兴趣、收获最大,也欢迎在评论区聊聊你的学习感受。
以上就是本期博客的全部内容了,感谢各位的阅读以及关注。如有内容存在疏漏或不足之处,恳请各位技术大佬不吝赐教、多多指正。

