C++实验100道习题答案:从刷题到调试的实战指南
简介:这份资源是西北工业大学C++实验课程的100道习题答案合集,面向正在学习C++程序设计的高校学生与自学者,帮助解决实验课中语法不熟、思路卡壳、调试困难等问题。压缩包共98个文件,全部为cpp源代码,整体约32KB,每份代码对应一道习题的完整实现,涵盖基础语法、函数与递归、类与对象、数组与动态内存、字符串处理、输入输出流、结构体与联合体、指针、模板及异常处理等核心知识点。目前已有901人学习下载。每道题的解答均包含清晰的逻辑分析、步骤分解与可运行代码,读者可对照题目理解变量声明、流程控制、封装继承多态、new与delete内存管理等具体用法,并借助源码调试过程掌握排错思路,从而举一反三,系统提升C++编程能力,为后续课程与项目开发打下基础。
1. 从一份 C++ 习题答案说起:它到底能帮你省下多少时间
如果你正在啃 C++ 基础,大概率经历过这种场景:对着实验指导书上的题目,语法能看懂,思路也大概有,但真到动手写的时候,指针怎么传、引用什么时候用、类里的构造函数为什么报错,全卡住了。这时候你需要的不是再翻一遍教材,而是一份能跑通、能对照、能帮你定位问题的参考实现。这份“C++ 实验 100 道习题答案”就是干这个的——它覆盖了从基本语法、函数、数组、指针,到类与对象、继承多态、运算符重载、模板和文件操作这些 C++ 实验课的核心考点,每道题都有对应的源码和必要注释。适合正在上 C++ 实验课、准备期末机考、或者想靠刷题把语法肌肉记忆练出来的同学。它不是那种只给最终输出的“答案册”,而是能让你逐行对照、理解每一步为什么这么写的参考代码集。下面我从怎么用、怎么避坑、怎么进阶三个层面,把这份资源拆开讲清楚。
2. 先搞清楚这 100 道题的技术分布:别拿到就从头刷
2.1 题目类型与知识点映射
这份习题答案按实验课的常见进度组织,大致可以分成几个模块。我拿到手第一件事不是从第一题开始抄,而是先扫一遍目录,看看哪些是我已经熟的、哪些是我一写就翻车的。常见的分布是这样的:
| 模块 | 题量占比 | 核心考点 | 典型翻车点 |
|---|---|---|---|
| 基础语法与流程控制 | 约 15% | 循环、分支、输入输出格式 | cin与getline混用导致跳过输入 |
| 函数与参数传递 | 约 15% | 值传递、引用传递、默认参数 | 引用传参时误传临时变量 |
| 数组与指针 | 约 20% | 一维/二维数组、指针运算、动态内存 | 数组越界、delete与delete[]混用 |
| 类与对象 | 约 20% | 构造/析构、拷贝构造、友元 | 浅拷贝导致双重释放 |
| 继承与多态 | 约 15% | 虚函数、纯虚函数、虚析构 | 基类指针delete不触发派生类析构 |
| 运算符重载与模板 | 约 10% | operator+、函数模板、类模板 | 重载<<时返回类型写错 |
| 文件操作与综合 | 约 5% | fstream、字符串流 | 文件打开模式组合错误 |
这个表不是让你背的,是让你在刷题前先定位自己的薄弱区。比如你指针本来就虚,那就别急着往后翻类与对象,先把数组与指针那 20 道集中做掉。常见做法是:每天集中攻一个模块,做完对照答案,把编译报错和逻辑错误分开记录。
2.2 怎么判断一道题值不值得深挖
不是每道题都值得你花 30 分钟死磕。我的判断标准很简单:如果这道题涉及内存管理、对象生命周期、或者多态行为,那就值得深挖;如果只是换个循环边界或者改个输出格式,扫一眼答案知道套路就行。举个例子,下面这种题就属于必须亲手写一遍的:
// 题目:实现一个 String 类,要求支持拷贝构造和赋值运算符 class String { private: char* data; int len; public: String(const char* str = "") { len = strlen(str); data = new char[len + 1]; strcpy(data, str); } // 拷贝构造:深拷贝 String(const String& other) { len = other.len; data = new char[len + 1]; strcpy(data, other.data); } // 赋值运算符:先释放旧内存,再深拷贝 String& operator=(const String& other) { if (this == &other) return *this; // 自赋值检查 delete[] data; len = other.len; data = new char[len + 1]; strcpy(data, other.data); return *this; } ~String() { delete[] data; } };这段代码的关键不在语法,而在三个地方:拷贝构造里new的时机、赋值运算符里的自赋值检查、析构里delete[]和new[]的配对。参数说明:other是常量引用,避免拷贝且防止修改原对象;this == &other是自赋值保护,少了这行在s = s时会先释放再访问已释放内存。逻辑说明:先分配新内存再拷贝,顺序反了就会丢数据。这种题你抄一遍没用,必须自己写一遍然后故意把自赋值检查删掉,看它怎么崩,才能记住。
2.3 答案的使用姿势:对照而不是复制
我见过太多人把答案下载下来,编译跑通,输出对上了,就觉得自己会了。结果考试换个数字就写不出来。正确的用法是:先自己写,写完编译,编译不过看报错,报错解决不了再翻答案。翻答案的时候重点看三样东西——变量命名习惯、边界条件处理、资源释放位置。比如指针题里,答案在delete之后有没有把指针置空?类题里,拷贝构造有没有处理自赋值?这些细节才是这份资源真正值钱的地方。你如果只是复制粘贴,那这 100 道题对你来说就是 100 个孤立的代码片段,串不起来。
3. 把答案跑起来:环境配置与逐题验证流程
3.1 编译环境的选择与常见报错
这份答案里的代码大部分是标准 C++,用 g++ 或者 clang++ 都能编。但实验课常用的 IDE 可能是 Visual Studio、Dev-C++ 或者 Code::Blocks,不同环境对标准的支持程度不一样。我一般会先用命令行 g++ 过一遍,确认代码本身没问题,再放到 IDE 里跑。常见做法是:
# 编译单个 cpp 文件,开启所有警告 g++ -std=c++11 -Wall -Wextra -o test test.cpp # 如果用到数学库,加 -lm g++ -std=c++11 -Wall -o test test.cpp -lm # 运行 ./test参数说明:-std=c++11指定标准版本,有些题用了auto或者范围 for,不指定会报错;-Wall -Wextra打开额外警告,能帮你发现未初始化变量、符号比较这些隐性问题。逻辑说明:先编译再运行,不要一上来就在 IDE 里点运行,IDE 的报错信息有时候被截断,命令行的完整输出更容易定位问题。
如果你在 Windows 上用 Visual Studio,注意两点:一是strcpy、scanf这类函数会报安全警告,可以在文件开头加#define _CRT_SECURE_NO_WARNINGS;二是有些题用了变长数组,VS 不支持,需要改成动态分配或者用vector。
3.2 逐题验证的标准化步骤
不要一次编译 100 个文件,那样报错混在一起根本看不过来。我一般按模块来,每个模块建一个文件夹,里面放题目源码和测试输入。步骤是这样的:
- 从答案里挑出当前模块的题目,复制到自己的工程目录,重命名成有意义的文件名,比如
ex03_pointer_swap.cpp。 - 先不编译,快速扫一遍代码,看有没有明显的语法问题,比如漏分号、括号不匹配。
- 编译,把警告当错误看。警告往往指向潜在的运行时问题。
- 构造测试用例。不要只用题目给的样例,自己造边界数据:空输入、最大值、最小值、重复值。
- 运行,对比输出。如果输出不对,用
cout在关键位置打印中间变量,或者用调试器单步。
下面是一个典型的测试用例构造示例,以“判断素数”这道题为例:
// 测试用例:边界值 + 特殊值 void testIsPrime() { // 正常情况 assert(isPrime(7) == true); assert(isPrime(10) == false); // 边界情况 assert(isPrime(2) == true); // 最小素数 assert(isPrime(1) == false); // 1 不是素数 assert(isPrime(0) == false); // 0 不是素数 assert(isPrime(-5) == false); // 负数不是素数 // 大数测试 assert(isPrime(9973) == true); // 接近 int 范围的素数 }逻辑说明:assert在调试时有用,发布时可以用#define NDEBUG关掉。参数说明:每个断言覆盖一种边界,这样你改代码的时候能立刻知道有没有破坏原有逻辑。常见坑是只测了正常情况,结果负数输入导致死循环或者错误输出。
3.3 用版本管理记录你的修改
这份答案是参考,不是圣旨。你一定会遇到答案写法和你思路不一样、或者答案本身有 bug 的情况。这时候别直接改原文件,用 git 建个仓库,每改一道题提交一次。这样你既能保留原始答案,又能看到自己的修改轨迹。命令很简单:
git init git add ex03_pointer_swap.cpp git commit -m "完成指针交换题,修正了答案中未置空指针的问题"这样做的好处是,过一周你回头看,能清楚知道自己当时卡在哪、怎么解决的。血泪经验:没有版本管理,改乱了想回退都回不去,只能重新下载。
4. 避坑与排查:这 100 道题里最容易翻车的五个地方
4.1 指针与动态内存:删了又用,用了又删
现象:程序在delete之后还能访问那块内存,输出看起来正常,但偶尔崩溃或者输出乱码。原因:delete只是把内存还给系统,指针本身还指向原来的地址,继续访问就是悬空指针。解决:delete之后立刻把指针置为nullptr,后续使用前先判空。常见做法是封装一个safeDelete函数,但更根本的是养成习惯——谁分配谁释放,释放完就置空。
4.2 拷贝构造与赋值:浅拷贝导致双重释放
现象:程序在析构时崩溃,报double free or corruption。原因:类里用了指针成员,但没有自定义拷贝构造和赋值运算符,编译器生成的默认版本只复制指针地址,两个对象指向同一块内存,析构时释放两次。解决:只要类里有指针成员,就老老实实写拷贝构造、赋值运算符和析构,也就是常说的“三法则”。如果用了 C++11,还可以考虑移动构造和移动赋值,变成“五法则”。
4.3 虚析构缺失:基类指针删派生类对象
现象:通过基类指针delete派生类对象时,派生类的析构函数没被调用,导致资源泄漏。原因:基类析构函数不是虚函数,delete时只调用了基类析构。解决:只要类可能被继承并通过基类指针删除,基类析构必须声明为virtual。这条在继承与多态模块的题目里反复出现,别嫌烦。
4.4 输入流状态:cin失败后后续输入全乱
现象:输入一个字母,程序进入死循环,或者后续所有输入都被跳过。原因:cin进入失败状态,缓冲区里的错误字符没被清除,后续读取直接失败。解决:输入失败后调用cin.clear()清除状态,再用cin.ignore()清空缓冲区。常见写法:
if (!(cin >> num)) { cin.clear(); // 清除错误状态 cin.ignore(10000, '\n'); // 丢弃缓冲区内容 cout << "输入无效,请重新输入" << endl; }参数说明:10000是丢弃的最大字符数,'\n'是分隔符,表示一直丢到换行。逻辑说明:先清状态再清缓冲区,顺序反了没用。
4.5 文件操作:打开模式组合错误
现象:文件明明存在,但读取为空;或者写入后文件内容被清空。原因:fstream的打开模式没组合对,比如用ios::in打开输出文件,或者用ios::out打开已有文件导致截断。解决:读文件用ifstream,写文件用ofstream,追加用ios::app。如果既要读又要写,用fstream并明确指定ios::in | ios::out,需要追加时再加ios::app。注意ios::out单独使用会截断文件,这是最常见的翻车点。
5. 从答案到能力:用这 100 道题练出调试直觉
5.1 把每道题当成一次小型代码审查
答案跑通只是起点。我后来养成一个习惯:每道题对照答案之后,问自己三个问题——如果输入规模扩大十倍,这段代码还成立吗?如果去掉某个边界检查,会在什么情况下出错?有没有更简洁的写法?比如数组排序题,答案用了冒泡,你能不能改成std::sort?指针遍历能不能改成迭代器?这种追问能把一道题的价值放大三倍。
5.2 用调试器替代打印大法
新手喜欢用cout打印中间变量,这没错,但效率低。这 100 道题里至少有 20 道涉及指针和内存,用调试器看地址和值的变化直观得多。以 gdb 为例:
g++ -g -o test test.cpp # -g 生成调试信息 gdb ./test (gdb) break main # 在 main 函数打断点 (gdb) run # 运行 (gdb) next # 单步执行 (gdb) print ptr # 打印指针地址 (gdb) print *ptr # 打印指针指向的值 (gdb) continue # 继续运行参数说明:-g是关键,没有它 gdb 看不到源码和变量名。逻辑说明:先断点再运行,用next逐行,用print检查状态。一旦你习惯用调试器,定位段错误的速度会快很多。
5.3 建立自己的错题本
这 100 道题里,你一定会反复踩同一类坑。我的做法是建一个 Markdown 文件,每踩一个坑就记一条:现象、原因、解决、涉及题目编号。比如“悬空指针”这条,我会记下是在哪几道题里出现的,下次再遇到类似代码,先搜错题本。这个习惯坚持下来,期末复习的时候你只需要翻错题本,不用重新刷 100 道题。
5.4 进阶技巧:用静态分析工具提前发现问题
编译器警告只是第一道防线。像cppcheck这类静态分析工具能在不运行代码的情况下发现内存泄漏、未初始化变量、数组越界等问题。安装后直接跑:
cppcheck --enable=all --inconclusive test.cpp参数说明:--enable=all打开所有检查,--inconclusive显示不确定的警告。逻辑说明:它不能替代编译器,但能发现一些编译器忽略的隐患。我一般会在提交实验报告前跑一遍,把明显的问题先修掉。
从那以后我每次拿到一份习题答案,都强制自己先跑通、再改错、最后用调试器走一遍关键题,而不是直接复制。希望这份拆解能帮你把这 100 道题真正变成自己的东西。
本文还有配套的精品资源,点击获取