C++期末考试复习题高效刷题:考点拆解、编译验证与模板默写
2026/9/17 18:33:20 网站建设 项目流程

简介: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++*(p+2)p = a打印地址和值,看移动步长
全局变量 / 局部变量同名变量在两个作用域内加块作用域{}后重新观察
字符串数组初始化char s[]="abc"char s[3]="abc"sizeofstrlen对比
字符串转数组std::stringchar[]混用s.c_str()s.data()
结构体链表Node *nextnew Node打印每个节点的地址
流 I/Ocin >>getline连用输入带空格的名字
冒泡排序双层循环、交换相邻元素逆序数组跑一遍看比较次数
二分查找有序数组、midlo/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(key=lambda x: x["no"]) json.dump(items, open("questions.json", "w", encoding="utf-8"), ensure_ascii=False, indent=2) 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++ -std=c++17 -Wall -Wextra -g -O0 -fsanitize=address,undefined q12_ptr.cpp -o q12 ./q12

参数逐个说明:-std=c++17锁定语言标准,避免不同机器行为不一致;-Wall -Wextra打开警告,很多「能跑但有隐患」的写法会在这里被点出来;-g生成调试信息,供 gdb 使用;-O0关闭优化,优化会让单步调试的变量值对不上源码;-fsanitize=address,undefined在运行时捕获越界和未定义行为,排错阶段必开,交卷前关了再测一次看是否有警告被优化掉。

Windows 上走 MSVC 的话,命令是:

cl /std:c++17 /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": "c++17", "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": [ "-std=c++17", "-Wall", "-Wextra", "-g", "-O0", "${file}", "-o", "${fileDirname}/${fileBasenameNoExtension}.exe" ], "group": { "kind": "build", "isDefault": true }, "problemMatcher": ["$gcc"] } ] }

${file}是当前打开的文件,${fileBasenameNoExtension}去掉扩展名做输出名,所以一个文件夹里几十道复习题互不干扰,按Ctrl+Shift+B就编译当前那道。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++ -std=c++17 -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) next

x/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 字节
template<int N> void f(int (&a)[N])任意长度数组4*N 字节
#include <iostream> void byPointer(int *a, int n) { // 数组名退化为指针 std::cout << sizeof(a) << "\n"; // 8(64 位) } 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"; // 错误:没有空间存放 '\0'

s1sizeof是 6,strlen是 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::vector<char> buf(s.begin(), s.end()); buf.push_back('\0'); // 想要可写副本就自己补结束符

s.c_str()返回的指针生命周期跟着s走,s被修改或销毁后不能再用。需要可写副本时用vector<char>拷一份,别直接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; // 防 (lo+hi) 溢出 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::greater<int>()); // 降序

判断质数按试除到平方根写,快速幂按二进制拆分写:

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/O:cin 与 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::fixedstd::setprecision是持续生效的,std::setw只影响紧接着的一个输出项,这两者的作用范围差别是概念题的常客。读整行含空格的字符串时统一用getline,读单个单词用>>,混用时永远记得补ignore

5. 进阶:用自动对拍脚本和错题本把复习效率拉满

到这一步,题目都会做了,剩下的是稳定性和速度。手工一题一题编译运行,五十道题要花掉两小时,而且容易漏。把程序题统一命名成q01.cppq02.cpp,输入输出放在cases/下,用脚本批量跑。

#!/usr/bin/env bash # run_all.sh:批量编译复习题里的程序题,并与期望输出对拍 set -u mkdir -p out pass=0; fail=0 for src in q*.cpp; do name="${src%.cpp}" if ! g++ -std=c++17 -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" 2>&1 else timeout 3 "out/$name" > "out/$name.actual" 2>&1 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给每道题三秒上限,链表或二分题一旦写出死循环不会把整个脚本挂住;2>&1把标准错误合并进实际输出,段错误和未定义行为触发的 sanitizer 提示都会落到.actual文件里,与期望输出一比对就暴露出来;diff -uhead -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输出的那几行差异原文粘进错因栏,比写「粗心」有用得多,因为下次翻到这张表时你能立刻想起当时到底错在哪一行、错在哪个符号上。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询