玄武纪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 是这个类型的一个变量,namescorepassed 是它的成员。结构体只规定记录的形状;定义变量以后,程序才有具体的一条记录。

多个结构体对象各自保存一份成员。修改 a.score 不会自动影响 b.score,即使 ab 的类型相同。

二、普通对象用点,指针用箭头

成员访问最常考的就是 .->

Student s = {"Lin", 92, true};
Student *p = &s;

cout << s.score;      // s 是对象,用 .
cout << p->score;     // p 是指针,用 ->
cout << (*p).score;   // 与 p->score 等价

记忆时先看符号左边:

圆括号很重要,因为成员访问运算符 . 的优先级高于一元 *。因此必须写 (*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。类型标记不属于联合体的自动功能,需要程序员自己维护。若标记与实际写入成员不一致,程序仍可能读错。

联合体共享存储与类型标记知识卡

七、成员访问题的检查顺序

  1. 圈出变量定义,判断左边是对象还是指针;
  2. 对象用 .,指针用 ->
  3. 若看到 (*p).x,先确认括号,再把它改写成 p->x
  4. 若是结构体数组,同时检查数组下标和成员名;
  5. 若是联合体,标记“最后一次写入的是哪个成员”;
  6. 计算 sizeof 前先看题目是否说明类型大小和对齐规则。

今日选择题

第 1 题为近五年 CSP-J 第一轮单选原题;第 2~5 题为专题练习;第 6~8 题围绕同一段结构体程序阅读。

  1. 【CSP-J 2023·第 3 题】 阅读下述代码,请问修改 datavalue 成员以存储 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;

  2. 已知 Student s;,访问对象 s 的成员 score,正确写法是( )。

    A. s->score  B. s.score  C. score.s  D. *s.score

  3. 已知 Student *p = &s;,下列与 p->score 等价的是( )。

    A. *p.score  B. *(p.score)  C. (*p).score  D. p.score

  4. 关于结构体和联合体,下列说法正确的是( )。

    A. 结构体的所有成员共享同一块存储
    B. 联合体的所有成员通常可以同时保存互不影响的值
    C. 修改一个结构体对象的成员,会自动修改同类型的所有对象
    D. 联合体各成员共享存储,程序应明确当前有效成员

  5. 在未说明编译器对齐规则时,关于下列联合体大小的说法最准确的是( )。

    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;
}
  1. 执行 Player *p = &a[1]; 后,p 指向( )。

    A. a[0]  B. a[1]  C. a[2]  D. 整个数组之后的位置

  2. 执行 p->score += p->bonus; 后,a[1].score 的值是( )。

    A. 70  B. 75  C. 78  D. 80

  3. 程序最终输出( )。

    A. 78 5  B. 78 6  C. 80 6  D. 70 3

暂停 · 先完成并提交

先分清对象和指针

请先独立完成全部题目,并到玄武 OJ 提交今日答案。

  • 看到成员访问,先判断左边是对象还是指针;
  • 联合体题标记最后写入的成员;
  • 结构体数组画成表格再追踪修改。
继续向下:每道题的完整题目、答案与解析
本页用于 CSP-J 第一轮自主复习。请先看知识卡并独立完成练习,提交玄武 OJ 后再查看解析。
← 上一天 返回学习中心 下一天 →