在C语言中,自定义类型为数据组织提供了极大的灵活性。除了常用的结构体,联合体(共用体)和枚举也是非常重要的自定义类型。本文将结合实例,详细解析联合体和枚举的特性、用法及实际应用场景。
一、联合体(Union):共用内存的特殊类型
联合体(又称共用体)是一种特殊的自定义类型,它的所有成员共用同一块内存空间,这一特性使其在内存优化场景中非常实用。
1.1 联合体的声明与定义
联合体的声明语法与结构体类似,但成员的内存布局完全不同。
c
// 联合体类型声明
union Un {
char c; // 字符型成员
int i; // 整型成员
};
int main() {
// 联合体变量定义并初始化
union Un un = {0};
// 计算联合体大小
printf("联合体大小:%d\n", sizeof(un)); // 输出结果:4
return 0;
}
为什么输出结果是4?
因为联合体的大小至少是最大成员的大小,上述代码中int
类型成员i
占4字节,因此联合体大小为4。
1.2 联合体的核心特点:成员共用内存
联合体最核心的特性是所有成员共用同一块内存空间,这意味着:
- 联合体变量的地址与各成员的地址相同;
- 给一个成员赋值,可能会覆盖其他成员的值。
实例1:验证成员地址相同
c
#include <stdio.h>
union Un {
char c;
int i;
};
int main() {
union Un un = {0};
// 打印联合体变量及成员的地址
printf("&un:%p\n", &un);
printf("&un.i:%p\n", &un.i);
printf("&un.c:%p\n", &un.c);
return 0;
}
输出结果:三个地址完全相同,证明成员共用同一块内存。
实例2:成员赋值的相互影响
c
#include <stdio.h>
union Un {
char c;
int i;
};
int main() {
union Un un = {0};
un.i = 0x11223344; // 给整型成员赋值
un.c = 0x55; // 给字符型成员赋值(覆盖低字节)
printf("un.i = %x\n", un.i); // 输出结果:11223355
return 0;
}
解析:
int
类型在内存中占4字节,0x11223344
的内存布局为(假设小端存储):44 33 22 11
;char
类型仅占1字节(低地址字节),赋值0x55
后覆盖了低字节,内存变为55 33 22 11
,因此un.i
最终为0x11223355
。
1.3 结构体与联合体的内存布局对比
类型 | 内存布局特点 | 示例大小(char+int) |
---|---|---|
结构体(struct) | 成员按顺序存储,存在内存对齐浪费 | 8字节(1+3对齐+4) |
联合体(union) | 成员共用内存,无对齐浪费 | 4字节(取最大成员大小) |
内存布局示意图:
- 结构体:
[char][3字节对齐浪费][int]
- 联合体:
[char和int共用4字节空间]
1.4 联合体大小的计算规则
联合体的大小需满足两个条件:
- 至少是最大成员的大小(保证能容纳最大成员);
- 必须是最大对齐数的整数倍(内存对齐要求)。
对齐数定义:
每个成员的对齐数 = 成员自身大小与编译器默认对齐数(通常为8)的较小值。
实例计算:
c
#include <stdio.h>
// 案例1:char[5]与int
union Un1 {
char c[5]; // 大小5,对齐数1(char大小1)
int i; // 大小4,对齐数4(int大小4)
};
// 案例2:short[7]与int
union Un2 {
short c[7]; // 大小14(2*7),对齐数2(short大小2)
int i; // 大小4,对齐数4(int大小4)
};
int main() {
printf("Un1大小:%d\n", sizeof(union Un1)); // 输出:8
printf("Un2大小:%d\n", sizeof(union Un2)); // 输出:16
return 0;
}
计算过程:
- Un1:最大成员大小5,最大对齐数4。5不是4的倍数,向上对齐到8(4×2);
- Un2:最大成员大小14,最大对齐数4。14不是4的倍数,向上对齐到16(4×4)。
1.5 联合体的实用场景
场景1:节省内存空间
当不同属性不会同时使用时,用联合体共用内存可大幅减少内存占用。例如礼品兑换单设计:
c
// 优化前:结构体包含所有属性,内存浪费
struct gift_list_old {
int stock_number; // 库存量
double price; // 定价
int item_type; // 商品类型
char title[20]; // 书名(仅图书用)
char author[20]; // 作者(仅图书用)
char design[30]; // 设计(仅杯子/衬衫用)
int colors; // 颜色(仅衬衫用)
};
// 优化后:用联合体存储差异化属性
struct gift_list {
int stock_number; // 公共属性
double price;
int item_type;
union { // 差异化属性共用内存
struct { char title[20]; char author[20]; } book; // 图书
struct { char design[30]; } mug; // 杯子
struct { char design[30]; int colors; } shirt; // 衬衫
} item;
};
场景2:判断机器字节序(大小端)
利用联合体成员共用内存的特性,可简单判断机器存储方式:
c
// 返回1:小端存储;返回0:大端存储
int check_sys() {
union {
int i; // 4字节整型
char c; // 1字节字符
} un;
un.i = 1; // 内存存储为0x00000001(大端)或0x01000000(小端)
return un.c; // 小端返回1,大端返回0
}
二、枚举类型(Enum):常量的有序集合
枚举类型用于定义一组具有离散值的常量,使代码更具可读性和可维护性。
2.1 枚举类型的声明与初始化
枚举通过enum
关键字声明,其中的成员称为枚举常量。
c
// 基本声明(默认值从0开始递增)
enum Day {
Mon, // 0
Tues, // 1
Wed, // 2
Thur, // 3
Fri, // 4
Sat, // 5
Sun // 6
};
// 自定义初始值(后续值依次递增)
enum Color {
RED = 2, // 2
GREEN = 4, // 4
BLUE = 8 // 8
};
2.2 枚举的优点:为何不用#define?
与#define
定义常量相比,枚举具有明显优势:
- 增强可读性:枚举常量有明确的类型归属,代码逻辑更清晰;
- 类型检查 :枚举是强类型,编译器会进行类型校验(
#define
无类型); - 便于调试 :枚举常量在调试阶段可见(
#define
在预处理阶段被替换,调试中无符号); - 批量定义 :一次可定义多个相关常量,无需重复写
#define
; - 作用域规则:枚举声明在函数内时,仅在函数内有效,避免命名冲突。
2.3 枚举类型的使用
枚举变量需用枚举常量赋值,C语言允许整数赋值但不推荐(C++严格禁止)。
c
enum Color {
RED = 1,
GREEN = 2,
BLUE = 4
};
int main() {
enum Color clr = GREEN; // 正确:用枚举常量赋值
// enum Color clr2 = 2; // C允许,C++禁止(类型不匹配)
return 0;
}
三、总结
- 联合体通过成员共用内存实现内存优化,适用于不同属性不同时使用的场景,大小计算需满足最大成员大小和对齐要求;
- 枚举 用于定义离散常量集合,相比
#define
具有更强的类型安全性和可读性,是提升代码质量的重要工具。
合理使用联合体和枚举,能让C语言代码更高效、更易维护,尤其在嵌入式开发、内存受限场景中发挥重要作用。