什么是联合体?
联合体(union)是一种用户自定义的数据类型,与结构体(struct)非常相似,它允许你在同一个内存位置存储不同类型的数据。

与结构体不同,结构体为每个成员分配独立的内存空间,而联合体的所有成员共享同一段内存空间。
核心思想: 在任意时刻,联合体中只存储一个成员的值,当你给一个成员赋值时,其他成员的值也会随之改变,因为它们指向的是同一块内存。
union 的语法
定义一个联合体与定义一个结构体非常相似,只是关键字从 struct 换成了 union。
// 定义一个名为 Data 的联合体
union Data {
int i;
float f;
char str[20];
};
// 声明一个联合体变量
Data data;
union 的工作原理:内存共享
这是理解 union 的关键,我们通过一个例子来看内存是如何分配的。

假设我们有以下联合体:
#include <stdio.h>
union Test {
char c;
int i;
double d;
};
int main() {
union Test test;
printf("Size of union Test: %zu bytes\n", sizeof(test));
printf("Size of char c: %zu bytes\n", sizeof(test.c));
printf("Size of int i: %zu bytes\n", sizeof(test.i));
printf("Size of double d: %zu bytes\n", sizeof(test.d));
return 0;
}
输出结果(在大多数系统上):
Size of union Test: 8 bytes
Size of char c: 1 bytes
Size of int i: 4 bytes
Size of double d: 8 bytes
分析:
联合体 Test 的大小是其最大成员的大小,在这个例子中,double 类型最大,占用 8 字节,所以整个联合体 test 也只占用 8 字节。
当你给 test.i 赋值时,这 8 字节中的前 4 个字节被使用;当你给 test.d 赋值时,全部 8 个字节被使用,它们都从同一块内存的起始地址开始操作。

union vs. struct:关键区别
这是一个非常常见的面试题,我们用一个表格来清晰地对比它们:
| 特性 | struct (结构体) |
union (联合体) |
|---|---|---|
| 内存分配 | 为每个成员分配独立的内存空间,所有成员的内存累加起来构成结构体的总大小。 | 所有成员共享同一段内存空间,联合体的大小是其最大成员的大小。 |
| 总大小 | sizeof(struct) = sizeof(member1) + sizeof(member2) + ... |
sizeof(union) = max(sizeof(member1), sizeof(member2), ...) |
| 数据存储 | 可以同时存储所有成员的值,互不干扰。 | 在任意时刻,只能存储一个成员的值,对一个成员赋值会覆盖其他成员的值。 |
| 用途 | 用于将不同但相关的数据组合成一个单一的对象,一个学生的信息(姓名、年龄、学号)。 | 用于需要在不同类型间切换,且在同一时间只需要一种类型数据的场景,主要用于节省内存或实现特定功能。 |
union 的实际应用场景
union 主要用于以下两种情况:
节省内存
当你需要处理一个数据,它有多种可能的类型,但在任何时刻都只有一种类型会被使用时,使用 union 可以大大节省内存。
经典例子: 一个简单的计算器程序,可以处理整数、浮点数和字符串。
#include <stdio.h>
#include <string.h>
// 定义一个联合体来存储不同类型的操作数
union Operand {
int i_val;
double f_val;
char str_val[50];
};
// 定义一个联合体来存储不同类型的运算符
// 这里我们用一个字符来表示,但也可以更复杂
union Operator {
char op_char;
// ... 可以有其他类型的操作符
};
int main() {
// 假设我们正在处理一个整数加法
union Operand op1, op2;
op1.i_val = 10;
op2.i_val = 20;
printf("Integer Addition: %d + %d = %d\n", op1.i_val, op2.i_val, op1.i_val + op2.i_val);
// 现在假设我们处理一个浮点数乘法
// 注意:直接使用 op1.f_val 会覆盖掉 i_val
op1.f_val = 2.5;
op2.f_val = 4.0;
printf("Float Multiplication: %.2f * %.2f = %.2f\n", op1.f_val, op2.f_val, op1.f_val * op2.f_val);
return 0;
}
类型转换(或称“类型双关”,Type Punning)
这是 union 一个非常强大但也容易出错的用法,你可以利用 union 来读取一个变量以不同的数据类型解释其内存内容。
经典例子: 获取一个 float 变量的二进制表示(IEEE 754 格式)。
#include <stdio.h>
// 联合体允许我们以 int 的方式查看 float 的内存
union FloatToInt {
float f;
unsigned int i;
};
int main() {
union FloatToInt converter;
converter.f = 3.14f;
printf("Float value: %f\n", converter.f);
printf("Its memory representation as an integer (hex): 0x%X\n", converter.i);
return 0;
}
注意: 这种“类型双关”行为在 C 标准中被称为“未定义行为”(Undefined Behavior, UB),因为它绕过了类型系统,虽然它在几乎所有现代编译器上都能按预期工作,但严格来说并不符合标准,C11 引入了 memcpy 作为更安全、更标准的替代方案。
使用 union 的注意事项
-
数据覆盖: 这是最重要的一点,给联合体的一个成员赋值,会立即影响所有其他成员,你必须清楚地知道当前存储的是哪种类型。
-
需要额外的信息来追踪类型: 因为联合体本身不记录当前存储的是哪种数据,所以你需要一个额外的变量(通常是
enum)来标记当前活跃的成员。enum DataType { INT_TYPE, FLOAT_TYPE, STRING_TYPE }; struct Data { enum DataType type; union Value { int i; float f; char s[20]; } val; }; // 使用时 struct Data data; data.type = INT_TYPE; data.val.i = 100; // 读取时 if (data.type == INT_TYPE) { printf("The integer value is: %d\n", data.val.i); } -
对齐问题: 联合体的大小是其最大成员的大小,但要考虑内存对齐,在某些架构上,为了性能,编译器可能会在成员之间填充一些字节,
sizeof(union)可能会比最大成员的大小还要大一点点。 -
初始化: 你只能初始化联合体的第一个成员。
union Data data = {10}; // 正确,初始化了 int i // union Data data = {3.14}; // 错误!不能初始化 float f // union Data data = {.f = 3.14}; // C99 及以后版本支持,但通常推荐只初始化第一个成员
union是什么? 一种能让不同类型数据共享同一块内存的自定义类型。- 核心特点: 内存共享,大小等于最大成员的大小。
- 与
struct的核心区别:struct是内存叠加,union是内存重叠。 - 主要用途:
- 在数据类型互斥的场景下节省内存。
- 实现类型双关,用于底层数据操作(需谨慎)。
- 使用要点: 必须用额外的机制(如
enum)来追踪当前存储的数据类型,以避免错误地读取被覆盖的数据。
