C++享元模式实战:从内存优化到粒子系统与文本渲染
我做这个系列整理有个习惯先从一个真实到让人头疼的场景讲起。有一次我帮人优化一个特效渲染组件打开内存监控时整个人都愣住了——两万多个粒子每个粒子都抱着自己那份独立的纹理引用和材质参数一帧一帧更新下来堆上头塞满了除了位置和速度不一样、其他几乎一模一样的对象。整个模块的内存占用直接突破了几百兆帧率也肉眼可见地往下掉。那次重构让我第一次真正体会到享元模式的威力。后来我在多个项目里反复用过这个模式包括粒子系统、文本编辑器、场景地形管理甚至配置表加载模块。这篇就完整分享一下C中享元模式的实战经验设计思路、关键实现、可以抄作业的代码以及我从坑里爬出来的那些排查记录。1. 从一次内存危机认识享元模式1.1 那个让我决定重构的粒子系统案例场景很简单一个2D横版战斗特效由大量粒子组成。每个粒子有自己的位置、速度、旋转角、生命周期这些内容天然是独立的。但问题出在纹理和材质上——整个特效一共就3种纹理、2套材质参数系统却给每个粒子都准备了一份完整的副本。当时看代码粒子类里直接放了一个纹理指针和一个材质对象创建粒子的地方也是每次都去磁盘加载一遍纹理路径。这个设计在粒子数量少的时候完全没毛病一百个粒子撑死也就多占几MB没人会在意。可一旦粒子数量到了万级问题就炸了同样的纹理被加载了几百次显存和堆内存双爆炸材质的拷贝构造也在频繁触发深拷贝每一帧更新粒子都像在做大量无意义的内存搬运。我清理完这些副本之后内存占用直接从近300MB降到了80MB左右渲染开销也明显下降。1.2 享元模式解决的到底是什么问题享元模式的核心思路是把对象的属性拆成两类一类叫内部状态是对象之间可以共享的部分比如纹理数据、字体字形、网格数据、配置模板这类内容通常体积大、创建成本高、而且内容不变另一类叫外部状态是每个使用方自己的、不能共享的部分比如粒子的位置、文本的坐标、字体的大小颜色。模式的做法是把内部状态封装成享元对象由一个统一的管理容器持有和复用外部状态由客户端各自保存在使用时临时传给享元对象的方法。用一句话概括就是相同的内容只保存一份不同的内容各自管好自己。回到粒子系统纹理和材质就是内部状态位置、速度、生命值就是外部状态。我只需要在纹理管理器里维护一张“路径到纹理”的映射表所有粒子共享同一个纹理实例各自只保存自己的变换数据。这样一来从“每个粒子都是一套完整资源”变成了“一份资源 一万份轻量状态”。1.3 这招适合什么样的项目场景不是所有项目都需要享元模式但遇到下面这些信号时就该考虑它了程序里大量对象都在引用同一批资源而且这些资源创建成本极高纹理、音频、网络连接、复杂计算结果。内存比较紧张瓶颈集中在重复对象上。对象数量大且可以明确分出“变化的部分”和“不变的部分”。对象创建和销毁非常频繁复用能明显减少分配和释放的开销。在C里做粒子系统、UI框架、游戏引擎、编辑器、配置文件解析器这些都是享元模式的高频使用区。我做过的另一个比较典型的场景是文本编辑器一屏文本有几百个字符如果每个字符都持有一个完整的字形数据对象内存得多浪费。把字形做成共享享元字符只保存索引信息、位置和颜色渲染时再到字形表里去取对应的形状数据这个思路跟粒子系统一模一样。2. 设计方案与核心原理拆解2.1 三个核心角色的职责划分实现享元模式在C里通常有三个角色享元接口定义内部状态的对外访问方式一般是一个抽象类或者具体类内部状态通过成员变量持有。具体享元类实现享元接口保存内部状态内部状态通常定义为const或者在构造时固定下来。享元工厂负责创建和管理享元对象维护一个对象池。请求某个享元时先从池里查存在就直接返回不存在就创建后放入池中。拿我常写的纹理管理器举例接口和具体享元类可以合在一起工厂就是那个管理器类。下面是最简骨架class Texture { public: Texture(const std::string path) : path_(path) { // 从文件加载像素数据上传到GPU比较耗时 } void Bind() const { // 绑定纹理供渲染使用 } const std::string GetPath() const { return path_; } private: std::string path_; // 省略存储纹理描述符的成员 }; class TextureManager { public: std::shared_ptrTexture Get(const std::string path) { auto it pool_.find(path); if (it ! pool_.end()) { return it-second; } auto texture std::make_sharedTexture(path); pool_[path] texture; return texture; } size_t Count() const { return pool_.size(); } private: std::unordered_mapstd::string, std::shared_ptrTexture pool_; };这个结构很简单但它把享元模式的核心思路体现出来了复用靠工厂保证工厂通过对象池来实现。实际项目里我会在这个基础上加引用计数、线程锁、资源释放回调但骨架不变。2.2 内部状态与外部状态怎么划分很多人在实操中最纠结的问题就是到底哪些算内部状态哪些算外部状态。我的经验是问三个问题这个数据在生命周期内会不会变化如果会变那它大概率是外部状态。这个数据是不是由对象本身决定的比如纹理文件路径、字形编码这些是内部状态。把数据放到共享对象的成员里会不会导致其他使用方互相影响如果会必须拿出来作为外部状态。有一种常见的反例把颜色放在字形共享对象里。比如先渲染了红色字符然后把字形对象的颜色改成红色接着渲染蓝色字符时再把颜色改成蓝色。这种方式在单线程里勉强能跑一旦并发或者缓存了渲染指令共享对象的状态就被改乱了。字形本身的轮廓数据可以共享但颜色、大小、位置、旋转这些每一次使用都可能不同的数据一律属于外部状态。我之前做的一个文本渲染Demo就是这么处理的字形对象只有字符编码和轮廓顶点颜色和字号的变换矩阵放在渲染命令里每个字符一次性指定互不干扰。class Glyph { public: Glyph(wchar_t code, std::vectorVertex outline) : code_(code), outline_(std::move(outline)) {} const std::vectorVertex GetOutline() const { return outline_; } private: wchar_t code_; std::vectorVertex outline_; }; class CharacterRenderInfo { public: wchar_t code; float x, y; float fontSize; float r, g, b; std::shared_ptrGlyph glyph; };2.3 为什么用shared_ptr管理享元更安全C里实现享元模式最让我在意的是对象生命周期。裸指针可以复用但谁来释放、何时释放是老大难问题。如果工厂负责释放客户端可能在工厂销毁之后还持有指针如果客户端负责释放那工厂里的缓存会变成悬垂指针。我通常默认用std::shared_ptr。理由很实际享元对象是共享资源天然适合共享所有权只要还有客户端持有引用对象就不会被释放工厂缓存也仅作为一个“活着的对象注册表”存在。具体代码就是在工厂里存std::shared_ptr返回时也返回std::shared_ptr。如果项目对性能要求极其苛刻可以用std::shared_ptr的别名构造函数或者把关键路径改成裸指针集中式生命周期管理但这是后期优化的事。工程上先用最不容易出错的方案把正确性保证好再谈性能。3. 实战一粒子系统里的具体落地3.1 粒子类与外部状态的重构我重构那个粒子系统时第一步就是把粒子类缩小。原来粒子类里有纹理对象和材质对象现在只保留变换相关的数据再加上一个指向共享纹理的shared_ptr。struct ParticleState { float x, y; float vx, vy; float rotation; float life; float maxLife; }; class Particle { public: Particle(ParticleState state, std::shared_ptrTexture texture) : state_(state), texture_(std::move(texture)) {} void Update(float dt) { state_.x state_.vx * dt; state_.y state_.vy * dt; state_.life - dt; } void Render() const { if (state_.life 0.0f) return; texture_-Bind(); // 用 state_ 里的位置、旋转来绘制四边形 } private: ParticleState state_; std::shared_ptrTexture texture_; };这样每个粒子占的内存不到原来的一半因为重的纹理数据不再跟着粒子复制了。实际重构后粒子的内存占用降了将近70%。3.2 工厂如何在创建时挡住重复资源粒子系统里通常会有一个“发射器”或者“特效配置”的概念根据配置里的纹理路径去创建粒子。我把这块逻辑全部接到TextureManager上class ParticleSystem { public: void Emit(const std::string texturePath, ParticleState initial) { auto texture textureManager_.Get(texturePath); activeParticles_.emplace_back(initial, texture); } private: TextureManager textureManager_; std::vectorParticle activeParticles_; };这样写的好处是整个粒子系统启动时真正执行的纹理加载次数等于纹理种类数而不是粒子数量。假设场景里一共有5种特效纹理那么最多只会加载5次纹理而在重构之前同样的粒子配置可能触发几百次重复加载。当然Get()内部还有一个值得处理的点纹理路径的规范化。我遇到过同一个纹理用相对路径和绝对路径各加载了一次的情况导致缓存失效。处理方式很简单在Get()入口做一次路径规范化比如统一转成小写、统一去掉结尾分隔符。3.3 性能对比与内存数据记录重构完之后我做了个简单的基准测试一万个粒子5种纹理每帧做更新和渲染。对比数据如下指标重构前重构后纹理加载次数约10000次5次堆内存占用约290MB约85MB粒子更新耗时约1.8ms约0.9ms渲染调用耗时约4.2ms约2.6ms看到这个结果我更加确信这个模式的价值。内存和耗时的双重下降源于对象体积变小后缓存命中率提升、分配次数变少、以及纹理绑定的状态切换变少。当然数字因平台和实现而异但趋势是确定的。3.4 一些容易被忽略的实现细节享元模式下粒子更新函数里不应该出现和纹理相关的逻辑因为那是共享的、不变的。所有需要变化的内容——包括每个粒子的颜色叠加、透明度、缩放——都应该放在ParticleState里。我在重构时吃了亏一开始把缩放系数放进纹理对象里想着纹理的采样区域一样给纹理加个缩放不就行了。结果两个粒子用了同一份纹理一个缩放1.0一个缩放2.0渲染就互相干扰了。正确做法是渲染时把外部状态作为参数传给绘制函数绘制函数在内部组合共享资源和外部状态。纹理绑定是统一的但位置、旋转、缩放矩阵是从粒子自己的状态里拼出来的。void Particle::Render(const RenderTransform transform) const { if (state_.life 0.0f) return; texture_-Bind(); // 使用 transform 和 state_ 构建模型矩阵 }4. 实战二文本编辑器中的共享字形4.1 字符对象的轻量化设计文本编辑器和粒子系统差异很大但它同样是享元模式的好素材。一屏文本可能两千个字符如果每个字符都加载一份字形数据那字体文件被读取了多少次我做过一个简单的编辑器实现文本内容是动态的每输入一个字符就创建一个字符对象。原来的设计是字符对象持有字形数据 坐标 颜色 字号。问题非常明显同样的汉字“的”出现在屏幕上一百次那一百份字形数据完全一样只是坐标不同。后来我把字形数据抽成享元字符对象只保存字符编码、坐标、颜色、字号、指向字形对象的指针。class GlyphManager { public: std::shared_ptrGlyph GetGlyph(wchar_t code) { auto it glyphs_.find(code); if (it ! glyphs_.end()) { return it-second; } auto glyph LoadGlyphFromFont(code); // 比较耗时 glyphs_[code] glyph; return glyph; } private: std::unordered_mapwchar_t, std::shared_ptrGlyph glyphs_; };4.2 渲染循环如何组合内部与外部状态渲染时每个字符需要先取到享元字形数据再叠加上自己的位置和颜色。这个过程在代码里体现为“查表 组装”。void TextRenderer::DrawText(std::wstring_view text, float x, float y) { float cursorX x; for (wchar_t ch : text) { auto glyph glyphManager_.GetGlyph(ch); CharacterRenderInfo info; info.code ch; info.x cursorX; info.y y; info.fontSize fontSize_; info.r color_.r; info.g color_.g; info.b color_.b; info.glyph glyph; renderQueue_.push_back(info); cursorX glyph-GetAdvanceWidth(); } }这里有一个细节glyph-GetAdvanceWidth()是字形数据的一部分属于内部状态所有字号和颜色下都一样可以直接从享元对象里拿。但如果是按比例缩放的字宽需要在外部状态里额外保存缩放因子渲染时再乘上。4.3 关于缓存的边界条件字形的缓存永远只增不减这在编辑器这种场景没问题因为字体文件的总字形数是有限的。但实际操作中如果渲染支持动态缩放或子像素抗锯齿同一个字符在不同字号下可能生成不同位图这时缓存键就要变成“字符编码字号”。我后来确实遇到这个问题把GetGlyph的接口改成了GetGlyph(code, font_size, outline_thickness)缓存键从单个wchar_t变成了一个自定义结构体。缓存键的变化提醒我设计享元工厂时不能只考虑“对象是什么”还要考虑“不同的使用参数”。一个工厂的参数最终决定了缓存的粒度参数越多复用的可能性越低参数太少又可能导致对象之间互相污染。需要在复用率和正确性之间找平衡。5. 进阶线程安全与资源释放策略5.1 多线程环境下工厂加锁的正确姿势游戏或编辑器里纹理加载和字形读取经常发生在后台线程。如果多个线程同时调用TextureManager::Get()内部unordered_map的读写就可能产生数据竞争。最直接的做法是加互斥锁std::shared_ptrTexture TextureManager::Get(const std::string path) { std::lock_guardstd::mutex lock(mutex_); auto it pool_.find(path); if (it ! pool_.end()) { return it-second; } auto texture std::make_sharedTexture(path); pool_[path] texture; return texture; }但这种写法有个坑make_sharedTexture本身是个耗时的加载操作。持锁进行文件IO会让所有线程在加载纹理时全部阻塞。优化方案是“先查后加载再确认”或者“加载完成后原子发布”。我常用的一个简单方案是双检查加锁std::shared_ptrTexture TextureManager::Get(const std::string path) { { std::lock_guardstd::mutex lock(mutex_); auto it pool_.find(path); if (it ! pool_.end()) { return it-second; } } // 没有缓存先在锁外构造纹理对象 auto texture std::make_sharedTexture(path); std::lock_guardstd::mutex lock(mutex_); auto result pool_.emplace(path, texture); return result.first-second; }这种写法的问题在于如果两个线程同时发现没有缓存它们会各自构造一份纹理最后只有一个进入缓存另一个会白白浪费一次加载。不过在很多场景下并发加载同一个资源的概率低这种优化就能明显提升吞吐。如果项目要求绝对不重复加载就要用一个“加载中”标记来去重。实现会更复杂但思路清晰缓存里加一个状态字段标记“未加载/加载中/已加载”。5.2 shared_ptr循环引用的隐患我遇到过一个循环引用问题纹理管理器持有shared_ptrTexture纹理对象里为了回调方便又保存了一个指向管理器的shared_ptrTextureManager。结果就是管理器永远不会被释放纹理也永远不会被释放程序退出时出现大量泄漏报告。排查后解决方案很简单纹理对象里的管理器指针改为裸指针或者改用std::weak_ptr。享元对象本身不应该持有工厂的强引用它只需要在创建时使用工厂资源之后就不需要回引了。5.3 动态资源释放还是常驻内存不同项目对资源释放的要求不一样。像关卡制游戏某些纹理只在特定关卡出现关卡结束后就应该释放掉而字体、通用UI图标这类全局资源适合常驻。针对关卡资源我实现过引用计数定期清理工厂记录每个享元对象的使用计数当计数归零后不立即释放而是进入一个“待回收”列表每隔一段时间或者在下一次加载资源量达到阈值时统一清理。这样能避免“加载-释放-又加载”的抖动问题同时又不会让无用的资源一直霸占内存。class RefCountedTextureCache { public: std::shared_ptrTexture Acquire(const std::string path) { std::lock_guardstd::mutex lock(mutex_); auto it pool_.find(path); if (it ! pool_.end()) { it-second.refCount; return it-second.texture; } auto tex std::make_sharedTexture(path); pool_[path] {tex, 1}; return tex; } void Release(const std::string path) { std::lock_guardstd::mutex lock(mutex_); auto it pool_.find(path); if (it ! pool_.end()) { if (--it-second.refCount 0) { // 在某个时机再真正释放 pendingRelease_.push_back(path); } } } private: struct Entry { std::shared_ptrTexture texture; int refCount; }; std::unordered_mapstd::string, Entry pool_; std::vectorstd::string pendingRelease_; std::mutex mutex_; };使用计数由工厂统一维护而不是依靠shared_ptr的强引用计数是因为这样能把“业务引用”和“缓存保留”两个生命周期分开。比如某些资源在缓存里常驻但业务引用已经没了这种情况下shared_ptr计数为零会直接释放不符合我“延迟回收”的预期。6. 常见坑与排查技巧实录6.1 内部状态被意外修改这是享元模式最高频的坑。有人图省事把每个使用方的独有参数塞进共享对象导致后续使用者看到的是上一个使用者留下的残留值。排查技巧是在享元对象的成员上加上const声明并且在接口函数里把不该暴露的写入方法去掉。如果设计上就是只读的共享数据就让它彻底不可变。我还会在调试版本里加一个“校验和”给每个共享对象在构造时算出一个哈希每次使用前再算一次如果哈希对不上立刻断言。这个技巧帮我抓到过几次神不知鬼不觉的越权写入。6.2 缓存键设计不合理导致复用率极低刚开始做字形缓存时我曾用过 “字符编码 字体名” 作为键。结果发现系统里字体名有各种别名同一款字体加载出来名字却不一样。排查后发现有些场景传的是Arial有些传的是Arial MT。修法是先把字体名通过一个统一映射函数标准化再放进缓存键。缓存键的设计原则键必须能唯一标识“这个享元对象是否可被复用”的全部要素。要素太多会导致复用率低要素太少会导致错误共享。这个边界需要在真实数据下反复验证。6.3 shared_ptr的原子开销在极端热路径上不可忽视有些性能敏感模块比如每帧都要执行几千次纹理获取的渲染循环shared_ptr的引用计数原子操作会成为瓶颈。我做过性能剖析发现Get()函数里花在shared_ptr拷贝上的时间占比超过了纹理绑定。这种情况下我的处理办法是把“获取共享指针”和“使用共享指针”分成两级第一级在缓存里拿到裸指针第二级在渲染循环外批量持有一批shared_ptr循环内只传递裸指针。也就是说把共享所有权控制在较长生命周期的持有者手里而不是每一次渲染命令都去做一次原子加一减一。6.4 误把享元模式当成单例模式经常有人问享元工厂是不是就是单例从代码形态看有点像工厂往往是全局唯一的但它不是模式的重点。单例模式强调的是“一个类只有一个实例”享元模式强调的是“多个对象共享一份内部数据”。我见过一个反面案例把整个粒子类型做成了单例结果两个粒子系统只能共享一套粒子配置完全没法并行。正确做法是每个粒子系统持有一个属于自己的TextureManager实例系统之间不共享系统内部的多个粒子共享同一份纹理。7. 最后的经验总结与后续扩展思路我在实际项目中实践享元模式已经有几年了最大的体会是这个模式的价值不在于代码结构有多花哨而在于它逼着你认真思考“什么是真正共享的什么是必须独立的”。这个思考过程往往比套用模式本身更能提升代码质量。如果让我给后面的扩展提几个方向一是结合对象池技术把粒子这类大量短命对象放进一个预分配的内存池里避免频繁堆分配二是结合原型模式当需要创建大量相似对象时先克隆享元对象再修改外部状态三是引入资源管理框架把纹理、音频、字体、网格统一纳入一个带优先级清理的缓存系统。最后再分享一个我常用的调试小技巧在工厂里加一段日志或者计数器统计每个键被请求的次数。如果某个键被请求了上千次但缓存命中率接近100%说明这个享元设计是合理的如果缓存命中率很低说明缓存键的粒度可能太粗或者配置本身太分散需要调整。用数据说话比拍脑袋设计靠谱得多。

相关新闻

Python打卡30天复盘:重写旧代码与装饰器实战

Python打卡30天复盘:重写旧代码与装饰器实战

1. 为什么我在第30天没有学新东西1.1 这一晚我只做了一件事:把之前的代码重新拆开今天是我Python打卡的第30天。标题写着"王者归来",但真坐到电脑前,我没有急着打开新的教程,也没有去啃所谓的进阶框架。我把过去29天写过…

2026/10/15 23:17:38 阅读更多 →
AI学习笔记第一课:在VSCode中配置Continue接入TaoToken

AI学习笔记第一课:在VSCode中配置Continue接入TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 23:17:38 阅读更多 →
小程序视频播放避坑指南:同层渲染、编码兼容与性能优化

小程序视频播放避坑指南:同层渲染、编码兼容与性能优化

1. 视频播放的第一座山:同层渲染与层级遮挡问题 说句实在话,小程序里做视频功能,第一个坑往往不是代码逻辑,而是“视频组件到底被谁盖住了”。早年做原生组件开发, video 在旧版本基础库上属于“原生组件”&#xff…

2026/10/15 23:17:38 阅读更多 →

最新新闻

Redis内存管理:过期删除与淘汰策略实战解析

Redis内存管理:过期删除与淘汰策略实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 1:11:35 阅读更多 →
JavaScript every()和some()深度解析:数组布尔判断的原理、陷阱与工程实践

JavaScript every()和some()深度解析:数组布尔判断的原理、陷阱与工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 1:11:35 阅读更多 →
学生报名系统v2012源码:教育类业务系统演进的活体标本

学生报名系统v2012源码:教育类业务系统演进的活体标本

简介:这是一套基于PHP开发的学生报名系统源码(v2012版),面向中小教育机构、职业院校及培训单位,用于快速部署标准化报名管理流程,解决信息登记、考场分配、准考证生成与数据汇总等核心业务需求。系统功能完…

2026/10/16 1:11:35 阅读更多 →
UCOS移植文件深度解析:从OS_CPU.H到汇编,告别HardFault

UCOS移植文件深度解析:从OS_CPU.H到汇编,告别HardFault

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 1:11:35 阅读更多 →
储能电池被动均衡方案:MP2642GR-Z与PIC18F86J15硬件设计与固件实现

储能电池被动均衡方案:MP2642GR-Z与PIC18F86J15硬件设计与固件实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 1:11:35 阅读更多 →
《奇特的一生》与时间统计法:用56年记录重塑人生

《奇特的一生》与时间统计法:用56年记录重塑人生

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 1:10:34 阅读更多 →

日新闻

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 0:00:05 阅读更多 →
信号与系统频域分析作业解题指南

信号与系统频域分析作业解题指南

1. 作业背景与核心要求解析信号与系统作为电子信息类专业的核心基础课,其作业设计往往直接关联课程的核心知识框架。2026年春季第六次作业的布置,通常对应着课程进展到频域分析的关键阶段。根据常规教学进度推测,本次作业可能涉及以下核心内容…

2026/10/16 0:00:05 阅读更多 →
Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

1. 从一个"四不像"的练手项目说起如果你已经看完了Django的官方教程前几章,跟着敲完了投票应用,大概会有一种"我好像会了,又好像什么都不会"的割裂感。教程里每个知识点都是拆开的——这一章讲模型,下一章讲视…

2026/10/16 0:00:05 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →