简介:C++编程实例100篇是一套汇集100个C++编程实例的源码压缩包,面向C++初学者及希望巩固基础知识的开发者,通过实例讲解变量声明、数据类型、运算符、流程控制、函数定义等基础语法,并覆盖类与对象、封装继承与多态、模板与泛型、文件读写、异常处理以及标准模板库容器与算法等进阶主题,能帮助读者夯实语法基础,提升面向对象设计与泛型编程能力。压缩包共包含557个文件,主要类型包括107个cpp源文件、100个exe可执行程序、106个bmp示例图片,以及头文件、VC工程文件、图形驱动文件等辅助文件,整体大小仅2.73MB,便于下载与归类学习。目前已有1659人浏览学习,通过运行这些可执行程序,可直观理解现代C++特性(如lambda表达式、auto自动类型推导),并对照输出结果调试源码,无论是自学、教学还是复习备考,都是实用且高效的参考资料。
1. 100篇C++实例源码,下载之后的第一件事不是打开
在各类技术论坛的网盘分享里,C++编程实例100篇这类压缩包几乎是常青资源,下载量动辄几十万。但大多数人的使用记录停留在解压那一刻:看到一百个命名随意、风格各异的cpp文件堆在同一个文件夹里,翻不到十个就关掉了。原因不在实例本身,而在于这类资源没有学习路径——它只给了你散装源码,没告诉你按什么顺序编译、哪些例子已经过期、哪些代码可以拆出来直接用。这里打算把这个过程补完:从配置编译环境、按主题拆解实例、处理典型编译错误,到把能用的代码改造成自己的函数库。适合手里已有类似资源但一直没下手的读者,也适合想快速找回C++手感的人。
2. 搭建可编译的C++环境:从命令行到CMake统一管理100个实例
2.1 VSCode配置C/C++环境的最小做法
拿到一百个cpp文件,第一步不是读代码,而是保证它们能被编译。编译环境我建议选 VSCode + GCC,理由有二:一是这套组合完全免费、跨平台一致;二是实例包里的代码大多是单个main函数的短文件,VSCode 的单文件编译工作流比 Visual Studio 建解决方案要快得多。
解压后先花五分钟做一次目录整理:把一百个 cpp 统一收进 examples/ 子目录,编译产物放 bin/,编译日志放 logs/。后面所有批量操作都依赖这个结构,顺序不要颠倒。配置步骤按常见做法是:下载 MinGW-w64 工具链(选 x86_64-posix-seh 版本),把 bin 目录加进系统 PATH;VSCode 安装微软官方的 C/C++ 扩展;然后在终端里执行g++ --version确认版本,能打印出版本号就算就绪。不需要额外装 Code Runner 之类的插件,默认的 C/C++ 扩展已经自带构建和调试能力。
g++ --version gcc --version两条命令输出一致说明工具链可用,其中 gcc 和 g++ 的前三位版本号最好一致,否则可能是混装了多个发行版。这里不推荐用在线编译器代替本地环境,原因是后续要做批量编译和性能对比,文件系统访问和运行耗时统计是刚需,浏览器里跑不了这些验证。
2.2 命令行编译单个C++实例的参数拆解
单个实例的编译命令我一般写成这样:
g++ -std=c++17 -Wall -Wextra -O0 -g example001.cpp -o bin/example001参数含义如下:-std=c++17是必不可少的,实例包里大量代码是最近几年的写法,默认的 gnu++14 也能编,但 C++17 的std::string_view、结构化绑定会让老代码少很多改动的机会;-Wall -Wextra把警告全开,实例源码常有一堆无符号有符号比较的警告,开着能提前知道哪行有隐患;-O0配合-g是给调试器准备的,跑逻辑题用不到优化。编译产物统一放进 bin/ 目录,和源码分开,方便后面写批量脚本。
常用参数速查:
| 参数 | 作用 | 使用场景 |
|---|---|---|
| -std=c++17 | 指定语言标准 | 所有实例统一用 C++17 |
| -Wall -Wextra | 开启全部警告 | 定位潜在隐患 |
| -O0 -g | 关闭优化、保留调试信息 | 单步调试 |
| -O2 | 开启常规优化 | 算法性能对比 |
| -fsanitize=address | 开启内存越界检测 | 运行期崩溃排查 |
一百个实例挨个执行上面的命令显然不现实,批量编译的正道是一个三行脚本:
mkdir -p bin logs for f in examples/*.cpp; do name=$(basename "$f" .cpp) g++ -std=c++17 -Wall "$f" -o "bin/$name" 2>"logs/$name.log" \ && echo "OK $name" || echo "FAIL $name -> logs/$name.log" done这个脚本会把每个文件的编译错误输出到独立日志里,&&与||把结果分成两类打印。跑完之后扫一遍 FAIL 列表,基本就能定位这个实例包的质量状况:哪些文件缺头文件、哪些用了已删除的库函数,问题会比想象中集中。
2.3 用 CMake 把100个实例组织成统一工程
批量脚本适合一次性验证,但如果你打算长期维护这批实例,CMake 是更可靠的方案。用file(GLOB ...)收集所有源文件,循环为每个 cpp 生成一个可执行目标:
cmake_minimum_required(VERSION 3.16) project(cpp100 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) file(GLOB EXAMPLE_SRC CONFIGURE_DEPENDS examples/*.cpp) foreach(src ${EXAMPLE_SRC}) get_filename_component(name ${src} NAME_WE) add_executable(${name} ${src}) endforeach()逐行说明:CONFIGURE_DEPENDS让 CMake 在每次构建前重新扫描目录,新增 cpp 文件后不需要手动改 CMakeLists;NAME_WE取文件名去掉扩展名的部分,作为目标名。生成的 Makefile 支持make example001单独编译某一个,也支持make -j8全量编译,后者会在第一处错误处停下来,配合make example001 VERBOSE=1能看到完整编译命令行,排查 include 路径问题非常有用。
CMake 方案的另一个好处是天然兼容 Visual Studio 的生成器。Windows 上想用 MSVC 编译时,cmake -G "Visual Studio 17 2022"一条命令就能切换,不需要重写任何构建逻辑。
3. 拆解100个C++实例的主题分类:语法、算法、类与流的复用点
3.1 基础语法类实例:C++字符串数组初始化与水仙花数
一百个实例里数量最多的永远是基础语法类,它们的价值密度不高,但恰好覆盖了搜索引擎里高频出现的几个关键词:C++字符串数组初始化、水仙花数、判断质数。我处理这类实例的顺序是:先把代码跑通,再对照自己写过的版本找差异。先按主题给实例分类,后续筛选会省事很多:
| 分类 | 典型实例 | 复用价值 |
|---|---|---|
| 基础语法 | 字符串数组初始化、水仙花数 | 中 |
| 算法 | 冒泡排序、二分查找、质数判断 | 高 |
| 类设计 | 继承、覆盖与隐藏 | 高 |
| 文件I/O | 流读写、逐行解析 | 高 |
| 小游戏 | 猜数字、贪吃蛇简化版 | 低 |
字符串数组初始化是最容易出错的点,实例包里的写法通常是三种并存:
// 写法一:C风格指针数组 const char* legacy_names[] = {"alpha", "beta", "gamma"}; // 写法二:std::string 数组 std::string names[] = {"alpha", "beta", "gamma"}; // 写法三:C++11 起的 vector 初始化 std::vector<std::string> name_list = {"alpha", "beta", "gamma"};三种写法对应不同的内存语义:const char*数组只存指针,字符串字面量存放在只读数据段,不能修改内容;std::string数组每个元素管理自己的堆内存,可修改但构造开销更大;vector则把容量管理也接管了。实例源码里经常出现写法一向写法二赋值的代码,直接改legacy_names[0]指向的内容会触发编译错误或运行期崩溃,这是读到这类代码时值得停下来想两分钟的地方。
水仙花数实例在前段时间的编程题热搜里出现过完整描述,它的实现正好用来讨论浮点运算的边界:
#include <iostream> int main() { for (int n = 10000; n <= 99999; ++n) { int sum = 0, m = n; while (m) { int d = m % 10; sum += d * d * d * d * d; // 避免 pow 的浮点调用 m /= 10; } if (sum == n) std::cout << n << '\n'; } }这里刻意用d * d * d * d * d而不是std::pow(d, 5)。单数字的五次方最大不过 59049,double 能精确表示,所以 pow 在这个规模下不会出现精度问题;但 pow 走的是浮点函数调用链,比五个整数乘法慢一个量级,而且一旦把实例改造成处理 long long 范围或把指数改成运行时变量,浮点舍入导致的 sum 差 1 就得花时间排查。实例包里的老代码用 pow 不算错,但替换成整数乘法是更值得养成的习惯。
3.2 算法类C++实例:冒泡排序、二分查找与质数判断的优化点
算法类是实例包中复用价值最高的部分。冒泡排序、二分查找、判断质数这三个几乎每套实例包都有,它们恰好代表了三类典型算法结构:双层循环的排序、区间收敛的查找、按整除性剪枝的数学判断。
// 冒泡排序,带提前退出标志 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]) { std::swap(a[j], a[j + 1]); swapped = true; } } if (!swapped) break; // 本轮无交换,说明已有序 } }这里的关键改进是swapped标志:经典教材版冒泡排序无论输入是否有序都跑满 n(n-1)/2 次比较,加了这个标志后,对接近有序的数组可以提前终止。j < n - 1 - i的边界也很容易被新手改错,它保证每轮比较范围逐步缩小,且j+1不越界。
二分查找实例最常见的实现错误是mid = (lo + hi) / 2,当lo + hi超过 int 上限时会溢出成负数。正确的写法是mid = lo + (hi - lo) / 2,这个细节在 C++ 面试里几乎是必问点,实例源码里两种写法往往并存,正好用来对比:
int binarySearch(const std::vector<int>& v, int target) { int lo = 0, hi = static_cast<int>(v.size()) - 1; while (lo <= hi) { int mid = lo + (hi - lo) / 2; // 防止 lo+hi 溢出 if (v[mid] == target) return mid; if (v[mid] < target) lo = mid + 1; else hi = mid - 1; } return -1; }循环条件是lo <= hi还是lo < hi对应两种不同的区间定义,实例里出现不一致时,判定标准只有一个:每次循环后区间是否严格收缩。lo <= hi配合mid±1收缩是闭区间写法,漏写±1会导致死循环。
提示:把
mid ± 1写错成mid是二分查找死循环的常见根源,调试时打印每一步的 lo、hi、mid 三个值,一眼就能看出来。
质数判断的优化版本同样值得从实例里抽出来单独存:
bool isPrime(int n) { if (n < 2) return false; if (n % 2 == 0) return n == 2; for (int d = 3; d <= n / d; d += 2) { if (n % d == 0) return false; } return true; }比课本版多做了三件事:排除了小于 2 的情况、单独处理偶数、循环步长用 2 跳过所有偶数。d <= n / d替代d <= sqrt(n)是刻意为之,前者是整数运算不会引入浮点舍入,也避免了 sqrt 每次循环都重算的开销。
3.3 类设计类实例:覆盖与隐藏的经典陷阱
C++ 实例包到了中段必然出现类的继承和多态示例,这里的高频检索词是"c++ 覆盖 隐藏"。初学者最容易混淆的两个概念正好可以用一小段代码演示:
class Base { public: virtual void show(int x) { std::cout << "Base::show(int)\n"; } void print() { std::cout << "Base::print\n"; } virtual ~Base() = default; }; class Derived : public Base { public: void show(int x) override { std::cout << "Derived::show(int)\n"; } void print() { std::cout << "Derived::print\n"; } // 隐藏,不是覆盖 }; Base* p = new Derived(); p->show(1); // 动态绑定,输出 Derived 版本 p->print(); // 静态绑定,输出 Base 版本 delete p;判断规则只有一条:override修饰且基类函数是virtual的才叫覆盖,走动态绑定;同签名但基类非虚,或者参数列表不同,都叫隐藏,走静态绑定。把print改成protected或用using Base::print引入基类重载,会得到完全不同的调用结果。实例里常见的错误是把隐藏当覆盖,基类指针调用后发现没走到派生类实现。把派生类里漏写override的虚函数编译一下,编译器会直接报错——这正是 100 个实例里最值得动手改的练习。
类设计类实例还要特别留意析构函数。凡是作为基类出现的类,析构函数必须声明为virtual,否则delete p时派生类析构不执行,内存泄漏在实例短程序中看不出危害,但移植到长生命周期服务里就是隐患。
3.4 文件处理类实例:C++流I/O的标准读写模型
文件读写类是实例包中功能最贴近"软件源码"的部分,高频检索词里出现过"c++流i/o"和"c++字符串数组初始化",这两者经常在同一段代码里出现——读文件拿到的原始字节要转成字符串再逐行处理:
#include <fstream> #include <sstream> #include <string> std::ifstream in("data.txt"); std::string line; while (std::getline(in, line)) { std::istringstream iss(line); // 每行拆成独立流 int id; std::string name; if (iss >> id >> name) { // 成功解析一行 } }std::getline读到 EOF 返回 false,循环自然退出,这是标准写法。std::istringstream的作用是把一行字符串变成可格式化的输入流,iss >> id >> name按空白拆分字段。这里常见的坑在 Windows 下有两种表现:以文本模式打开时 CRT 会把\r\n转成\n,大多数情况没有副作用;一旦用std::ios::binary打开,每行末尾就会残留\r,再拿去匹配字符串时就会出问题。稳妥做法是读入后统一检查尾部:if (!line.empty() && line.back() == '\r') line.pop_back();。
这个主题下实例的复用度很高:把data.txt换成管道或 socket 缓冲区,读写逻辑几乎不用改。我在处理实例包时会专门建立一个 io/ 子目录,把所有文件读写相关实例集中存放,后续找起来不用翻整个目录。
4. C++实例编译失败的三个高频原因:编码、运行库与标准版本
4.1 源码文件编码与中文字符串乱码
实例包从网上下载下来,最先遇到的问题往往不是语法而是编码。老论坛时代源码默认 GBK/GB2312 保存,而新版编译器默认按 UTF-8 解析源码,两者碰撞的结果是一堆 "converting to execution character set: Invalid argument" 或乱码注释。判断一个 cpp 文件是什么编码,在 VSCode 里看右下角编码指示器,或者用file examples/example001.cpp命令看输出里的 charset 字段。
修复不用改文件,编译时加两个参数即可:
g++ -std=c++17 -finput-charset=GBK -fexec-charset=UTF-8 example001.cpp -o example001-finput-charset告诉编译器源文件用什么编码读入,-fexec-charset指定字符串常量在可执行文件里的编码。两条组合解决源码是 GBK、控制台输出乱码的双重问题。如果源码混杂多种编码,最省事的做法是统一转成 UTF-8:iconv -f GBK -t UTF-8 example001.cpp > fixed.cpp,转完再编译就只需要-fexec-charset=UTF-8一个参数。
控制台输出方面,Windows 上老代码常见system("pause")和windows.h里的中文输出,前者在 WSL 和 CI 环境里会直接报 "command not found",后者依赖的 API 在非 Windows 平台不存在。实例包如果混入这类平台相关代码,建议直接标注"仅限 Windows"并跳过,不要浪费时间跨平台修。
4.2 visual C++ redistributable 与运行库依赖
实例包有时会附带编译好的 exe,这类"软件源码"式的资源会引出另一个高频检索词:visual C++ redistributable。用 MSVC 编译的程序依赖动态运行库,目标机器上没有对应版本就会弹 "VCRUNTIME140.dll 丢失"。判定某个 exe 依赖哪个运行库,用 VS 自带的 dumpbin 工具最直接:
dumpbin /dependents example001.exe输出里的 msvcp140.dll、vcruntime140.dll 分别对应 C++ 标准库和 C 运行库。常见的 VC++ 运行库版本对应关系如下:
| Redistributable 版本 | 关键 DLL | 典型编译环境 |
|---|---|---|
| VC++ 2005 (8.0) | msvcp80.dll | VS2005 |
| VC++ 2008 (9.0) | msvcp90.dll | VS2008 |
| VC++ 2010 (10.0) | msvcp100.dll | VS2010 |
| VC++ 2013 (12.0) | msvcp120.dll | VS2013 |
| VC++ 2015-2022 (14.x) | vcruntime140.dll | VS2015 及以上 |
高版本运行库向后兼容低版本编译的程序,14.x 系列跨 2015 到 2022 全部适用。但运行库并不能让一个程序在缺失系统组件的精简版 Windows 上运行,依赖还在 msvcp 之外的 api-ms-win-* 系列时,优先考虑重编译而不是补运行库。用 MinGW 编译的 exe 则只依赖 msvcrt.dll 和 libstdc++-6.dll,后者需要随程序一起分发。
4.3 C++ 标准版本差异与已经删除的写法
实例包里的代码跨度往往超过十年,这导致编译错误里有一类非常典型:用了老标准允许、新标准已删除的语法。gets()在 C++14 起被移除,实例里凡是读入一行字符串的代码基本都会踩这个坑;std::auto_ptr在 C++17 删除,老代码的智能指针部分需要换成std::unique_ptr;register关键字从 C++17 起不再有语义,保留字还在但已无作用。一个实际的修复对照:
// 老代码 char buf[128]; gets(buf); // 编译错误:gets 未声明 std::auto_ptr<int> p(new int(42)); // C++17 起不可用 // 新标准等价写法 std::string line; std::getline(std::cin, line); std::unique_ptr<int> p(new int(42));修复这类问题没有技巧。先确认实例包在什么标准下能编过,再加-std=c++14而不是盲目上 C++20:很多老实例在 C++14 下能零修改编译,升级到 C++17 反而报错,并非代码有问题,而是标准收紧。批量编译脚本里先跑 C++14 再跑 C++17,对比两个版本的 FAIL 列表,能快速判断出实例包的真实年代。
提示:实例包年代不明的场景,先按 C++14 编译一遍,FAIL 列表里全是 C++17 专属语法,再决定是否整体升级。
内存越界相关的问题在实例包里同样高频,老代码喜欢用裸指针加固定数组,运行期崩溃比编译错误隐蔽得多。处理这类实例时我会统一加-fsanitize=address重新编译,ASan 会把越界读写定位到具体行号,比对着控制台乱码猜快十倍。
5. 把100篇C++实例重构为个人函数库的验证技巧
一百个实例看完之后,真正有留存价值的大约只有三成。我习惯把这三成代码重构成一个头文件库,每个被挑选出来的函数都配上三个以上的断言。取舍标准很直接,见下表:
| 取舍 | 实例类型 | 处理方式 |
|---|---|---|
| 必留 | 排序、查找、数学判断 | 提取为独立函数,补齐边界测试 |
| 改造后留 | 类设计、STL容器应用 | 抽象接口后重写最小复现 |
| 不留 | 纯输出演示、小游戏、平台绑定代码 | 保留原始文件,不进个人库 |
重构的模式是统一的:把 main 里的核心逻辑提出为函数,函数只接收参数、返回结果,不负责打印;所有验证集中到一个 test.cpp 里,用宏做断言:
// cpp100_utils.h 片段 #pragma once #include <vector> #include <string> bool isPrime(int n); int binarySearch(const std::vector<int>& v, int target); // 轻量断言宏 static int failures = 0; #define CHECK(cond) \ do { if (!(cond)) { \ std::cerr << "FAIL: " #cond " @line " << __LINE__ << '\n'; \ ++failures; \ } } while (0)测试用例的选取比函数本身更重要。isPrime 至少覆盖负数、0、1、2、3、大偶数、相邻质数这几个边界;binarySearch 要覆盖目标在首位、末位、中间、不存在、数组长度 1、数组长度 0 六种情况。每个从实例里提出来的函数都按这个标准写断言,然后统一编译运行:
g++ -std=c++17 -Wall -Wextra -fsanitize=address test.cpp -o test && ./test加上-fsanitize=address之后,越界读写和内存泄漏会在断言之外单独暴露;failures 计数非零说明有函数没过边界测试,逐条修即可。运行库版本这类环境问题不会在这套流程里出现,因为全部使用 MinGW 静态链接。
收尾还有一个小技巧:给库加一个版本号宏#define CPP100_UTILS_VERSION 2,用#if控制功能的兼容行为。以后再拿到新的 C++ 实例源码包,只需要把候选函数按同样的模式提取、写断言、加进 test.cpp,跑一次./test——failures 为零就合并,非零就退回。新资源能不能收编,几分钟就有结论,整个个人函数库也始终处在验证通过的状态。
本文还有配套的精品资源,点击获取