ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

纯C++17实现塔防游戏核心架构与性能优化

纯C++17实现塔防游戏核心架构与性能优化 简介这是一份基于C实现的《保卫萝卜》塔防游戏课程设计项目面向计算机专业本科生及C初学者用于巩固面向对象编程、Qt图形界面开发与游戏逻辑设计能力。资源包含294个文件主体为66个cpp源码、55个h头文件、81张png素材图及3个可执行exe程序辅以qrc资源文件、pro工程配置与mp3音效完整覆盖游戏启动、关卡管理、塔建模、怪物路径寻迹与金币经济系统等核心模块包体大小23.48MB。已有482人学习下载提供可直接编译运行的完整工程含TowerDefense主程序、MainWindow界面逻辑、资源加载机制及多关卡测试用例代码结构清晰、注释充分便于理解塔防类游戏架构设计与Qt事件驱动机制的实际应用。1. 用纯 C 从零搭建《保卫萝卜》式塔防游戏不依赖 Unity 或 Unreal你不需要 Unity、不装 Unreal Engine、甚至不用 SDL2 或 SFML 的“轻量级”塔防框架——这个标题指向的是一套完全基于标准 C17含 STL与 Windows API或跨平台 minimal GLFW OpenGL Core Profile实现的塔防游戏最小可行原型。它解决的是想练 C 工程能力、图形逻辑拆解和游戏循环控制但被引擎黑盒吓退的中级学习者的真实痛点。项目编号【100013158】暗示其来自某高校课程设计或开源实训库核心价值不在画面精美而在用std::vectorstd::unique_ptrTower管理炮塔、用std::priority_queuePathNode, std::vectorPathNode, CompareDistance实现路径寻路、用std::chrono::steady_clock驱动帧同步——所有逻辑都暴露在.cpp文件里可调试、可打断点、可逐行改参数。适合 C 学习进入「能写类、懂 RAII、会模板但缺实战」阶段的开发者也适合作为 C 面试中「手写游戏主循环」题目的参考实现。2. 用标准 C 构建塔防游戏四大核心模块对象建模、事件驱动、渲染抽象与时间调度塔防游戏表面是“放塔打怪”底层是四组强耦合又需解耦的子系统怪物沿路径移动Pathing、炮塔瞄准攻击Tower Logic、子弹飞行与碰撞Projectile System、资源与波次管理Wave Resource。C 实现的关键不是“画出像素”而是用类型系统把这四件事的职责边界划清楚。常见误用是把所有逻辑塞进一个Game类导致update()函数超 300 行、无法单元测试。我一般会按 SRP单一职责原则拆成四个独立类并用std::shared_ptr建立弱引用关系避免循环持有。2.1 定义可扩展的实体基类与资源管理器所有游戏对象萝卜、炮塔、怪物、子弹都继承自GameObject它不包含图形数据只提供位置、生命值、更新接口// GameObject.h #include memory #include vector #include cmath struct Vec2 { float x 0.0f, y 0.0f; Vec2 operator(const Vec2 other) const { return {x other.x, y other.y}; } Vec2 operator-(const Vec2 other) const { return {x - other.x, y - other.y}; } float length() const { return std::sqrt(x*x y*y); } }; class GameObject { public: virtual ~GameObject() default; virtual void update(float deltaTime) 0; virtual void render() const 0; // 纯虚函数由具体渲染后端实现 Vec2 position{0.0f, 0.0f}; float health 100.0f; bool isAlive true; };提示Vec2不用glm::vec2是为了剥离第三方依赖length()用std::sqrt而非hypotf是因后者在某些 MinGW 版本中存在精度问题且sqrt指令级优化更成熟。资源管理器ResourceManager负责统一加载/缓存纹理、音效、配置文件避免重复读磁盘// ResourceManager.h #include unordered_map #include string #include memory #include fstream class Texture { public: unsigned int id; // OpenGL texture ID 或 Windows GDI bitmap handle int width, height; }; class ResourceManager { public: static std::unordered_mapstd::string, std::shared_ptrTexture textures; static std::shared_ptrTexture loadTexture(const std::string path); private: static std::shared_ptrTexture loadFromBMP(const std::string path); // BMP 格式最简无需 libpng/libjpeg };textures声明为static成员确保整个进程内单例loadFromBMP只解析 BMP 文件头与像素数据不处理调色板限定 24-bit RGB代码量可控在 150 行内比集成 stb_image 更易调试。2.2 用 std::priority_queue 实现 A* 路径寻路替代硬编码路径点《保卫萝卜》的关卡路径不是直线而是带拐角的折线。若用数组存固定路径点新增关卡就得改代码。正确做法是将地图抽象为网格Grid每个格子为Node怪物在update()中调用findPath()获取下一步目标// PathFinder.h #include queue #include vector #include unordered_set struct Node { int x, y; float gScore FLT_MAX; // 从起点到此节点的实际代价 float fScore FLT_MAX; // gScore heuristic Node* parent nullptr; bool operator(const Node other) const { return x other.x y other.y; } }; struct CompareNode { bool operator()(const std::shared_ptrNode a, const std::shared_ptrNode b) const { return a-fScore b-fScore; // min-heap: lowest fScore first } }; class PathFinder { public: std::vectorVec2 findPath(const Vec2 start, const Vec2 end, const std::vectorstd::vectorbool walkable); private: std::shared_ptrNode getNode(int x, int y); float heuristic(const Node a, const Node b) const { return std::abs(a.x - b.x) std::abs(a.y - b.y); // Manhattan distance } std::unordered_setstd::shared_ptrNode closedSet; std::priority_queuestd::shared_ptrNode, std::vectorstd::shared_ptrNode, CompareNode openSet; };findPath()返回std::vectorVec2供怪物每帧向下一个点移动。heuristic()用曼哈顿距离而非欧氏距离因塔防地图多为网格对齐且整数运算更快openSet用std::priority_queue而非std::set因插入/弹出频次高前者均摊 O(log n)后者红黑树常数更大。2.3 渲染抽象层OpenGL Core Profile 最小化绑定不使用 GLFW 初始化窗口而用 Windows API 创建HWND再通过wglCreateContextAttribsARB创建 OpenGL 4.1 Core Context兼容性好于 4.5且 VS2019 默认支持。顶点着色器仅做 MVP 变换片元着色器输出纯色// vertex_shader.glsl #version 410 core layout (location 0) in vec2 aPos; layout (location 1) in vec3 aColor; uniform mat4 uMVP; out vec3 color; void main() { gl_Position uMVP * vec4(aPos.x, aPos.y, 0.0, 1.0); color aColor; }C 端用std::arrayfloat, 12存储矩形顶点4 个顶点 × 2 坐标 3 颜色 12 元素每次render()调用glBufferData(GL_ARRAY_BUFFER, ...)更新 VBO避免动态分配// Renderer.cpp void Renderer::drawRect(const Vec2 pos, float width, float height, const Vec3 color) { std::arrayfloat, 12 vertices { pos.x, pos.y, color.r, color.g, color.b, pos.x width, pos.y, color.r, color.g, color.b, pos.x width, pos.y height, color.r, color.g, color.b, pos.x, pos.y height, color.r, color.g, color.b }; glBindBuffer(GL_ARRAY_BUFFER, vbo_); glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_DYNAMIC_DRAW); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); }GL_DYNAMIC_DRAW告知驱动该缓冲区每帧更新启用显存映射优化GL_TRIANGLE_FAN绘制实心矩形比GL_TRIANGLES少 2 个顶点减少 GPU 传输量。2.4 主循环std::chrono::steady_clock 驱动的固定步长更新while(running)循环必须分离逻辑更新与渲染否则帧率波动导致怪物移动忽快忽慢。标准做法是累积deltaTime每 16ms60 FPS触发一次update()// GameLoop.cpp #include chrono #include thread class GameLoop { static constexpr float TARGET_DELTA_TIME 16.0f / 1000.0f; // 16ms per frame std::chrono::steady_clock::time_point lastTime_; float accumulator_ 0.0f; public: void run() { lastTime_ std::chrono::steady_clock::now(); while (running_) { auto now std::chrono::steady_clock::now(); float frameTime std::chrono::durationfloat(now - lastTime_).count(); lastTime_ now; accumulator_ frameTime; while (accumulator_ TARGET_DELTA_TIME) { game_.update(TARGET_DELTA_TIME); accumulator_ - TARGET_DELTA_TIME; } game_.render(); // vsync or sleep to cap FPS std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } };std::this_thread::sleep_for仅作粗略限帧真实项目应调用SwapBuffers(hDC)后等待垂直同步accumulator_用float而非double因std::chrono::durationfloat在多数编译器下生成更优汇编且游戏逻辑对微秒级误差不敏感。3. 实现炮塔攻击逻辑状态机驱动的冷却、瞄准与子弹生成炮塔不是“一直射”而是经历「空闲 → 锁定目标 → 冷却中 → 空闲」的状态流转。用enum class TowerState显式定义状态比布尔标志位如isCooling,hasTarget更易维护、防错。3.1 Tower 类的状态机与成员变量设计// Tower.h #include memory #include vector #include optional enum class TowerState { IDLE, LOCKING, FIRING, COOLDOWN }; class Tower : public GameObject { public: Tower(float range, float damage, float fireRate); // range: 攻击半径, fireRate: 秒/发 void update(float deltaTime) override; void render() const override; void setTarget(std::shared_ptrGameObject target); // 弱引用避免循环持有 void onFire(); // 子弹生成入口 private: TowerState state_ TowerState::IDLE; std::weak_ptrGameObject target_; // 指向怪物不增加引用计数 float range_; float damage_; float fireRate_; float cooldownTimer_ 0.0f; float lockTimer_ 0.0f; // 锁定目标所需时间模拟瞄准 };std::weak_ptr是关键怪物死亡时shared_ptr计数归零target_.lock()返回空shared_ptr塔自动切换回IDLE状态无需手动清理。lockTimer_模拟真实塔的“瞄准延迟”避免瞬发导致策略失衡。3.2 状态流转逻辑与冷却计时实现update()中按状态分支处理每帧更新计时器并检查条件// Tower.cpp void Tower::update(float deltaTime) { switch (state_) { case TowerState::IDLE: if (auto locked target_.lock()) { // 目标存活且在范围内 Vec2 toTarget locked-position - position; if (toTarget.length() range_) { state_ TowerState::LOCKING; lockTimer_ 0.0f; } } break; case TowerState::LOCKING: lockTimer_ deltaTime; if (lockTimer_ 0.3f) { // 瞄准耗时 300ms state_ TowerState::FIRING; onFire(); cooldownTimer_ 0.0f; state_ TowerState::COOLDOWN; } break; case TowerState::COOLDOWN: cooldownTimer_ deltaTime; if (cooldownTimer_ 1.0f / fireRate_) { // fireRate 单位发/秒 state_ TowerState::IDLE; } break; case TowerState::FIRING: // FIRING 是瞬时状态onFire() 执行后立即进入 COOLDOWN break; } }1.0f / fireRate_将“每秒发射次数”转为“每次冷却秒数”例如fireRate_2.0f→ 冷却0.5slockTimer_固定0.3f而非与fireRate关联因瞄准是独立物理过程与射速无关。3.3 子弹生成与飞行逻辑基于 Vec2 的运动积分子弹Bullet继承GameObject但重写update()为匀速直线运动// Bullet.h class Bullet : public GameObject { public: Bullet(const Vec2 startPos, const Vec2 direction, float speed, float damage); void update(float deltaTime) override; void render() const override; private: Vec2 velocity_; float speed_; float damage_; static constexpr float LIFETIME 3.0f; // 子弹最大存活时间秒 float lifeTimer_ 0.0f; }; // Bullet.cpp Bullet::Bullet(const Vec2 startPos, const Vec2 direction, float speed, float damage) : position(startPos), speed_(speed), damage_(damage) { float len direction.length(); velocity_ {direction.x / len * speed_, direction.y / len * speed_}; } void Bullet::update(float deltaTime) { position position velocity_ * deltaTime; lifeTimer_ deltaTime; if (lifeTimer_ LIFETIME) { isAlive false; } }velocity_在构造时归一化方向向量再乘speed_避免每帧重复除法position velocity_ * deltaTime是欧拉积分对子弹这种无加速度物体足够精确LIFETIME防止子弹飞出屏幕后持续占用内存。3.4 碰撞检测轴对齐包围盒AABB的高效实现不使用物理引擎用 AABB 检测子弹与怪物是否相交// Collision.h struct AABB { Vec2 center; float halfWidth, halfHeight; bool intersects(const AABB other) const { return std::abs(center.x - other.center.x) (halfWidth other.halfWidth) std::abs(center.y - other.center.y) (halfHeight other.halfHeight); } }; // 在 Bullet::update() 后添加 void Bullet::checkCollision(std::vectorstd::shared_ptrGameObject monsters) { AABB bulletBox{position, 2.0f, 2.0f}; // 子弹视为 4x4 像素矩形 for (auto monster : monsters) { if (!monster-isAlive) continue; AABB monsterBox{monster-position, 8.0f, 8.0f}; // 怪物 16x16 if (bulletBox.intersects(monsterBox)) { monster-health - damage_; isAlive false; break; } } }intersects()用std::abs和短路求值比调用sqrt计算距离快 5 倍以上halfWidth/halfHeight为预设常量避免运行时计算包围盒尺寸。4. 关卡与波次系统用 JSON 配置驱动怪物生成与资源经济硬编码怪物类型和波次数量会让关卡迭代成本飙升。正确做法是将波次定义为 JSON 文件C 用nlohmann/json解析头文件仅json.hpp无编译依赖// level_1.json { waves: [ { delay: 2.0, monsters: [ {type: basic, count: 5, interval: 1.0}, {type: fast, count: 3, interval: 1.5} ] }, { delay: 5.0, monsters: [{type: tank, count: 2, interval: 2.0}] } ], startGold: 200, startLives: 20 }4.1 WaveManager 类解析 JSON 并调度生成// WaveManager.h #include nlohmann/json.hpp #include vector #include string #include memory struct MonsterSpawn { std::string type; int count; float interval; }; struct Wave { float delay; // 波次启动延迟秒 std::vectorMonsterSpawn monsters; }; class WaveManager { public: void loadFromFile(const std::string path); void update(float deltaTime); void spawnNextMonster(); private: std::vectorWave waves_; size_t currentWaveIndex_ 0; float waveTimer_ 0.0f; float spawnTimer_ 0.0f; size_t currentSpawnIndex_ 0; std::vectorMonsterSpawn currentSpawns_; };loadFromFile()用nlohmann::json::parse(std::ifstream)读取waves_存储解析后的结构spawnNextMonster()根据currentSpawns_[currentSpawnIndex_]创建对应类型怪物。4.2 MonsterFactory用工厂模式解耦怪物类型创建避免if-else判断类型用std::mapstd::string, std::functionstd::shared_ptrMonster()注册构造器// MonsterFactory.h #include memory #include functional #include unordered_map class MonsterFactory { public: static void registerType(const std::string type, std::functionstd::shared_ptrMonster() creator); static std::shared_ptrMonster create(const std::string type); private: static std::unordered_mapstd::string, std::functionstd::shared_ptrMonster() creators_; }; // 在 main() 中注册 MonsterFactory::registerType(basic, []() { return std::make_sharedBasicMonster(); }); MonsterFactory::registerType(fast, []() { return std::make_sharedFastMonster(); }); MonsterFactory::registerType(tank, []() { return std::make_sharedTankMonster(); });registerType()在程序启动时调用create()通过字符串查表获取 lambda调用即返回新实例。相比虚函数表此方式新增怪物类型只需加一行注册不改已有代码。4.3 资源系统金币与生命值的线程安全操作金币和生命值需被多个系统塔购买、怪物到达终点、击杀奖励修改用std::atomicint保证无锁安全// ResourceManager.h续 class ResourceManager { public: static std::atomicint gold; static std::atomicint lives; static void addGold(int amount) { gold.fetch_add(amount, std::memory_order_relaxed); } static void deductGold(int amount) { if (gold.load(std::memory_order_relaxed) amount) { gold.fetch_sub(amount, std::memory_order_relaxed); } } static bool canAfford(int cost) { return gold.load(std::memory_order_relaxed) cost; } };std::memory_order_relaxed足够因金币/lives 更新不要求全局顺序只保证原子性canAfford()先读后减避免竞态条件如读到 100另一线程减到 50当前线程仍执行减法。4.4 配置文件热重载监听文件修改并重新加载开发时频繁改 JSON 需重启游戏用FindFirstChangeNotificationW监听文件变化// FileWatcher.cppWindows only #include windows.h class FileWatcher { public: FileWatcher(const std::string path) : path_(path) { hNotify_ FindFirstChangeNotificationA(path_.c_str(), FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE); } bool hasChanged() { if (WaitForSingleObject(hNotify_, 0) WAIT_OBJECT_0) { FindNextChangeNotification(hNotify_); return true; } return false; } private: std::string path_; HANDLE hNotify_; }; // 在 GameLoop::run() 中 FileWatcher levelWatcher(level_1.json); while (running_) { if (levelWatcher.hasChanged()) { waveManager_.loadFromFile(level_1.json); std::cout [INFO] Level config reloaded\n; } // ... rest of loop }FILE_NOTIFY_CHANGE_LAST_WRITE仅监控修改时间开销最低WaitForSingleObject(hNotify_, 0)非阻塞检查不影响主循环性能。5. VSCode 配置 C/C 环境与调试技巧让 C 塔防开发真正可维护VSCode 本身不编译 C需配置tasks.json调用 MSVC 或 MinGW。标题中【100013158】大概率对应 Visual Studio 2019/2022 环境故以 MSVC 为例。关键不是“能编译”而是让 IntelliSense 正确索引、断点精准命中、内存泄漏可追踪。5.1 c_cpp_properties.json精准设置 includePath 与 defines.vscode/c_cpp_properties.json必须匹配实际工具链否则#include memory报红{ configurations: [ { name: Win32, includePath: [ ${workspaceFolder}/**, C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.29.30133/include/**, C:/Program Files (x86)/Windows Kits/10/Include/10.0.19041.0/um, C:/Program Files (x86)/Windows Kits/10/Include/10.0.19041.0/shared ], defines: [_CRT_SECURE_NO_WARNINGS, NOMINMAX], compilerPath: C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.29.30133/bin/Hostx64/x64/cl.exe, cStandard: c17, cppStandard: c17, intelliSenseMode: msvc-x64 } ], version: 4 }includePath指向 VS 安装目录下的include而非$(vcpkgRoot)—— vcpkg 会污染 IntelliSense_CRT_SECURE_NO_WARNINGS禁用strcpy警告因塔防项目无需严格安全审计intelliSenseMode设为msvc-x64确保语法高亮与跳转准确。5.2 tasks.json一键构建带 PDB 符号的 Release 版本Debug 版本太慢Release 版本无符号无法调试。折中方案是/Zi生成 PDB/O2优化// .vscode/tasks.json { version: 2.0.0, tasks: [ { type: shell, label: Build Game (Release with PDB), command: cl.exe, args: [ /Zi, /O2, /EHsc, /Fe:${fileDirname}/game.exe, /I${fileDirname}/include, ${fileDirname}/src/*.cpp, opengl32.lib, gdi32.lib, user32.lib ], group: build, presentation: { echo: true, reveal: silent, focus: false, panel: shared, showReuseMessage: true, clear: true } } ] }/Zi生成game.pdbVSCode 调试时自动加载/O2启用速度优化/EHsc启用 C 异常链接opengl32.lib等是 Windows API 必需库。5.3 launch.json附加到进程调试与内存泄漏检测塔防游戏易出现new/delete不匹配。用_CrtSetDbgFlag启用 CRT 调试堆// main.cpp 开头 #ifdef _DEBUG #include crtdbg.h #endif int main() { #ifdef _DEBUG _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); #endif // ... rest of code }launch.json配置为启动后自动附加// .vscode/launch.json { version: 0.2.0, configurations: [ { name: (Windows) Launch, type: cppvsdbg, request: launch, program: ${fileDirname}/game.exe, args: [], stopAtEntry: false, cwd: ${fileDirname}, environment: [], externalConsole: true, logging: { moduleLoad: false } } ] }externalConsole: true保留控制台输出方便std::cout查看日志logging.moduleLoad: false关闭模块加载日志避免干扰。5.4 实用调试技巧在 update() 中设置条件断点怪物移动异常在Monster::update()第一行设条件断点position.x 1000.0f当怪物飞出屏幕时中断炮塔不攻击在Tower::update()中if (state_ TowerState::LOCKING)行设断点观察lockTimer_是否递增。VSCode 支持console.log式打印右键断点 → “Edit Breakpoint” → 输入console.log(Tower at position.x , position.y)无需修改代码。注意std::vector迭代器失效是高频坑。例如在for (auto it monsters.begin(); it ! monsters.end(); it)循环中调用monsters.erase(it)后it失效。正确写法是it monsters.erase(it)或改用std::remove_iferase惯用法。6. 性能调优用 std::vector::reserve 避免动态扩容用对象池复用子弹塔防游戏每秒生成数十发子弹若每发都new Bullet会导致频繁堆分配帧率骤降。解决方案是预分配内存池复用已销毁的子弹。6.1 BulletPool基于 std::vector 的对象池实现// BulletPool.h #include vector #include memory #include stack class BulletPool { public: BulletPool(size_t capacity 100) : pool_(capacity) { for (size_t i 0; i capacity; i) { pool_[i] std::make_uniqueBullet(Vec2{0,0}, Vec2{0,0}, 0, 0); } } std::shared_ptrBullet acquire(const Vec2 startPos, const Vec2 direction, float speed, float damage) { if (freeList_.empty()) { // 池满返回新实例罕见 return std::make_sharedBullet(startPos, direction, speed, damage); } size_t idx freeList_.top(); freeList_.pop(); auto bullet pool_[idx]; bullet-reset(startPos, direction, speed, damage); // reset() 重置成员变量 return bullet; } void release(std::shared_ptrBullet bullet) { // 检查 bullet 是否属于本池用地址判断 auto ptr bullet.get(); for (size_t i 0; i pool_.size(); i) { if (pool_[i].get() ptr) { freeList_.push(i); return; } } // 不属于本池忽略可能是 new 出来的 } private: std::vectorstd::unique_ptrBullet pool_; std::stacksize_t freeList_; };pool_在构造时reserve并resize避免运行时扩容freeList_用std::stack管理空闲索引O(1)分配/释放release()通过原始指针地址比对确认归属安全复用。6.2 std::vector 预分配实践怪物、炮塔、子弹容器所有动态容器在初始化时调用reserve()// Game.h class Game { std::vectorstd::shared_ptrMonster monsters_; std::vectorstd::shared_ptrTower towers_; std::vectorstd::shared_ptrBullet bullets_; public: Game() { monsters_.reserve(200); // 一关最多 200 怪 towers_.reserve(50); // 最多 50 塔 bullets_.reserve(200); // 最多 200 子弹 } };reserve(200)分配连续内存后续push_back()不触发 realloc容量设为预估上限而非10—— 小容量频繁扩容代价更高。6.3 缓存友好性优化结构体数组替代指针数组std::vectorstd::shared_ptrTower中每个shared_ptr指向堆上分散的Tower对象CPU cache line 命中率低。改为std::vectorTower值语义用索引代替指针// Game.h优化版 class Game { std::vectorTower towers_; // 直接存储对象 std::vectorsize_t activeTowerIndices_; // 活跃塔的索引列表 public: void addTower(const Tower t) { towers_.push_back(t); activeTowerIndices_.push_back(towers_.size() - 1); } void updateTowers(float dt) { for (size_t idx : activeTowerIndices_) { towers_[idx].update(dt); } } };towers_连续存储update()时 CPU 可预取相邻Tower数据activeTowerIndices_存储活跃索引避免遍历全部如部分塔被摧毁。6.4 内存布局分析用 offsetof 验证结构体对齐Tower类若成员顺序不当会因对齐填充浪费内存。用offsetof检查#include cstddef static_assert(offsetof(Tower, state_) 0, state_ must be first); static_assert(offsetof(Tower, range_) 4, range_ must follow state_);将小成员enum class,float放在前面大成员std::weak_ptr放后面减少 padding。例如TowerState占 4 字节紧跟其后的float range_对齐无 gap若std::weak_ptr放前面则state_前需 4 字节 padding。提示在 VS2019 中/d1reportAllClassLayout编译选项可输出所有类的内存布局直接查看 padding 字节数。本文还有配套的精品资源点击获取
返回列表