7.总结
7.1 知识点

主要学习内容

总结目的

完成需求有千百种方式

练习

7.2 核心要点速览
架构与设计(UML + 面向对象原则)
UML 在项目中的落地方式
| 核心点 | 落地价值 | 实战建议 |
|---|---|---|
| 类图先行 | 先确定类职责、关系、依赖方向,再落代码 | 对贪吃蛇这种小项目,先画场景系统与核心对象图 |
| 关系识别 | 组合、聚合、依赖、继承是拆模块的依据 | 场景类共享逻辑可抽基类,状态切换依赖枚举和接口 |
| 从图到代码 | 类图不是文档摆设,要映射到头文件和实现文件 | 类图中的每个职责都对应到一个清晰的类 |
七大原则在本系列的映射
| 原则 | 在本项目中的体现 | 常见误区 |
|---|---|---|
| 单一职责 | Map 只管墙体,Snake 只管移动和生长 |
一个类既管输入又管渲染又管状态 |
| 开闭原则 | 新场景通过实现同一接口接入,不改主循环框架 | 每加场景就改大量旧逻辑 |
| 依赖倒置 | 通过 ISceneUpdate、IDraw 做抽象依赖 |
业务层直接依赖具体类导致耦合变重 |
| 组合复用 | GameScene 组合 Map/Snake/Food |
只靠继承堆功能,基类越改越臃肿 |
C++ 内存与构造语义
构造、初始化列表、默认构造的边界
| 场景 | 正确做法 | 关键原因 |
|---|---|---|
| 类成员是对象 | 用初始化列表初始化 | 构造发生在函数体前,函数体内赋值不等于构造 |
| 类成员是指针 | 明确初始化为 nullptr,按需 new |
未初始化指针参与 delete 是未定义行为 |
| 对象数组 | 保证存在无参构造或提供可行初始化方式 | 数组每个元素都要可构造 |
注意:析构中 delete 指针前,先保证该指针来源清晰且可释放;释放后立即置 nullptr。
delete / delete[] 的配对规则
| 分配方式 | 释放方式 | 备注 |
|---|---|---|
new T |
delete p |
单对象释放 |
new T[n] |
delete[] p |
连续对象数组释放 |
栈上指针数组 T* a[n] |
逐个 delete a[i] |
数组本体在栈上自动销毁 |
堆上指针数组 T** p = new T*[n] |
先逐个 delete p[i],再 delete[] p |
两层内存都要释放 |
实时输入与游戏主循环
控制台非阻塞输入
| API | 作用 | 组合方式 |
|---|---|---|
_kbhit() |
检查是否有键盘输入 | 作为输入门卫,避免阻塞主循环 |
_getch() |
读取按键且不回显 | 在 _kbhit() 返回真时读取 |
if (_kbhit()) {
int input = _getch();
// 处理方向切换
}
// 无输入时继续更新游戏逻辑
贪吃蛇项目结构主线
| 模块 | 核心职责 | 协作关系 |
|---|---|---|
Game |
生命周期与场景切换 | 持有当前场景抽象并驱动 Update |
BeginScene/EndScene |
菜单与状态跳转 | 复用基类输入与展示逻辑 |
GameScene |
运行时逻辑调度 | 组织地图、蛇、食物协同更新 |
Snake |
运动、碰撞、生长 | 与 Food、Map 交互判断 |
Map/Food |
场地边界与目标点 | 提供碰撞边界和目标生成 |
7.3 面试题精选
基础题
1. delete 和 delete[] 的区别是什么?什么时候会出错?
题目
解释 delete 与 delete[] 的使用边界,并说明常见错误场景。
深入解析
核心是“分配方式和释放方式必须严格配对”。new 分配单对象,配 delete;new[] 分配连续对象数组,配 delete[]。
栈上指针数组不是 new[] 产生的数组本体,不能直接 delete[];它只需要逐个释放内部指针指向的堆对象。
堆上二级指针数组要分两层释放:先释放每个元素指向的对象,再释放指针数组本身。
答题示例
判断规则就一条:谁分配谁配对。
new对delete,new[]对delete[]。如果是T* arr[5]这种栈上指针数组,不能delete[] arr,只能逐个delete arr[i]。T** arr = new T*[5]这种两层结构,要先删对象层,再删指针数组层,不然要么泄漏要么崩溃。
参考文章
- 5.必备知识点-关于delete[]
2. 为什么自定义类成员通常建议用初始化列表初始化?
题目
说明构造函数体赋值与初始化列表初始化的本质区别。
深入解析
成员对象在进入构造函数体前就已经构造完成。
如果不在初始化列表中初始化成员对象,而在函数体里赋值,本质是“先默认构造,再赋值”,可能多一次构造/析构开销,还可能引入临时对象。
当成员类型没有无参构造时,只有初始化列表才能正确构造该成员。
答题示例
对象成员的构造时机早于构造函数体,所以推荐初始化列表。函数体赋值不等于构造,只是二次赋值。遇到没有默认构造的成员类型,初始化列表是必选项,不然直接编译失败。
参考文章
- 3.必备知识点-构造函数知识补充
进阶题
1. 贪吃蛇的主循环里,为什么要把输入检测做成非阻塞?
题目
结合控制台版实时游戏,说明阻塞输入对帧循环的影响和改造方式。
深入解析
阻塞式 _getch() 会卡住循环,导致移动、碰撞、渲染全部暂停。
非阻塞模式用 _kbhit() 先探测输入,再按需 _getch(),无输入时仍持续执行更新逻辑。
这是一类典型“输入系统与逻辑系统解耦”问题:输入是事件触发,逻辑是持续推进。
答题示例
实时循环最怕阻塞。直接
_getch()会让帧更新停住,角色就像卡死。正确做法是_kbhit()先判断,有输入再读键值;没输入就继续跑移动和碰撞。这样输入是可选事件,游戏更新是持续进行的。
参考文章
- 4.必备知识点-不卡死控制台的输入检测
- 6.贪吃蛇实践
深度题
1. 这个贪吃蛇项目的架构如何体现“低耦合、可扩展”?
题目
从接口抽象、场景切换、对象协作三个角度做架构分析。
深入解析
项目把“场景更新能力”抽到 ISceneUpdate,Game 只依赖抽象并统一驱动。
场景切换通过 E_SceneType + ChangeScene 收口,避免散落式状态跳转。GameScene 组合 Map/Snake/Food,每个类职责明确,新增玩法时可在局部扩展而非全局改动。
这种设计在小项目中就能提前建立工程化思维:接口隔离、职责分离、生命周期清晰。
答题示例
这个项目的关键是“主循环稳定、模块可替换”。
Game只管驱动当前场景接口,不关心具体实现;切场景统一走ChangeScene;运行场景内部再组合地图、蛇、食物。这样新增场景或替换规则时,大多是增量改动,不会牵一发而动全身。
参考文章
- 1.必备知识点-UML类图
- 2.必备知识点-面向对象七大原则
- 6.贪吃蛇实践
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com