
简介这是一份以深海炸弹小游戏为载体的C期末大作业完整资料包特别适合刚学完语法、想通过项目巩固面向对象编程的初学者。压缩包内共79个文件、约41.92MB既有cpp/h源码和可直接运行的exe也有Visual Studio工程配置sln、vcxproj、dsw等、docx格式实验报告及运行截图源码、构建配置与文档分布清晰工程内还包含调试文件与资源文件可在VS中直接打开编译运行。游戏核心是控制潜艇在深海发射炸弹摧毁目标涉及类的封装、碰撞检测、计分逻辑、图形界面交互等经典知识点其中爆炸效果与GUI部分可引导读者接触SDL/SFML等图形库的用法。报告书涵盖设计思路、技术实现细节和常见问题排查能帮助理解从需求分析、模块划分到编码调试的完整流程。目前已有1168人学习下载对准备课程设计或自学实战的C入门者来说是一份可直接运行的参照模板。1. 一个能跑的 C 期末大作业比一百个教程都顶用期末拿到“用 C 做一个游戏”的作业从零写到一半才发现坑比想象的多消息循环、窗口重绘、碰撞判定、随机刷新任何一个环节卡住整个程序都跑不起来。这份深海炸弹小游戏资源正好是解决这类问题的完整样本——它包含 C 源代码、可直接运行的 exe 文件以及一份实验报告书是一个能编译、能运行、能讲解的完整 MFC 对话框项目。适合三类人急着交 C 期末大作业的学生、想通过一个完整 c 游戏项目入门 MFC 和面向对象编程的人以及想研究“小船发射炸弹炸目标”这类核心玩法到底怎么在代码里落地的初学者。拿到手不是让你照抄交差而是把它当骨架学会读懂别人的工程再改成自己的东西。2. 先读懂工程骨架MFC 消息循环与四类核心对象2.1 为什么期末大作业首选 MFC 对话框程序在 Windows 平台上做一个 C 小游戏摆在面前的无非三条路纯 Win32 API 手写窗口、MFC 封装框架、或者 SDL/SFML 这类第三方图形库。纯 Win32 API 的窗口过程、消息处理、设备上下文都要自己写一个“显示一张图片”就要几十行代码初学者光是把窗口建出来就耗掉大半时间。SDL 和 SFML 功能现代但需要额外配置依赖库期末环境里老师不一定支持机房也可能没装。MFC 的对话框程序则是折中方案Visual Studio 自动生成了OnInitDialog和消息映射你只需要在对话框上画控件、挂定时器、写逻辑剩下的窗口生命周期交给框架管。这份资源里的主类是ex_1lgqDlg从文件名ex_1lgqDlg.cpp和ex_1lgqDlg.h能看出来这是一个典型的 MFC 对话框工程。游戏界面、按钮响应、定时刷新全部挂在这个对话框类上。Spy 类的消息分发机制在这里体现得特别清楚用户按键盘、定时器到点、鼠标点击都会变成消息被 MFC 的消息映射表分发到对应的处理函数。初学者最容易忽略的一点是MFC 程序不是“从上到下顺序执行”的而是“等消息、分消息、处理完继续等”理解这个模型再去看代码很多顺序问题就通透了。这类项目做完你能带走的能力很实际会看BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间的映射知道哪个宏对应哪个消息会区分OnTimer、OnPaint、OnKeyDown分别在什么时候被调用会用对话框资源编辑器摆放静态控件。这些技能出去做任何 Windows 桌面工具都用得上不会白学。2.2 拆文件清单从 .dsw 到 .sln 的工程全貌解压后第一个感受是文件多而杂光顶层就有几十项。我建议先看骨架再管血肉。清单里有几组关键文件值得注意。ex_1lgq.dsw和ex_1lgq.dsp是 Visual C 6.0 时代的工程文件格式而ex_1lgq.sln和ex_1lgq.vcxproj是 Visual Studio 2008 之后的新格式。同一套源码里两种格式都存在说明这个工程从老版本 VS 迁移过但作者保留了旧文件作为备份。你打开工程时优先选.sln不要碰.dsw否则可能触发不兼容提示。文件/目录作用使用建议ex_1lgq.sln/.vcxproj新版 VS 工程入口双击打开主力入口ex_1lgq.dsw/.dspVC6 老工程文件备用通常不碰ex_1lgqDlg.cpp对话框主逻辑游戏循环、按键响应的核心MyShip.cpp/Submarine.cpp船与目标对象移动和绘制逻辑Bomb.cpp/Explosion.cpp炸弹与爆炸效果碰撞判定和视觉反馈Score.cpp计分逻辑分数累加与显示res/与.rc图标、字符串、对话框资源改界面文案时动这里Debug/exe 与编译中间文件找ex_1lgq.exe还有几个带.aps、.ncb、.sdf、.ipch后缀的文件这些是 VS 的智能感知和资源缓存文件属于黑匣子程序本身不依赖它们删掉也能重新生成。ReadMe.txt是 VS 自动生成的项目说明我建议大家先读一行它会写清楚这个工程是基于什么框架创建的对判断版本兼容性很有帮助。StdAfx.h是预编译头文件#include StdAfx.h出现在每个 cpp 的第一行这是老 MFC 工程的固定写法不要自作主张删掉。真正值钱的资产不在这些工程文件里而是作业报告目录下的实验报告 docx 和三张结构图流程.PNG、可视化图形与cpp分布.PNG、运行图.PNG。报告会把设计思路、模块划分、遇到的问题写清楚这是期末答辩时老师最想看的东西也有助于你快速定位“哪个类管哪块逻辑”。2.3 船、炸弹、爆炸、计分四个类如何协作从类名能直接看出作者用的面向对象思路MyShip管玩家控制的船Submarine是被打击的敌方目标Bomb是发射出去的炸弹实体Explosion管爆炸动画Score独立负责计分MyObject看起来是所有游戏对象的基类。一个典型的游戏帧流程是这样的玩家按下发射键对话框类ex_1lgqDlg创建一枚Bomb对象挂到链表里每个定时器周期所有Bomb调用自己的Move()向上移动每次移动后遍历当前所有Submarine目标用炸弹坐标和目标坐标做距离判断命中后触发Explosion生成爆炸动画同时调用Score::AddScore()加分目标被标记移除。这里ex_1lgqDlg是导演四个对象类各司其职谁都不越权。// 示意对话框类持有游戏对象OnTimer 里统一调度 void CEx_1lgqDlg::OnTimer(UINT_PTR nIDEvent) { // nIDEvent 用于区分多个定时器游戏场景一般只挂一个 if (nIDEvent GAME_TIMER_ID) { m_Bomb.Move(); // 炸弹每帧向上移动 CheckCollision(); // 碰撞检测 m_Explosion.Update(); // 爆炸动画推进 UpdateScoreDisplay(); // 刷新计分显示 Invalidate(FALSE); // 触发重绘 } CDialogEx::OnTimer(nIDEvent); // 调用基类版本保持消息不中断 }参数说明GAME_TIMER_ID是一个自定义的整数常量一般定义为#define GAME_TIMER_ID 1用来防止多个定时器的回调互相串号。Invalidate(FALSE)第二个参数FALSE表示不擦除背景能明显减少闪烁。CheckCollision不是 MFC 标准函数是作者写在对话框类里的私有方法这也是常见做法——把不属于某个具体对象的协调逻辑放在控制类里。类与类之间是单向依赖对话框类依赖所有对象类Bomb不依赖SubmarineScore被对话框调用。这种设计的好处是你想替换爆炸效果只动Explosion类不会牵一发动全身。初学者做游戏容易犯的反模式是把所有逻辑堆在对话框类里一个 cpp 几千行最后自己都找不到函数在哪。这个项目在类划分上做了一个好示范值得照搬。3. 游戏循环背后的三个机制定时器、碰撞检测与随机数3.1 定时器驱动一切SetTimer 与 OnTimer 的消息管道控制台程序可以写死循环跑游戏逻辑但 MFC 程序的主线程属于消息泵不能阻塞一阻塞窗口就卡死。所以 MFC 游戏的标准做法是用SetTimer弄一个周期性的定时器消息让 Windows 每几十毫秒往你的窗口扔一条WM_TIMER程序在OnTimer里做一小步更新然后立即返回。// 在 OnInitDialog 里启动定时器 SetTimer(GAME_TIMER_ID, 50, NULL); // 参数1定时器ID用来区分多个定时器 // 参数2间隔时间单位毫秒50ms 约等于每秒更新20帧 // 参数3回调函数填NULL表示消息发给窗口 // 在 OnDestroy 里释放定时器 KillTimer(GAME_TIMER_ID);这里的 50 毫秒值得细说。市面上常见游戏是 60 FPS约 16ms 一帧但对深海炸弹这种物体移动速度不快的休闲小游戏20 FPS 够用而且 CPU 占用低。改大数值游戏节奏变慢改小到 10ms画面是流畅了但OnTimer的执行频率会压垮 CPU而且如果单帧逻辑超过 10ms消息会堆积游戏越来越卡这是新手常踩的性能坑。在OnTimer里不要做耗时操作比如循环遍历一个大数组做碰撞检测、加载图片、播放声音。这些事应该提前做好或者分批做。如果检测到单帧执行时间过长常见做法是记录上次执行的时间点用GetTickCount()计算真实帧间隔让移动距离和时间挂钩而不是每帧固定移动一个像素。这个项目里大概率是固定步长但你自己改的时候要意识到 50ms 不是绝对均匀的Windows 定时器精度只有 15ms 左右会有抖动。3.2 碰撞判定用坐标距离判断“炸没炸到”碰撞检测是游戏逻辑里最直白的部分。炸弹在画布上有一个中心坐标(bombX, bombY)潜艇目标也有一个中心坐标(subX, subY)两者都是基于同一个客户区坐标系。最简单的判定就是算两点的欧几里得距离小于某个阈值就算命中。这个阈值一般取两个对象半径之和或者取一个固定值当“爆炸半径”。bool CheckHit(float bombX, float bombY, float subX, float subY, float hitRadius) { float dx bombX - subX; float dy bombY - subY; float distSq dx * dx dy * dy; // 直接用平方距离和半径平方比省去开方运算 return distSq (hitRadius * hitRadius); }参数说明dx和dy是两点的坐标差distSq是距离的平方之所以不写成sqrt(distSq)而是和hitRadius * hitRadius比是因为sqrt是相对昂贵的浮点运算在每帧要对几十个目标循环判断时省掉它能省一大截 CPU。hitRadius的取值取决于你图片素材的实际尺寸调太小会出现“明明砸中了却没反应”调太大则会出现“没碰到也判定炸了”的假阳性这个值建议在OnInitDialog里做成宏或成员变量方便反复试。真正的坑在于坐标基准不一致。MFC 的OnPaint里使用CPaintDC绘制时坐标默认是客户区左上角为 (0,0)向右向下为正。但如果你用GetClientRect拿到的矩形宽度是包含滚动条的或者图片素材本身自带透明边距实际显示的中心点和你在逻辑里算的中心点就对不上导致永远检测不到碰撞。排查时我会先在OnPaint里画一个十字叉标出目标中心点用肉眼确认坐标对不对比盲调数字快得多。还有一种常见做法是 AABB 轴对齐包围盒检测即比较两个矩形的左、右、上、下边界是否重叠。它的优点是计算简单、不涉及浮点乘法适合矩形碰撞缺点是圆形物体爆炸范围有误差。起点项目用简单的圆心距离就够了能把“判定命中→加分→移除目标”这条链路跑通后面再升级也不突兀。3.3 随机刷新目标rand 的种子与分布陷阱游戏里目标被炸毁后要让它在别的位置重新出现否则玩第二次的人直接背板。这里用到的就是 C 标准库的rand()。// 在初始化或目标重生时调用 srand((unsigned int)time(NULL)); // 生成目标位置 float newX rand() % (clientWidth - 100) 50; float newY rand() % (clientHeight / 2) 50;参数说明time(NULL)返回当前时间的秒数每次运行游戏时的种子不同随机序列才不同。clientWidth - 100是为了把生成范围向内缩 50 像素避免目标刷到窗口边缘被裁掉一半clientHeight / 2限制目标只出现在上半部分和炸弹从下往上的弹道配合避免一出生就撞到炸弹。这里有两层坑。第一层是种子问题srand放在OnInitDialog里可以但如果你把srand写在rand()调用所在的函数里循环里每次刷新都重新播种此时time(NULL)返回的秒级时间在快速循环中可能相同导致连续几次生成的位置完全一样。第二层是rand() % N的低位分布问题C 标准库rand的算法在部分平台上低位周期短适合玩具项目工程上要更均匀的分布应该用 C11 的random库。但期末作业场景用rand完全没问题老师看的是你有没有处理种子。还有一点容易忽略随机生成的新位置要避开当前已经有目标的地方否则两个目标重叠显示成“只有一个目标”。方案是生成后做一个距离检查新位置和所有现存目标距离都大于最小间距才接受否则重新生成。加一个while循环就能解决但要设最大尝试次数比如 10 次防死循环这个属于典型的“边界条件”意识写进报告里会很加分。4. 动手复现编译、运行、改参数三连4.1 环境准备VS 版本与 MFC 组件一个都不能少原工程的.sln是老式格式但 Visual Studio 2010 到 2022 都能打开并自动升级。我建议用 VS2015 或 VS2019兼容性最稳。安装时有一个关键选项必须勾选“使用 C 的桌面开发”工作负载并且左侧勾选“适用于最新 v143 生成工具的 C MFC (x86 和 x64)”不然打开工程会报“无法找到 MFC.h”或者“找不到 afxwin.h”。现在常见做法是装 VS2022 社区版免费且支持旧工程升级。升级时会弹出“需要重新定向项目”窗口选“否”保持原来的平台工具集能少改很多编译选项。老工程代码里可能会用到已经被标记废弃的 API高版本编译器默认把这些当错误处理如果编译报 C4996 一类错误可以在项目属性→C/C→预处理器里加_CRT_SECURE_NO_WARNINGS这是 MFC 老工程迁移时最常见的开关几乎必加。按 VS2019 默认装完整桌面开发组件大约占用 10GB 空间机房机器紧张的话只装 MSVC v142 编译器和 Windows 10 SDK 也能编MFC 组件必须单独选上它不会自动带上。exe 是 x86 还是 x64 由工程的“活动解决方案平台”决定默认应该是 Win32也就是 32 位任何 Windows 都能跑这也是老工程能存活这么久的原因之一。4.2 从打开 .sln 到双击 exe细到每一步的操作清单拿到手不要先点 exe要先从源码编译一遍确认环境没问题的同时也熟悉整个构建流程。对着这份资源我建议的操作顺序如下解压long-master.zip路径中不要包含中文和空格比如放到D:\deepsea_bomb。老版编译器对中文路径的#include支持很差动不动报 “cannot open include file”玄学问题大多出在这里。双击ex_1lgq.slnVS 弹出安全提示和升级向导选“仍要打开”升级向导里选“否”。在“解决方案资源管理器”里右键解决方案名选“属性”确认“配置”为 Debug“平台”为 Win32。按 CtrlShiftB 生成解决方案输出窗口出现“生成成功”且 0 个错误。按 F5 启动调试游戏窗口弹出。此时Debug\ex_1lgq.exe已经被生成退出 VS 后直接双击它也能运行。用命令行编译也可以适合想研究构建过程的同学# 使用 VS 开发者命令提示符路径换成你自己的工程位置 cd /d D:\deepsea_bomb msbuild ex_1lgq.sln /p:ConfigurationDebug /p:PlatformWin32参数说明/p:ConfigurationDebug指定构建配置Release 也是一样但要先把工程属性里的“字符集”改成“使用多字节字符集”后面避坑部分会解释为什么。/p:PlatformWin32对应 32 位输出输出路径在.\Debug\下。如果你的 VS 装在非默认路径msbuild可能不在 PATH 里需要从“开始菜单→Visual Studio 文件夹→Developer Command Prompt”进入这一步不做好会报“msbuild 不是内部或外部命令”其实是环境变量问题不是工程问题。如果按 F5 时弹出“无法启动程序系统找不到指定的文件”先检查是不是没生成成功输出窗口会有具体错误如果生成显示成功却有红字基本都是 MFC 库缺失或平台工具集版本不对回到 4.1 勾选组件后修复安装即可。4.3 改三个参数看游戏变化速度、数量、计分规则代码能跑起来之后下一步就是动手改参数。这是理解“哪些变量控制哪些行为”最快的方式也是你写实验报告时“实验内容”部分的素材。我挑三个改动成本低、效果肉眼可见的参数。第一个是炸弹移动速度在Bomb.cpp的Move()函数里找到类似m_y - speed或y - 5的行把 5 改成 8。这个数值就是每帧炸弹向上移动的像素数定时器 50ms 触发一次所以 5 像素每帧等于每秒移动 100 像素改成 8 就是每秒 160 像素游戏难度直接上升一截。// Bomb.cpp 移动逻辑示意 void Bomb::Move() { m_y - speed; // speed 是成员变量初始值在构造函数里设 // speed 5 时下落缓慢适合新手改成 12 后会明显紧张 if (m_y 0) { m_active false; // 超出边界标记本轮炸弹失效 } }参数说明m_y是炸弹的纵坐标减号表示向上移动客户区坐标系 y 轴向下。speed的定义位置可能在构造函数里写死也可能用了函数参数传入找Bomb::Bomb构造函数后面那个赋值就行。m_active false这一步很关键不给炸弹设生命周期上限内存里会堆满出界对象迟早卡死。第二个是同一屏目标数量。在ex_1lgqDlg::OnInitDialog里能找到初始化目标列表的循环大概长这样for (int i 0; i m_targetCount; i) // m_targetCount 当前是 3 { Submarine sub; sub.Init(rand() % width, rand() % height); m_submarines.push_back(sub); }把m_targetCount从 3 改成 5画面上的潜艇变多碰撞检测每帧的比较次数从 3 次变 5 次游戏难度和 CPU 负载同时上升。注意目标数量不宜超过 20因为每一帧都要做“炸弹数 × 目标数”次距离计算虽然这个量级机器扛得住但代码习惯上要为后续扩展留余量。第三个是计分规则。Score.cpp里的AddScore函数原始逻辑大概率是命中一个目标加固定分比如m_score 10。改成根据爆炸距离得分——命中时计算炸弹中心和目标中心的距离越近分越高这能让游戏策略性变强。实现上就是把CheckHit里的distSq传出来用距离做分数映射。这个改动涉及两个文件的协作做完你会对“数据怎么跨类流动”有实感。三处改完编译运行对比默认版本的手感差异记录在实验报告的“测试结果”表格里。这个流程走完这份资源对你来说就从“别人的代码”变成了“我改过的项目”答辩时能讲得头头是道。5. 避坑记录初学 C 游戏项目最容易翻车的五个瞬间5.1 Debug 能跑 Release 崩配置不一致的第一现场现象用 Debug 配置调试几天都正常切到 Release 重新生成后运行一秒就崩溃或者画面错乱弹各种内存访问错误。原因两个常见来源。一是未初始化的成员变量Debug 模式下编译器会把内存填充为特定值指针空值能被发现Release 模式下内存内容随机空指针变成野指针二是优化选项把某些嵌套调用内联了时序变化导致依赖副作用位置的代码出问题。解决先把项目属性→C/C→优化改成“禁用”让 Release 行为贴近 Debug定位到问题后再开启优化逐步排除。同时在所有类构造函数里用初始化列表给每个成员变量赋初值尤其是坐标、激活标记、分数这三类不要依赖“默认是 0”。5.2 按钮和菜单全是方块字字符集与资源文件的坑现象代码里中文字符串在界面上显示成乱码或方块要么编译直接报错“error C2664 无法将参数从 const char [N] 转换为 LPCTSTR”。原因MFC 老工程默认使用多字节字符集而新版 VS 工程默认 Unicode 字符集。字符串字面量开始游戏在 Unicode 下是宽字符被当成窄字符处理时编码对不上资源文件.rc里的中文也可能因为文件编码不是 UTF-8 而错乱。解决右键工程→属性→常规→字符集选“使用多字节字符集”然后重新生成中文显示即可恢复。如果你硬要在 Unicode 下改源码就要把所有char*换成TCHAR*或CString改动量大期末阶段不建议。顺便检查.rc文件保存编码用 VS 打开资源文件另存为“Unicode (UTF-8 带签名)”能避免第二次踩坑。5.3 炸弹“发射”后闪退空指针与未初始化坐标现象点击发射按钮游戏正常但炸弹飞到一半程序突然无响应随后弹出“0xC0000005 访问冲突”的调试框。原因炸弹对象在离屏后被移出列表但定时器里还在用旧指针引用它或者Bomb构造函数里没有初始化m_y导致它一出场就飞出边界绘制函数访问了非法绘制区域。解决每次访问对象前检查m_active标记为false直接跳过在构造函数里用初始化列表给m_x、m_y、m_active都赋明确初值。调试时在“异常设置”里勾选“C 异常”和“访问冲突”崩溃时调试器会停在出错行比盲猜有效得多。5.4 每次重开游戏目标位置都一样随机种子没重置现象第一次运行目标分布在左上角和右下角退出重新运行目标还是这两个位置完全没有随机感。原因srand()没有被调用或者调用位置在rand()之后。rand的默认种子是常数 1每次程序启动生成的序列完全一致。解决在OnInitDialog最开始加一行srand((unsigned int)time(NULL));确认这行只执行一次。如果重开游戏需要新布局在“重新开始”按钮的处理函数里再调用一次srand同时注意time的秒级精度可能相同初始化后强制随机序列偏移一下比如在srand后连续调用几次rand()丢弃前几个结果。5.5 OnTimer 不触发ID 冲突与 KillTimer 时机现象窗口弹出后画面静止炸弹不动潜艇不换位置像是整个游戏卡死。原因SetTimer和KillTimer使用同一个 ID但 ID 被定义成了枚举值或宏某个地方把 ID 宏改动了或者SetTimer被放在OnPaint里每次重绘都重新 SetTimer定时器资源被反复抢占。解决全局搜GAME_TIMER_ID确认所有SetTimer、KillTimer、OnTimer用的是同一个常量并且SetTimer只在OnInitDialog调用一次。用 Spy 或断点检查消息是否到达窗口另外检查OnTimer函数签名是不是afx_msg void OnTimer(UINT_PTR nIDEvent)如果拼成了OnTimer(UINT nIDEvent)在老版本还能编译新版直接不进入消息映射这个细节藏得很深。6. 从“能跑”到“会调”三个验收技巧帮你把项目讲明白6.1 打断点看消息第一次把黑匣子打开运行游戏时在OnTimer第一行和CheckCollision返回处各下一个断点按 F5 启动游戏运行两秒后断点命中。此时按 F10 单步走你能亲眼看到炸弹坐标如何逐帧变化碰撞判定何时返回真Score.cpp在哪里被调用。这个方法比连读十遍源码都管用因为执行顺序一目了然。键盘消息的处理函数同理在OnKeyDown下断点按方向键观察焦点是不是真的在你这个窗口上很多“按键没反应”的 bug 其实是对话框焦点被某个按钮控件抢走了。6.2 给炸弹加“下潜速度”验证你对代码的理解推荐一个进阶练习现在炸弹从下往上匀速飞你给它加一个随时间加速的逻辑——炸弹飞出后每帧的速度递增 0.2 像素。实现方案是给Bomb类加一个float m_speed成员构造函数里设初值Move()里每帧m_speed 0.2f然后m_y - m_speed。这个改动完成后炮弹轨迹变成一条加速上升的曲线玩起来手感完全不同。void Bomb::Move() { m_speed 0.2f; // 每帧加速模拟持续推力 m_y - m_speed; if (m_y 0) m_active false; }参数说明0.2f的f后缀表示 float 常量不加会被当 double 处理并触发隐式转换警告。加速值要调平衡太大会导致玩家还没瞄准炸弹就飞出屏幕太小则和匀速没区别我试下来每帧 0.15 到 0.25 是合理区间。这个练习能验证你对“成员变量”“每帧更新”“边界清理”三个概念的理解也是实验报告里最有含金量的一段。6.3 答辩前用一张表整理改动记录临交作业前把你在源码上做的所有改动整理成一张表贴在报告附录里。格式建议如下改动位置原值新值改动效果Bomb::Move()速度58弹道更快游戏难度提升OnInitDialog目标数量35同屏目标变多碰撞频率增高Score::AddScore()计分固定 10按距离递减越准分数越高策略性增强新增m_speed成员无0.2f/帧炸弹曲线飞行这张表的价值在于它把“我改了哪行、为什么改、效果怎么样”串成了老师最容易理解的叙事线。写报告时每一行展开一段源码和现象截图就是一份实打实的实验记录比空谈“我学会了 C”有说服力得多。从那以后我每次拿到别人的工程都会先花十分钟把类名、消息映射、核心变量列表过一遍再动手改第一行代码这个习惯帮我省下了大量重复踩坑的时间希望帮到你。本文还有配套的精品资源点击获取