简介:这是一份东北林业大学《C++程序设计》课程期末考试试卷,适合正在系统复习C++语法、面向对象编程及STL用法的本科生和自学人群。试卷考核范围覆盖基础语法、函数与递归、指针与动态内存、数组与字符串、结构体与联合体、类与对象、继承与多态、模板、STL容器与算法、异常处理以及文件I/O等核心章节,可用于模拟自测、考前冲刺和查漏补缺。资源为单个PDF文件,整体压缩包仅160KB,清晰易读,便于电脑、平板或手机随时翻阅。该试卷已吸引超过1000人学习下载,说明内容贴近许多高校C++课程的常见考点。借助这套试卷,读者既能熟悉期末考试题型与命题侧重,也能对照题目检验自己对指针、虚函数、容器等易错难点的掌握程度,并针对薄弱环节进行专项强化,是高性价比的期末复习资料。
1. 一份 C++ 期末考试试卷,能把“会敲代码”和“真会”分得很清楚
每到期末,总有人到处找「东北林业大学《C++程序设计》期末考试试卷.pdf」这类资料。真拿到手多半会失望:试卷没几页,题目看着都眼熟,可合上 IDE 手写答案,立刻有人露怯——考场上没人替你指出少了个分号。
程序设计基础课的期末卷,命题范围相当固定:语法细节、指针数组、面向对象、流 I/O 与文件读写,再加两三道经典算法手写题。难点不在深度,在密度,一次构造析构顺序判断错、一个 const 漏写,就是三五分的差距。
下面这份复习路线适合正在备考 C++ 期末的本科生,也适合带课助教用来划重点。先拆题型分布,再过高频考点,最后给一套不依赖编译器的走查法,按这个路子练,比多刷十份试卷更划算。
2. 从题型分布推演 C++ 程序设计期末卷:阅读程序与手写代码的复习权重
把试卷当“知识点清单”复习是低效的,按题型复习才贴近真实得分结构。多所理工科院校程序设计基础课的期末卷题型构成高度一致,分值有浮动但骨架稳定,先看这张分布表。
2.1 选择题与填空:指针用法和默认参数是命题密度最高的语法点
| 题型 | 常见分值 | 考查重心 | 复习优先级 |
|---|---|---|---|
| 选择题 | 20~30 | 语法细节、指针用法、运算符优先级 | 高 |
| 填空题 | 10~20 | 输出结果、参数传递、类成员访问 | 高 |
| 阅读程序写结果 | 15~25 | 构造析构顺序、重载解析、静态成员 | 极高 |
| 程序设计题 | 20~35 | 排序查找、字符串处理、文件读写 | 极高 |
选择题最爱考一组“近亲”:*p++与(*p)++的区别、const int *p与int *const p的区别、函数重载的匹配顺序、默认参数必须从右往左连续给出。这些都能背成结论,属于典型的 C++ 八股,不背熟就会在考场上犹豫三五分钟。
#include <iostream> int main() { int a[5] = {1, 2, 3, 4, 5}; int *p = a; std::cout << *p++ << " "; // 先取 *p,p 再后移 std::cout << (*p)++ << " "; // 先取 *p,元素再自增 std::cout << *++p << std::endl; return 0; }输出是1 2 3。逻辑拆开看:*p++等价于*(p++),后缀自增优先级高于解引用,先取值 1,p 再移到 a[1];(*p)++先取值 2,随后 a[1] 变成 3;*++p先把 p 移到 a[2],再取值 3。试卷换个包装就难倒一批人:把cout换成printf,把数组换成字符串,本质还是同一套优先级问题。
填空题的另一半命题点在sizeof与strlen、sizeof(a)与sizeof(p)。数组名作实参传入函数后就地退化为指针,sizeof的语义随之改变,这是“数组与指针联动”考法里最经典的一题。
2.2 阅读程序写结果:构造析构顺序和虚析构是拉开差距的题眼
这类题不给编译机会,只能手推,最能暴露“凭感觉写代码”的问题。典型出题方式是把全局对象、局部对象、静态对象混在同一个程序里,考构造与析构的先后。
#include <iostream> class Demo { public: Demo(char c) : tag(c) { std::cout << tag; } ~Demo() { std::cout << static_cast<char>(tag + 32); } private: char tag; }; Demo g('A'); int main() { Demo d('B'); static Demo s('C'); return 0; }构造顺序是固定的:程序装载期先构造全局对象 g,输出 A;进入 main 后按声明顺序构造局部对象 d 和静态对象 s,输出 B、C。析构顺序与构造完全相反:main 返回先析构局部对象 d,输出 b;静态对象 s 在 main 全部结束后析构,输出 c;全局对象 g 最后析构,输出 a。完整输出是ABCbca。
注意:很多同学误以为静态对象和全局对象同时析构,实际顺序是“局部先走、静态次之、全局殿后”,同类内部按声明逆序。派生类参与时,构造顺序是“基类→成员→自身”,析构完全反转。
这个考点通常和虚析构绑定出现。基类析构函数缺virtual时,通过Base *p = new Derived; delete p;只调用~Base(),派生类析构被跳过。重载解析的考点则集中在匹配优先级:实参类型精确匹配优先于标准转换,标准转换优先于用户定义转换,建议两处一起复习。
2.3 手写代码题的题库范围:冒泡排序、二分查找与判断质数的考场版本
程序设计题不会超纲到 STL 全套,但“排序、查找、数论入门”三件套出现概率极高。手写不需要奇技淫巧,把边界条件写干净就是高分。
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) { 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);交换用临时变量手写,避免部分判卷环境不认std::swap。很多教材不写提前退出,加上反而体现对冒泡排序算法 C++ 版本复杂度的理解。
二分查找的满分写法则盯着边界看:
int binarySearch(int a[], int n, int key) { int lo = 0, hi = n - 1; while (lo <= hi) { int mid = lo + (hi - lo) / 2; if (a[mid] == key) return mid; if (a[mid] < key) lo = mid + 1; else hi = mid - 1; } return -1; }两个必记点:mid = lo + (hi - lo) / 2避免lo + hi溢出 int;循环用lo <= hi,区间真正空出来才退出,能覆盖单元素数组和查找失败。把<=写成<是最常见的手误,走查数组[1]找1立刻露馅。
判断质数的优化版本,核心是“试到 √n,并且跳过偶数”:
bool isPrime(int n) { if (n < 2) return false; if (n == 2) return true; if (n % 2 == 0) return false; for (int i = 3; 1LL * i * i <= n; i += 2) { if (n % i == 0) return false; } return true; }1LL * i * i把乘法提升到 long long,防止 i 接近 int 上限时溢出;先排除 2 和偶数,循环从 3 开始步进 2,遍历次数减半。if (n < 2) return false;是对 0 和 1 的兜底,漏掉它判断题立刻翻车。
提示:手写代码题在函数开头注释里写明前置条件,例如“a 已按非递减排序”,阅卷时这部分计入完整性,也帮自己稳住思路。
3. 指针、数组与字符串:C++ 期末卷里最容易翻车的三个连续考点
指针在试卷里的存在感最强,选择、填空、阅读程序、手写代码都会掺一脚。难的不是语法本身,而是同一段代码在不同上下文里含义不同。复习时按声明、传参、返回三组对照来记,效率最高。
3.1 指针用法对照表:4 种 const 组合与传参退化的记忆方法
| 写法 | 含义 | 高频雷区 |
|---|---|---|
const int *p; | 指向 const 的指针,*p不可改 | 误记成指针本身不可改 |
int *const p; | 常量指针,p 不可改 | 误以为指向内容不可改 |
int *p[3]; | 指针数组:3 个 int* | 与int (*p)[3]混淆 |
int (*p)[3]; | 指向含 3 个 int 数组的指针 | 二维数组传参时才用 |
记忆口诀:const 在*左边,锁住内容;const 在*右边,锁住指针本身。下面这段是最高频的辨析题素材:
int x = 10, y = 20; const int *p1 = &x; // 可改 p1,不可改 *p1 p1 = &y; // 合法 // *p1 = 30; // 编译错误 int *const p2 = &x; // 可改 *p2,不可改 p2 *p2 = 30; // 合法 // p2 = &y; // 编译错误传参退化是另一张必考面孔。void f(int a[], int n)、void f(int *a, int n)在函数签名层面是同一个东西,数组形参就是指针形参,所以长度 n 必须一并传入;函数内部对a做sizeof得到的是指针大小。返回值的雷区更简单:永远不要返回指向局部变量的指针,局部变量随函数调用结束而销毁,悬空指针是判断改错题的高频素材。
3.2 字符串数组初始化的 4 种写法与 strlen 陷阱
字符串考题集中在初始化写法和长度计算两处。同样存 hello,四种写法结局完全不同:
char s1[] = "hello"; // 6 字节,末尾有 '\0' char s2[6] = "hello"; // 正好放下,含 '\0' char s3[] = {'h', 'e', 'l', 'l', 'o'}; // 5 字节,没有 '\0' char *s4 = "hello"; // 字符串字面量,只读s1的strlen(s1)是 5,sizeof(s1)是 6;s3缺'\0',任何把它当 C 风格字符串输出的操作都会越界读到未知内存,直到碰上一个 0 字节才停,这是“看起来正常实则越界”的典型。s4指向只读区,s4[0] = 'H'是未定义行为。
字符串和数组互转是程序设计题的热门前置步骤。C 风格数组转std::string直接用构造函数;反向拿可写缓冲区,用strcpy:
#include <cstring> #include <string> char buf[64]; strcpy(buf, s1); // 数组 ← C 风格字符串,目标要够大 std::string str(s1); // 数组 → std::string const char *p = str.c_str(); // string → const char*,只读视角c_str()返回的指针在 string 对象后续被修改后可能失效,字符串转数组时最好先复制到本地缓冲区再用。这个细节在填空题里常被包装成“请写出不产生悬垂指针的写法”。
3.3 用 vscode 配置 C/C++ 环境做离线刷题,把“我以为”变成“编译器告诉我”
复习指针和字符串最有效的方式,是把选择题改写成小程序当场验证。本地装一套轻量 C/C++ 环境最实用:Windows 装 MinGW-w64,macOS 装 Xcode Command Line Tools,Linux 直接装 g++。配合 vscode 的 C/C++ 扩展,就有带智能提示的调试体验。
g++ -std=c++17 -Wall -Wextra -o main main.cpp && ./main-Wall -Wextra打开常规警告,定义未使用、符号比较、隐式转换这类考试代码里的常见毛病都会被点名;-o main指定输出文件名;&&保证编译通过才执行。验证字符串陷阱时,一行printf("%zu %zu\n", strlen(s3), sizeof(s3));就能把书上的道理变成亲眼所见。
如果 vscode 的智能提示和实际编译结果对不上,多半是标准版本没对齐。在.vscode/c_cpp_properties.json里显式指定:
{ "configurations": [ { "name": "exam-env", "compilerPath": "C:/mingw64/bin/g++.exe", "cppStandard": "c++17", "intelliSenseMode": "windows-gcc-x64" } ], "version": 4 }compilerPath要与你实际装的编译器一致,intelliSenseMode里的windows-gcc-x64对应 MinGW,换成 MSVC 或 clang 都要改字段。这样刷题只需要 g++ 就能编译运行,不必为平时练习安装整套 Microsoft Visual C++ 构建工具,省掉一堆环境变量问题。
4. 面向对象与流 I/O:C++ 程序设计期末卷里分值最密的两块
面向对象和流 I/O 是试卷后半部分的主场,阅读程序题和文件读写题几乎都从这里出。这两块的特点是规则少而严,背准规则就能稳定得分。
4.1 继承体系下的构造析构顺序与虚析构的必要性
写出下面程序的结果,是这类题的标配:
#include <iostream> class Base { public: Base() { std::cout << "Base() "; } virtual ~Base() { std::cout << "~Base() "; } }; class Derived : public Base { public: Derived() { std::cout << "Derived() "; } ~Derived() override { std::cout << "~Derived() "; } }; int main() { Base *p = new Derived; delete p; return 0; }输出是Base() Derived() ~Derived() ~Base()。构造时基类先执行,派生类后执行;析构时派生类先执行,基类最后执行,与构造严格逆序。真正的考点在virtual:去掉它,delete p按指针静态类型 Base 调用析构,派生类析构函数根本不会执行。试卷常把这个改动包装成“程序输出是什么”或“存在什么隐患”。
注意:凡是可能通过基类指针删除派生类对象的类体系,基类析构函数必须声明为 virtual,否则属于未定义行为。
4.2 重载、默认参数与 const 成员:声明顺序决定能否编译
默认参数只有一条规则:从右往左连续给出。void f(int a, int b = 1, int c = 2);合法,void f(int a, int b = 1, int c);非法,因为 c 没有默认值却排在带默认值的 b 之后,调用时无法按位置对应实参。填空题经常把第二条包装成“指出编译错误并改正”。
const 成员函数的重载是另一个高频点。同一个类写两个at,靠对象的 const 性质区分:
class Str { public: char &at(int i) { return buf[i]; } // 普通对象可读写 const char &at(int i) const { return buf[i]; } // const 对象只读 private: char buf[128]; }; void printInfo(const Str &s) { std::cout << s.at(0); // const 对象 → 调用 const 版本 } Str s; s.at(0) = 'A'; // 普通对象 → 调用非 const 版本const 对象只能调用 const 成员函数,非 const 对象优先匹配非 const 版本;const 成员函数内部不能修改成员变量,除非声明为mutable。这是一个接口、两种语义的惯用法,在文本处理类程序设计题里几乎是标准答案。
4.3 流 I/O 与文件读写的考场写法:getline 循环代替 eof 判断
文件读写题的套路稳定:打开文件、逐行读取、处理、写回。最容易扣分的是循环终止条件。
#include <fstream> #include <iostream> #include <string> int main() { std::ifstream fin("score.txt"); if (!fin.is_open()) { std::cerr << "open failed" << std::endl; return 1; } std::string line; while (std::getline(fin, line)) { std::cout << line << std::endl; } fin.close(); std::ofstream fout("result.txt", std::ios::app); fout << "total 120" << std::endl; fout.close(); return 0; }读取循环用std::getline的返回值判断读取是否成功,不要先while (!fin.eof())再读——后者在读到文件末尾后还会多进一次循环体,造成“多处理一行空数据”的经典错误。打开文件后立刻检查is_open(),失败时打印错误并返回非零值,这个习惯在改错题里也是加分项。
std::ios::app表示追加写入,std::ios::trunc表示打开即清空,多个模式用|组合。常用模式对照如下:
| 模式 | 效果 | 应用场景 |
|---|---|---|
std::ios::in | 只读打开 | ifstream 默认 |
std::ios::out | 写入并清空 | ofstream 默认 |
std::ios::app | 追加写 | 日志、累加记录 |
std::ios::ate | 打开后定位到末尾 | 可读可写 |
std::ios::binary | 二进制模式 | 非文本文件 |
统计文件里整数和这类任务,配合std::istringstream逐行解析即可:
#include <sstream> int sum = 0, v; std::istringstream iss(line); while (iss >> v) sum += v;输出要保留固定小数位时,用std::fixed << std::setprecision(2),对应头文件<iomanip>。这两个小工具是流 I/O 题里最常见的临门一脚。
5. 考前两周的“走查法”复习路线:不依赖编译器也能抓出逻辑错误
最后两周的复习重心不是增加新知识,而是把已知内容练成条件反射。推荐“清单 + 走查”的组合:白天按清单刷题,晚上用走查法重演错题,比盲目刷套卷更划算。
5.1 把复习范围收敛成一张可勾选的清单
| 模块 | 必会内容 | 自测标准 |
|---|---|---|
| 语法 | 运算符优先级、默认参数、重载匹配 | 选择题不出错 |
| 指针 | 4 种 const 组合、数组退化、悬空指针 | 能口头讲清差异 |
| 面向对象 | 构造析构顺序、虚析构、const 成员 | 阅读题不丢分 |
| 流 I/O | getline 循环、打开模式、setprecision | 文件题一遍过 |
清单只写“能默写”和“还不行”两栏,第二天优先处理“还不行”那一格。程序设计实践类题目不必额外找新题,把课堂例题换输入重跑,就是最贴近考场的训练。
5.2 手写代码的卷面规范:阅卷视角的 3 个加分动作
手写代码没有编译器提醒,卷面即文档。第一,函数开头用一行注释写明前置条件,例如“数组 a 非空且按升序”;第二,变量名用可读形式,i、j做循环下标可以,t1、t2这类含义不明的缩写要避免;第三,边界条件单独成行处理,if (n <= 0) return;放在函数入口第一行。这三个动作不改变算法正确性,但能显著降低阅卷误判概率,也帮你在检查时快速回忆思路。
5.3 走查法:用一张纸模拟编译器
走查法是把“我以为是”改成“实际是”:给定最小测试用例,逐行记录变量值。以二分查找为例,数组[1, 3, 5, 7],查找 5:
| 步 | lo | hi | mid | 动作 |
|---|---|---|---|---|
| 1 | 0 | 3 | 1 | 3 < 5,lo = 2 |
| 2 | 2 | 3 | 2 | 5 命中,返回 2 |
再换一个不存在的 key=6:mid=1 时 3 < 6,lo 变 2;mid=2 时 5 < 6,lo 变 3;mid=3 时 7 > 6,hi 变 2;此时 lo=3 > hi=2,循环退出返回 -1。三步走完,边界条件的对错一目了然。
提示:拿到手写题先花两分钟写测试用例再动笔,这个“先测后写”的顺序本身就是一次走查。
考场上没有 GDB,走查表就是你的调试器。把这张 lo、hi、mid 走查表直接画在演算纸空白处,先填测试用例和变量列,再动笔写代码,就是一份不依赖编译器的调试循环。
本文还有配套的精品资源,点击获取