简介:本资源是西北工业大学C++程序设计课程配套的100道实验习题完整参考答案集,面向该校本科生及C++初学者,聚焦语法夯实、面向对象实践与工程化编程能力提升。全包共98个独立.cpp源文件,覆盖基础语法、函数设计、类与对象、指针与动态内存、数组与字符串处理、I/O流、结构体、模板及异常处理等十大核心模块,每份代码均经验证可编译运行,含清晰注释与逻辑分步说明,便于对照题目理解实现思路与关键细节。压缩包为RAR格式,总大小仅32KB,轻量易下载,文件命名规范(如CH0707.CPP、SXF06.CPP等),体现章节与题号体系,利于按教学进度逐题研习。目前已有902人学习下载,是系统梳理C++实验要点、查漏补缺、巩固课堂所学的高实用性参考资料。
1. 这不是题库搬运工,而是一份能跑起来、能调试、能改出新题的C++实验工程包
某高校实验室带大一学生做C++基础实验时,发现学生卡在“编译通过但结果不对”“输入输出格式死活对不上”“指针和数组下标越界找不到在哪”这三座大山。他们试过网上零散答案——复制粘贴后编译报错、变量名不一致、main函数签名和实验要求不符;也试过PDF版答案——没法单步调试、看不到内存变化、改个测试用例得重写整页。直到有人把西北工业大学C++实验100道习题的答案,全部重构为可独立编译的.cpp文件 + 统一Makefile + 标准输入输出重定向脚本 + 每题配套test.in/test.out验证集,才真正让“做完一道题=掌握一个知识点”成为可能。这份资源不是答案快照,而是一个开箱即用的C++训练沙盒:你改一行代码,就能立刻看到它如何影响输出;你加一个断点,就能看清vector扩容时内存地址怎么跳;你换一组test.in,就能验证边界条件是否真被覆盖。适合刚学完语法想动手、正在备考实验考试、或需要快速搭建教学演示环境的开发者。
2. 工程结构与构建体系:从100个独立.cpp到一键全量验证
2.1 目录组织:按实验编号分层,拒绝文件海洋
项目采用扁平化+语义化双路径管理。根目录下只有4个一级子目录:
src/:存放全部100道题的源码,命名严格遵循ex001.cpp~ex100.cpp(三位数字前导零),避免排序错乱;test/:每个题对应一个子目录,如test/ex001/下含test.in(标准输入)、test.out(期望输出)、test.sh(自动化比对脚本);build/:编译产物目录,Makefile自动创建,不纳入Git;docs/:仅含一份README.md(说明编译依赖、运行方式、评分规则)和EXPERIMENT_GUIDE.pdf(原始实验手册扫描件,标注题号对应关系)。
提示:所有
.cpp文件均以#include <iostream>开头,禁用<bits/stdc++.h>;main()函数统一返回int,无void main();输入读取严格使用std::cin,禁用scanf—— 这是为了保证在不同编译器(g++ 9.4 / clang++ 12 / MSVC 19.3x)下行为一致,避免因流缓冲差异导致测试失败。
2.2 Makefile设计:支持单题编译、批量验证、覆盖率统计
核心逻辑是三层目标驱动:
make ex001:编译src/ex001.cpp→build/ex001,并静默运行test/ex001/test.sh;make all:顺序编译全部100题,每题失败时中断并打印错误行号;make verify:对全部通过的题目,额外执行valgrind --tool=memcheck ./build/ex001 < test/ex001/test.in 2>&1 | grep -q "no leaks are possible",检测内存泄漏。
关键片段如下(Makefile第37–45行):
# 定义所有题目编号 EXERCISES := $(shell seq -f "%03g" 1 100) # 单题编译规则:make ex001 %: src/%.cpp @echo "[COMPILING] $@" g++ -std=c++17 -O0 -g3 -Wall -Wextra -Wconversion -pedantic $< -o build/$@ @if [ -f "test/$@/test.sh" ]; then \ echo "[RUNNING TEST] $@"; \ bash test/$@/test.sh || exit 1; \ else \ echo "[WARNING] No test found for $@, skipping verification"; \ fi # 批量构建:make all all: $(EXERCISES)参数说明:
-std=c++17:强制使用C++17标准,避免std::optional或if constexpr等特性在旧标准下不可用;-O0:关闭优化,确保调试时变量值可见(尤其对const int N = 10; int arr[N];这类变长数组调试至关重要);-g3:生成三级调试信息,支持查看宏展开和内联函数源码;-Wall -Wextra -Wconversion:开启全警告,捕获隐式类型转换(如int i = 3.14;)、未使用变量等典型新手错误。
2.3 测试脚本机制:输入/输出分离 + 行末空格容错
test/ex001/test.sh不是简单./build/ex001 < test.in > actual.out && diff actual.out test.out。它做了三件事:
- 标准化换行符:
dos2unix处理test.in,防止Windows回车符\r\n导致std::getline()多读字符; - 忽略行末空格与空行:用
sed 's/[[:space:]]*$$//'清洗actual.out和test.out后再比对; - 超时保护:
timeout 2s ./build/ex001 < test.in > actual.out 2>/dev/null || { echo "TIMEOUT"; exit 1; },防止单题死循环拖垮整个验证流程。
实测案例:ex023(字符串反转)原题要求“输入一行字符串,输出反转后字符串”,学生常写成while (cin >> s) cout << string(s.rbegin(), s.rend()) << endl;—— 这会导致多组输入时输出错位。测试脚本能精准捕获该问题:test.in含两行"abc"和"def",期望输出"cba"和"fed"各占一行,而错误实现会输出"cbafed"连在一起。
3. 核心模块拆解:ex042(链表合并)与ex078(二叉搜索树遍历)的实现逻辑
3.1 ex042:有序单链表合并——从内存布局看指针操作本质
题目要求:给定两个升序单链表L1和L2,合并为一个升序链表,不得申请新节点,仅调整指针指向。
关键代码(src/ex042.cpp第48–72行):
struct ListNode { int val; ListNode* next; ListNode() : val(0), next(nullptr) {} ListNode(int x) : val(x), next(nullptr) {} }; ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { // 哨兵节点简化边界处理 ListNode dummy(0); ListNode* tail = &dummy; while (l1 != nullptr && l2 != nullptr) { if (l1->val <= l2->val) { tail->next = l1; // 将l1节点"嫁接"到结果链表尾部 l1 = l1->next; // l1前进,但原l1->next已失效,需提前保存 } else { tail->next = l2; l2 = l2->next; } tail = tail->next; // tail始终指向结果链表最后一个有效节点 } // 连接剩余非空链表 tail->next = (l1 != nullptr) ? l1 : l2; return dummy.next; }逻辑说明:
- 为什么用哨兵节点?避免对第一个节点单独判断
head == nullptr,所有节点插入逻辑统一为tail->next = xxx; l1 = l1->next必须在tail->next = l1之后?是。因为tail->next = l1已将l1节点从原链表摘下,此时l1->next指向的是它在原链表中的后继,而非结果链表中的后继(结果链表中它的后继是l2或下一个l1);tail = tail->next的时机?必须在每次tail->next = xxx后立即执行,否则下次插入会覆盖上一个节点的next指针。
调试技巧:在GDB中执行p *l1可直接打印当前节点值和next地址,观察指针跳转轨迹。
3.2 ex078:二叉搜索树中序遍历非递归实现——栈模拟调用栈
题目要求:不用递归,用栈实现BST中序遍历(左-根-右),输出节点值序列。
关键代码(src/ex078.cpp第61–85行):
struct TreeNode { int val; TreeNode* left; TreeNode* right; TreeNode() : val(0), left(nullptr), right(nullptr) {} TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} }; std::vector<int> inorderTraversal(TreeNode* root) { std::vector<int> result; std::stack<TreeNode*> stk; TreeNode* curr = root; while (curr != nullptr || !stk.empty()) { // 一路向左压栈,直到空节点 while (curr != nullptr) { stk.push(curr); curr = curr->left; } // 弹出栈顶,访问其值(此时左子树已全处理完) curr = stk.top(); stk.pop(); result.push_back(curr->val); // 转向右子树 curr = curr->right; } return result; }参数说明:
stk存储的是“待访问根节点”,不是“待处理子树”;- 内层
while循环的终止条件curr != nullptr确保不会将空指针入栈; curr = curr->right后,外层while循环会再次进入内层循环,继续向右子树的最左端探索 —— 这正是中序遍历“左-根-右”的栈模拟本质。
常见误写:把curr = curr->right放在result.push_back()前,会导致右子树根节点被跳过。正确顺序是先访问当前节点(push_back),再转向右子树(curr = curr->right)。
4. 避坑指南:编译、运行、调试阶段的5个高频翻车点
4.1 现象:make ex015报错error: ‘nullptr’ was not declared in this scope
原因:系统默认g++版本过低(如CentOS 7自带g++ 4.8.5),不支持C++11的nullptr关键字。
解决:升级g++至5.0+,或临时修改Makefile中编译命令为g++ -std=c++11 ...(但部分题目用到C++17特性如std::optional,仍需升级)。
4.2 现象:test/ex033/test.sh显示diff: test.out: No such file or directory
原因:test/目录未完整下载,或解压时权限丢失导致子目录不可读。
解决:执行ls -l test/ex033/确认文件存在;若缺失,重新下载资源包并用unzip -X解压(保留Unix权限)。
4.3 现象:gdb build/ex056后break main提示Function main not defined
原因:src/ex056.cpp中main()函数被注释或误删,或文件编码为UTF-8 BOM格式导致编译器解析失败。
解决:用file src/ex056.cpp查看编码,若含BOM则用sed -i '1s/^\xEF\xBB\xBF//' src/ex056.cpp清除;检查文件末尾是否有// main() { ... }注释块。
4.4 现象:valgrind检测提示Invalid read of size 4在ex089.cpp第112行
原因:题目要求实现动态二维数组,学生写了int** arr = new int*[m]; for (int i=0; i<m; ++i) arr[i] = new int[n];,但释放时只写了delete[] arr;,漏掉for (int i=0; i<m; ++i) delete[] arr[i];。
解决:在析构逻辑中补全双重释放;或改用std::vector<std::vector<int>>避免手动内存管理。
4.5 现象:make verify时ex092通过但valgrind报Conditional jump or move depends on uninitialised value(s)
原因:题目涉及结构体数组初始化,学生用Student stu[100];但未显式初始化成员(如stu[i].score = 0;),而valgrind检测到后续if (stu[i].score > 90)使用了未初始化值。
解决:结构体定义时添加默认构造函数Student() : score(0), id(0) {},或声明时用Student stu[100] = {};进行零初始化。
5. 二次开发实战:如何快速新增第101题并接入现有验证体系
5.1 新增题目模板:从零创建ex101.cpp
假设新增题目为“实现哈希表(开放寻址法),支持insert、find、erase操作”。步骤如下:
- 在
src/下新建ex101.cpp,按统一头文件规范编写:
#include <iostream> #include <string> #include <vector> #include <cmath> // 哈希表实现(开放寻址,线性探测) class HashTable { private: static const int TABLE_SIZE = 10007; // 大质数 std::vector<std::pair<std::string, int>> table; int size; public: HashTable() : table(TABLE_SIZE), size(0) {} int hash(const std::string& key) { int h = 0; for (char c : key) h = (h * 31 + c) % TABLE_SIZE; return h; } void insert(const std::string& key, int value) { int idx = hash(key); while (table[idx].first != "" && table[idx].first != key) { idx = (idx + 1) % TABLE_SIZE; } if (table[idx].first == "") size++; table[idx] = {key, value}; } int find(const std::string& key) { int idx = hash(key); while (table[idx].first != "") { if (table[idx].first == key) return table[idx].second; idx = (idx + 1) % TABLE_SIZE; } return -1; // not found } void erase(const std::string& key) { int idx = hash(key); while (table[idx].first != "") { if (table[idx].first == key) { table[idx].first = ""; // 逻辑删除 size--; return; } idx = (idx + 1) % TABLE_SIZE; } } }; int main() { HashTable ht; std::string op; while (std::cin >> op) { if (op == "insert") { std::string key; int val; std::cin >> key >> val; ht.insert(key, val); } else if (op == "find") { std::string key; std::cin >> key; std::cout << ht.find(key) << "\n"; } else if (op == "erase") { std::string key; std::cin >> key; ht.erase(key); } } return 0; }- 创建测试目录:
mkdir -p test/ex101; - 编写
test/ex101/test.in(模拟交互输入):
insert apple 5 insert banana 3 find apple erase banana find banana- 编写
test/ex101/test.out(期望输出):
5 -1- 编写
test/ex101/test.sh(复用通用逻辑):
#!/bin/bash cd "$(dirname "$0")/../../" timeout 3s ./build/ex101 < test.in > actual.out 2>/dev/null || { echo "TIMEOUT"; exit 1; } sed 's/[[:space:]]*$$//' actual.out > actual_clean.out sed 's/[[:space:]]*$$//' test.out > test_clean.out diff actual_clean.out test_clean.out > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "TEST FAILED for ex101" echo "Expected:" cat test_clean.out echo "Got:" cat actual_clean.out exit 1 fi echo "TEST PASSED for ex101"5.2 接入Makefile:让ex101参与全量构建
修改Makefile中EXERCISES变量:
EXERCISES := $(shell seq -f "%03g" 1 100) 101或更健壮地改为:
EXERCISES := $(shell ls src/*.cpp | xargs -n1 basename | sed 's/\.cpp$$//' | sort -n)这样新增任意exXXX.cpp都会被自动识别。
5.3 验证与调试:用GDB定位哈希冲突逻辑
当test/ex101/test.sh失败时,直接调试:
gdb build/ex101 (gdb) run < test/ex101/test.in # 程序停在崩溃处,用 bt 查看调用栈 (gdb) p idx (gdb) p table[idx] (gdb) p table[(idx+1)%10007]重点观察idx是否越界、table[idx].first是否为""但table[idx].second有脏数据 —— 这往往是逻辑删除未清空second导致的。
从那以后我每次新增题目,都强制走一遍make ex101 && make verify,再用valgrind --leak-check=full ./build/ex101 < test/ex101/test.in过一遍内存。不是为了炫技,而是因为曾经在ex067里漏掉一个delete[],导致后续10道题的测试结果全错,debug花了整整一个通宵。希望帮到你。
本文还有配套的精品资源,点击获取