27个经典C++控制台程序:覆盖链表递归栈排序,比刷题更接近真实编码
简介面向C初学者和编程爱好者的经典趣味程序设计合集内含27个可直接运行的C示例程序覆盖从基础语法到面向对象、STL应用等不同学习阶段适合边练边理解核心概念。压缩包共184个文件以28个cpp源码为主体并配有dsw/dsp工程文件便于直接打开编译ncb/opt辅助文件用于记录工作区信息另有19份doc说明文档帮助梳理每段程序的要点整体仅222KB轻量便携尤其适合课前自学与课后巩固。目前已有174人学习下载。其中既有Hello World变体、数据类型、数组与字符串处理等入门示例也包含指针运算、类与继承、模板与异常处理等进阶内容并设有简单小游戏和数学问题求解器读者可在趣味项目中综合运用所学知识逐步提升调试与程序设计能力实现从模仿到自主编写的过渡。1. 二十七个经典C程序比刷题更接近真实编码某开发者期末课程设计选题阶段对着空白的 main 函数发愁了半天最后从旧硬盘翻出一个压缩包里面躺着 27 个经典趣味 C 程序。它们清一色控制台界面、键盘交互没有图形库渲染却覆盖了链表、递归、栈、排序、文件读写这些课程里最常考的知识点。资源适合三类人刚学完 C 语法、想知道代码怎么组织才像样的新手正在准备课程设计、需要一份能讲得出原理的参考程序的人面试前想快速过数据结构基本功的求职者。这 27 个程序从游戏到管理工具都有多数只用标准库不需要额外安装第三方依赖解压后本地编译就能跑。我的判断是它比刷 OJ 更接近真实编码因为每一步操作都有可见的界面反馈改错一行立刻能从运行结果里看出问题。下文先拆开这 27 个程序看它们的设计逻辑再讲怎么编译、怎么排坑、怎么改成自己的东西目标是让你拿到手就能跑跑起来就能改。2. 项目拆解27 个程序的功能布局与代码骨架拿到 rar 包第一件事不是急着解压先双击打开看一眼里面的结构。这 27 个程序被拆成一个个独立目录大部分目录就是一个 cpp 源文件加一个 IDE 工程文件单文件设计反而让阅读负担小。把整个资源当黑匣子看没有意义先弄清里面装了什么东西再决定从哪个程序下手。2.1 五类题目覆盖从游戏到工具的全部经典场景我把这批程序按功能分了五类每类考察的能力侧重点不一样整理成表格看着更直观。类别常见程序考察点游戏类贪吃蛇、俄罗斯方块、五子棋、扫雷二维数组、碰撞检测、胜负判断算法演示类汉诺塔、八皇后、约瑟夫环、迷宫寻路递归、回溯、栈、循环链表信息管理类通讯录、学生成绩、图书借阅、停车场计费结构体、链表增删改查、文件读写实用工具类万年历、进制转换、模拟计算器、凯撒加密日期计算、字符串处理、编码转换数学计算类大数加法、大数阶乘、素数分解、水仙花数数组模拟进位、数位拆分、循环优化归类之后能看出这批程序刻意覆盖了从入门到进阶的完整梯度。游戏类适合建立兴趣信息管理类最贴近课程设计常见题目算法演示类则是面试必考的底层逻辑。如果你时间有限我建议先读游戏类里的贪吃蛇再读算法演示类里的约瑟夫环最后读信息管理类里的通讯录三个读下来基本能掌握这批代码的共同套路。2.2 同一个主循环控制台程序的通用骨架读几个程序之后会发现游戏类和管理类程序在结构上极其相似都是同一个主循环思路初始化数据、进入循环、刷新界面、读取输入、更新状态然后重复。理解了这一个骨架再看哪一份代码都不陌生。// 典型控制台程序的通用主循环骨架 #include iostream #include conio.h #include windows.h using namespace std; int main() { init(); // 1. 初始化数据、绘制初始界面 while (true) { // 2. 主循环 render(); // 3. 重绘画面或输出菜单 char op getch(); // 4. 读取一个按键不需要回车 if (op q) break; // 5. 退出条件 update(op); // 6. 根据按键更新程序状态 Sleep(100); // 7. 控制刷新频率避免 CPU 占满 } return 0; }这个骨架在贪吃蛇、俄罗斯方块、迷宫寻路里几乎原样出现区别只在 update 函数内部怎么处理数据。getch() 来自 conio.h作用是立即读取一个按键不等回车Sleep(100) 的单位是毫秒100 意味着每秒刷新 10 次窗口游戏手感正好。把 Sleep 数值调小游戏速度会变快这也是后面做难度档位的入口。2.3 数据结构的落点链表、栈、二维数组都在哪里用这批程序最值钱的地方是把抽象的数据结构落到了具体场景里。贪吃蛇的蛇身用数组或链表保存每移动一次就头插尾删迷宫寻路的路径探索本质是栈走到死胡同就弹栈回退约瑟夫环用循环链表模拟人围成一圈数到 m 就摘掉一个节点。这些对应关系想通了数据结构才真正变成能用的工具。约瑟夫环是算法演示类里最有代表性的一个代码短但逻辑完整直接贴出来看。// 约瑟夫环n 个人围成一圈从 1 开始报数数到 m 的人出列 struct Node { int id; // 编号 Node* next; // 指向下一个节点 }; // 假设 createCircle(n) 已经创建好循环链表 Node* p head; Node* prev nullptr; while (p-next ! p) { // 只剩最后一个节点时停止 for (int i 1; i m; i) { prev p; // 记录前驱节点 p p-next; // 向后走一步 } prev-next p-next; // 把当前节点从链表中摘除 delete p; // 释放内存 p prev-next; // 从下一个节点继续报数 } cout 最后剩下的人编号是 p-id endl;参数含义n 是总人数m 是报数的退出阈值。循环终止条件是 p-next p说明链表只剩一个节点也就是最后幸存者。删除节点前先用 prev 记录前驱再把 prev-next 指向 p-next这一步是链表删除的标准操作顺序错了就会造成野指针。这段代码在资源里通常还配了图形化的出列顺序打印方便对着结果验证。管理类程序则把结构体数组和排序算法串起来用。学生成绩管理会定义一个 stu 结构体存学号、姓名、三科成绩然后用冒泡或快速排序按总分排序再把结果写进文件。这套组合在课程设计里出现频率极高读一遍胜过去背十道算法题。3. 运行指南从解压到跑出第一个窗口很多人下载这类资源后卡在第一步不知道用哪个软件打开也不知道怎么编译。既然都是控制台程序编译门槛其实很低任意一款 C IDE 都能处理。这一章按「解压 → 看目录 → 编译 → 跑通」的步骤走每一步都有可复现的操作。3.1 解压后的目录结构长什么样资源是一个 rar 压缩包用任意解压软件解压后典型的结构如下。27个经典趣味C程序/ ├── 01_贪吃蛇/ │ ├── snake.cpp # 源代码核心文件 │ └── snake.dev # Dev-C 工程文件 ├── 02_俄罗斯方块/ │ ├── tetris.cpp │ └── tetris.dev ├── 03_五子棋/ │ ├── five.cpp │ └── five.dev ├── ... # 其余程序依次类推 └── 27_图形面积计算/ ├── area.cpp └── area.dev每个程序一个文件夹文件夹里通常只有一个 cpp 源文件和一个 dev 工程文件。dev 是 Dev-C 的工程文件双击就能在 Dev-C 里打开整个项目没有 Dev-C 也没关系cpp 文件本身是纯文本用任何编辑器都能打开手动建工程或命令行编译都行。3.2 命令行编译g 一条命令跑通如果你电脑里装了 g 编译器比如通过 VS Code 搭配 MinGW 或者单独装了编译器编译单个 cpp 文件很简单。命令行这种方式最直接也能顺带看清编译过程到底发生了什么。cd 01_贪吃蛇 g -stdc11 snake.cpp -o snake.exe ./snake.exe第一条命令进入程序目录第二条把 snake.cpp 编译成 snake.exe第三条运行生成的可执行文件。-stdc11 指定 C 标准这批老代码多按 C98 风格写的用 c11 编译兼容性更好只会出现少量 warning 不会报错。-o 表示指定输出文件名不写的话默认生成 a.exe。如果提示找不到 conio.h 或 windows.h说明当前是 Linux 或 macOS 环境这批程序大量使用 Windows 特有的控制台函数建议直接在 Windows 下编译运行省去一堆兼容问题。3.3 在 IDE 里打开两种常用操作方式命令行适合快速验证日常修改代码还是 IDE 方便。用 Dev-C 的话直接双击 .dev 工程文件按 F11 一键编译运行。用 VS 系列 IDE 的话需要新建一个空项目把 cpp 文件添加进源文件目录再按 F5 调试运行控制台窗口会自动弹出。提示运行程序后如果窗口闪一下就不见了多半是程序正常跑完退出了。在 main 函数末尾加一行 system(pause); 或者 getchar(); 就能让窗口停住这是老 C 程序最常见的收尾写法。跑通第一个程序之后建议顺手把万年历程序也编译一次输入年份对照手机日历验证打印结果是否正确。这两步能确认编译器环境和代码本身都没问题再往后改代码心里才有底。3.4 跑一次学生成绩管理程序从菜单到文件输出资源里的信息管理类程序适合用来理解「菜单驱动」设计学生成绩管理是其中最标准的一个。编译运行后控制台会打印一个菜单等待用户输入序号。请选择操作 1. 添加学生 2. 显示全部 3. 按总分排序 4. 保存到文件并退出输入 1程序会连续提示输入学号、姓名、语文成绩、数学成绩、英语成绩输入 3程序内部会对结构体数组按总分做冒泡排序把学生信息从高到低重新排列输入 4程序用文件流把数据写入本地文本文件退出后重新运行程序还能把上次保存的数据读回来。这个「添加 → 修改 → 排序 → 保存 / 读取」的闭环是信息管理类课程设计的标准答题模板值得反复看几遍看清楚 saveToFile 和 loadFromFile 两个函数怎么跟主循环配合。4. 避坑指南老 C 程序最容易翻车的五个位置这批程序写的时间比较早加上控制台编程的固有习惯运行和改造时容易在几个固定位置踩坑。下面五条都是实际调试中反复遇到的情况每条按「现象 → 原因 → 解决」的顺序写方便对照排查。4.1 gets() 在 VS 里直接编译失败现象打开通讯录程序编译时直接报错错误信息类似 C4996说 gets 不安全被弃用。原因gets() 无法限制输入长度缓冲区溢出风险太高新标准把它废弃了VS 默认把这类警告当错误处理。老教材和这批老代码里到处都是 gets()于是编译第一步就卡住。解决把 gets(name) 换成 cin.getline(name, 20)第二个参数是最大字符数或者在项目属性里定义 _CRT_SECURE_NO_WARNINGS 宏关掉这种安全性提示。我建议前者顺手把输入约束补上比屏蔽警告更稳妥。4.2 getch() 读方向键读出两个字节现象玩贪吃蛇的时候按方向键没反应或者按一下走了两步。原因Windows 控制台里方向键不是单个字符而是两个字节的扫描码第一个字节固定是 224第二个字节才是真实方向码。老代码如果只 getch() 一次只能读到 224方向判断自然失败。解决遇到首字节是 224 时再调用一次 getch() 取真正的方向码用 72、80、75、77 分别表示上下左右。char c getch(); if (c 224) { // 方向键的固定首字节 c getch(); // 读取真正的方向码 if (c 72) { /* 上 */ } if (c 80) { /* 下 */ } if (c 75) { /* 左 */ } if (c 77) { /* 右 */ } }这段代码的注释已经把键值对应关系标清楚了。如果想偷懒把操作改成 WASD 四个字母键就永远不会踩这个坑改游戏时顺手改掉输入映射也可以。4.3 中文注释和菜单变成乱码现象编译成功后运行窗口里的中文菜单、中文提示全是乱码英文正常。原因源文件保存的编码和控制台代码页不一致。老资源多是 GBK/ANSI 编码保存而现在很多编辑器默认 UTF-8编译器按 UTF-8 解析后输出到按 GBK 显示的终端就乱成一团。解决用记事本打开 cpp 文件另存为时把编码选成 ANSI重新编译即可或者在 main 开头调用 SetConsoleOutputCP(65001) 把控制台切到 UTF-8 代码页。第一种做法影响范围最小改完这个文件只影响当前程序。4.4 整屏清屏导致画面闪烁现象贪吃蛇每次移动都一闪一闪地刷屏眼睛看着难受。原因老代码里用 system(cls) 清空整个屏幕再重画cls 每次要清一次缓冲区量一大就闪烁。解决用 SetConsoleCursorPosition 定位光标只在需要更新的位置重新绘制不清空全屏。常用的封装函数如下。void moveTo(int x, int y) { COORD pos { x, y }; // COORD 是 Windows 定义的结构体 SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos); }调用 moveTo() 把光标挪到指定坐标再输出字符画面就稳定了。做俄罗斯方块这类需要频繁刷新的程序时这招是必学的。4.5 链表删除节点后野指针崩溃现象停车场计费或通讯录程序运行到删除记录时偶尔崩溃或者删完之后打印数据出现乱码。原因内存释放之后没有把指针置空后续代码访问了已经 delete 的节点。delete p 会把 p 指向的内存回收但 p 本身还保存着旧地址它成了野指针。解决先备份后释放释放后立刻置空养成习惯。Node* t cur-next; // 先备份待删除节点 if (t ! nullptr) { cur-next t-next; // 断开链表 delete t; // 释放内存 t nullptr; // 置空指针防止野指针 }如果改完链表类的程序还崩溃优先怀疑删除节点的前驱有没有保存对这是链表题调试的血泪经验。5. 二次开发把控制台程序改成自己的课程设计资源里的程序都能跑但真要用来交课程设计还得跟别人做出差异化。这里给两个最小的改造方向一个改游戏参数一个补完整算法逻辑。5.1 给贪吃蛇加等级与变速原版贪吃蛇速度基本恒定玩两分钟就腻。最简单的改造是把 Sleep 延时跟分数绑定分数越高速度越快。int level 1; int speed 200 - level * 30; // 延时随等级递减最低 80 if (score 50) level 2; if (score 100) level 3; Sleep(speed); // 用计算后的 speed 代替固定值speed 的初始值 200 毫秒level 每升一级减 30 毫秒等级到 4 时 speed 是 80 毫秒手感已经很快了。加这个逻辑只涉及一个变量和几行判断却能明显提升可玩性也能在答辩时讲出「难度曲线设计」这个亮点。5.2 给五子棋补一个可靠的五连判断五子棋程序版本很多有些版本的胜负判断只检查了当前落子点的行和列斜向连珠判断不全。改造时直接写一个通用判断函数覆盖横、竖、左斜、右斜四个方向。bool checkWin(int board[15][15], int x, int y) { int dirs[4][2] {{1, 0}, {0, 1}, {1, 1}, {1, -1}}; int color board[x][y]; // 当前落子颜色 for (int d 0; d 4; d) { int cnt 1; for (int sign -1; sign 1; sign 2) { // 正反两个方向扩展 int nx x dirs[d][0] * sign; int ny y dirs[d][1] * sign; while (nx 0 nx 15 ny 0 ny 15 board[nx][ny] color) { cnt; nx dirs[d][0] * sign; ny dirs[d][1] * sign; } } if (cnt 5) return true; } return false; }board 是 15 路棋盘数组0 表示空1 和 2 表示两种颜色dirs 定义了四个方向的坐标增量sign 在 -1 和 1 之间切换意思是从落子点沿某个方向正着数一遍、反着数一遍。cnt 累加两个方向上的相同颜色棋子数大于等于 5 就胜利。这个函数单独抽出来后续加 AI 判断、加悔棋功能都不会动到界面代码。从那以后我每次拿到这种老程序都会先全局搜一遍 gets、getch、Sleep 和 system(cls) 这几个高频雷点再动手改业务逻辑改完一个程序就编译运行一次确认没炸再继续下一个。这套流程救了我很多次希望帮到你。本文还有配套的精品资源点击获取