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

共用体三条特点,一句一条:① 所有成员共用同一块内存(地址相同);② 任一时刻只有一个成员有效(后写的覆盖先写的);③ 大小取最大成员(并对齐)。由此推出一条纪律:读之前必须知道"现在有效的是谁"——要么自己心里有数,要么像 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 演示过)。

四、四个必须小心的坑

  1. 读错成员:写 i 却读 f,得到的是无意义的字节组合。编译器不会提示你——这是共用体最危险的地方。
  2. 跨平台差异:"同一块内存怎么解读"依赖字节序(大端/小端)。x86 是小端,但别的架构可能不同。所以"用共用体做类型转换"的代码换平台就可能失灵。
  3. 浮点表示的假设:想靠共用体"拆开 float 的位",就要依赖 IEEE 754 表示——这也不是语言保证的。
  4. 不能直接比较两个共用体: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 左右——这就是"并排"和"重叠"的代价对比。

六、常见误区

  1. "共用体是结构体的省内存版"——危险的说法。它不是"更省的结构体",而是"一次只能放一个"的容器。当成省空间的结构体用,程序一定会读到垃圾。
  2. "共用体可以随便做类型转换"——依赖字节序和浮点表示,换平台可能失灵。语言层面没有保证。
  3. "sizeof(union) 就是最大成员的大小"——还要考虑对齐。union Big { char c[10]; int i; } 是 12,不是 10(8.5.2)。
  4. "共用体可以像结构体那样初始化多个成员"——只能给第一个成员(8.5.1)。
  5. "用不到就不用学"——考试必考,协议/嵌入式/二进制读写里常见。至少要能:算大小、说清"只有一个成员有效"、写出"标签 + 共用体"这个模式。
自测三问(点开对答案) 1. 用一句话说明什么时候用 `struct`、什么时候用 `union`。——数据**要同时存在**用 `struct`;数据**互斥、一次只用其中一个**用 `union`。 2. 共用体大小的完整规则是什么?——`max(各成员大小)`,再按**最宽成员的对齐要求**向上取整。 3. 怎么让共用体的使用变"安全"?——配一个**类型标签**(`tag`):写入时记下类型,读取时先看标签再取对应成员。

相关笔记