共用体(union)跟结构体长得几乎一模一样,唯一致命的不同在内存:结构体的成员各自占一块内存,共用体的所有成员共享同一块内存。这块共享内存的大小,等于其中最大的那个成员。
一句话记住共用体
结构体是一堆变量住各自房间;共用体是一堆名字指向同一个房间。同一个内存位置,你想按 int 读就按 int 读,想按字符数组读就按字符数组读。
#include <stdio.h>
union Student {
int rollNo;
float height;
char firstLetter;
};
int main() {
union Student data;
data.rollNo = 21;
printf("%d\n", data.rollNo); // 21
data.height = 5.2;
printf("%.2f\n", data.height); // 5.20,此时 rollNo 已被覆盖!
data.firstLetter = 'N';
printf("%c\n", data.firstLetter); // N
return 0;
}关键行为:给某个成员赋值,会覆盖掉其他成员的值。同一个时刻只有一个成员有意义。访问语法和结构体一样用 .。
结构体是成员各住各的房间;共用体是一堆名字指向同一个房间,同一时刻只有一个有意义。
共用体的大小
共用体的内存 = 最大成员的大小(可能再被编译器补点对齐填充):
union A {
int x; /* 4 字节 */
char y; /* 1 字节 */
};
union B {
int arr[10]; /* 40 字节 */
char y; /* 1 字节 */
};
printf("Sizeof A: %zu\n", sizeof(union A)); // 4
printf("Sizeof B: %zu\n", sizeof(union B)); // 40
小成员能安全地存在大成员的空间里不会溢出,因为空间本来就按最大的划好了。
典型玩法:把整数拆成字节看
想让 int 的每个字节都能单独读,用指针转来转去很麻烦,共用体直接给你命名好的通道:
union intParts {
int theInt;
char bytes[sizeof(int)]; /* 4 个字符,和一个 int 共享内存 */
};
union intParts parts;
parts.theInt = 5968145; /* 一个大于 255 的整数 */
printf("整数是 %d\n", parts.theInt);
printf("四个字节分别是 [%d, %d, %d, %d]\n",
parts.bytes[0], parts.bytes[1],
parts.bytes[2], parts.bytes[3]);对应地,如果不用共用体就得写一坨指针转换:*((char*)&theInt + 0)……共用体版清晰得多。
省内存:带类型标签的联合
假设要一个数值结构,可能是整数也可能是浮点。不用共用体会这样浪费:
struct operator {
int intNum;
float floatNum;
int type;
double doubleNum; /* 四个都住各自房间,占一大块内存 */
};程序里这种结构多了,内存哗哗地走。改用共用体,只占"int + 最大成员":
struct operator {
int type; /* 记录现在存的是哪种类型 */
union {
int intNum;
float floatNum;
double doubleNum;
} types; /* 三种类型共享一块内存 */
};使用时先定 type 再塞对应成员:
struct operator op;
op.type = 0; /* 0 表示存整数,真实代码里会用枚举或宏 */
op.types.intNum = 352;如果不给 union 起名字(匿名共用体),成员可以直接挂到外层结构体上访问:
struct operator {
int type;
union {
int intNum;
float floatNum;
double doubleNum;
}; /* 没起名 */
};
op.type = 0;
op.intNum = 352; /* 直接访问,不用绕 types.intNum */
最常见的用法:名字和下标互相换
有时候想用可读的名字,又想能按下标循环访问。把命名的成员和一个数组塞进同一个共用体:
union Coins {
struct {
int quarter;
int dime;
int nickel;
int penny;
}; /* 匿名结构体,成员直接挂在 union 上 */
int coins[4]; /* 和上面四个成员共享同一块内存 */
};
union Coins change;
for (int i = 0; i < 4; i++) {
change.coins[i] = i * 10; /* 用下标填 */
}
printf("quarter=%d dime=%d nickel=%d penny=%d\n",
change.quarter, change.dime,
change.nickel, change.penny); /* 用名字读 */coins[i] 恰好对应 quarter/dime/nickel/penny,因为它们在内存里按声明顺序排。
共用体的适用场合
- 省内存:多个类型共享一片空间。
- 硬件映射:同一个寄存器,既能当状态位看又能当命令字看。
- 单类型场景:数据是 A 或 B 但不能同时,比如一个消息要么是 ID 要么是字符串。
结构体浪费内存但安全,共用体省内存但要你自己盯住现在存的是哪个。