玄武纪8 月 · CSP-J 初赛打卡DAY 17 / 20
Day 17 结构体、联合体与成员访问
建议用时:20~25 分钟
今日目标:理解结构体与联合体的存储差异,熟练使用 . 和 -> 访问成员,并能读懂由结构体、指针和数组组成的短程序。
大纲定位:入门级 C++ 语言中的结构体与联合体;不展开类、继承、多态等面向对象内容。
一、把一条记录装进同一个“盒子”
数组适合存放一批同类型数据,但一名学生的信息可能同时包含姓名、成绩和是否通过。结构体可以把这些不同类型、属于同一对象的数据组合成一条记录。
struct Student {
string name;
int score;
bool passed;
};
Student s = {"Lin", 92, true};
Student 是新定义的结构体类型,s 是这个类型的一个变量,name、score、passed 是它的成员。结构体只规定记录的形状;定义变量以后,程序才有具体的一条记录。
多个结构体对象各自保存一份成员。修改 a.score 不会自动影响 b.score,即使 a、b 的类型相同。
二、普通对象用点,指针用箭头
成员访问最常考的就是 . 和 ->:
Student s = {"Lin", 92, true};
Student *p = &s;
cout << s.score; // s 是对象,用 .
cout << p->score; // p 是指针,用 ->
cout << (*p).score; // 与 p->score 等价
记忆时先看符号左边:
- 左边是结构体对象:
对象.成员;
- 左边是指向结构体的指针:
指针->成员;
p->x 等价于 (*p).x,但 *p.x 会先计算 p.x,不是同一回事。
圆括号很重要,因为成员访问运算符 . 的优先级高于一元 *。因此必须写 (*p).score。

三、结构体数组是一张“记录表”
定义 Student a[3] 后,a[0]、a[1]、a[2] 都是一条完整的学生记录。访问第二名学生的成绩写作 a[1].score。
Student a[3] = {
{"A", 85, true},
{"B", 76, true},
{"C", 58, false}
};
for (int i = 0; i < 3; ++i)
if (a[i].score >= 60) cout << a[i].name << ' ';
做这类程序阅读时,可以把每个结构体对象画成一行表格。代码修改哪个对象的哪个成员,就只改对应单元格,避免把成员和对象下标弄混。
四、联合体让多个成员共享同一块存储
联合体使用 union 定义:
union Data {
int num;
float value;
char symbol;
};
结构体的成员分别占据各自的存储区域,而联合体的成员从同一个起始位置共享存储区域。同一时刻通常只把其中一个成员当作有效值。
Data d;
d.num = 42; // 此时按 num 使用
d.value = 3.14f; // 改为按 value 使用,原来的 num 不再是应继续读取的有效值
联合体适合“几种数据只会选一种保存”的场景。它节省空间,但程序必须知道当前有效的是哪个成员。写入一个成员后,再随意读取另一个成员,不能把得到的位模式当作可靠、可移植的结果。

五、结构体和联合体最容易混淆的地方
| 比较 |
struct 结构体 |
union 联合体 |
| 成员存储 |
各成员有各自存储 |
各成员共享一块存储 |
| 同时有效 |
多个成员可同时保存值 |
通常只有最近写入的成员有效 |
| 主要用途 |
表示包含多个字段的一条记录 |
多种表示中择一保存 |
| 大小理解 |
与各成员、对齐方式有关 |
至少能容纳最大的成员,并满足对齐要求 |
不要机械地说“结构体大小一定等于所有成员大小之和”或“联合体大小一定严格等于最大成员大小”。编译器还可能为了对齐加入填充字节;如果题目没有给类型大小和对齐条件,就不应凭空算出唯一字节数。
六、联合体常和“类型标记”配合
若程序需要在整数和小数中二选一保存,可以额外记录当前类型:
struct Item {
char kind; // 'i' 表示整数,'f' 表示小数
union {
int i;
float f;
} data;
};
当 kind == 'i' 时读取 data.i,当 kind == 'f' 时读取 data.f。类型标记不属于联合体的自动功能,需要程序员自己维护。若标记与实际写入成员不一致,程序仍可能读错。

