十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C++期末考试复习题高效刷题:考点拆解、编译验证与模板默写

C++期末考试复习题高效刷题:考点拆解、编译验证与模板默写 简介C期末考试复习题.docx 面向高校计算机相关专业、正在备考C语言程序设计期末考试的本科生聚焦面向对象编程核心考点的系统梳理与自测。内容以选择题和填空题为骨架覆盖类的声明与数据成员、访问修饰符的任意顺序与默认权限、构造函数与析构函数、new与delete的内存管理、运算符重载与友元函数、继承与多态、虚基类与纯虚函数、重载函数的匹配依据、引用的初始化、静态成员、内联函数以及fstream文件操作等章节题目后附有参考答案便于对照查漏补缺。整包仅1个docx文件体积约136KB可直接打开打印或批注适合考前集中刷题与知识点串联。目前已有126人学习下载可用来快速定位薄弱环节配合教材复习形成完整闭环。1. 一份 C期末考试复习题.docx真正卡住人的地方在哪期末前一周拿到一份 C期末考试复习题.docx多数人的第一反应是打开从头刷到尾。刷完两轮遇到读程序写结果还是靠猜遇到编程题还是写不出能编译通过的完整代码——问题不在题量在于这份文档只给了题目和答案没给「这条语法到底在机器上怎么表现」的验证路径。我一般把这份复习题当成三个东西用一份考纲题目分布就是老师的重点分布、一份语法清单每道题对应一到两个语言点、一份待验证断言所有关于输出结果的判断都必须扔进编译器跑一遍。指针用法、字符串数组初始化、结构体链表、流 I/O、冒泡排序、二分查找这些考点在纸面上看都眼熟只有跑起来才知道哪里会越界、哪里会读到空串。这篇按复习顺序推先拆考点、再配环境把每道题跑起来、然后把五类高频题型的可抄模板写死、最后落到错题驱动和自动对拍。适合正在备考的人也适合带课设、带实验的助教。2. 把 C期末考试复习题拆成可打卡的知识点清单拿到文档先别做题花四十分钟做一次「分类」后面能省下十倍时间。做法是只看题干关键词不看答案把每道题归到五类里。2.1 按题型分类复习题的分数其实是不均匀的题型文档里的典型问法复习动作建议时间占比概念判断题引用与指针的区别、const 放在 * 左右只记结论 一个反例10%读程序写结果循环嵌套、指针自增、输出格式手算后必须编译核对30%改错题数组越界、忘 delete、缺分号、头文件建错误清单按错因归类15%编程题冒泡排序、二分查找、链表、判断质数默写模板到能一次编译通过35%简答/概念题三大特性、重载与重写的区别关键词答题按点给分10%读程序写结果这一类最值得投资。它同时考察运算符优先级、变量作用域、内存布局三件事而这三件事恰好是编程题出错的主要来源。2.2 知识点到题号模式的映射表建立下面这张表左边是考点右边是你在复习题里应该能一眼认出的提问特征。填不满的行就是你还没复习到位的部分。考点识别特征最小验证手段指针用法*p、*(p2)、p a打印地址和值看移动步长全局变量 / 局部变量同名变量在两个作用域内加块作用域{}后重新观察字符串数组初始化char s[]abc、char s[3]abcsizeof与strlen对比字符串转数组std::string与char[]混用s.c_str()与s.data()结构体链表Node *next、new Node打印每个节点的地址流 I/Ocin 与getline连用输入带空格的名字冒泡排序双层循环、交换相邻元素逆序数组跑一遍看比较次数二分查找有序数组、mid、lo/hi找不存在的值看返回什么sort 引入库要求用标准库排序检查#include algorithm判断质数优化大数判断、超时提示试到sqrt(n)的耗时对比注意表里任何一行只要你在复习题里找不到对应题目说明这份文档不是全覆盖需要拿教材目录补齐。2.3 用脚本把复习题.docx 拆成可勾选的任务队列文档很长时手工划题号容易漏。用 python-docx 把段落按题号切开导出成 JSON再逐条打勾。from docx import Document import re, json doc Document(C期末考试复习题.docx) # 匹配 1. 2、 3) (4) 这类题号开头的段落 pat re.compile(r^\s*[(]?(\d{1,3})[).、]\s*) items, cur [], None for p in doc.paragraphs: # paragraphs 不含表格单元格 t p.text.strip() if not t: continue if pat.match(t): if cur: items.append(cur) cur {no: int(pat.match(t).group(1)), text: t, done: False} elif cur is not None: cur[text] \n t # 题干换行后续行拼回去 if cur: items.append(cur) for tbl in doc.tables: # 表格里的题目单独取避免漏题 for row in tbl.rows: cells [c.text.strip() for c in row.cells] if cells and pat.match(cells[0]): items.append({no: int(pat.match(cells[0]).group(1)), text: | .join(cells), done: False}) items.sort(keylambda x: x[no]) json.dump(items, open(questions.json, w, encodingutf-8), ensure_asciiFalse, indent2) print(题量:, len(items))正则pat决定题号识别能力如果你的文档用「第 3 题」这种写法把正则改成r^\s*第\s*(\d)\s*题。遍历doc.paragraphs拿不到表格内容所以额外扫一遍doc.tables两边合并后按题号排序done字段留作后续勾选状态。题量打印出来和文档目录对一下数量对不上说明有题号格式不统一回去补正则而不是手工补题。2.4 编译期错误和运行期错误必须分开记改错题里最常见的陷阱是把两类错误混在一起。编译期错误有明确报错行号和英文提示运行期错误往往什么都不报或者干脆段错误。复习时给每道改错题标一个标签CE编译不过、RE运行崩溃、WA结果不对、UB能跑但行为未定义。第四类最坑比如delete之后再用指针、越界读一个元素考试时可能刚好「跑对了」一问原理就露馅。3. 用 VS Code 与 g 把每道复习题跑成可观察的程序纸上推演的结论只有跑起来才算数。这一章给一套最小可用流程单文件编译、VS Code 配置、gdb 单步、以及常见报错的处理。3.1 单文件编译g 和 cl 两条路复习题的每题单独存成一个文件比如q12_ptr.cpp编译命令固定成下面这行别省编译选项。g -stdc17 -Wall -Wextra -g -O0 -fsanitizeaddress,undefined q12_ptr.cpp -o q12 ./q12参数逐个说明-stdc17锁定语言标准避免不同机器行为不一致-Wall -Wextra打开警告很多「能跑但有隐患」的写法会在这里被点出来-g生成调试信息供 gdb 使用-O0关闭优化优化会让单步调试的变量值对不上源码-fsanitizeaddress,undefined在运行时捕获越界和未定义行为排错阶段必开交卷前关了再测一次看是否有警告被优化掉。Windows 上走 MSVC 的话命令是cl /std:c17 /EHsc /W4 /Zi /Od q12_ptr.cpp q12_ptr.exe/EHsc打开标准 C 异常模型/W4是高警告等级对应 g 的-Wall -Wextra。3.2 VS Code 里三份配置文件的写法与路径优先级在题目文件夹下建.vscode放三份文件。c_cpp_properties.json只影响智能提示和跳转不影响编译结果写错的表现是红线乱标、跳转跳到别的头文件。{ version: 4, configurations: [ { name: gcc-x64, compilerPath: C:/msys64/ucrt64/bin/g.exe, includePath: [ ${workspaceFolder}/**, C:/msys64/ucrt64/include/** ], cStandard: c17, cppStandard: c17, intelliSenseMode: windows-gcc-x64 } ] }路径优先级按includePath数组顺序从上往下找命中即停所以把自己写的头文件目录放在最前面系统目录放后面compilerPath一旦填对编译器自带的系统头路径会自动补上比手工在includePath里堆路径可靠得多。改完配置执行一次命令面板里的C/C: Reset IntelliSense Database否则旧缓存会继续生效。tasks.json负责编译当前文件{ version: 2.0.0, tasks: [ { label: build-active-file, type: shell, command: g, args: [ -stdc17, -Wall, -Wextra, -g, -O0, ${file}, -o, ${fileDirname}/${fileBasenameNoExtension}.exe ], group: { kind: build, isDefault: true }, problemMatcher: [$gcc] } ] }${file}是当前打开的文件${fileBasenameNoExtension}去掉扩展名做输出名所以一个文件夹里几十道复习题互不干扰按CtrlShiftB就编译当前那道。problemMatcher填$gcc后编译器报错会直接标在源码行上跳转到出错位置只要按 F8。调试用launch.json把 gdb 接上{ version: 0.2.0, configurations: [ { name: gdb-launch, type: cppdbg, request: launch, program: ${fileDirname}/${fileBasenameNoExtension}.exe, preLaunchTask: build-active-file, MIMode: gdb, miDebuggerPath: C:/msys64/ucrt64/bin/gdb.exe, externalConsole: true } ] }preLaunchTask指向tasks.json里的 label保证每次调试前先重新编译避免调试的是上一版二进制。3.3 一道指针题从手算到 gdb 核对复习题里出现*p之类表达式时光靠口诀很容易记反。写一个最小程序然后用 gdb 看每一步。#include iostream int main() { int a[4] {10, 20, 30, 40}; int *p a; // 数组名退化为首元素地址 std::cout *p \n; // 10 (*p); // 先解引用再自增a[0] 变 11 std::cout *p \n; // 11 int x *p; // 先取值 11再让 p 指向 a[1] std::cout x *p \n; // 11 20 std::cout sizeof(a) / sizeof(a[0]) \n; // 4 return 0; }gdb 里逐条验证g -stdc17 -g -O0 q_ptr.cpp -o q_ptr gdb -q ./q_ptr (gdb) break main (gdb) run (gdb) next (gdb) print a[0] (gdb) print p (gdb) print *p (gdb) x/4dw a # 以 4 字节十进制查看整个数组 (gdb) display *p (gdb) nextx/4dw a的意思是从地址a开始按 4 字节一组、以十进制显示 4 组。用它比对数组内容比一个个print a[i]快。display *p每次停下都自动打印*p观察指针移动值的变化过程一目了然。括号的位置决定结果(*p)改的是数组元素*p改的是指针本身这两者在复习题里几乎是必考项。3.4 复习题附带工程跑不起来时的几类报错现象常见原因处理undefined reference to foo()只有声明没有定义或多文件没一起编译把定义补齐或写成g a.cpp b.cpp运行时报缺少运行库程序依赖的运行时组件没装安装对应版本的 Microsoft Visual C Redistributable装 Python 包时报error: Microsoft Visual C 14.0 or greater is required缺 C 编译器工具链装 Visual Studio Build Tools勾选「使用 C 的桌面开发」expected ;指向前一行前一行少分号、括号没闭合往上一行找别看报错行中文输出乱码源文件编码与控制台代码页不一致统一保存为 UTF-8或改用英文输出那个14.0指的是编译器工具集版本号代表 VS 2015 这一代后续版本向下兼容装更新的版本同样满足要求。判断顺序是先看编译期报错再看运行期依赖最后才怀疑代码逻辑——很多人一上来就改代码结果白改半小时。4. 复习题里最容易丢分的五类题型与可抄模板这一章把五类高频题型的代码模板固定下来目标是看到题目能默写、写完能一次编译通过。4.1 指针与内存数组名退化与函数传参写法实参类型形参里sizeof的值void f(int a[], int n)数组指针大小8 或 4 字节void f(int *a, int n)数组或指针指针大小void f(int (a)[5])长度恰为 5 的数组20 字节templateint N void f(int (a)[N])任意长度数组4*N 字节#include iostream void byPointer(int *a, int n) { // 数组名退化为指针 std::cout sizeof(a) \n; // 864 位 } template int N void byRef(int (a)[N]) { // 真正的数组引用 std::cout sizeof(a) \n; // 4*N } int main() { int a[5] {1, 2, 3, 4, 5}; byPointer(a, 5); byRef(a); std::cout (a)[1] - a \n; // a 是数组指针1 跨 20 字节 return 0; }byPointer拿到的只是一个地址长度必须额外传参这是复习题里改错题的常见考点。byRef用模板推导数组长度sizeof在函数内仍然有效代价是每个长度生成一份实例。a的类型是「指向长度为 5 的 int 数组的指针」加一跨过整个数组(a)[1] - a结果是 5这个表达式在概念题里出现频率很高。函数指针做回调也常在复习题里冒头结构通常是「遍历函数 对每个元素执行的操作」void forEach(int *a, int n, void (*action)(int)) { for (int i 0; i n; i) action(a[i]); // 通过回调修改元素 } void twice(int v) { v * 2; } // 调用forEach(arr, n, twice);action的参数用引用而不是值回调才能改到原数组这是判断回调写得对不对的关键。4.2 字符串与字符数组初始化sizeof 和 strlen 必须分清char s1[] hello; // 6 字节5 个字符 \0 char s2[10] hi; // 10 字节后 8 个字节全补 0 const char *s3 hello; // 指向字面量不能改内容 char s4[5] hello; // 错误没有空间存放 \0s1的sizeof是 6strlen是 5如果换成char *p s1;sizeof(p)变成指针大小和strlen(p)无关。s4这种写法在新标准下直接编译不过能通过的老编译器会把\0挤掉后面凡是当字符串用的地方都可能越界读——这是复习题最爱设的陷阱。std::string转字符数组的两种方式#include string #include cstring #include vector std::string s a,b,c; const char *p s.c_str(); // 只读访问s 改动后 p 可能失效 std::vectorchar buf(s.begin(), s.end()); buf.push_back(\0); // 想要可写副本就自己补结束符s.c_str()返回的指针生命周期跟着s走s被修改或销毁后不能再用。需要可写副本时用vectorchar拷一份别直接const_cast去改字面量那是未定义行为。4.3 冒泡排序、二分查找与两个常用小算法模板冒泡排序背加法而不是背代码void bubbleSort(int a[], int n) { for (int i 0; i n - 1; i) { bool swapped false; // 本轮是否发生交换 for (int j 0; j n - 1 - i; j) { // 末尾 i 个已就位 if (a[j] a[j 1]) { int t a[j]; a[j] a[j 1]; a[j 1] t; swapped true; } } if (!swapped) break; // 已经有序提前退出 } }内层上界是n-1-i因为每完成一轮最大值就被推到末尾swapped让最好情况降到 O(n)。考试时写j n-1也能出正确结果只是多做无用比较不算错但会丢优化分。二分查找统一用闭区间写法不容易错int lowerBound(const int a[], int n, int target) { int lo 0, hi n - 1, ans -1; while (lo hi) { int mid lo (hi - lo) / 2; // 防 (lohi) 溢出 if (a[mid] target) { ans mid; hi mid - 1; } // 找左边界 else lo mid 1; } return ans; }四个必查点数组必须有序mid用减法算避免溢出区间开闭与循环条件必须配套闭区间配lo hi重复元素时返回的是哪一个要按题目要求定找左边界和找任意一个的写法不同。排序本身可以直接用标准库别忘引入头文件#include algorithm // std::sort 在这里 #include functional // std::greater std::sort(a, a n); // 升序 std::sort(a, a n, std::greaterint()); // 降序判断质数按试除到平方根写快速幂按二进制拆分写bool isPrime(long long n) { if (n 2) return false; if (n % 2 0) return n 2; // 2 是唯一的偶质数 for (long long d 3; d * d n; d 2) // 只试奇数到 sqrt(n) if (n % d 0) return false; return true; } long long qpow(long long a, long long e, long long mod) { long long r 1 % mod; // mod 为 1 时结果为 0 a % mod; while (e) { if (e 1) r r * a % mod; a a * a % mod; e 1; } return r; }d * d n写成d n / d可以进一步避免溢出1 % mod而不是直接写 1是为了处理模数为 1 的边界。这两个模板在填空题里大概率以「补全循环条件」的形式出现。4.4 结构体与链表new/delete 配对与销毁顺序struct Node { int val; Node *next; Node(int v) : val(v), next(nullptr) {} }; Node *pushFront(Node *head, int v) { // 头插返回新的头 Node *p new Node(v); p-next head; return p; } void destroy(Node *head) { while (head) { Node *nxt head-next; // 先存下一个节点 delete head; head nxt; // 再移动顺序不能颠倒 } }destroy里两行的顺序是最常见的改错点先delete再取head-next就是访问已释放内存。pushFront返回新头指针调用方必须接住返回值写pushFront(head, v);而不赋值链表就白改了。遍历和计数要区分空链表int length(Node *head) { int cnt 0; for (Node *p head; p ! nullptr; p p-next) cnt; return cnt; }循环条件写p ! nullptr而不是p-next ! nullptr后者遇到空链表会直接解引用空指针。链表题如果要求返回中间节点或倒数第 k 个用快慢指针别先求长度再走一遍。4.5 流 I/Ocin 与 getline 混用的那个换行符这是复习题里出错率最高的一处细节#include iostream #include iomanip #include string int main() { int n; std::string name; std::cin n; // 只读走数字\n 留在缓冲区 std::cin.ignore(1, \n); // 关键一行吃掉残留换行 std::getline(std::cin, name); // 否则这里拿到空串 std::cout name n \n; double pi 3.14159265; std::cout std::fixed std::setprecision(2) pi \n; // 3.14 std::cout std::setw(6) std::setfill(0) 42 \n; // 000042 return 0; }operator会跳过前导空白并在读到不匹配字符时停止不消费那个分隔符getline默认以\n结束并把它吃掉所以中间必须有一次ignore。格式控制里std::fixed和std::setprecision是持续生效的std::setw只影响紧接着的一个输出项这两者的作用范围差别是概念题的常客。读整行含空格的字符串时统一用getline读单个单词用混用时永远记得补ignore。5. 进阶用自动对拍脚本和错题本把复习效率拉满到这一步题目都会做了剩下的是稳定性和速度。手工一题一题编译运行五十道题要花掉两小时而且容易漏。把程序题统一命名成q01.cpp、q02.cpp输入输出放在cases/下用脚本批量跑。#!/usr/bin/env bash # run_all.sh批量编译复习题里的程序题并与期望输出对拍 set -u mkdir -p out pass0; fail0 for src in q*.cpp; do name${src%.cpp} if ! g -stdc17 -Wall -Wextra -g -O0 $src -o out/$name 2 out/$name.build.log; then echo [BUILD-FAIL] $src; fail$((fail 1)); continue fi if [ -f cases/$name.in ]; then timeout 3 out/$name cases/$name.in out/$name.actual 21 else timeout 3 out/$name out/$name.actual 21 fi if [ -f cases/$name.expected ] ! diff -q cases/$name.expected out/$name.actual /dev/null; then echo [WA] $src diff -u cases/$name.expected out/$name.actual | head -20 fail$((fail 1)) else echo [OK] $src; pass$((pass 1)) fi done echo pass$pass fail$fail几个参数的意义set -u让引用未定义变量时直接报错退出避免路径写错却静默跑完timeout 3给每道题三秒上限链表或二分题一旦写出死循环不会把整个脚本挂住21把标准错误合并进实际输出段错误和未定义行为触发的 sanitizer 提示都会落到.actual文件里与期望输出一比对就暴露出来diff -u加head -20只显示前 20 行差异够定位又不刷屏。期望输出用记事本存成q07.expected注意行末不要留多余空格。错题本用一张表维护字段固定下来日期题号类型错因一句话修正要点复做轮次第 1 天q07WA二分边界写成开区间lo hi配闭区间1/3/7第 1 天q15RE销毁链表先 delete 后取 next先存 next1/3/7第 3 天q22UBc_str 指针在 string 改动后继续用改用 vector 副本7复做轮次按 1、3、7 天间隔安排第二遍只做第一遍错的题第三遍只做第二遍还错的题五轮下来剩下的通常不超过三道。每道错题处理完把diff -u输出的那几行差异原文粘进错因栏比写「粗心」有用得多因为下次翻到这张表时你能立刻想起当时到底错在哪一行、错在哪个符号上。本文还有配套的精品资源点击获取
返回列表