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

定义共用体只需要把 struct 换成 union,其他语法一模一样:union 共用体名 { 成员列表 };,末尾分号同样不能漏。声明变量、访问成员也全照旧:Data d; d.i。所以这一节的"新知识"其实只有一条:改一个关键字,语义就从"并排"变成"重叠"。

📝 本节和前面几节的关系

8.1.1(结构体定义)和 7.x 的语法全都直接照搬。唯一要小心的是"不能想当然"——union 的定义看起来和 struct 一样,但行为完全不同。这一节把语法钉牢,下一节 8.5.2 用 sizeof 和实例05 把"重叠"这个事实实测出来。

一、定义:union 换 struct

union Data {                // 只把 struct 换成 union
    int    i;
    float  f;
    char   c;
};                          // 末尾分号照旧不能漏(8.1.1 的第一大坑)

读成大白话:"凡是叫 Data 的东西,可以在同一块内存上,被当成 int 或 float 或 char 来用。"

和结构体定义并排看:

struct Data { int i; float f; char c; };    // 三间房:4 + 4 + 1 → 12 字节(含对齐)
union  Data { int i; float f; char c; };    // 一间房:取最大 4 → 4 字节

语法上唯一的差别就是那个关键字——一个词的差别,12 字节变 4 字节。

二、声明变量:和结构体完全一样

union Data d1;              // 一个共用体变量
union Data d2, d3;          // 一行多个
Data d4;                    // 如果在 C++ 里(或用了 typedef),可以省 union

三种写法(和 8.1.1 一一对应):

// ① 先定义类型,用到再声明变量(推荐)
union Data { int i; float f; char c; };
Data d;

// ② 定义类型时顺便声明变量
union Data { int i; float f; char c; } d1, d2;

// ③ 匿名共用体(只用一次)
union { int i; float f; char c; } d;

加 typedef 也可以(8.2 的老套路):

typedef union Data {
    int   i;
    float f;
    char  c;
} Data;                     // 之后可以直接写 Data d;

三、访问成员:还是那个点

Data d;

d.i = 10;                   // 给 i 这一"解读方式"赋值
cout << d.i << endl;        // 10

d.f = 3.14f;                // ⚠️ 这次赋值会覆盖刚才的 i(下一节讲)
cout << d.f << endl;        // 3.14

语法和结构体一模一样:变量.成员。

但语义完全不同——这里每一个赋值都在"用同一块内存":

结构体 共用体
d.i = 10; 之后再 d.f = 3.14; 两个人各占一间,互不影响 后写的把先写的覆盖掉
能不能同时读 d.i 和 d.f 能,都是有效值 不能,只有一个有效

一句话记牢:共用体里,"写一个成员"就是"把另外几个成员废掉"。

四、初始化:只能给第一个成员初始化

这是一个容易踩的坑:

union Data d = {10};        // ✅ 只初始化第一个成员(i = 10)
union Data e = {1, 2, 3};   // ❌ 编译错:共用体只能初始化一个成员

为什么?因为它们共用同一块内存——你没法同时把 10、2.5、'A' 三种值塞进同一间房。标准规定:初始化列表里只能有一个值,且对应第一个成员。

想要别的成员有初值,只能声明之后再赋值:

union Data d;
d.f = 3.14f;                // 声明后赋值,不受限制(但会覆盖其他解读)

五、动手 Demo:定义、赋值、看地址

#include <iostream>
using namespace std;

union Data {
    int   i;
    float f;
    char  c;
};

int main() {
    // ① 只初始化第一个成员
    union Data d1 = {10};
    cout << "d1.i = " << d1.i << endl;

    // ② 声明后逐个赋值(相互覆盖)
    union Data d2;
    d2.i = 100;
    cout << "写 i 后 d2.i = " << d2.i << endl;

    d2.f = 3.14f;                       // 覆盖
    cout << "写 f 后 d2.f = " << d2.f << endl;
    cout << "此时再读 d2.i = " << d2.i << "(不是 100 了)" << endl;

    d2.c = 'A';                         // 又覆盖
    cout << "写 c 后 d2.c = " << d2.c << endl;
    cout << "此时再读 d2.f = " << d2.f << "(也被破坏了)" << endl;

    // ③ 地址证明:"两个成员住同一个门牌号"
    cout << "&d2     = " << &d2   << endl;
    cout << "&d2.i   = " << &d2.i << "(相同)" << endl;
    cout << "&d2.f   = " << &d2.f << "(相同)" << endl;
    cout << "&d2.c   = " << &d2.c << "(相同)" << endl;

    // ④ 和结构体对比
    struct S { int i; float f; char c; };
    cout << "sizeof(union Data) = " << sizeof(Data) << endl;
    cout << "sizeof(struct S)   = " << sizeof(S)    << endl;
    return 0;
}

看到什么算成功:

d1.i = 10
写 i 后 d2.i = 100
写 f 后 d2.f = 3.14
此时再读 d2.i = (一个奇怪的值,不是 100)
写 c 后 d2.c = A
此时再读 d2.f = (也被破坏了)
&d2     = 0x...
&d2.i   = 0x...(相同)
&d2.f   = 0x...(相同)
&d2.c   = 0x...(相同)
sizeof(union Data) = 4
sizeof(struct S)   = 12

sizeof(struct S) 在你机器上也可能是 8 或 12(看对齐),重点是它比 union 的 4 大得多。

两个"后写覆盖先写"的证据: - 写 d2.f 之后,d2.i 不再是 100; - 写 d2.c 之后,d2.f 不再是 3.14。

一个"同居"的证据:四个地址打印出来完全一样。

30 秒实验:把 d2.c = 'A'; 这一句挪到最前面(在 d2.i = 100; 之前),重新运行——这次最后读 d2.i 就是 100(因为 i 是最后写的)。谁最后写,谁说了算。

六、常见误区

  1. "共用体可以像结构体那样多个成员同时初始化"——不行。只能给第一个成员一个初值(union Data d = {10};),其他成员只能声明后赋值。
  2. "定义时忘了末尾分号没关系"——和结构体一样是硬伤(8.1.1)。漏了会让后面一整段代码报莫名其妙的错。
  3. "共用体成员可以同时使用"——绝对不行。"最后一次写的是哪个成员,就只有哪个成员有效",其他成员读出来是"被搅乱的字节"。
  4. "union 和 struct 只是关键字不同,功能一样"——语法像,语义相反(并排 vs 重叠)。这是本章最需要分清的对比。
自测三问(点开对答案) 1. 定义共用体的语句里,`struct` 该换成什么?其他有变化吗?——换成 `union`。语法(花括号、末尾分号、成员列表)完全一样。 2. `union Data d = {10};` 合法吗?`{1, 2, 3}` 呢?——第一个合法(初始化第一个成员);第二个**不合法**,共用体只能初始化一个成员。 3. 怎么用代码证明两个成员"住在同一块内存"?——打印 `&d.i`、`&d.f`、`&d.c`,**三者相同**。

相关笔记