简介:这是一份面向高校计算机专业学生的C++语言程序设计期末考试复习资料,以试题与参考答案的形式,覆盖类与对象、引用、抽象类、函数重载、常对象与常成员函数、递归调用、拷贝构造函数、继承与多态、动态内存管理、作用域解析运算符及访问控制等核心考点,适合作为期末冲刺自测与知识点查漏补缺使用。资源包共1个pdf文件,压缩包约58KB,体积轻便,便于随时打开阅读或打印练习。试题包含填空与程序阅读两类题型,每道程序题都给出完整代码与预期运行结果,并附有错误原因分析,例如浅拷贝导致同一块内存被重复释放、派生类多层继承下成员函数的调用顺序与输出推导等,能帮助读者理解易错细节。目前已有127人学习。整体内容可作为考前集中梳理语法与面向对象机制的参考答案,也可用于课堂练习与教师命题参考。
1. 一份「C++语言程序设计期末考试试题及答案」,正确的用法是反着推
考前一晚把试题答案从头翻到尾,第二天进考场仍然写不对 for 循环里分号的位置,这个场景在 C++语言程序设计期末里反复上演。一份带答案的期末试卷,价值不在背,而在反推评分点:选择题考语法边界,填空题考关键字和运算符优先级,程序阅读题考指针与内存的手工推演,编程题考类设计与边界处理。拿到卷子先别急着看答案,按题型切块,统计每章出现频次,再用编译器把答案代码逐条跑一遍,看实际输出和卷面是否吻合,不吻合的地方就是命题人埋的坑。这套反推法适合准备期末与补考的人,也适合专升本、实习笔试前热身的人,对带课讲评的助教同样省事。手上没有现成卷子也没关系,按下面几个考点模块自己拼一份,效果接近。
2. C++期末高频考点拆解:指针、数组与字符串的内存题怎么答
期末卷子里拉开差距的地方几乎都在内存模型上。选择题、填空题、程序阅读题三类题型共用同一批知识点,但考法不同:选择题给你一个表达式问结果,填空题让你补一个关键字,程序阅读题给你一整段代码问输出。三种考法背后是同一套推演规则,把这套规则固化下来,比刷一百道同质题有用。我一般按「表达式优先级 → 数组退化 → 字符串存储区」的顺序推进,因为这三层错误会层层叠加,前一层错后面全错。
2.1 选择题和填空题里,C++指针用法最常设的三个陷阱
指针运算符里,*和++的优先级关系是命题人的首选素材。*p++等价于*(p++),先取当前值再把指针往后挪一个元素;(*p)++是值自增,指针不动;*++p是先挪指针再取值。这三个写法的结果完全不同,但在卷面上只差一对括号,非常容易看走眼。
第二类陷阱是数组名退化。在绝大多数表达式里,数组名会退化成指向首元素的指针,sizeof(数组名)却是唯一不退化的场合,返回整个数组的字节数。int a[10]的sizeof(a)是 40,把它传进函数之后sizeof(a)变成 8,这个差值是必考项。
第三类陷阱是 const 的位置。const char *p是「指向常量的指针」,内容不能改,指针可以改;char * const p是「常量指针」,指针不能改,内容可以改;const char * const p两个都不能改。读法是从右往左念,谁离 const 近谁被锁死。
| 表达式 | 等价写法 | 实际结果 |
|---|---|---|
*p++ | *(p++) | 取当前指向的值,指针后移一个单位 |
(*p)++ | — | 当前值加一,指针位置不变 |
*++p | *(++p) | 指针先后移,再取新位置的值 |
++*p | ++(*p) | 当前值加一,等价于(*p)++的前缀形式 |
sizeof(a) | — | 数组处为总字节数,函数形参处为指针宽度 |
把这张表默写下来再进考场,前三道选择题基本不会丢分。
2.2 数组与 C++字符串数组初始化:一张参数表锁定易错点
字符串题的分歧点在于「存在哪儿」。char a[] = "hello";是栈上的字符数组,可读可写,长度是 6 个字节(末尾有'\0');const char *p = "hello";的指针本身在栈上,字符串字面量在只读段,任何写入都是未定义行为,编译器不一定报错,运行时可能直接段错误。
#include <cstring> #include <cstdio> int main() { char a[] = "hello"; // 栈上数组,6 字节,含结尾 '\0' const char *p = "hello"; // 指针 8 字节,指向只读字面量 printf("%zu %zu\n", sizeof(a), strlen(a)); // 6 5 printf("%zu %zu\n", sizeof(p), strlen(p)); // 8 5 a[0] = 'H'; // 合法,数组可写 // p[0] = 'H'; // 未定义行为,注释掉才是正确姿势 return 0; }把这段存成str_test.cpp,用g++ -std=c++17 -Wall -o str_test str_test.cpp && ./str_test跑一遍。参数说明:-std=c++17指定语言标准,期末机试环境通常不低于 C++11,写 C++17 比较稳;-Wall打开常用警告,能提前发现printf格式串与实参不匹配;-o指定可执行文件名,末尾的&& ./str_test是编译成功后立即运行。输出里sizeof(a)是 6、sizeof(p)是 8,这两个数字记住,填空题出现「sizeof与strlen之差」时立刻能答。
还有一类是初始化题:int a[5] = {1};只填首元素,其余补 0;int a[5] = {};全 0;char s[3] = "abc";会因放不下结尾'\0'而编译报错。这几种写法在卷面上长得几乎一样,判分点全在细节。
2.3 程序阅读题的手工推演:把答案代码丢给 g++ 验证
程序阅读题最忌讳「看着像对就写」。正确做法是拿一张纸,画出变量和内存格,每执行一行划掉旧值写上新值,快到卷面答案时再上机核对。
#include <iostream> using namespace std; int main() { int a[5] = {1, 2, 3, 4, 5}; int *p = a; // 数组名退化为首元素地址 cout << *p++ << endl; // 输出 1,p 指向 a[1] cout << (*p)++ << endl; // 输出 2,a[1] 被改成 3 cout << *p << endl; // 输出 3 cout << a[1] << endl; // 输出 3,与上一行同一块内存 return 0; }四行输出分别是 1、2、3、3。第三行和第四行指向同一个地址,这是命题人最爱的「同一内存两个名字」考法。上机核对时如果结果和卷面答案不一致,先检查是不是把*p++看成了(*p)++,这类笔误占错题的六成以上。想再狠一点,编译时加-fsanitize=address,任何越界访问都会被单独报出来,配合-g还能给出出错行号,用来验证自己手工推演里「这个下标是不是已经越界」的怀疑非常直接。
3. 面向对象题型:类、继承、虚函数与运算符重载的答题模板
面向对象部分的分值通常占卷面的三到四成,题型以代码补全和简答为主。这部分丢分往往不是不会,而是写得不完整:构造函数漏初始化列表、虚函数忘了virtual、运算符重载返回值类型写错。期末阅卷按点给分,把几个固定模板背熟,比临场推导快得多。
3.1 构造函数与析构顺序:答案里最容易丢分的一行
一个派生类对象构造时,执行顺序固定为:基类构造函数 → 成员对象构造函数(按声明顺序,不是按初始化列表书写顺序)→ 派生类自身构造函数体。析构顺序完全相反。这里有两个坑:初始化列表里把b(a)写在a(x)前面,实际仍按成员声明顺序执行a再执行b,如果b依赖a就会拿到未初始化值;另一个是基类析构函数没写virtual,通过基类指针 delete 派生类对象时只调基类析构,派生类里申请的资源泄漏。
#include <iostream> using namespace std; class Base { public: Base() { cout << "Base ctor" << endl; } virtual ~Base() { cout << "Base dtor" << endl; } // 必须 virtual }; class Member { public: Member() { cout << "Member ctor" << endl; } ~Member() { cout << "Member dtor" << endl; } }; class Derived : public Base { Member m; // 声明顺序决定构造顺序 public: Derived() { cout << "Derived ctor" << endl; } ~Derived() { cout << "Derived dtor" << endl; } }; int main() { Base *p = new Derived(); // 构造:Base -> Member -> Derived delete p; // 析构:Derived -> Member -> Base return 0; }输出顺序是构造由上到下、析构由下到上。答题时如果要求写「构造和析构的调用次序」,把这两行完整写出来就能拿满。参数说明:virtual ~Base()里的virtual是这一题的核心判分点,缺了它delete p只输出一行 Base dtor,派生类的成员析构不会执行。
3.2 虚函数与多态的笔试题,先用一张表分清静态绑定和动态绑定
判断一个调用走静态绑定还是动态绑定,看两个条件是否同时成立:通过指针或引用调用、被调函数是虚函数。两者都满足才是运行时决定,其余情况编译期就定死了,派生类对象被切片赋值给基类对象时尤其容易踩坑。
| 调用形式 | 函数是否 virtual | 绑定方式 | 实际执行版本 |
|---|---|---|---|
| 基类指针 → 函数 | 是 | 动态绑定 | 实际对象类型对应的版本 |
| 基类指针 → 函数 | 否 | 静态绑定 | 基类版本 |
| 派生类对象.函数 | 是 | 静态绑定 | 派生类版本(对象类型已知) |
| 基类对象 = 派生类对象(切片) | 是 | 静态绑定 | 基类版本,派生部分被切掉 |
| 构造函数/析构函数内调用虚函数 | 是 | 静态绑定 | 当前正在构造的类版本 |
最后一行是简答题常客:构造函数里调虚函数不会触发多态,因为此时虚表指针还没指向派生类。答题时把这个原因写清楚,比只写结论多拿一到两分。
3.3 运算符重载与友元的代码补全模板
运算符重载题的固定套路是:能改成成员函数的写成成员函数,左操作数不是本类对象(比如cout << obj)或需要访问私有成员的对称运算,写成友元。
#include <iostream> using namespace std; class Complex { double re, im; public: Complex(double r = 0, double i = 0) : re(r), im(i) {} Complex operator+(const Complex &o) const { // 成员形式,返回值不是引用 return Complex(re + o.re, im + o.im); } friend Complex operator-(const Complex &a, const Complex &b); // 对称运算用友元 friend ostream& operator<<(ostream &os, const Complex &c); // 左操作数是 ostream }; Complex operator-(const Complex &a, const Complex &b) { return Complex(a.re - b.re, a.im - b.im); } ostream& operator<<(ostream &os, const Complex &c) { os << "(" << c.re << "," << c.im << ")"; return os; // 必须返回 os 以支持链式调用 } int main() { Complex x(1, 2), y(3, 4); cout << x + y << " " << x - y << endl; // (4,6) (-2,-2) return 0; }三个判分点:operator+返回新对象而非引用,不能返回局部变量的引用;operator<<必须返回ostream&,否则cout << a << b编译不过;友元声明要在类内、定义可以在类外,期末常考「声明与定义分离」。写成Complex& operator+再返回局部对象,是阅卷时最常见的扣分点。
3.4 模板函数与 STL 容器题:手写冒泡排序算法c++与 std::sort 的差别
卷面上「用冒泡排序算法对数组排序」和「用 STL 排序」是两道不同的题,前者要手写双重循环,后者一行sort解决,但必须写对头文件和迭代器区间。
#include <iostream> #include <vector> #include <algorithm> // sort / binary_search 都在这里 using namespace std; void bubbleSort(vector<int> &v) { int n = v.size(); for (int i = 0; i < n - 1; ++i) { bool swapped = false; // 提前退出标记 for (int j = 0; j < n - 1 - i; ++j) if (v[j] > v[j + 1]) { swap(v[j], v[j + 1]); swapped = true; } if (!swapped) break; // 已有序时退化为 O(n) } } int main() { vector<int> v{5, 2, 9, 1, 7}; bubbleSort(v); for (int x : v) cout << x << " "; // 1 2 5 7 9 cout << endl; sort(v.begin(), v.end(), greater<int>()); // 降序,需 <functional> cout << boolalpha << binary_search(v.begin(), v.end(), 7, greater<int>()) << endl; return 0; }参数说明:bubbleSort用引用传参避免拷贝,swapped标记是优化点,写了能说明「最好情况 O(n)」;sort的第三个参数是自定义比较器,改成greater<int>()就是降序;binary_search要求区间已按同一规则有序,第三个参数之后传的比较器必须和排序时一致,否则结果不可信。二分查找题如果要求手写,注意mid = lo + (hi - lo) / 2这种写法能避免lo + hi溢出,是加分细节。
链表题也是同类,结构体定义、next指针、头插尾插三种操作的写法每个期末都出现,答题时把struct Node { int data; Node *next; };和插入时的指针赋值顺序写清楚,顺序写反会断链,这是判分重点。
4. 让答案代码真正跑起来:vscode 配置 c/c++ 环境与编译报错排查
答案看得再熟,不跑一遍心里没底。期末阶段的验证环境有两条路:命令行 g++ 最快,vscode 适合反复改代码。两条路都踩过一遍,考场上遇到「代码能编译但输出不对」的情况才有排查方向。
4.1 vscode 配置 c/c++ 环境的最小步骤
装好 MinGW-w64 或 Linux 下的 g++ 之后,先确认g++ --version能打印版本。然后建一个练习目录,在 vscode 里打开,需要动两个文件:.vscode/tasks.json管编译,.vscode/c_cpp_properties.json管智能提示。
{ "version": "2.0.0", "tasks": [ { "label": "g++ build active file", "type": "shell", "command": "g++", "args": ["-std=c++17", "-Wall", "-g", "${file}", "-o", "${fileDirname}/${fileBasenameNoExtension}"], "group": { "kind": "build", "isDefault": true } } ] }参数说明:${file}是当前打开的源文件绝对路径,${fileDirname}是它所在目录,${fileBasenameNoExtension}是不带后缀的文件名,三者拼起来就是「在源文件旁边生成同名可执行文件」。-g加上调试信息,配isDefault: true之后按 Ctrl+Shift+B 直接编译。
c_cpp_properties.json里真正影响体验的是includePath和compilerPath。智能提示路径优先级是:当前工作区的includePath在前,编译器内置头文件目录在后,系统环境变量里的路径最后兜底。出现红波浪线但能编译通过,基本是includePath没把标准库目录加进去,把compilerPath指到g++.exe或/usr/bin/g++上,让插件自己去推导,比手写一长串目录可靠。
4.2 g++ 命令行替代方案与常用参数表
不想折腾编辑器就用命令行,一步到位。下面这张表里的参数在期末验证阶段出现频率最高。
| 参数 | 作用 | 什么时候加 |
|---|---|---|
-std=c++17 | 指定语言标准 | 用到结构化绑定、if初始化语句时必加 |
-Wall -Wextra | 打开常用和额外警告 | 检查未使用变量、格式串不匹配 |
-g | 生成调试信息 | 要用 gdb 单步时必须加 |
-O2 | 开启优化 | 只影响性能,不影响语法题验证 |
-fsanitize=address | 运行期检测越界与悬垂指针 | 验证指针越界的手工推演结论 |
-o out | 指定输出文件名 | 不加会默认生成a.out |
典型命令是g++ -std=c++17 -Wall -g -fsanitize=address -o main main.cpp && ./main。如果编译报「undefined reference tostd::cout」,多半是用了 gcc 而不是 g++ 去链接 C++ 程序,换成 g++ 即可。
4.3 Microsoft Visual C++ Redistributable 缺失与 MSVC 14.0 报错的处理思路
在 Windows 上跑别人给的.exe,报「找不到 VCRUNTIME140.dll」或者0xc000007b,说明目标机器没装对应的运行库,装上 Microsoft Visual C++ Redistributable 的 x64 版本一般能解决。另一类报错出现在装 Python 扩展时:「Microsoft Visual C++ 14.0 is required」,这不是运行库缺失,而是编译扩展需要 MSVC 的构建工具链,装「使用 C++ 的桌面开发」工作负载即可,跟期末写 C++ 没有直接关系,别把两者混为一谈。
真正和期末相关的是标准差异:MSVC 对某些 GCC 能通过的写法更严格,比如变长数组int a[n]在 MSVC 下直接报错,strcpy会被提示不安全并建议换成strcpy_s。考场上如果机试环境是 Visual Studio,写数组一定用常量长度,字符串操作优先用std::string,能绕开一大半平台差异。
5. 进阶:用错题分布反推复习优先级,编程题的过程分怎么写
把整份试题刷完之后,别急着对完答案就翻篇。把错题按知识点打标签,统计每个标签的出现次数和错题数,得到一张自己的失分表。
| 知识点标签 | 卷面出现次数 | 我的错题数 | 优先级 |
|---|---|---|---|
| 指针与运算符优先级 | 6 | 4 | 高 |
| 构造析构顺序 | 3 | 2 | 高 |
| 虚函数绑定 | 4 | 1 | 中 |
| 字符串与字符数组 | 5 | 1 | 中 |
| 模板与 STL | 3 | 0 | 低 |
错题数乘以出现次数就是投入产出比,优先补前两行。补法不是重看答案,而是把原题的条件改一改自己重做:把*p++改成++*p,把基类析构去掉virtual,把数组长度从 5 改成 3。改完立刻上机跑,输出和推理不一致的地方就是没掌握的地方。
编程题的过程分有固定套路。阅卷时按「能编译」「能运行」「逻辑正确」「边界正确」四层给分,所以第一原则是先写出能编译的骨架再填逻辑:把#include、using namespace std;、main函数和函数签名一次写全,空实现也比半截代码强,编译不过整题归零。第二原则是边界显式处理:数组长度为 0、查找目标不存在、除以零、字符串为空这四种情况各写一行判断,比任何注释都值钱。第三原则是变量命名别偷懒,i、j之外的关键变量写全名,卷面可读性直接影响阅卷老师给分松紧。
最后留一个自查清单:结构体指针操作里p->next = q之后有没有把q的next接回去;动态申请的内存有没有配对的delete;for循环边界用<还是<=与你画的内存格是否一致。把这三条在交卷前过一遍,能拦下大部分低级失分。
本文还有配套的精品资源,点击获取