共 74 页 · 点击任意板块可直接跳转;也可按 M 打开右侧完整目录。
先看清考试怎么考:考纲要求、近年分值分布、高频考点与重难点,把有限的复习时间压在最值钱的地方。
考纲要求:结构体、共用体的概念及链表的操作。
考试以 程序分析结果题、程序改错题、程序设计题 三种题型呈现,全部是“读代码 / 改代码 / 写代码”的能力考查。
关键词一 · 概念
关键词二 · 操作
| 考 点 | 题 型 | 2023 年 | 2024 年 | 2025 年 |
|---|---|---|---|---|
| 结构体的定义及应用 | 程序分析结果题、程序改错题、程序设计题 | — | 5 分 | 5 分 |
| 共用体的定义及应用 | 程序分析结果题、程序改错题、程序设计题 | — | — | — |
| 链表的操作 | 程序分析结果题、程序改错题、程序设计题 | 25 分 | 20 分 | 20 分 |
| typedef | 程序分析结果题、程序改错题、程序设计题 | — | — | — |
左图为试卷原页表格切片,完整保留原始排版与表格线;本章考点历年题型完全一致,仅为分值不同。
高频考点:结构体、共用体及链表的操作
(创建、删除、插入、反序、输出)
重难点分析:链表的操作
难点不在语法,而在指针的操作顺序。
最基础的构造数据类型,四个问题一次讲透:类型怎么声明?变量怎么定义?成员怎么引用?初始化怎么写?
数组:具有相同数据类型的数据序列。
各元素类型必须一致,适合表示“一批同类数据”,但无法表示一条记录中的多种属性。
结构体:不同数据类型的数据序列。
把一个对象的多种属性“打包”在一起,适合表示一条完整记录。
例如,一个学生的“学号 + 姓名 + 性别 + 成绩”是 4 个不同属性,用数组无法表示,用结构体就非常自然:
struct 结构体名
{ 成员表列 };
struct 是关键字,声明结构体类型时必须写;先定义结构体类型,再声明结构体变量。这是最常用、最规范的一种写法。
声明了两个变量 a1 和 a2 为 struct stu 结构类型。也可以用宏定义使一个符号常量来表示一个结构类型:
在定义结构体类型的同时,声明结构体变量。类型和变量写在一起,一步到位。
优点:写法紧凑,定义与使用靠近,可读性好。
注意:此处 a1、a2 声明在结构体类型之后,因此该类型是匿名的 —— 后面无法再定义同类型的其他变量。
struct stu 的麻烦。直接声明结构体变量。连结构体名都省去,直接给出变量名。
| 方 法 | 写 法 | 特 点 | 考试中的出现形式 |
|---|---|---|---|
| 方法一 | 先 struct 类型,再 struct 类型 变量名; | 最规范,类型可复用;用宏定义还可进一步简化 | 改错题 / 设计题最常用 |
| 方法二 | 定义类型的同时,在 } 后写变量名 | 紧凑、一步到位;但类型匿名,无法复用 | 程序分析结果题中常见 |
| 方法三 | struct { … } 变量名; | 完全匿名,只此一次 | 偶见于分析结果题 |
变量名.成员名;(1)结构体变量名.成员名 → a.num
(2)(*指针变量名).成员名 → (*p).num
(3)指针变量名->成员名 → p->num
要引用 a 中的 num 成员,以下三种写法完全等价:
a.num (*p).num p->num
推荐:变量用 .,指针用 -> —— p->num 最简洁、最不容易写错。
(*p).num 的括号不能省!写成 *p.num 会被理解为 *(p.num),编译报错。① “.” 和 “->” 运算符的优先级最高。
因此 x.a++ 是 (x.a)++,而不是 x.(a++)。
② 对结构体中的成员(即“域”),可以单独使用,它的作用与地位相当于普通变量。
可以 a.num = 100;、a.num++、&a.num,和普通 int 变量用法完全一样。
③ 成员可以是一个结构体变量。
这就是结构体的嵌套,引用时要逐级写成员运算符(详见 ⑥)。
④ 成员名可以与程序中的变量名相同,但二者不代表同一对象。
例如程序中有一个变量 num,结构体中也有成员 num,两者互不干扰,各占各的内存。
⑤ 不能将结构体变量作为一个整体进行输入和输出。
例如:已定义 a1 和 a2 为结构体变量,并且它们已有值。
正确做法:逐个成员输出 ——printf("%d,%s\n", a1.num, a1.name);
⑥ 如果成员本身属于一个结构体类型,则要用若干个成员运算符,一级一级地找到最低一级的成员。只能对最低级的成员进行赋值、存取和运算。
⑦ 对结构体变量的成员可以像普通变量一样进行各种运算(根据其类型决定可以进行的运算)。
整型成员可做 + - * / % ++ -- 等运算;字符数组成员则受数组限制,不能整体赋值。
⑧ 可以引用结构体变量成员的地址,也可以引用结构体变量的地址。
结构体变量的地址主要用作函数参数或结合结构体指针,用来传递结构体变量的地址。例如:
可以在定义结构体变量时对其赋初值。初值用花括号括起,按成员在结构体中的顺序一一对应。
char c[10] = "abc";。方式一:用 scanf 逐项输入
注意:x.c 是数组名,前面不加 &。
方式二:逐个成员赋值
关键:x.c = "abc"; 是错误的写法。
strcpy(目标, 源)(需 #include <string.h>)。要包含所需的头文件,这一点在程序改错题中出现频率极高。struct 名 { 成员表列 }; 分号不能少;.、(*p).、->;请判断下列写法是否正确:
struct stu { int a; }(无分号)printf("%d", a);(a 为结构体变量)a.name = "Li";(name 为字符数组)p.a(p 为结构体指针)全部错误
};;strcpy(a.name, "Li");;p->a 或 (*p).a。这四处正好对应本章改错题的四大高频考点:缺分号、整体输入输出、数组整体赋值、指针访问成员符号用错。把这四句话背下来,改错题至少能拿一半分。
与结构体形式相似、内存模型却完全不同:成员共享同一段空间,同一时刻只有一个成员有效。
union 共用体名
{ 成员表列 };
a.i、(*p).i、p->i。| 对比项 | struct 结构体 | union 共用体 |
|---|---|---|
| 内存占用 | 各成员各占独立空间,总长 ≥ 各成员之和 | 所有成员共用一段空间,长度 = 最长成员 |
| 成员地址 | 各成员地址互不相同 | 所有成员首地址相同 |
| 赋值影响 | 修改一个成员不影响其他成员 | 修改一个成员会覆盖其他成员 |
| 有效成员 | 所有成员同时有效 | 只有最后被赋值的那个成员有效 |
| 引用方式 | 完全一样:. 和 -> | 完全一样:. 和 -> |
sizeof 的值4
有定义 union un { int a; char b; float c; };,则 sizeof(union un) 是 9 吗?
不是 9,而是 4。共用体长度取最长成员:int 4、char 1、float 4 —— 最长是 4 字节,而不是 4 + 1 + 4 = 9。
对照记忆:
struct { int a; char b; float c; } → 各成员相加并考虑对齐,通常是 12;union { int a; char b; float c; } → 取最长成员,即 4。全章最重要的 20–25 分:创建、插入、删除、反序、输出五大操作。抓住“指针顺序”这个牛鼻子,题目就变成填空题。
链表:用一组任意的存储单元存放线性表的元素,每个元素除了存数据,还要存下一个元素的地址。
因此每个元素称为一个结点,结点 = 数据域 + 指针域。
头指针 head:指向第一个结点,是链表的“入口”,绝不能丢;
表尾:最后一个结点的 next 必须为 NULL,这是遍历结束的标志。
struct node *next?因为在定义这个类型的过程中,类型别名 Node 尚未生效,C 语言规定此时必须使用 struct node * 这种完整写法,不能直接写 Node *next;。三步走:
p = (LinkList)malloc(sizeof(Node));p->data = x; p->next = NULL;tail->next = p; tail = p;p->next 置 NULL → 遍历时越界或死循环;② 只用 head 逐个找表尾 → 时间复杂度退化成 O(n²);③ 直接移动 head → 链表入口丢失,函数外再也访问不到这条链。三句口诀:
p != NULL(不是 p->next != NULL);p = p->next 后移。典型错误一:写成 head = head->next;,头指针被一步步“吃掉”,函数返回后链表再也无法访问。
典型错误二:循环条件写成 while (p->next != NULL),则最后一个结点不会被输出,结果少打印一个数。
顺序是命门:
s->next = p->next;p->next = s;p->next = s;,p 原来后继结点的地址就被覆盖丢失了,s 的 next 再也接不上原链表 —— 链表断裂,丢失后面所有结点。单链表的局限:要找 p 的前驱必须从头遍历(无法 O(1) 前插)。若题目要求频繁前插,可考虑双向链表,或在遍历时额外记录前驱指针 pre。
口诀:先摘链,再释放。
q = p->next; 用 q 记住要删的结点;p->next = q->next; 让 p 跨过 q;free(q); 归还不用的空间。q->next 成为野指针,结果错误;四步口诀:
r = p->next;p->next = q;q = p; p = r;另一种思路:头插法重建。依次取出原表的每个结点,把它头插到一个新表中,结果自然就是反序的。
两种方法效果相同;就地逆置不额外申请空间,考试通常要求写就地逆置。
head = Reverse(head);。若只调用 Reverse(head); 而不接收返回值,head 仍指向原表头(此时已是表尾),链表只剩一个结点可用。p->next = NULL;malloc 返回值未检查、未强制类型转换;p->next != NULL,漏输出最后一个结点;四句话模板(背下来就能默写)
给已有类型起个别名,让结构体、链表的写法瞬间变短,是链表代码中出场率最高的“配角”。
typedef 已有类型 新类型名;
typedef struct node { … } Node; Node 是类型名;struct node { … } n; n 是变量名。typedef —— 这也是常考的辨析点。教材第 193–197 页原页扫描件,完整保留原始排版,随时可与前面的重排讲解页对照核验。
本页内容已在上文“知识点精讲”中完整重排讲解,此处保留原卷原貌,便于课堂对照核验。
本页内容已在上文“知识点精讲”中完整重排讲解,此处保留原卷原貌,便于课堂对照核验。
本页内容已在上文“知识点精讲”中完整重排讲解,此处保留原卷原貌,便于课堂对照核验。
本页内容已在上文“知识点精讲”中完整重排讲解,此处保留原卷原貌,便于课堂对照核验。
本页内容已在上文“知识点精讲”中完整重排讲解,此处保留原卷原貌,便于课堂对照核验。
按考点表中的三种题型,加上选择题与填空题,共 27 题,每题一页:先做题,再点开答案与解析。
| 题 型 | 题 量 | 主要考查点 |
|---|---|---|
| 选择题 | 8 题 | 概念辨析、sizeof、指针符号 |
| 填空题 | 6 题 | 术语记忆、关键代码片段 |
| 程序分析结果题 | 5 题 | 读代码算结果 |
| 程序改错题 | 4 题 | 缺分号 / 数组赋值 / 指针顺序 |
| 程序设计题 | 4 题 | 结构体统计 → 链表建表与逆置 |
课堂互动方式
A 可一键展开或收起本页答案;M 打开目录。本组题覆盖:结构体的定义与成员引用、sizeof 的计算、共用体的内存特点、typedef 的规则,以及链表插入与删除的核心指针操作。
每题一页,答案与解析默认折叠,课堂上先让学生举手作答,再点开核验。
A 键一键展开 / 收起本页答案;按 → 翻下一页C
结构体是“不同数据类型”的集合,各成员类型可以互不相同。
struct 类型名 变量名; 定义变量;struct stu a, *p; 且 p = &a;,不能正确引用成员 num 的是( )D
-> 只能作用于指针,a 是结构体变量而不是指针。
.;*p 就是 a,再加圆括号即可;->;(&a)->num,不能写 a->num。struct node { int a; char b; float c; } x;(int 占 4 字节、char 占 1 字节、float 占 4 字节,按 4 字节对齐),则 sizeof(x) 的值通常是( )C → 12
按成员顺序计算偏移量:
| 成 员 | 类 型 | 占用字节 | 偏 移 量 |
|---|---|---|---|
| a | int | 4 | 0 ~ 3 |
| b | char | 1 | 4 |
| 对齐填充 | — | 3 | 5 ~ 7 |
| c | float | 4 | 8 ~ 11 |
合计 12 字节。这也正好说明:结构体长度 ≠ 各成员长度之和(7),因为编译器会为了满足对齐要求插入填充字节。
C
共用体的所有成员共用同一段内存,长度取最长成员。
union { int a; char b; float c; } 的长度是 4;union un { char c; int i; double d; } u;(char 1、int 4、double 8 字节),则 sizeof(u) 的值是( )C → 8
共用体取最长成员:char 1 字节、int 4 字节、double 8 字节 → 最长是 double 的 8 字节,同时 8 也满足所有成员的对齐要求,所以结果是 8。
注意与结构体区分:同样三个成员放在 struct 中,长度会是 16(1 + 填充 3 + 4 + 8)。
typedef 的叙述中,错误的是( )C
typedef 只是建立“别名”,原类型名依然有效,两者可以混用。
Node a; 等价于 struct node a;;struct node 与 Node 指的是同一个类型,两者都可以使用;p 指向某结点(其后继结点存在),要删除 p 所指结点的后继结点,正确的语句组是( )A
free,被删结点空间无法回收 → 内存泄漏;free(p->next) 之后 p->next 成为野指针,链表断裂。p 指向单向链表中某结点,s 指向待插入的新结点(s 已申请到空间)。要把 s 插入到 p 之后,正确的操作是( )A
p->next = s; 后,p 的原后继地址已经丢失,s->next = p->next 实际等于 s->next = s,链表断裂;s->next = p 会形成环,方向错误;p->next = s->next 把 p 的后继指向了未定义位置。填空题主要考查概念性记忆和关键代码片段:成员的引用形式、结构体定义的分号、共用体长度、字符串赋值函数,以及链表动态内存的申请与释放。
作答提示:空白处的答案要与教材用词一致,如“最长成员”“分号”这类术语不能写成近义表述。
(*指针变量名).成员名
即 (*p).num 这种写法。
三种形式完全等价:
| 写 法 | 适 用 对 象 | 示 例 |
|---|---|---|
| 变量名.成员名 | 结构体变量 | a.num |
| (*指针名).成员名 | 结构体指针(先解引用) | (*p).num |
| 指针名->成员名 | 结构体指针(简写形式) | p->num |
易错:括号不能省!*p.num 会被解释为 *(p.num),编译出错。
分号 ;
} 后面没有分号,直接就能标记出错误。最长成员(占用字节数最多的成员)
共用体的所有成员共用同一段内存,因此容量必须容得下最大的那个成员。
对比:结构体的长度约等于各成员之和(并考虑对齐),共用体取最大值。
struct node { int a; char b, c[10]; } x;,要把字符串 "abc" 存入成员 c,应使用语句 。strcpy(x.c, "abc");
并需在文件开头包含 #include <string.h>。
为什么不能写 x.c = "abc";?
c 是地址常量,不能作为赋值号的左值;strcpy(目标, 源);char c[10] = "abc";。malloc
malloc 返回 void *,必须强制类型转换为结点指针类型;sizeof(Node);<stdlib.h>。free
free 只能释放由 malloc / calloc / realloc 申请的动态空间;这是本考点分值最高、最常见的题型:给出一段完整程序,要求写出运行结果。
解题三步法:① 找出数据结构(结构体 / 共用体 / 链表);② 在草稿纸上画内存图与指针箭头;③ 按语句顺序逐步演算,特别注意格式串与输出顺序。
101,Li
a 在定义的同时整体赋初值 {101, "Li"},按成员顺序依次对应 num 与 name;a.num、a.name;printf("%d,%s\n", a); 是错误的 —— 结构体变量不能整体输入输出。A
u 的成员 i 与 ch 共用同一段内存,起始地址相同;u.i = 65; 存入后,内存中低地址字节为 0x41,即字符 'A'(ASCII 码 65);u.ch 读的正是这个字节,所以输出 A;10,A
15
p 指向 x,所以 p->a、(*p).a、x.a 访问的是同一个存储单元;p->a += 5 等价于 x.a = x.a + 5,10 → 15;%c 输出字符,%d 输出整数。123
链表结构为 a → b → c → NULL,三个结点通过 next 依次相连。
本题没有 malloc 也没有 free,结点全部是普通结构体变量,用取地址的方式构成链 —— 这是分析结果题中最常见的“简化链表”。
4,13
x.a++ 是后缀自增:先取 x.a 的原值 3 参与运算,然后 x.a 自增为 4;x.b = 3 + 10 = 13;x.a 在前、x.b 在后,故结果为 4,13;. 的优先级最高,x.a++ 一定被解释为 (x.a)++。改错题的答案必须写成“指出错误 + 说明原因 + 给出正确写法”三要素,只写“改成分号”往往拿不到全分。
本组覆盖结构体与链表最经典的四个坑:缺分号、字符数组整体赋值、指针与变量类型不匹配、插入与删除的指针顺序。
错误 ①:结构体类型定义结束时缺少分号。应改为 };
错误 ②:字符数组不能用赋值号整体赋值。应改为 strcpy(a1.name, "Li");,并在文件开头加上 #include <string.h>
} 后必须有分号;name 是数组名,代表地址常量,不能作赋值号的左值;100,Li;scanf("%d%s", &a1.num, a1.name); 输入。错误:p = a; —— p 是指针变量,而 a 是结构体变量,两者类型不匹配,不能把结构体变量直接赋给指针。
应改为:
&a)或另一个结构体指针的值;p 指向 a,程序输出 1;p = a.next; 之后随意解引用,必须先保证赋的是合法有效地址。错误:第二句写成了 p->next = s->next;。此时 s->next 的值就是 p 原来的后继,执行后 p->next 又变回原后继,新结点 s 根本没有被插入链表。
应改为:
遗漏:free(q);
q 所指结点的空间是用 malloc 动态申请的,摘链之后必须用 free(q) 归还,否则会造成内存泄漏。
q->next,就访问了已释放的内存,结果错误;head = head->next;;设计题要求写出完整可运行的代码,分值高、步骤多。建议按“定义类型 → 写函数 → 写主函数 → 输出结果”的顺序书写,并保留必要注释。
本组难度递进:① 结构体数组统计 → ② 结构体作函数参数与返回值 → ③ 建立并遍历链表 → ④ 链表就地逆置。
struct stu { int num; char name[10]; float score[3]; float sum; float avg; };,输入 5 名学生的学号、姓名和 3 门课成绩,计算并输出每人的总分和平均分。s[N] 表示 N 名学生,每个元素是一条完整记录;score[j] 与 num 前面要加 &,而 name 是字符数组名(本身即地址),不加 &;s[i].sum = 0; 漏写会得到随机值;%.1f 保留一位小数;scanf("%d%s", &s[i].num, s[i].name) 中 s[i].name 不能写成 &s[i].name。struct complex add(...);next 必须先置 NULL,否则链表末尾没有终止标志,遍历会越界;malloc 的返回值要强制类型转换,并检查是否为 NULL;free 释放结点空间。r = p->next; 记住后继;② p->next = q; 方向反转;③ q = p; p = r; 两根指针一起前移;p == NULL,q 指向原表尾,它就是新的头指针;head = Reverse(head); 必须用返回值更新 head。若只写 Reverse(head);,head 仍指向原表头(此时已是表尾),链表只剩一个结点可用;结构体 = 声明类型 → 定义变量 → 引用成员 → 初始化;
链表 = 创建 → 插入 → 删除 → 反序 → 输出,抓住“指针顺序”这一个核心。
.,指针用 ->按 M 打开目录 · 按 A 展开答案 · 按 Esc 关闭面板