简介:这份C++程序设计例题源代码源自清华大学出版社郑莉教授主编的《C++程序设计》教材,专为初学C++的在校学生与自学者设计,用于配合教材逐步掌握变量、流程控制、类与对象、继承多态、模板、STL、文件操作等核心知识点。资源共227个文件、压缩包仅1.33MB,其中以149个cpp源文件和37个h头文件为主体,覆盖各章节可运行示例;同时包含dsw/dsp/opt等工程配置文件及少量pdb/obj调试产物,便于直接打开编译和跟踪调试。已有413人下载学习。通过逐行研读这些例题,读者能理解构造函数与析构函数的执行时机、运算符重载的实现方式、new/delete内存管理的注意事项以及异常处理的基本结构,还能学会利用标准库容器和算法解决实际问题;例题顺序由浅入深,从简单语法到MFC界面程序,完整呈现面向对象设计的典型应用,为后续开发打下扎实基础。
1. 例题源代码不是拿来抄的:先把这套 C++ 教材源码的用法搞清楚
打开下载好的《C++程序设计》例题源代码压缩包,很多人的第一反应是找“参考答案”,这恰恰是最亏的用法。这套与高校课堂配套的例题源码,覆盖了从语法、指针、函数重载,到类、继承、多态、模板与 STL 的完整主线,真正的价值不是让你抄,而是给你一条“可执行的知识路线图”:书里每讲完一个抽象概念,源码里就有一份能编译、能运行、能改参数看效果的最小示例。它适合正在跟课复习的学生,也适合自学 C++ 卡在面向对象门槛上的从业者——你缺的不是书,而是一批能亲手改动的验证样本。接下来按“环境跑通 → 分类精读 → 报错排查 → 改造复用”的顺序,把这些散落的 .cpp 文件变成你自己的练习场。先花十分钟把它跑起来,后面的事才谈得上。
2. 拿到例题代码先跑通:先把本地环境与最小编译命令配好
2.1 先看文件组织再选环境:源码包往往不止一种形态
常见的误区是下载完直接用 IDE 双击 .cpp 文件。双击只会打开文本编辑器,并不会帮你完成编译和运行。拿到压缩包后应该先解压,再看一眼目录结构。教材类源码包最常见的组织方式有两种:一种是按章节建目录,比如ch01、ch02这样一路排下去,每个目录里有几个独立的小程序;另一种是按知识点命名,比如point.cpp、student.cpp、circle.cpp。这两种方式都意味着“每个文件自带main函数”,遇到这种文件时,单独编译它就能得到一个可执行程序。
如果看到.h和.cpp分离成多份,说明这是讲“多文件组织”的章节。这种文件不能只编译主文件,而是要把同目录下的所有实现文件一起交给编译器。编译对象从“一个文件”变成“一组文件”,这是新手最容易卡住的第一个台阶。我的建议是:在 Windows 上装一个 MinGW 并把g++加进 PATH,在 macOS 或 Linux 上直接用系统自带的g++,暂时不要一上来就装体积庞大的 IDE。原因后面说。
2.2 用命令行把第一个例题跑起来:最小编译三步
先把当前目录切到解压后的源码目录,用命令确认目录下到底有哪些源文件,再决定下一步编译谁。整段操作如下:
# 列出目录内容,确认源文件名和是否存在 .h / .cpp 分离 ls -la # 单文件编译:-std=c++11 指定 C++ 标准,-Wall 打开常见警告,-o 指定输出文件名 g++ -std=c++11 -Wall -o example_01 example_01.cpp # 运行当前目录下生成的可执行程序 ./example_01这个命令块的逻辑是:g++负责把源文件编译链接成可执行文件,-o后面跟的是生成文件名,example_01.cpp是你要编译的源文件。先执行ls -la,是因为教材源码里的文件名往往不止一个,你需要看清目标文件叫什么,盲目编译只会得到“No such file”的报错。Windows 下的cmd没有ls,可以用dir代替;如果在 PowerShell 里,ls反而能用,这也是不少人第一次跑命令时感到“玄学”的原因——工具链不一样,命令自然有差异。
在 Windows 下运行可执行程序时,命令是example_01.exe或.\example_01.exe,而不是./example_01。这是 Windows 与 Linux/macOS 在路径写法上的一个基本区别。如果例题是老代码,用了<iostream.h>这类旧头文件,把-std=c++11先去掉,直接g++ example_01.cpp -o example_01再试一次,很多旧教材代码是在 C++98 时代写的,标准太高反而编不过。
2.3 为什么建议先命令行后 IDE:把黑匣子拆开看一眼
我不是反对用 IDE,而是反对在“还没搞懂编译链接是怎么回事”的时候就用 IDE。IDE 会把g++或cl.exe的完整调用过程包装成按钮,报错信息被折叠、被截断,新手看到一大片红色警告根本不知道问题出在语法层还是链接层。命令行虽然简陋,但它把每一步都摊开了:哪一步编译、哪一步链接、哪个文件哪一行有问题,原样摆在面前。例题代码大多是一个或两个.cpp的小程序,命令行完全够用。
等到例题变成多文件项目、需要断点调试、需要查看变量在内存里的变化时,再迁移到 CLion、Visual Studio 或 VS Code + 插件都不迟。那时候你已经有“编译是一个可观察过程”的直觉,不会被 IDE 的抽象界面带到沟里。命令行还有一个隐藏好处:很多课堂讲义的编译命令就是g++写法,你把这套命令练熟,去应付课程实验、期末上机,都比折腾 IDE 配置省时间。
3. 例题精读的四个台阶:从过程化语法读到模板与容器
3.1 语法类例题当字典用:循环、指针、函数重载适合“看一行改一行”
教材前几章的例题通常是语法演示,代码很短,功能也很简单,比如用for循环算累加、用指针交换两个变量、写一个函数重载求不同类型数据的最大值。这类例题的精读方式不是抄,而是“看一行、遮住下一行、自己补一行”。补不出来再翻源码,看过之后再删掉重写一遍。这个过程只需要用到一个很朴素的技巧:把例题文件复制一份,删掉函数体内部的核心逻辑,只留函数签名和空壳,然后自己把函数体填回去。
这类语法例题还有一个容易被忽略的用途:查旧写法。教材里的代码年代较早,很多写法在今天看来已经不推荐,比如用 C 风格字符串数组而不是std::string。当你在这类例题里看到char name[20]时,不要急着照着用,先打开一个小的测试程序验证当前环境下的行为,再把它替换成std::string。例题源码是学习的参照系,不是编码规范的权威,这点先想清楚,后面才不会把旧习惯带到工作中。
3.2 类与对象的例题:重点不是语法,是“谁拥有数据、谁负责清理”
进入类与对象章节后,例题开始出现构造函数、析构函数、拷贝构造函数和成员函数。很多自学者在这段会失去耐心,因为语法细节多、代码变得很长。我的经验是看例题时只问三个问题:对象在什么时候创建、什么时候销毁、谁负责释放资源。
#include <iostream> #include <string> using namespace std; class Employee { private: string name; int id; public: // 构造函数:对象创建时自动调用 Employee(string n, int i) : name(n), id(i) { cout << "Employee constructed: " << name << endl; } // 析构函数:对象销毁时自动调用 ~Employee() { cout << "Employee destroyed: " << name << endl; } void show() const { cout << "ID: " << id << ", Name: " << name << endl; } }; int main() { Employee a("A", 1); a.show(); return 0; }这个示例代表了教材例题最常见的主线:一个类、两个成员函数、一个main里创建并调用。构造函数的初始化列表: name(n), id(i)是例题里反复出现的写法,作用是让成员变量在进入函数体之前就被赋值。析构函数在这里只打了一行输出,但你可以把它换成delete或关闭文件的代码,观察资源在对象生命周期结束时是否被正确处理。把每一处构造函数和析构函数的调用时机标出来,比背十遍语法都管用。
3.3 继承与多态的例题:抓住虚函数这条主线
到了继承与多态章节,例题的篇幅会长很多,通常会出现一个基类、多个派生类,以及一个通过基类指针调用派生类函数的main函数。读这类例题时,先找virtual关键字。找到了,就找到了理解多态的钥匙。
#include <iostream> using namespace std; class Shape { public: virtual void draw() const { cout << "draw a shape" << endl; } // 基类析构函数要声明为虚函数,否则派生类资源可能泄漏 virtual ~Shape() {} }; class Circle : public Shape { public: void draw() const override { cout << "draw a circle" << endl; } }; int main() { Shape* s = new Circle; s->draw(); // 输出 draw a circle delete s; // 通过虚析构正确调用派生类析构 return 0; }这里的关键点有两个。第一,s静态类型是Shape*,实际指向的是Circle对象。如果不加virtual,s->draw()只会调用Shape::draw(),输出的是“draw a shape”,这就是静态绑定与动态绑定的区别。第二,析构函数加了virtual之后,delete s才能沿着虚表找到派生类的析构函数。很多旧教材例题在讲多态时,适合用“如果把virtual去掉,输出会怎么变”来验证,我建议每读到这种例题,就把这个例子复制出来,删掉virtual再编译运行,对比两次输出,比你记住结论更扎实。
3.4 输入输出流与 STL 的例题:别自己重造轮子
教材后半程会出现文件流、vector、string这类标准库例题。此时你可能会看到教材用 C 风格方式实现字符串拼接、动态数组扩容,这是时代留下的痕迹。今天再写同样功能,应当优先用标准库。比如字符串拼接用std::string的+运算符,动态数组用std::vector,文件读写用std::ifstream/std::ofstream。这阶段的例题不是让你照抄实现,而是让你理解“标准库为什么长这样”。
建议把这类例题当作“接口清单”来读:打开头文件看有哪些公开成员函数,然后在源码里找到每一个成员函数的调用点。比如看到一个sort(v.begin(), v.end())的调用,就去查vector的begin()/end()返回什么类型,sort的三个参数分别是什么含义。把每个调用点搞懂,你就在不知不觉中完成了从“写代码”到“用库”的转变。教材例题在这里已经讲完了大部分 C++ 语法知识点,接下来真正拉开差距的,是你对标准库熟悉到什么程度。
4. 例题代码编译报错的五个高频坑:现象、原因与解决
4.1 中文注释全部乱码:源文件编码与编译器默认编码不一致
现象:打开源代码文件,中文注释在编辑器里正常,但编译后运行,程序里输出的中文变成了乱码,甚至编译阶段就报“C4819 文件包含不能在当前代码页中表示的字符”。
原因:旧教材源码大多用 GBK 或 GB2312 编码保存,而现代编译器在 Windows 以外默认使用 UTF-8。MinGW 的g++按 UTF-8 解释源文件,把 GBK 字节当成了非法字符或错位字符。
解决:最简单的方式是用 VS Code 或任意编辑器把源码另存为 UTF-8 编码。如果不想转码,也可以给编译器加参数:g++ -finput-charset=GBK -fexec-charset=UTF-8 example_01.cpp -o example_01,告诉编译器源文件用 GBK 读取、生成的可执行文件用 UTF-8 输出。我的习惯是统一转成 UTF-8,因为你每改一次源码基本都需要转码,加参数治标不治本。
4.2 #include <iostream.h> 编译失败:老式头文件在现代编译器中已移除
现象:编译时报“iostream.h: No such file or directory”,而源码第一行明明写着#include <iostream.h>。
原因:C++ 标准在 1998 年正式确立后,标准库头文件不再带.h后缀。教材例题如果写于标准确立之前,就会留下这种写法。现代编译器不会再提供<iostream.h>。
解决:把#include <iostream.h>改写成#include <iostream>,并在其下一行加上using namespace std;。同样的问题还会出现在<string.h>、<stdlib.h>这类头文件上。如果源码里用了printf、malloc这类 C 库函数,应该改成<cstdio>、<cstdlib>,并把printf直接使用即可,因为 C 标准库在 C++ 里依然可用。逐个把旧头文件替换掉,这类报错就清零了。
4.3 system("pause") 在非 Windows 平台直接失效:项目卡在运行结束前
现象:程序在 Windows 上正常运行,复制到 Linux 或 macOS 上编译成功,但运行到一半没有任何输出,或者最后一行输出之后没有正常退出,退出码异常。
原因:源码里写了system("pause")。这行代码在 Windows 下会调用系统命令pause,让窗口暂停等待按键;在 Linux/macOS 的 shell 下没有pause这个命令,system()调用会返回错误码,程序表现因环境而异。
解决:跨平台的环境下把system("pause")整行删掉,程序本来就会在main返回后正常退出。如果你确实需要“运行结束后停住看输出”,改用cin.get()读取一次回车,这在 Linux 和 Windows 下都可编译运行。以后在新项目里也建议不要写system("pause"),这是 Windows 专属写法,谁写谁踩坑。
4.4 M_PI 与 strcpy 的新标准报错:数学常量和 C 字符串函数的“时差”
现象:源码里直接用M_PI计算圆面积,结果报“M_PI 未声明”;或使用strcpy时报“warning: 'strcpy' is deprecated”,在 MSVC 下直接报错 C4996。
原因:M_PI不是 C/C++ 标准库数学头文件里必须提供的宏,它在很多平台的<cmath>/<math.h>里属于扩展定义。在 Visual Studio 中,必须提前定义_USE_MATH_DEFINES才能看到这些常量。而strcpy在 C++11 后已被标记为不推荐使用,因为它的接口无法保证目标缓冲区足够大。
解决:使用M_PI时,在#include <cmath>之前加一行#define _USE_MATH_DEFINES;如果编译器仍报错,手动定义常量:const double PI = 3.14159265358979323846;。至于strcpy,把它换成std::strcpy并检查缓冲区大小,或者直接用std::string的赋值和+操作,后者是更符合现代 C++ 的写法。
4.5 窗口一闪而过:分不清是死循环还是正常退出
现象:在 Windows 上双击编译好的.exe,窗口闪现后立刻消失,你根本看不到输出,甚至怀疑程序是不是进入了死循环。
原因:绝大多数情况是程序正常执行完,控制台窗口被系统立即关闭。这和死循环无关,只是因为你的程序没有在退出前等待输入。
解决:不要用双击方式运行命令行程序,改成先在cmd里cd到可执行文件所在目录,再输入example_01.exe回车运行。这样无论程序输出多少内容,窗口都会在程序退出后保留在终端里。想留在 IDE 的终端里看输出,也可以,但前提是你已经学会了用命令行运行程序,而不是把终端里最后的输出截图当完成标志。死循环的典型特征是光标一直闪烁、程序不退出,此时按Ctrl+C中断重新检查循环条件即可。
5. 把例题源码改造成自己的代码骨架:命名、注释与组织
5.1 十分钟改造法:给源码补一份规范注释头
例题源码往往注释很少、变量名以a、b、i、j为主。直接读还行,但想长期复用,必须改造。我的做法是每个例题文件先加一段头注释,标明三件事:这个程序演示了什么知识点、输入是什么、输出是什么。然后给核心函数补上函数级注释,说明参数含义和返回值。
// 文件名: demo_polymorphism.cpp // 知识点: 虚函数与动态绑定 // 输入: 无 // 输出: 不同派生类各自的 draw 输出 #include <iostream> using namespace std; // 基类: 提供统一接口 class Shape { public: virtual void draw() const { cout << "shape" << endl; } virtual ~Shape() {} };这段改造看起来简单,但它的价值在于:当你三个月后再回来看这个文件,不需要从头读代码就能知道它在讲什么。给变量改名时要格外小心,例题源码的变量名虽然不好,但函数体内部逻辑也依赖它们,建议用编译器的“重命名”功能或纯文本替换,改一处就编译一次。不要一次改太多,否则报错时你分不清是逻辑错误还是改名改坏。
5.2 用 CMake 把散落的例题统一编译:一份最小 CMakeLists.txt
当你积累了十几个例题文件,再一个个手动敲g++命令就很累了。这时可以建一个项目根目录,把所有例题按章节放进src子目录,然后用 CMake 统一管理。下面是一份可以照抄的配置:
cmake_minimum_required(VERSION 3.10) project(cpp_examples) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 收集 src 目录下所有 .cpp,为每个文件单独生成可执行程序 file(GLOB EXAMPLE_SOURCES "src/*.cpp") foreach(example_src ${EXAMPLE_SOURCES}) get_filename_component(example_name ${example_src} NAME_WE) add_executable(${example_name} ${example_src}) endforeach()这段 CMake 的逻辑是:file(GLOB ...)收集src目录下所有.cpp文件名,foreach遍历每一个文件,用get_filename_component去掉路径和扩展名得到纯文件名,再调用add_executable为它单独生成一个可执行目标。这样你在构建目录里会得到一批与源文件同名的可执行文件。需要注意,GLOB不会自动感知新增文件,添加新例题后,要重新运行一次cmake,否则新文件不会出现在构建列表里。这一点踩的人特别多。
使用 CMake 的前提是你的源码是“每个文件自带main”的独立程序。如果某个例题涉及多文件联合编译,add_executable那行要显式列出所有相关文件,不能再用GLOB的方式自动收集。
5.3 从“跑通”到“能复用”:把例题拆成小模块
例题源码最容易被丢弃的部分,其实是那些设计良好的类。比如第 3 章里的Employee类、Shape类,你完全可以把它们从示例main函数中剥离出来,存成独立的.h和.cpp,放进一个公共目录,后续练习引用它们。这个动作比“多写十道练习题”更能锻炼设计能力,因为拆分过程中你必须回答:哪些成员是公共接口,哪些是内部实现。
一个可参考的拆分规则是:把类声明放.h,把成员函数实现放.cpp,main函数单独保留在原来的示例文件里。编译时需要把实现文件和示例文件一起交给编译器,比如g++ shape.cpp example_shape.cpp -o example_shape。当这类独立模块积累到五六个,你就会自然想到用上一节说的 CMake 把它们纳入统一构建。到这一步,例题源码已经不再属于教材,而变成了你私人的 C++ 零件库。
6. 把例题当活教材:三个能让知识长在身上的验证习惯
6.1 改一个参数再看输出,比多写十行笔记强
例题源码最大的学习价值是“可改动”。每读到一个例题,先不改逻辑,只改一个参数:把输出格式里的数字改成负数、把循环终点从 5 改成 100、把构造函数的初始值换成一个边界值,观察程序输出是否和你预期一致。这里最容易暴露问题:你以为你懂for循环,但把终点改小之后,循环体一次都不执行,这个行为你在背教材语法时可能没仔细想过。改完参数,再把每个变量的值在纸上推导一遍,推导结果和运行结果不一致的地方,就是你今天真正的收获。这套方法比任何笔记都有效,因为笔记只记录了你“知道的东西”,而运行结果会直接指出你不知道的部分。
6.2 用单步调试跟一次 main 的调用链
从例题里挑一个你“觉得很懂”的类,下断点后逐行走一遍。重点观察三样东西:构造函数被调用了几次、析构函数被调用了几次、局部变量在离开作用域时的销毁顺序。你会发现,很多事情和直觉相反:函数返回时,局部对象会先析构,然后才回到main的下一行;基类指针指向派生类对象时,析构顺序是先派生类、再基类。用调试器亲眼看一次,比在书上反复看时序图印象深得多。哪怕你最后不从事 C++ 开发,这种“用工具验证推理”的习惯,放到任何语言里都不会吃亏。
6.3 给例题补一个边界输入
给每个例题的输入设计一个“非法值”:比如要求输入正整数的例题,给它输入 0 或负数;要求数组长度的例题,给它输入 1 或一个很大的数。观察程序是否崩溃、是否输出了莫名其妙的数字。这个习惯是从例题源码走向工程代码的重要一步,因为教材例题通常只演示“正常路径”,而真实程序里大量问题都出在“非正常路径”上。你会发现许多例题代码根本没有对非法输入做处理,这也是旧式教学代码的局限——它教你知识,但不会教你防御性编程。
我后来养成的习惯是,每读完一个例题,至少做两次改动:一次改合法参数加深理解,一次改非法参数观察防护。前者帮我掌握原理,后者帮我认清边界。能把一个例题从“能跑”改到“能扛住乱输入”,才算真正把它读透了。希望这些方法能帮到你,让你手里的例题源码不再是一堆压箱底的旧文件,而是一套越用越顺手的训练工具。
本文还有配套的精品资源,点击获取