共用体三条特点,一句一条:① 所有成员共用同一块内存(地址相同);② 任一时刻只有一个成员有效(后写的覆盖先写的);③ 大小取最大成员(并对齐)。由此推出一条纪律:读之前必须知道"现在有效的是谁"——要么自己心里有数,要么像 8.5.2 那样配一个类型标签。
8.5~8.5.2 已经把这三点分别讲过,本节是收口:把共用体和结构体做一次彻底对比,再补上"它到底该用在哪、有哪些坑"。学完本节,第八章的"数据打包"部分就完整了——下一节 8.6 的枚举,是一种"只能取几个固定值"的类型,也是本章最后一块拼图。
一、三条特点
特点 1:所有成员共用同一块内存
union Data { int i; float f; char c; };
Data d;
cout << &d.i << endl; // 同一个地址
cout << &d.f << endl; // 同一个地址
cout << &d.c << endl; // 同一个地址
这是最本质的一条,另外两条都是它的推论。
特点 2:任一时刻只有一个成员有效
d.i = 100;
d.f = 3.14f; // 此刻"i 已经无效了"
"最后写的是谁,谁就有效"。不是"谁最后写,其他成员自动清零"——其他成员只是"字节被改了,值失去意义"。
特点 3:大小取最大成员(再对齐)
union Data { int i; float f; char c; }; // 4
union Big { char c[10]; int i; }; // 12(10 → 对齐到 12)
union Mix { int i; double d; }; // 8
规则:先取 max(各成员大小),再按最宽成员的对齐要求向上补齐(8.1.5 的规则 2 在共用体上同样生效)。
二、结构体 vs 共用体(一表看清)
| 对比项 | 结构体 struct |
共用体 union |
|---|---|---|
| 内存安排 | 成员并排,各占一块 | 成员重叠,共用一块 |
| 成员地址 | 各不相同 | 完全相同 |
| 大小 | 成员大小相加 + 对齐填充 | 最大成员大小(再对齐) |
| 能否同时保存多个值 | 能,互不干扰 | 不能,后写的覆盖先写的 |
| 典型用途 | 一条记录(姓名+年龄+成绩) | 互斥的几种解读(协议字段 / 类型可变的记录) |
| 初始化 | 可以给多个成员初值 | 只能给第一个成员一个初值 |
| 省不省空间 | 不省 | 省(几个成员共用一块) |
一句话选择标准:
"这些数据会同时被用到吗?" 会 →
struct;不会(互斥)→union。
三、共用体的三个典型用途
用途 1:省内存(嵌入式 / 大数据量)
几个互斥的字段共用一个空间。数据量大时收益明显。
用途 2:同一块内存的不同解读(类型转换 / 协议解析)
union View { int n; char bytes[4]; };
View v;
v.n = 65;
cout << v.bytes[0]; // 'A' —— 同一个 65,换个类型看就得到字符
协议解析里常用:收到 4 个字节,按协议规定当成 int 或 float 解读。
用途 3:表示"任意类型的一个值"(标签 + 共用体)
struct Variant {
char tag; // 记下"现在是什么类型"
union { int i; double d; char c; } value;
};
这是共用体在实际代码里最常见的形态(8.5.2 的实例05 演示过)。
四、四个必须小心的坑
- 读错成员:写
i却读f,得到的是无意义的字节组合。编译器不会提示你——这是共用体最危险的地方。 - 跨平台差异:"同一块内存怎么解读"依赖字节序(大端/小端)。x86 是小端,但别的架构可能不同。所以"用共用体做类型转换"的代码换平台就可能失灵。
- 浮点表示的假设:想靠共用体"拆开
float的位",就要依赖 IEEE 754 表示——这也不是语言保证的。 - 不能直接比较两个共用体:
d1 == d2编译不过。因为"有效成员"这件事,语言层面无法判断。
五、动手 Demo:一张"选择清单"的实际演练
#include <iostream>
#include <cstring>
using namespace std;
// 场景 A:一条记录,三样都要 —— 用结构体
struct Student {
char name[20];
int age;
double score;
};
// 场景 B:一个值,只可能是三种之一 —— 用共用体 + 标签
struct Value {
char tag; // 'i' 整数 / 'd' 小数 / 's' 字符串标志
union {
int i;
double d;
char s[20];
} data;
};
void printValue(const Value &v) {
switch (v.tag) {
case 'i': cout << "整数 " << v.data.i << endl; break;
case 'd': cout << "小数 " << v.data.d << endl; break;
case 's': cout << "文字 " << v.data.s << endl; break;
default: cout << "未知" << endl;
}
}
int main() {
cout << "=== 场景 A:结构体(三样同时有效) ===" << endl;
Student stu = {"张三", 18, 92.5};
cout << stu.name << " " << stu.age << " " << stu.score << endl; // 三样都在
cout << "sizeof(Student) = " << sizeof(Student) << " 字节" << endl;
cout << "=== 场景 B:共用体(一次只有一个有效) ===" << endl;
Value v1, v2, v3;
v1.tag = 'i'; v1.data.i = 42;
v2.tag = 'd'; v2.data.d = 3.14159;
v3.tag = 's'; strcpy(v3.data.s, "你好");
printValue(v1); printValue(v2); printValue(v3);
cout << "sizeof(Value) = " << sizeof(Value) << " 字节" << endl;
cout << "=== 反例:读错成员会得到什么 ===" << endl;
Value bad;
bad.tag = 'i';
bad.data.i = 100;
bad.data.d = 2.5; // 覆盖了 i
cout << "写了 i=100 又写 d=2.5,读 i 得到:" << bad.data.i << "(无意义)" << endl;
return 0;
}
看到什么算成功:
=== 场景 A:结构体(三样同时有效) ===
张三 18 92.5
sizeof(Student) = 32 字节
=== 场景 B:共用体(一次只有一个有效) ===
整数 42
小数 3.14159
文字 你好
sizeof(Value) = 24 字节
=== 反例:读错成员会得到什么 ===
写了 i=100 又写 d=2.5,读 i 得到:(无意义的数)
读结果:
- 场景 A 三样同时有效——因为它是结构体;
- 场景 B 靠 tag 才知道该读哪个——因为共用体自己不记得;
- 反例那行就是"读错成员"的代价——不报错,只是给你一堆无意义的数。
30 秒实验:把 Value 里的 union { int i; double d; char s[20]; } 改成 struct { ... },重新运行。输出还能对,sizeof(Value) 却从 24 涨到 40 左右——这就是"并排"和"重叠"的代价对比。
六、常见误区
- "共用体是结构体的省内存版"——危险的说法。它不是"更省的结构体",而是"一次只能放一个"的容器。当成省空间的结构体用,程序一定会读到垃圾。
- "共用体可以随便做类型转换"——依赖字节序和浮点表示,换平台可能失灵。语言层面没有保证。
- "
sizeof(union)就是最大成员的大小"——还要考虑对齐。union Big { char c[10]; int i; }是 12,不是 10(8.5.2)。 - "共用体可以像结构体那样初始化多个成员"——只能给第一个成员(8.5.1)。
- "用不到就不用学"——考试必考,协议/嵌入式/二进制读写里常见。至少要能:算大小、说清"只有一个成员有效"、写出"标签 + 共用体"这个模式。
自测三问(点开对答案)
1. 用一句话说明什么时候用 `struct`、什么时候用 `union`。——数据**要同时存在**用 `struct`;数据**互斥、一次只用其中一个**用 `union`。 2. 共用体大小的完整规则是什么?——`max(各成员大小)`,再按**最宽成员的对齐要求**向上取整。 3. 怎么让共用体的使用变"安全"?——配一个**类型标签**(`tag`):写入时记下类型,读取时先看标签再取对应成员。- ☐ 8.6 枚举类型(下一节)
相关笔记
- 共用体的大小 —— 上一节:大小与覆盖(含实例05)
- 枚举类型 —— 下一节:只能取固定值的类型
- 共用体 —— 并排 vs 重叠的起点
- switch语句 —— 标签模式的搭档
- 🔷 C++基础 · 索引 —— 全课路线图