七、成员访问题的检查顺序
- 圈出变量定义,判断左边是对象还是指针;
- 对象用
.,指针用 ->;
- 若看到
(*p).x,先确认括号,再把它改写成 p->x;
- 若是结构体数组,同时检查数组下标和成员名;
- 若是联合体,标记“最后一次写入的是哪个成员”;
- 计算
sizeof 前先看题目是否说明类型大小和对齐规则。
今日选择题
第 1 题为近五年 CSP-J 第一轮单选原题;第 2~5 题为专题练习;第 6~8 题围绕同一段结构体程序阅读。
【CSP-J 2023·第 3 题】 阅读下述代码,请问修改 data 的 value 成员以存储 3.14,正确的方式是( )。
union Data {
int num;
float value;
char symbol;
};
union Data data;
A. data.value = 3.14;
B. value.data = 3.14;
C. data->value = 3.14;
D. value->data = 3.14;
已知 Student s;,访问对象 s 的成员 score,正确写法是( )。
A. s->score B. s.score C. score.s D. *s.score
已知 Student *p = &s;,下列与 p->score 等价的是( )。
A. *p.score B. *(p.score) C. (*p).score D. p.score
关于结构体和联合体,下列说法正确的是( )。
A. 结构体的所有成员共享同一块存储
B. 联合体的所有成员通常可以同时保存互不影响的值
C. 修改一个结构体对象的成员,会自动修改同类型的所有对象
D. 联合体各成员共享存储,程序应明确当前有效成员
在未说明编译器对齐规则时,关于下列联合体大小的说法最准确的是( )。
union U {
char c;
int x;
double y;
};
A. 一定等于三个成员大小之和
B. 至少要能容纳最大的成员,并满足相应对齐要求
C. 一定等于 1 字节
D. 与成员类型完全无关
主题程序阅读
阅读下面的程序:
#include <iostream>
using namespace std;
struct Player {
int score;
int bonus;
};
int main() {
Player a[3] = {{60, 5}, {70, 8}, {80, 3}};
Player *p = &a[1];
p->score += p->bonus;
a[2].bonus = a[0].bonus + 1;
cout << a[1].score << ' ' << a[2].bonus << endl;
return 0;
}
执行 Player *p = &a[1]; 后,p 指向( )。
A. a[0] B. a[1] C. a[2] D. 整个数组之后的位置
执行 p->score += p->bonus; 后,a[1].score 的值是( )。
A. 70 B. 75 C. 78 D. 80
程序最终输出( )。
A. 78 5 B. 78 6 C. 80 6 D. 70 3
暂停 · 先完成并提交
先分清对象和指针
请先独立完成全部题目,并到玄武 OJ 提交今日答案。
- 看到成员访问,先判断左边是对象还是指针;
- 联合体题标记最后写入的成员;
- 结构体数组画成表格再追踪修改。
答案与解析
下面按“题目 → 答案 → 解析”的顺序订正。
第 1 题|联合体对象访问成员
【CSP-J 2023·第 3 题】 阅读下述代码,请问修改 data 的 value 成员以存储 3.14,正确的方式是( )。
union Data {
int num;
float value;
char symbol;
};
union Data data;
A. data.value = 3.14;
B. value.data = 3.14;
C. data->value = 3.14;
D. value->data = 3.14;
答案:A
解析: data 是联合体对象,不是指针,所以用点号访问成员:data.value。只有左边是指向联合体或结构体的指针时才使用 ->。
第 2 题|对象使用点号
已知 Student s;,访问对象 s 的成员 score,正确写法是( )。
A. s->score B. s.score C. score.s D. *s.score
答案:B
解析: s 是结构体对象,格式为“对象.成员”。-> 左边应当是指针。
第 3 题|箭头的等价写法
已知 Student *p = &s;,下列与 p->score 等价的是( )。
A. *p.score B. *(p.score) C. (*p).score D. p.score
答案:C
解析: 先用 *p 得到 p 所指向的对象,再用点号访问成员,因此要写 (*p).score。圆括号不能省略,因为 . 的优先级高于一元 *。
第 4 题|结构体与联合体的存储差异
关于结构体和联合体,下列说法正确的是( )。
A. 结构体的所有成员共享同一块存储
B. 联合体的所有成员通常可以同时保存互不影响的值
C. 修改一个结构体对象的成员,会自动修改同类型的所有对象
D. 联合体各成员共享存储,程序应明确当前有效成员
答案:D
解析: 结构体各成员分别保存数据,不同对象也互相独立;联合体成员共享存储,写入新成员会改变这块共享存储,因此应记录当前按哪个成员解释。
第 5 题|联合体大小
在未说明编译器对齐规则时,关于下列联合体大小的说法最准确的是( )。
union U {
char c;
int x;
double y;
};
A. 一定等于三个成员大小之和
B. 至少要能容纳最大的成员,并满足相应对齐要求
C. 一定等于 1 字节
D. 与成员类型完全无关
答案:B
解析: 联合体成员共享一块空间,所以不需要把三个成员大小直接相加;这块空间至少要容纳最大的成员,还要满足编译器的对齐要求。没有给出具体平台时,不应武断写出唯一字节数。
第 6~8 题共用程序|结构体数组与指针
阅读下面的程序:
#include <iostream>
using namespace std;
struct Player {
int score;
int bonus;
};
int main() {
Player a[3] = {{60, 5}, {70, 8}, {80, 3}};
Player *p = &a[1];
p->score += p->bonus;
a[2].bonus = a[0].bonus + 1;
cout << a[1].score << ' ' << a[2].bonus << endl;
return 0;
}
第 6 题|指向哪个对象
执行 Player *p = &a[1]; 后,p 指向( )。
A. a[0] B. a[1] C. a[2] D. 整个数组之后的位置
答案:B
解析: &a[1] 是数组中第 2 个结构体对象的地址,因此 p 指向 a[1]。
第 7 题|通过指针修改成员
执行 p->score += p->bonus; 后,a[1].score 的值是( )。
A. 70 B. 75 C. 78 D. 80
答案:C
解析: p 指向 a[1],所以语句等价于 a[1].score += a[1].bonus,即 70+8=78。
第 8 题|追踪两个成员
程序最终输出( )。
A. 78 5 B. 78 6 C. 80 6 D. 70 3
答案:B
解析: 第一处修改使 a[1].score=78;第二处修改使 a[2].bonus=a[0].bonus+1=6,因此输出 78 6。
完成后检查
- 我能根据左边是对象还是指针,正确选择
. 或 ->,也知道 p->x 等价于 (*p).x。
- 我能说明结构体各成员分别存储、联合体各成员共享存储。
- 我追踪结构体数组时,会同时记录“对象下标”和“成员名称”,不把两个维度混在一起。
本页用于 CSP-J 第一轮自主复习。请先看知识卡并独立完成练习,提交玄武 OJ 后再查看解析。