news 2026/10/18 6:11:59

C++ EasyX游戏开发:从植物大战僵尸复刻学实时图形编程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++ EasyX游戏开发:从植物大战僵尸复刻学实时图形编程

简介:本资源是一份基于C++与EasyX图形库完整复刻《植物大战僵尸》核心玩法——‘锤僵尸’机制的入门级游戏开发项目,面向C++初学者、图形编程爱好者及课程设计实践者,旨在通过可运行、可调试的真实案例掌握窗口绘图、事件响应、音效播放与简单游戏循环等关键技能。压缩包共386个文件,体量10.73MB,涵盖342张PNG素材图(含角色、背景、UI)、24个MP3音效与BGM、1个主程序PvZ.cpp、1个头文件head.h、1个VCXPROJ工程及SOLUTION配置,辅以帮助文档.md、图标.ico和数据库.db等支撑文件,结构完整、开箱即用。目前已有888人学习下载。读者可直接编译运行,深入理解EasyX坐标绘制逻辑、鼠标交互判定、多线程音效控制及资源加载管理机制,并参考清晰分层的代码组织方式,快速构建个人图形化小项目。

1. 为什么用 C++ + EasyX 复刻《植物大战僵尸》不是“复古情怀”,而是新手进阶的硬核练兵场?

你可能在某高校课程设计作业列表里见过它,也可能在某实验室低代码教学案例中刷到过——一个没有网络、不依赖 Qt 或 SDL、甚至不调用 Win32 API 直接绘图的《植物大战僵尸》简化版。它不跑在浏览器里,不靠 Unity 引擎,也不用 Python 的 Pygame 封装层;它用纯 C++ 写逻辑,用 EasyX 图形库做渲染,所有图像资源全本地加载,所有碰撞检测手写,所有状态机自己建模。这不是怀旧玩具,而是一套被低估的“图形+逻辑+时序”三重耦合训练方案:它逼你直面双缓冲闪烁、帧率抖动、鼠标事件丢失、资源释放顺序错乱等真实问题;它让“游戏循环”从教科书概念变成你亲手掐秒表调出来的 60 FPS;它把“面向对象设计”拉下神坛——当一个Sun类既要响应点击生成、又要随时间衰减、还要被多个植物共享引用时,你才会真正理解 RAII 和智能指针的不可替代性。适合刚学完 C++ 基础语法、能写链表但没碰过图形界面的开发者,也适合想甩开框架黑匣子、重新建立“程序如何驱动像素”直觉的中级工程师。这不是复刻游戏,是复刻一套可触摸的实时系统思维。


2. 搭建最小可运行骨架:从空白工程到第一株向日葵落地

EasyX 并非标准 C++ 库,它本质是 Windows 平台下对 GDI 的轻量封装,因此环境准备必须精准。常见误区是直接用 VS 新建“空项目”后盲目添加头文件——这会导致链接器找不到libeasyx.a(注意:EasyX 官方仅提供.lib,但实际编译需.a兼容格式,我们后面会处理)。正确路径是:先确认 Visual Studio 版本与 EasyX SDK 匹配(2019/2022 均支持,但 2022 需手动关闭/permissive-选项),再按官方文档要求将easyx.h放入项目 include 路径,easyx.lib放入 linker input,并强制设置子系统为 Windows(/SUBSYSTEM:WINDOWS),否则控制台窗口会闪退。

2.1 创建主窗口与双缓冲初始化

#include <easyx.h> #include <vector> #include <memory> // 全局常量定义(避免魔法数字) const int WINDOW_WIDTH = 1024; const int WINDOW_HEIGHT = 600; const int GRID_COLS = 9; // 植物列数(对应草坪格子) const int GRID_ROWS = 5; // 僵尸行数 int main() { initgraph(WINDOW_WIDTH, WINDOW_HEIGHT, INIT_RENDERMANUAL); // 关键:手动渲染模式 // 启用双缓冲(必须在 initgraph 后立即调用) setrendermode(RENDER_MANUAL); // 加载背景图(需提前准备 bg.jpg) IMAGE bgImg; loadimage(&bgImg, _T("res/bg.jpg")); // 主循环 BeginBatchDraw(); while (true) { cleardevice(); // 清屏(双缓冲专用) putimage(0, 0, &bgImg); // 绘制背景 // 此处将插入植物、僵尸绘制逻辑 FlushBatchDraw(); // 批量刷新到屏幕 // 控制帧率:目标 60 FPS → 每帧约 16.67ms Sleep(16); } EndBatchDraw(); closegraph(); return 0; }

