简介:谭浩强《C++程序设计》第四版教材的例题程序合集,覆盖书中全部章节的示例代码,适合正在系统学习C++的高校学生、自学者以及备课教师。读者可在阅读教材后对照源码上机运行,也可以直接在这些程序基础上修改和调试,把注意力集中在语法理解与算法实现上。压缩包内含198个文件,其中190个cpp源代码文件是主体,另有5个h头文件、2个txt说明和1个doc文档;整体大小仅85KB,下载和分发都非常轻量。已有496人学习下载,多数学习者将其作为教材配套练习使用。程序按章分文件夹存放,文件名与例题号一一对应,如c5-7.cpp即第5章例5.7;同一例题下若包含多个程序,还通过c10-4-1、c10-4-2等序号加以区分,定位非常直观。配合目录结构,读者可以按章节逐题运行验证,也可以针对某道例题快速抽取代码进行修改实验,省去手动录入例题源码的时间,提升上机练习效率。 前几天整理电脑,翻到了大学时期保存的谭浩强《C++程序设计(第4版)》配套例题源码。工作这些年基本都在写业务代码,猛然见到当年的Student类、Complex类,倒是有种“老朋友重逢”的感觉。我顺手把这些例题程序放到VS Code里重新编译了一遍,结果比想象中折腾——不是教材里代码写错了,而是这些例题大多从VC 6.0时代走来,面对现代g++和MSVC编译器,有一堆历史遗留的兼容问题要处理。
这篇文章就是我重新跑通第四版全部例题之后的一份梳理。内容包括:这套例题真正的学习价值在哪里、用什么开发环境最省心、老代码在现代编译器下要改哪些地方、哪些经典题目值得逐行手敲,以及怎么在例题基础上跨出“从作业到项目”的那一步。如果你是刚学完C++语法、正缺练手材料的人,或者想快速把C++核心知识串一遍,这篇应该能帮你少走不少弯路。
1. 第四版例题程序的真正价值:一套完整的C++语法地图
1.1 覆盖范围:从Hello World到模板和STL
很多人一提谭浩强的C++教材,第一反应就是“老”。但抛开这些先入为主的评价,单纯看例题程序的覆盖范围,它实际上是相当完整的C++语法地图。从最前面的Hello World开始,往后依次是数据类型、运算符、流程控制、数组、函数、指针、结构体、类与对象、构造函数、析构函数、友元、运算符重载、继承、虚函数、多态、输入输出流、模板、异常处理,直到后面简要引入STL。
这个覆盖面放到现在的大学课程里,依然是主流C++教学大纲的骨架。更难得的是,这些例题不是孤立的知识点堆砌,而是有明显的承继关系。第5章的排序函数,到了第6章会用指针重新实现一遍;第8章的Student类,到了后面的章节会不断被扩展和增强。这种“同一个问题反复出现、每次加深一点”的编排,比散落在网络博客里的零碎示例要系统得多。
1.2 递进逻辑:同一道题,一章比一章进化
我这次重新整理时注意到一个有意思的现象:教材里很多例题,其实已经在用面向对象的思路组织代码,只是当时没有用“设计模式”这种词去包装。比如学生成绩管理这个主题,一开始只是简单的类加成员函数,后面逐步引入构造函数初始化、静态成员统计人数、对象数组存储多个学生,再到用文件读写保存数据。
如果把它看成一条演进线,这实际上就是一个项目从无到有的真实过程。我在重温的时候,每看到一个例题就会问自己一句:这一版比上一版多解决了什么问题?这么问下来,你会突然理解很多当初背了又忘的语法细节——比如为什么要引入析构函数,为什么要用静态成员变量。因为每一项新语法都是为了解决某个真实问题才加进来的。
1.3 指针、数组、字符串这些难点的落地方式
第四版里最让人头皮发麻的章节就是指针。教材的处理方式很朴素,先用一维数组和指针的关系做例子,再讲二维数组与指针,接着是字符串与指针,最后是指针数组和函数指针。每个例题代码都写得很直白,几乎没有多余技巧。
比如经典的“用指针变量输出数组元素”,核心就几行:
int a[10] = {1,2,3,4,5,6,7,8,9,10}; int *p = a; for (; p < a + 10; p++) { cout << *p << endl; }这道题我建议你一定要亲手改一改:把循环条件改成p < &a[10],看看编译是否正常;试着在循环体里写a++,观察编译器报什么错。背十遍“数组名是常量指针”都不如亲手踩一次编译错误来得深刻。等你能解释清楚指针自增和数组下标的关系,指针这章就算真正过关了。
2. 把例题搬到现代编译器:环境选择与最小配置
2.1 为什么我不建议继续用VC 6.0
教材配套光盘里的工程文件大多基于Visual C++ 6.0,但到了今天,我确实不建议你再去折腾那个环境。一是它在Windows 10/11上的兼容性一直不理想,安装过程容易出幺蛾子;二是它支持的C++标准停留在C++98早期,很多东西和现代写法差别很大。如果你用VC 6.0跑通了一堆例题,换个版本编译器却编译不过,这种挫败感反而影响学习。
我建议的选择有两个:一个是VS Code加MinGW-w64的g++,轻量、免费、跨平台,配置一次之后编译速度很快;另一个是Visual Studio 2022,调试功能强大,对新手最友好,新建一个空项目,把例题代码粘贴进去就能跑。我自己的主力环境是VS Code加MinGW-w64,下面这套配置我用了很久,期间没出过问题。
2.2 我用VS Code + MinGW-w64跑通了全部例题
安装MinGW-w64之后,先把解压目录下的bin文件夹路径加入系统PATH环境变量,然后打开终端验证一下:
g++ --version能正常输出版本号,说明工具链就绪。接下来在VS Code里配置两个文件。第一个是.vscode/tasks.json,负责告诉VS Code怎么编译:
{ "version": "2.0.0", "tasks": [ { "label": "C++ 编译", "type": "cppbuild", "command": "g++", "args": [ "-g", "${file}", "-o", "${fileDirname}/${fileBasenameNoExtension}.exe", "-std=c++11", "-Wall" ], "group": { "kind": "build", "isDefault": true } } ] }第二个是.vscode/c_cpp_properties.json,让C/C++插件的智能提示认识MinGW的路径:
{ "configurations": [ { "name": "MinGW", "includePath": ["${workspaceFolder}/**"], "compilerPath": "C:/mingw64/bin/g++.exe", "cppStandard": "cpp11" } ], "version": 4 }编译参数里我习惯固定加-std=c++11,因为绝大部分教材例题在这个标准下能直接编译通过,少数涉及老式写法的再单独处理。加上-Wall是为了把警告也显示出来,很多隐患其实在警告里就能看到。手动在终端编译时,我一般直接用一行命令:
g++ -std=c++11 -Wall 例题.cpp -o 例题.exe如果你完全不想碰命令行,那就用Visual Studio 2022,它创建一个空项目、点一下本地Windows调试器就能出结果。我个人感觉,只要能把例题跑起来,选哪个工具并不重要,重要的是别让环境问题打断思考节奏。
3. 值得逐行手敲的几道经典例题
3.1 复数类:运算符重载最短学习路径
运算符重载这章,教材几乎一定会拿复数类Complex来演示。核心思路是用两个double成员保存实部和虚部,重载+、-、*、/,再重载<<和>>,让复数可以像基本数据类型一样输入输出。加法重载的写法非常典型:
class Complex { private: double real; double imag; public: Complex(double r = 0, double i = 0) : real(r), imag(i) {} Complex operator+(const Complex& c) const { return Complex(real + c.real, imag + c.imag); } };这道题的关键不是背代码,而是理解两个地方:为什么operator+返回的是对象而不是引用?为什么参数要用const引用?第4章的例题里会同时展示友元函数重载的写法,我强烈建议你把两种写法各写一遍,对比它们访问私有成员方式的差异。这样“为什么需要友元”就不用死记了。
你还可以试着扩展一个operator==,判断两个复数是否相等,再把!=也补上。这些扩展教材没写,但做完之后,运算符重载的基本套路就完全内化了。
3.2 图形类体系:继承和多态的实验场
到继承和多态这章,Shape类的例题可以说是整本书实验价值最高的一段代码。定义一个抽象基类Shape,里面放一个纯虚函数area(),然后让Circle、Rectangle、Triangle各自实现自己的面积计算逻辑。通过基类指针或引用调用虚函数,就能在运行时得到正确的面积结果。
class Shape { public: virtual double area() const = 0; }; class Circle : public Shape { private: double r; public: Circle(double radius) : r(radius) {} double area() const override { return 3.14159 * r * r; } };我建议你在编译器里做一个小实验:把基类中的virtual去掉,再用一个Shape*数组去保存不同派生类对象,循环调用area()。你会发现每次调到的都是基类里的版本,这就是静态绑定和动态绑定的直观区别。这个实验比看十页理论都管用。
3.3 学生成绩管理:类、对象数组、排序的集大成者
Student类在教材中反复出现,算是贯穿全书的主角。从最基础的成员函数开始,到构造函数、析构函数、静态成员、对象数组,再到文件读写,这套例题把所有核心知识点都串了起来。最能练手的一版是:定义Student类,包含学号、姓名、三门课成绩,并提供输入、计算总分、输出信息的函数,然后用对象数组存多个学生,最后按总分排序输出。
我当初做这道题时,是把固定的对象数组改成了vector<Student>,再用std::sort按总分降序排列。这里有两个层次:首先你得能用手写冒泡排序实现对Student对象的排序,这是基础;然后你再去接触std::sort和lambda表达式,这是工程化写法。两道工序都做一遍,才算真正掌握这道例题的精华。
4. 我在跑通全部例题时踩过的坑
4.1 头文件、main返回值和字符串字面量
老例题在现代编译器下的报错,大部分集中在三个经典位置。我把最常见的几种情况整理成了表格,遇到问题对照着改就行:
| 老代码写法 | 现代编译器反应 | 推荐改法 |
|---|---|---|
#include <iostream.h> | 找不到头文件 | 改成#include <iostream>,加using namespace std; |
void main() | MSVC直接报错,g++告警 | 改成int main(),末尾return 0; |
char* s = "hello"; | 字符串字面量不能转为char* | 改成const char* s = "hello"; |
gets(str) | C++14起被移除 | 换成cin.getline(str, len)或std::string |
int a[n],n是变量 | 变长数组不属于C++标准 | 改成vector<int> a(n)或new int[n] |
这些报错看着吓人,其实每一类都对应一个C++标准演进的故事。比如void main()在VC 6.0里能编译,是因为VC 6.0自己放宽了标准;现代编译器要较真,就必须按int main()来写。把这些坑都趟一遍,你对C++标准的感觉会比背条条框框要扎实得多。
4.2 中文编码:GBK与UTF-8的一笔乱账
这是最容易卡住的一道坎,而且它不是代码逻辑问题。教材配套例题源码大多是GBK编码,VS Code默认用UTF-8打开,直接就是满屏乱码。解决方法是右下角状态栏点一下“UTF-8”,选择“通过编码重新打开”,再选“GBK”,代码就恢复正常了。
更缠人的是控制台输出的中文乱码。Windows命令行默认代码页是936,也就是GBK,如果代码文件被存成UTF-8,程序输出中文时就可能变成一堆乱码。我的处理原则是:如果只是为了跑通教材例题,干脆把文件编码保持GBK,别顺手改成UTF-8。如果非要统一成UTF-8,可以在程序开头加system("chcp 65001 > nul");切换控制台代码页,但这招只对Windows本地测试有效,跨平台就没了。
4.3 内存分配与释放的配对意识
教材里涉及new和delete的例题,有些并没有像现代工程那样严格要求配对释放。学的时候容易忽略,但自己动手跑代码时要注意:new对应delete,new[]对应delete[],不能混用,更不能漏。
我习惯在每道涉及动态内存的例题代码后面加一行注释,标明“这里分配了数组,最后要记得释放”。后来在Linux环境下用valgrind检查这些例题,确实能发现不少内存泄漏警告。虽然教材例题程序体量小,泄漏一点不影响运行,但早点养成释放内存的意识,以后做真正的大项目时会轻松很多。
另外还有一类隐蔽问题:老例题为省事经常写char name[20],然后不检查输入边界。如果你在程序里输入一长串字符,直接就把内存布局搞坏了。这类问题早期很难定位,往往表现为程序毫无规律地崩溃。我后来养成一个习惯,凡是数组操作,先确认边界,再动手写逻辑。
5. 从例题到个人项目:我建议的三步改造
5.1 用vector和string替换裸指针与C风格字符串
刷完例题之后,可以试着来一次系统重构,第一步就是把教材里的定长数组和指针替换成现代C++容器。典型例子是Student stu[50]这种结构,如果实际学生人数超过50,程序就会越界;换成vector<Student>之后,容量自动扩展,很多边界问题自然消失,代码读起来也清爽。
vector<Student> students; students.push_back(Student("1001", "张三", 85, 90, 78));C风格字符串char name[20]也建议换成std::string,赋值、拼接、比较都更安全。这一步改造做完,你会发现代码量没有增加,但可读性和健壮性提升了一大截。
5.2 手写排序学原理,std::sort写工程
教材里的冒泡排序、选择排序例题,是理解算法本质的好材料,手写练习不能省。但工程里真正排序时,我会直接调用std::sort。比如把学生按总分从高到低排,一行代码就能搞定:
sort(students.begin(), students.end(), [](const Student& a, const Student& b) { return a.totalScore() > b.totalScore(); });这里正好把lambda表达式也练了。手写排序和std::sort并不冲突:前者让你明白底层发生了什么,后者让你的代码更贴近实际工作节奏。两个能力都具备,才算是完整的修炼。
5.3 加一个文件持久化,让演示程序变成工具
教材例题里的学生管理系统,运行完数据就丢了。最后一步改造,就是给它加上文件保存和读取能力:程序退出前把学生信息写入文件,下次启动时自动读回来。这一步很能锻炼综合能力,因为要同时用到文件流、异常处理和数据序列化。
ofstream out("students.txt"); for (const auto& s : students) { out << s.id << " " << s.name << " " << s.score1 << " " << s.score2 << " " << s.score3 << endl; } out.close();做完这一步,这个学生管理系统就不再是一个“演示程序”,而是一个可以日常使用的工具。你可以继续往里面加删除、查找、修改的功能,一个完整的练手项目就这样从教材的例题中孵化出来了。
最后说点实在的:教材可以老,但你的学习方式不必跟着老。谭浩强第四版的例题程序,对想踏踏实实掌握C++核心语法的人来说,依然是块很不错的磨刀石。关键在于不要闷头敲完了事,多问为什么、多动手改、多踩点坑。等你能把这些老例题用更现代的方式重写一遍,这些代码才真正变成了你自己能调用的能力。
本文还有配套的精品资源,点击获取