什么是 union?
union(联合体)是一种用户自定义的数据类型,与 struct(结构体)非常相似,但它有一个核心区别:

- 结构体 (
struct):每个成员都拥有自己独立的内存空间,结构体的大小是其所有成员大小之和(考虑对齐)。 - 联合体 (
union):所有成员共享同一段内存空间,联合体的大小是其最大成员的大小。
你可以把 union 想象成一个“覆盖”或“别名”的容器,在任何时候,你只能使用其中一个成员,因为当你给一个成员赋值时,会覆盖掉其他成员的数据。
union 的语法
union union_name {
member_type1 member_name1;
member_type2 member_name2;
// ...
member_typeN member_nameN;
} union_variable;
// 或者先声明,再定义
union union_name {
// ...
};
union union_name my_union;
示例:使用 union 表示一个 Date
假设我们需要表示一个日期,2025年10月27日,这个日期可以有不同的表示方式:
- 作为三个独立的整数:
year,month,day。 - 作为从某个固定基准点(如公元元年1月1日)开始计算的天数(一个整数)。
如果我们用 struct 来实现,会占用 int + int + int 的空间。
struct date_struct {
int year;
int month;
int day;
};
// 假设 int 占用 4 字节,这个结构体至少占用 12 字节
但如果我们知道在任何时候,我们要么需要 year/month/day,要么需要 total_days,但不会同时需要这两种格式,那么使用 union 就非常高效,因为它可以节省内存。
定义 union Date
#include <stdio.h>
#include <string.h>
// 定义一个联合体来表示日期
union Date {
// 成员1:用年、月、日表示
struct {
int year;
int month;
int day;
} ymd;
// 成员2:用总天数表示
int total_days;
};
// 为了方便打印,我们写一个函数
void print_date_union(union Date d) {
printf("Date in YMD format: %d-%d-%d\n", d.ymd.year, d.ymd.year, d.ymd.day);
printf("Date in total_days format: %d\n", d.total_days);
}
使用 union Date
int main() {
// 1. 声明一个 union Date 类型的变量
union Date my_date;
// 2. 使用 ymd 成员来赋值
printf("--- Using ymd members ---\n");
my_date.ymd.year = 2025;
my_date.ymd.month = 10;
my_date.ymd.day = 27;
// 打印 ymd 成员
printf("Year: %d\n", my_date.ymd.year); // 输出: 2025
printf("Month: %d\n", my_date.ymd.month); // 输出: 10
printf("Day: %d\n", my_date.ymd.day); // 输出: 27
// 尝试打印 total_days
// total_days 的值是未定义的,因为 ymd 成员的写入覆盖了它
printf("Total Days (undefined): %d\n\n", my_date.total_days); // 输出可能是一个随机数
// 3. 我们使用 total_days 成员来赋值
// 这会覆盖掉之前存入的 ymd 数据
printf("--- Switching to total_days member ---\n");
my_date.total_days = 738527; // 假设这是一个有效的天数
// 打印 total_days 成员
printf("Total Days: %d\n", my_date.total_days); // 输出: 738527
// 尝试打印 ymd 成员
// ymd 成员的值是未定义的,因为 total_days 成员的写入覆盖了它
printf("Year (undefined): %d\n", my_date.ymd.year); // 输出可能是一个随机数
printf("Month (undefined): %d\n", my_date.ymd.month);
printf("Day (undefined): %d\n", my_date.ymd.day);
return 0;
}
运行结果分析
--- Using ymd members ---
Year: 2025
Month: 10
Day: 27
Total Days (undefined): 1345130480 <-- 注意这里的值是随机的
--- Switching to total_days member ---
Total Days: 738527
Year (undefined): 12306856 <-- 注意这里的值是随机的
Month (undefined): 16909216
Day (undefined): 738527
从结果中可以清晰地看到:
- 当我们给
my_date.ymd.year赋值时,my_date.total_days的值被“污染”了。 - 当我们给
my_date.total_days赋值时,my_date.ymd中的所有成员的值都被“污染”了。 union的大小是其最大成员的大小,在这个例子中,struct {int, int, int}的大小(假设12字节)大于int的大小(4字节),union Date的大小是 12 字节。
union 的核心特点与用途
- 内存共享:所有成员共享同一块内存。
- 大小决定:
sizeof(union)等于其最大成员的大小。 - 数据覆盖:对一个成员的写入会影响其他成员的值。
- 同时性:在任意时刻,只有一个成员的值是有效的。
主要用途
-
节省内存 这是
union最经典的应用场景,当一个对象有多种可能的数据表示形式,但不会同时使用时,用union可以避免为所有可能的表示都分配空间。- 示例:网络协议中,一个字段可能表示整数、字符串或IP地址,它们不会同时出现。
-
类型转换(类型双关)
union可以提供一种安全(相对于强制类型转换而言)的方式来检查一个变量的字节级表示。-
经典示例:检查系统字节序
#include <stdio.h> int main() { union { int i; char c[sizeof(int)]; } un; un.i = 0x01020304; if (un.c[0] == 0x04) { printf("Little-endian\n"); // 小端序:低字节存低地址 } else { printf("Big-endian\n"); // 大端序:高字节存低地址 } return 0; }这个
union允许我们通过一个char数组来查看int变量i在内存中的每一个字节。
-
-
函数的多返回值 虽然不常见,但
union可以让一个函数返回不同类型的数据(这些类型必须能共存于一个union中)。-
示例:一个解析函数可能成功返回一个整数,失败返回一个错误码字符串。
union Result { int success_value; char error_msg[50]; }; union Result do_something() { // ... some logic ... if (success) { union Result r; r.success_value = 42; return r; } else { union Result r; strcpy(r.error_msg, "Invalid input"); return r; } }
-
union vs. struct 对比
| 特性 | union (联合体) |
struct (结构体) |
|---|---|---|
| 内存分配 | 所有成员共享同一块内存空间。 | 每个成员拥有独立的内存空间。 |
| 大小 | 等于最大成员的大小。 | 等于所有成员大小之和(考虑内存对齐)。 |
| 数据访问 | 对一个成员的写入会覆盖其他成员的值。 | 对一个成员的写入不会影响其他成员。 |
| 主要用途 | 节省内存、类型转换、处理多种数据格式。 | 将不同类型的数据组合成一个逻辑单元。 |
| 同时性 | 任意时刻,只有一个成员的值是有效的。 | 所有成员的值可以同时有效。 |
重要注意事项
-
永远不要同时访问所有成员:这是
union使用中最容易犯的错误,你必须清楚地知道当前union中哪个成员是“活跃”的(即最后被写入的那个)。 -
需要额外的“标签”来管理状态:在实际应用中,
union几乎总是和enum一起使用,用一个枚举变量来记录当前union中存储的是哪种类型的数据,这种模式被称为 tagged union 或 discriminated union。// 更安全的 Date 表示方式 enum DateType { YMD_TYPE, TOTAL_DAYS_TYPE }; struct SafeDate { enum DateType type; union Date data; // 使用我们之前定义的 union }; // 使用时 struct SafeDate sd; sd.type = YMD_TYPE; sd.data.ymd.year = 2025; // ... if (sd.type == YMD_TYPE) { // 安全地访问 ymd 成员 printf("Year: %d\n", sd.data.ymd.year); } -
初始化:只能初始化
union的第一个成员。union Date d1 = {2025}; // 正确,初始化 ymd.year // union Date d2 = {738527}; // 错误!不能初始化 total_days,因为它不是第一个成员 union Date d3; d3.total_days = 738527; // 正确,赋值
union 是 C 语言中一个强大但需要谨慎使用的工具,它通过内存共享实现了数据的高效存储和灵活的类型转换,在处理像 date 这样有多种表示形式的数据时,union 提供了比 struct 更节省内存的方案,它的核心特性——数据覆盖——也要求程序员必须明确管理 union 的当前状态,通常借助 enum 等机制来避免错误访问。