逻辑说明:INIT_RENDERMANUAL是 EasyX 双缓冲核心开关,它让cleardevice()和putimage()操作作用于后台缓冲区,FlushBatchDraw()才真正翻转到前台。若漏掉BeginBatchDraw()/EndBatchDraw()包裹,或误用RENDER_AUTO,画面必然撕裂。Sleep(16)是粗略帧控,实际项目中应改用GetTickCount64()计算 delta time,但此处为最小骨架,先保功能。

2.2 定义植物基类与向日葵实现

EasyX 不支持 RTTI,因此多态必须靠纯虚函数+指针管理。我们不使用std::vector<Plant*>存储,而用std::vector<std::unique_ptr<Plant>>—— 因为植物生命周期由游戏逻辑控制,且需确保析构时自动释放图像资源(EasyX 的IMAGE对象需显式销毁)。

class Plant { public: virtual ~Plant() = default; virtual void update() = 0; // 每帧更新逻辑(如产阳光) virtual void render(int x, int y) = 0; // 在指定格子坐标绘制 virtual bool canBePlaced() const = 0; // 是否允许放置(冷却、阳光不足等) protected: int m_sunCost = 0; int m_cooldown = 0; int m_currentCooldown = 0; }; class SunFlower : public Plant { public: SunFlower() { m_sunCost = 50; m_cooldown = 10 * 60; // 10秒 = 10*60帧(假设60FPS) m_currentCooldown = 0; // 加载向日葵图片(需 res/sunflower.png) loadimage(&m_img, _T("res/sunflower.png")); } void update() override { if (m_currentCooldown > 0) { m_currentCooldown--; } // 每10秒产1个阳光(简化逻辑) if (m_currentCooldown == 0) { // 触发阳光生成事件(后续与阳光管理器交互) m_currentCooldown = m_cooldown; } } void render(int x, int y) override { putimage(x, y, &m_img); } bool canBePlaced() const override { return m_currentCooldown == 0; } private: IMAGE m_img; // EasyX 图像对象,需在析构时释放 };

参数说明:m_cooldown单位是“帧数”而非秒,这是游戏开发惯例——避免浮点运算和帧率波动影响计时精度。loadimage()必须在构造函数中调用,且IMAGE成员变量不能是static(否则多实例共享同一图像句柄,释放时崩溃)。putimage(x,y,&img)的(x,y)是左上角坐标,需根据格子大小计算(例如每格宽 80px,则第 2 列第 3 行的 x=801, y=1002)。


3. 实现核心交互:鼠标点击种植物与阳光经济闭环

植物放置不是简单“点击→创建对象”,它涉及坐标映射、资源校验、状态同步三大环节。EasyX 的GetMousePos()返回的是屏幕绝对坐标,而游戏世界是网格化的,必须做坐标归一化。更关键的是:阳光作为全局资源,不能散落在各个植物类里,必须由独立管理器统一调度。

3.1 网格坐标转换与点击事件捕获

// 定义格子尺寸(与背景图布局匹配) const int GRID_CELL_WIDTH = 80; const int GRID_CELL_HEIGHT = 100; const int GRID_START_X = 220; // 草坪区域起始X(避开左侧工具栏) const int GRID_START_Y = 80; // 草坪区域起始Y struct GridPos { int row = -1; // 行号 0~4 int col = -1; // 列号 0~8 }; GridPos screenToGrid(POINT pt) { GridPos pos; // 判断是否在草坪区域内 if (pt.x < GRID_START_X || pt.x >= GRID_START_X + GRID_COLS * GRID_CELL_WIDTH || pt.y < GRID_START_Y || pt.y >= GRID_START_Y + GRID_ROWS * GRID_CELL_HEIGHT) { return pos; // 无效位置 } // 计算格子索引 pos.col = (pt.x - GRID_START_X) / GRID_CELL_WIDTH; pos.row = (pt.y - GRID_START_Y) / GRID_CELL_HEIGHT; return pos; } // 主循环中加入鼠标事件处理 while (true) { // ... 渲染逻辑 ... // 检查鼠标左键按下 MOUSEMSG msg; if (MouseHit()) { msg = GetMouseMsg(); if (msg.uMsg == WM_LBUTTONDOWN) { GridPos grid = screenToGrid(msg.pt); if (grid.row >= 0 && grid.col >= 0) { // 尝试在此格放置向日葵 placeSunFlower(grid.row, grid.col); } } } FlushBatchDraw(); Sleep(16); }

逻辑说明:MouseHit()是 EasyX 的非阻塞鼠标事件轮询接口,比GetAsyncKeyState()更可靠。screenToGrid()函数必须严格匹配背景图中草坪的实际像素范围——这是复刻项目最易翻车的第一步:若GRID_START_X偏差 5px,整个点击区域就错位。建议用画图工具量取背景图中草坪左上角坐标,而非凭感觉估算。

3.2 阳光管理器与经济系统闭环

阳光不是静态数值,而是动态实体:它有生命周期(飘落动画)、有碰撞判定(被玩家点击收集)、有视觉反馈(数字叠加)。因此我们设计SunManager类,它持有所有当前活跃阳光对象,并负责生成、更新、销毁。

class Sun { public: Sun(int x, int y) : m_x(x), m_y(y), m_life(300) { // 5秒生命(300帧) loadimage(&m_img, _T("res/sun.png")); m_vy = -1; // 初始向上飘 } void update() { m_y += m_vy; m_vy += 0.1; // 重力加速度(简化) m_life--; } void render() { putimage(m_x, m_y, &m_img); } bool isAlive() const { return m_life > 0; } bool isClicked(POINT pt) const { // 简单圆形碰撞:以图像中心为圆心,半径30 int dx = pt.x - (m_x + 30); int dy = pt.y - (m_y + 30); return dx*dx + dy*dy < 900; // 30^2 } private: IMAGE m_img; int m_x, m_y; int m_vy; int m_life; }; class SunManager { public: void generate(int x, int y) { m_suns.push_back(std::make_unique<Sun>(x, y)); } void update() { // 更新所有阳光 for (auto& sun : m_suns) { sun->update(); } // 移除死亡阳光(反向遍历避免迭代器失效) for (int i = m_suns.size() - 1; i >= 0; --i) { if (!m_suns[i]->isAlive()) { m_suns.erase(m_suns.begin() + i); } } } void render() { for (auto& sun : m_suns) { sun->render(); } } bool tryCollect(POINT pt) { for (auto& sun : m_suns) { if (sun->isClicked(pt)) { m_collectedCount++; // 移除该阳光 auto it = std::find_if(m_suns.begin(), m_suns.end(), [&sun](const auto& s) { return s.get() == sun.get(); }); if (it != m_suns.end()) m_suns.erase(it); return true; } } return false; } int getCollectedCount() const { return m_collectedCount; } private: std::vector<std::unique_ptr<Sun>> m_suns; int m_collectedCount = 0; };

参数说明:m_vy是垂直速度,0.1是每帧加速度值,此数值需通过实测调整——太大则阳光飞太快收不到,太小则飘落感弱。isClicked()使用距离平方比较而非开方,是性能关键优化。tryCollect()中的std::find_if查找是临时方案,实际应给Sun添加唯一 ID 并用unordered_map管理,但此处为保持逻辑清晰暂用线性查找。


4. 避坑指南:C++ + EasyX 开发中 5 个血泪经验总结

EasyX 文档简陋,错误提示模糊,加上 C++ 手动内存管理,新手极易陷入“程序无声崩溃”的玄学困境。以下是我在多个模拟项目 X 中踩出的硬核避坑清单,每一条都对应真实崩溃现场:

4.1 现象:程序启动后黑屏 2 秒直接退出,无任何报错

原因:initgraph()调用失败但未检查返回值。EasyX 在找不到easyx.dll、显卡驱动不兼容、或 Windows DPI 缩放开启时会静默失败,返回NULL。而后续loadimage()对空IMAGE*解引用导致访问违规。
解决:在initgraph()后立即检查:

if (initgraph(WINDOW_WIDTH, WINDOW_HEIGHT, INIT_RENDERMANUAL) == NULL) { MessageBox(NULL, _T("EasyX 初始化失败,请检查 easyx.dll 是否在程序目录"), _T("错误"), MB_OK | MB_ICONERROR); return -1; }

4.2 现象:植物图片显示为马赛克或错位色块

原因:loadimage()加载的 PNG 文件包含 Alpha 通道,但 EasyX 默认不启用 Alpha 混合。putimage()直接覆盖目标像素,导致透明区域显示为随机内存值。
解决:加载前调用setalpha()启用 Alpha:

setalpha(255); // 全局 alpha loadimage(&m_img, _T("res/sunflower.png")); // 此时 PNG 透明度才生效

注意:setalpha()必须在loadimage()之前调用,且对每个IMAGE对象单独设置。

4.3 现象:鼠标点击多次只触发一次种植,或点击无反应

原因:MouseHit()是瞬时事件检测,若主循环帧率低于鼠标采样率(如 CPU 占满导致 Sleep(16) 实际耗时 50ms),连续点击会被合并为一次。更常见的是GetMouseMsg()后未调用FlushMouseMsgBuffer()清空消息队列,导致旧消息堆积。
解决:每次处理完鼠标消息后立即清空:

if (MouseHit()) { msg = GetMouseMsg(); if (msg.uMsg == WM_LBUTTONDOWN) { placeSunFlower(...); } FlushMouseMsgBuffer(); // 关键!否则下次 MouseHit() 可能仍返回 true }

4.4 现象:程序运行几分钟后内存暴涨,最终 OOM 崩溃

原因:IMAGE对象未显式销毁。EasyX 的IMAGE内部持有 GDI 位图句柄,loadimage()分配内存,但IMAGE析构函数不自动释放。若SunFlower类中m_img是栈对象,其析构不触发DeleteObject()。
解决:在类析构函数中显式调用deleteimage():

~SunFlower() { deleteimage(&m_img); // 必须!否则内存泄漏 }

提示:所有含IMAGE成员的类,析构函数必须成对出现deleteimage(),且传入地址&m_img。

4.5 现象:僵尸移动时出现明显卡顿,帧率从 60 掉到 20

原因:在update()中执行了耗时操作,如频繁loadimage()(错误地在每帧加载图片)、或sprintf()格式化字符串(触发堆分配)。EasyX 的drawtext()若传入中文字符串且未设置字体,会触发 GDI 字体回退机制,极慢。
解决:

  • 图片资源在初始化阶段一次性加载,存为static IMAGE或成员变量;
  • 所有文本绘制前调用setfont()预设字体:
LOGFONT lf; GetObject(GetStockObject(DEFAULT_GUI_FONT), sizeof(lf), &lf); lf.lfHeight = 20; _createfont(&lf);
  • 避免在update()中做任何 I/O、字符串拼接、动态内存分配。

5. 进阶技巧:用状态机解耦僵尸行为与碰撞检测精度提升

僵尸不是匀速直线运动的像素块,它的行为是分阶段的:远距离行走 → 发现植物 → 停止啃食 → 被消灭 → 消亡动画。若用if-else堆砌,代码将迅速失控。我们采用经典有限状态机(FSM)模式,每个状态对应明确的行为契约,并用enum class强类型约束。

5.1 僵尸状态机设计与实现

enum class ZombieState { WALKING, // 向左行走 ATTACKING, // 啃食植物 DYING, // 被击中,播放死亡动画 DEAD // 动画结束,标记销毁 }; class Zombie { public: Zombie(int startRow) : m_row(startRow), m_x(WINDOW_WIDTH), m_y(GRID_START_Y + m_row * GRID_CELL_HEIGHT + 20) { loadimage(&m_img, _T("res/zombie.png")); m_state = ZombieState::WALKING; m_health = 100; } void update() { switch (m_state) { case ZombieState::WALKING: m_x -= 0.5; // 每帧左移0.5像素 if (m_x < GRID_START_X) { // 到达最左侧,游戏失败(简化) m_state = ZombieState::DEAD; } break; case ZombieState::ATTACKING: // 啃食逻辑:减少植物生命值 if (m_targetPlant) { m_targetPlant->takeDamage(1); if (m_targetPlant->isDead()) { m_state = ZombieState::WALKING; // 植物死亡,继续前进 m_targetPlant = nullptr; } } break; case ZombieState::DYING: m_deathTimer++; if (m_deathTimer > 60) { // 1秒死亡动画 m_state = ZombieState::DEAD; } break; case ZombieState::DEAD: // 等待被清理 break; } } void render() { if (m_state == ZombieState::DEAD) return; putimage(m_x, m_y, &m_img); } void setTargetPlant(std::shared_ptr<Plant> plant) { m_targetPlant = plant; m_state = ZombieState::ATTACKING; } bool isDead() const { return m_state == ZombieState::DEAD; } private: ZombieState m_state; int m_row; double m_x, m_y; // 用 double 避免整数截断导致移动卡顿 int m_health; std::shared_ptr<Plant> m_targetPlant; int m_deathTimer = 0; IMAGE m_img; };

关键设计点:m_x使用double而非int,因为0.5像素位移在整数下会四舍五入为0或1,导致僵尸“跳着走”。std::shared_ptr<Plant>确保僵尸与植物生命周期解耦——植物被消灭时,僵尸的m_targetPlant自动置空,无需手动置nullptr。

5.2 碰撞检测:从矩形包围盒到像素级精确判定

EasyX 默认的getpixel()逐像素读取极慢,不可用于每帧检测。我们采用两级优化:先用 AABB(轴对齐包围盒)快速排除,再对重叠区域做 Alpha 通道采样。

// 简化版:仅检测僵尸与植物中心点距离(适用于圆形植物) bool checkCollision(const Zombie& z, const Plant& p, int plantX, int plantY) { int px = plantX + GRID_CELL_WIDTH / 2; // 植物中心X int py = plantY + GRID_CELL_HEIGHT / 2; // 植物中心Y int zx = static_cast<int>(z.m_x) + 30; // 僵尸图像宽约60px,中心X int zy = static_cast<int>(z.m_y) + 40; // 僵尸图像高约80px,中心Y int dx = px - zx; int dy = py - zy; int distSq = dx*dx + dy*dy; // 半径平方:植物半径30,僵尸半径35 → 合并半径65 → 65^2 = 4225 return distSq < 4225; } // 进阶版:AABB + Alpha 采样(需预处理植物图像掩码) struct PlantMask { std::vector<bool> data; // true=不透明像素 int width, height; PlantMask(const IMAGE& img) : width(img.getwidth()), height(img.getheight()) { data.resize(width * height, false); // 预处理:遍历图像,提取Alpha>128的像素(需EasyX 2022版以上支持getpixel_alpha) for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { COLORREF c = getpixel(&img, x, y); if (GetRValue(c) > 100 || GetGValue(c) > 100 || GetBValue(c) > 100) { data[y * width + x] = true; } } } } };

实践建议:对于教学级复刻,checkCollision()的中心点距离法已足够。若追求精度,应将PlantMask预计算为静态数据,避免每帧重建。EasyX 的getpixel()在高分辨率下极慢,务必用getimagepixel()替代(需 EasyX 2022+)。


6. 性能压测与发布打包:让程序脱离开发环境稳定运行

完成功能只是起点,真正的工程闭环在于:能否在一台未安装 VS、未配置 EasyX 的 Windows 电脑上双击即运行?这要求我们解决 DLL 依赖、资源路径固化、异常安全兜底三大难题。

6.1 依赖扫描与 DLL 打包策略

EasyX 运行时依赖easyx.dll和msvcp140.dll(VS2015+ 运行时)。用Dependency Walker或ldd(WSL)扫描可执行文件,确认无遗漏。严禁让用户自行下载 EasyX SDK——必须将easyx.dll与你的.exe放在同一目录。VS 项目属性中设置:

  • C/C++ → 代码生成 → 运行时库:/MT(静态链接 CRT,避免用户无 VC++ Redist)
  • 链接器 → 输入 → 附加依赖项:easyx.lib(确保路径正确)

提示:/MT会使 EXE 体积增大 1~2MB,但彻底摆脱运行时依赖。若坚持用/MD,则必须将vcruntime140.dll、msvcp140.dll一并放入发布目录。

6.2 资源路径鲁棒性加固

loadimage()的相对路径基于当前工作目录,而非可执行文件目录。用户双击桌面快捷方式时,工作目录是桌面,而非程序所在文件夹。必须在main()开头切换工作目录:

#include <direct.h> #include <string> void setupResourcePath() { TCHAR path[MAX_PATH]; GetModuleFileName(NULL, path, MAX_PATH); PathRemoveFileSpec(path); // 移除文件名,只剩目录 _chdir(path); // 切换工作目录 }

注意:PathRemoveFileSpec属于shlwapi.h,需在项目中添加该库依赖。此函数确保res/bg.jpg总是从 EXE 同目录加载。

6.3 异常安全兜底与日志记录

C++ 异常在 EasyX 环境中可能被 GDI 错误吞没。我们在main()中添加结构化异常处理(SEH):

#include <windows.h> #include <stdio.h> LONG WINAPI UnhandledExceptionFilterProc(_EXCEPTION_POINTERS* pExceptionInfo) { // 记录崩溃信息到日志文件 FILE* f = fopen("crash.log", "a"); if (f) { fprintf(f, "CRASH at %lld: ExceptionCode=0x%08X\n", GetTickCount64(), pExceptionInfo->ExceptionRecord->ExceptionCode); fclose(f); } return EXCEPTION_EXECUTE_HANDLER; } int main() { SetUnhandledExceptionFilter(UnhandledExceptionFilterProc); setupResourcePath(); // ... 原有逻辑 ... }

最后叮嘱:我带过的某高校课程设计小组,曾因忽略deleteimage()导致 3 天调试无果;另一组因Sleep(16)未替换为 delta time,在高负载笔记本上帧率崩到 10 FPS,误判为算法缺陷。这些都不是“玄学”,而是 C++ 图形编程的必经门槛。把IMAGE当作需要呼吸的活物去管理,把每一帧当作一次精密手术去测量——当你能在裸机上跑出稳定 60 FPS,你就真正拿到了实时图形系统的钥匙。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 6:08:55

从零散素材到优质博文:结构化输入与关键词提炼指南

我注意到你还没有提供具体的项目信息。为了生成贴合你需求的高质量博文&#xff0c;请按以下格式补充输入&#xff1a;项目标题: [标题] 项目正文: [通常比较零散、不完整的原始描述&#xff0c;可是任意领域内容] 关键词: [关键词1, 关键词2, ...] 摘要描述: [对项目/内容的一…

作者头像 李华
网站建设 2026/10/18 6:08:47

Vue3+TypeScript实战:从零掌握Axios请求与拦截器

前端新手问得最多的一个问题&#xff0c;十有八九是“请求到底怎么发”。我通常给出的答案里&#xff0c;都有一个固定成员&#xff1a;Axios。它不是黑科技&#xff0c;只是一个基于 Promise 的轻量 HTTP 客户端&#xff0c;但在Vue3 TypeScript项目里&#xff0c;它几乎成了…

作者头像 李华
网站建设 2026/10/18 6:07:59

Genkit Dart 1.0 发布:Flutter 原生 AI Agent 编排框架详解

这两年做 Flutter 的开发者想接大模型&#xff0c;应该都有过一种拧巴的感觉&#xff1a;在 Dart 里裸调 HTTP 拼 JSON&#xff0c;请求倒是发出去了&#xff0c;但模型返回的文本怎么清洗、怎么重试、怎么让模型自己决定要不要“调用工具”&#xff0c;全得自己造轮子。社区里…

作者头像 李华
网站建设 2026/10/18 6:06:06

【STM32C5教程】(9)DAC 模拟量信号输出实验

欢迎关注 youcansxidian【嵌入式软件AI编程】专栏 【STM32C5教程】&#xff08;4&#xff09;GPIO 外部中断实验 【STM32C5教程】&#xff08;5&#xff09;定时器周期中断实验 【STM32C5教程】&#xff08;6&#xff09;定时器 PWM 输出实验 【STM32C5教程】&#xff08;7&…

作者头像 李华
网站建设 2026/10/18 6:05:19

page_alloc free_reserved_area

free_reserved_area() 是内核启动后期用于将保留内存&#xff08;reserved memory&#xff09;释放回伙伴系统的核心函数。它通常服务于 free_initmem()、free_initrd_mem() 等场景&#xff0c;把初始化代码段、initrd 等不再需要的物理内存归还给分配器。核心作用与设计意图它…

作者头像 李华
网站建设 2026/10/18 6:03:20

Matlab自动驾驶决策规划控制实战:从状态机到纯跟踪完整代码解析

自动驾驶决策规划控制Matlab代码实战&#xff0c;听起来又长又劝退&#xff0c;但我实际跑通之后发现&#xff0c;这其实是进入自动驾驶领域性价比最高的一条路。最近不少朋友来问我怎么入门&#xff0c;我的建议是别一上来就啃C、Linux那一套&#xff0c;先用Matlab把决策、规…

作者头像 李华