7.C++核心实践项目总结

7.总结


7.1 知识点

主要学习内容

总结目的

完成需求有千百种方式

练习


7.2 核心要点速览

架构与设计(UML + 面向对象原则)

UML 在项目中的落地方式

核心点 落地价值 实战建议
类图先行 先确定类职责、关系、依赖方向,再落代码 对贪吃蛇这种小项目,先画场景系统与核心对象图
关系识别 组合、聚合、依赖、继承是拆模块的依据 场景类共享逻辑可抽基类,状态切换依赖枚举和接口
从图到代码 类图不是文档摆设,要映射到头文件和实现文件 类图中的每个职责都对应到一个清晰的类

七大原则在本系列的映射

原则 在本项目中的体现 常见误区
单一职责 Map 只管墙体,Snake 只管移动和生长 一个类既管输入又管渲染又管状态
开闭原则 新场景通过实现同一接口接入,不改主循环框架 每加场景就改大量旧逻辑
依赖倒置 通过 ISceneUpdateIDraw 做抽象依赖 业务层直接依赖具体类导致耦合变重
组合复用 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 运动、碰撞、生长 FoodMap 交互判断
Map/Food 场地边界与目标点 提供碰撞边界和目标生成

7.3 面试题精选

基础题

1. deletedelete[] 的区别是什么?什么时候会出错?

题目

解释 deletedelete[] 的使用边界,并说明常见错误场景。

深入解析

核心是“分配方式和释放方式必须严格配对”。new 分配单对象,配 deletenew[] 分配连续对象数组,配 delete[]
栈上指针数组不是 new[] 产生的数组本体,不能直接 delete[];它只需要逐个释放内部指针指向的堆对象。
堆上二级指针数组要分两层释放:先释放每个元素指向的对象,再释放指针数组本身。

答题示例

判断规则就一条:谁分配谁配对。newdeletenew[]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. 这个贪吃蛇项目的架构如何体现“低耦合、可扩展”?

题目

从接口抽象、场景切换、对象协作三个角度做架构分析。

深入解析

项目把“场景更新能力”抽到 ISceneUpdateGame 只依赖抽象并统一驱动。
场景切换通过 E_SceneType + ChangeScene 收口,避免散落式状态跳转。
GameScene 组合 Map/Snake/Food,每个类职责明确,新增玩法时可在局部扩展而非全局改动。
这种设计在小项目中就能提前建立工程化思维:接口隔离、职责分离、生命周期清晰。

答题示例

这个项目的关键是“主循环稳定、模块可替换”。Game 只管驱动当前场景接口,不关心具体实现;切场景统一走 ChangeScene;运行场景内部再组合地图、蛇、食物。这样新增场景或替换规则时,大多是增量改动,不会牵一发而动全身。

参考文章
  • 1.必备知识点-UML类图
  • 2.必备知识点-面向对象七大原则
  • 6.贪吃蛇实践


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