🔷 第八章 结构体与共用体
📄 一句话概括

共用体(union,也叫联合体)和结构体长得几乎一模一样,但内存安排正好相反:结构体的成员是"并排住"(每人一间房,互不干扰);共用体的成员是"挤在同一间房里"(同一块内存,谁写谁占)。所以共用体的大小不是成员相加,而是取最大的那个成员。它只有一个真正的用途:让同一块内存,在不同时刻被当成不同的类型来用。

📝 本节和前面几节的关系

8.1~8.4 讲的是"怎么把几个数据打包在一起同时保存"。这一节讲一个反过来的需求:有些数据永远不可能同时用到,为什么要给它们各分一块空间?共用体就是"分时复用一间房"。它和结构体的对比(并排 vs 重叠)是本节的记忆锚点,也是"内存布局"这条线索(8.1.5 的对齐 → 本节的重叠)的延续。

一、一句话讲清区别

struct S { int i; double d; char c; };      // 结构体:三间房,并排
union  U { int i; double d; char c; };      // 共用体:一间房,三个人轮流住

画成图:

结构体 struct S             共用体 union U
┌──────┬──────┬────┐        ┌──────────────┐
│  i   │  d   │ c  │        │  i / d / c   │   ← 同一块内存
└──────┴──────┴────┘        └──────────────┘
  4字节   8字节  1字节          取最大的那个(8 字节)
  总共 24 字节(含对齐)        总共 8 字节

一句话记牢: - 结构体 = 同时都要 → 各分一间(相加) - 共用体 = 只用其中一个 → 挤一间(取最大)

二、什么时候真的需要它

判断标准:这几个数据"会不会同时被用到"?

场景 该用哪个 为什么
学生的姓名 + 年龄 + 成绩 结构体 三样随时都要看,缺一不可
一个数据要么是整数、要么是小数、要么是字符(同一时刻只可能是其中一种) 共用体 不可能同时是两种,没必要各分一间
通讯协议的一个"变长字段":按类型标志决定它是什么 共用体 每包只可能是其中一种解读
想省内存的嵌入式设备(内存按字节抠) 共用体 复用同一块空间

回到现实的一句话:共用体是"省内存 + 类型转换"的工具。桌面程序里用得不多,但在协议解析、二进制文件读写、嵌入式这三个领域很常见——所以教材要讲,考试要考。

三、一个直观的例子

假设要记录一个职工的"某项数据"——有时是工号(整数)、有时是工资(小数)、有时是级别(字符):

union StaffData {
    int    id;          // 工号
    double salary;      // 工资
    char   level;       // 级别
};

一条记录只需要 8 字节(最大的 double),而用结构体要 24 字节。而且这三个数据是互斥的——一个职工的数据,同一时刻只会以其中一种形式存在。

四、本节三篇的分工

小节 内容
8.5.1 共用体的定义与声明 语法(和 struct 几乎一样)
8.5.2 共用体的大小 为什么大小是"最大成员"(含实例05)
8.5.3 共用体的特点 三条特点 + 与结构体的完整对比

五、动手 Demo:先看一眼"同一块内存"

#include <iostream>
using namespace std;

union U {
    int  i;
    char c;
};

int main() {
    U u;
    u.i = 1000;
    cout << "写 i = 1000 后:u.i = " << u.i << endl;

    u.c = 'A';                  // 往同一块内存的低字节写了 'A'
    cout << "写 c = 'A' 后:u.i = " << u.i << "(被搅乱了!)" << endl;

    cout << "sizeof(U) = " << sizeof(U) << " 字节(取最大的 int)" << endl;
    cout << "u 的地址     = " << &u     << endl;
    cout << "u.i 的地址   = " << &u.i   << "(相同)" << endl;
    cout << "u.c 的地址   = " << &u.c   << "(还是相同!)" << endl;
    return 0;
}

看到什么算成功:

写 i = 1000 后:u.i = 1000
写 c = 'A' 后:u.i = (变成了另一个数,不是 1000)
sizeof(U) = 4 字节(取最大的 int)
u 的地址     = 0x...
u.i 的地址   = 0x...(相同)
u.c 的地址   = 0x...(还是相同!)

最震撼的一行是最后两行:&u.i 和 &u.c 打印出来是同一个地址——这就直接证明了"它们住在同一间房里"。

30 秒实验:把 union 改成 struct,再跑一遍。&u.i 和 &u.c 立刻变成两个不同的地址,而且 u.c = 'A' 再也搅不乱 u.i。一眼看懂"并排"和"重叠"的区别。

六、常见误区

  1. "共用体就是省内存的结构体"——只说对了一半。它不只是"省",而是语义完全不同:共用体的成员不能同时有效。当作"省空间的结构体"用,程序必然出错。
  2. "union 里可以随便存几个值"——只能存一个。写了 u.c 就破坏了 u.i(下一节详细讲)。
  3. "共用体很少见,可以跳过"——考试会考,实际也会遇到(协议、二进制文件、嵌入式)。至少要能看懂、能算大小。
  4. "struct 和 union 关键字可以互换"——不行。它们是两种不同的内存安排,语法像不代表语义像。
自测三问(点开对答案) 1. 结构体和共用体在内存安排上的根本区别?——结构体成员**并排**(各占一块,互不干扰);共用体成员**重叠**(共用同一块内存,谁写谁占)。 2. `union { int i; char c; };` 的大小是多少?为什么?——4 字节。因为成员共用一块内存,大小取**最大的那个成员**(`int` 的 4 字节)。 3. 什么情况下该用共用体而不是结构体?——几个数据**互斥、同一时刻只可能用到其中一个**的时候(协议字段、类型可变的记录、省内存场合)。

相关笔记