1. 为什么会有这个系列从一次性能焦虑说起去年年底我接手了一个内部工具项目需求很明确做一个能自动抓取数据、调用模型、执行本地脚本、再把结果整理成报告的小型智能体。团队里有人提议用 Python 快速搭一版毕竟生态成熟、上手快三天就能跑通 demo。但我犹豫了。犹豫的原因不是 Python 不好而是这个工具最终要嵌入到一个已有的桌面端产品里那个产品本身是 C 写的底层依赖了大量本地计算库和硬件接口。如果智能体用 Python 写就意味着要额外维护一套进程间通信、打包运行时、处理跨语言调用的内存边界问题。更麻烦的是这个智能体需要频繁做字符串解析、状态机切换、并发任务调度Python 的 GIL 在高频小任务场景下会变成瓶颈。于是我做了一个当时看起来有点“反直觉”的决定用 C 从零写一个 AI Agent 框架。这个系列就是那次实践的完整记录。你可能会问C 写 AI Agent 是不是自找麻烦我的答案是取决于你的场景。如果你只是做一个聊天机器人、一个 RAG 问答、一个跑在云端的轻量服务Python 或 TypeScript 确实是更优解。但如果你的智能体需要嵌入现有 C 系统、需要极低的调度延迟、需要精细控制内存和线程、需要和本地硬件或高性能计算库深度交互那么 C 的价值就出来了。这个系列适合三类人第一类是有 C 基础、想了解智能体架构怎么落地的开发者第二类是做嵌入式或桌面端、需要在现有 C 工程里集成 AI 能力的工程师第三类是想从底层理解 Agent 运行机制、不想只停留在调 API 层面的技术爱好者。我会从整体架构讲起然后一步步拆解核心模块最后给出可复现的代码骨架和踩坑记录。2. 整体架构设计一个 C Agent 应该长什么样2.1 核心模块划分与职责边界一个 AI Agent 不管用什么语言写本质上都是“感知—决策—执行”的循环。但在 C 里这个循环的每个环节都需要更明确的资源管理和线程模型。我把整个框架拆成了六个核心模块每个模块的职责边界必须清晰否则 C 的复杂性会迅速失控。第一个模块是Task 抽象层。它负责定义“一个任务是什么”包括输入参数、期望输出、超时时间、优先级、重试策略。在 C 里我选择用纯虚基类ITask来定义接口所有具体任务继承它。这样做的好处是任务可以统一进入队列调度器不需要知道任务的具体类型。第二个模块是Context 管理器。智能体在执行过程中需要维护对话历史、工具调用记录、中间状态。Python 里可以用字典随便塞但 C 里必须明确生命周期。我用一个Context类持有std::shared_ptr管理的状态对象所有状态变更都通过方法调用避免裸指针到处传。第三个模块是Tool 注册与调用层。智能体要干活就得调用外部工具读文件、发请求、执行命令、查数据库。每个工具都实现统一的ITool接口注册到ToolRegistry里。调用时通过名称查找参数用std::variant或 JSON 对象传递。这里的关键是工具调用的隔离性——一个工具崩溃不能拖垮整个 Agent。第四个模块是LLM 交互层。这一层负责和模型服务通信构造 prompt、发送请求、解析响应、处理流式输出。C 里没有像 LangChain 那样现成的库所以需要自己封装 HTTP 客户端和 JSON 解析。我选的是libcurl加nlohmann/json够用且稳定。第五个模块是调度器。这是整个 Agent 的心脏。它维护一个任务队列根据优先级和依赖关系决定下一个执行什么。调度器需要处理并发我用的是线程池加条件变量的方案避免每个任务都创建新线程。第六个模块是日志与可观测性。C 项目最容易忽视这块但 Agent 的行为链路很长没有日志根本没法调试。我集成了spdlog并且定义了统一的日志格式包含任务 ID、模块名、耗时、状态。这六个模块之间的关系可以用一句话概括调度器从队列取任务任务通过 Context 获取状态通过 ToolRegistry 调用工具通过 LLM 层和模型交互所有动作都写入日志。2.2 为什么不用现成框架C 生态的现实考量你可能会想不是有 LangChain 吗不是有 Spring AI 吗为什么不用答案很简单这些框架的 C 支持要么不存在要么只是社区维护的绑定版本滞后、文档稀缺、出问题很难排查。我实际调研过几个方案。LangChain 官方没有 C 版本社区有一些非官方移植但更新频率很低而且大量依赖 Python 运行时。Spring AI 是 Java 生态的和 C 集成需要走 JNI复杂度不比从零写低。还有一些所谓的“C AI 框架”点进去一看要么是只做了 HTTP 封装要么是几年前停更的玩具项目。更关键的是智能体的核心逻辑其实并不复杂。它不像深度学习训练那样需要庞大的算子库也不像推理引擎那样需要极致的性能优化。它本质上是一个任务编排系统加上模型调用和工具调用。这部分逻辑用 C 写代码量可控而且能完全掌控行为。当然自己写意味着要处理很多琐碎的事情JSON 解析、HTTP 超时、线程安全、错误传播。但这些恰恰是 C 工程师的日常不算不可逾越的障碍。2.3 阅读路线图从骨架到血肉的推进顺序这个系列我不会一上来就贴几千行代码那样没人看得下去。我的安排是沿着“先跑通、再优化、后扩展”的路线推进。第一阶段是最小可运行骨架。我会先定义一个最简单的 Agent它只能做一件事接收用户输入调用一次模型返回结果。这个阶段不涉及工具调用、不涉及并发、不涉及复杂状态管理。目的是让你先看到 C Agent 的基本形态。第二阶段是工具调用与状态管理。在骨架基础上加入 ToolRegistry 和 Context让 Agent 能调用外部函数并记住调用历史。这个阶段会涉及std::variant、std::function、智能指针的使用技巧。第三阶段是并发与调度。引入线程池和任务队列让 Agent 能同时处理多个请求。这里会重点讲 C 并发编程在 Agent 场景下的特殊考量比如如何避免死锁、如何做超时控制、如何保证线程安全。第四阶段是可观测性与错误处理。加入结构化日志、指标采集、错误重试机制。这部分是生产级 Agent 和 demo 级 Agent 的分水岭。第五阶段是扩展与集成。讲如何把 Agent 嵌入现有 C 工程如何和 Python 工具混合使用如何做性能调优。每个阶段都有可运行的代码你可以跟着一步步搭起来。我建议不要跳阶段因为后面的内容依赖前面的基础。3. 核心细节解析C 写 Agent 的关键技术点3.1 任务抽象与生命周期管理在 C 里设计任务接口第一个要解决的问题是任务的状态怎么表示我用的是一个枚举加状态机的方案。enum class TaskState { Pending, Running, Completed, Failed, Cancelled };每个任务对象内部维护自己的状态状态转换只能通过特定方法触发。比如markRunning()只能在Pending状态下调用否则抛异常。这样做的好处是当 Agent 并发执行时任何非法状态转换都会立刻暴露而不是悄悄产生错误结果。任务的输入输出我用std::any来承载虽然std::any有性能开销但在任务粒度上可以接受。如果你追求极致性能可以用模板化的任务类但那样会牺牲运行时的灵活性。我的选择是先用std::any保证通用性后续再针对热点路径做特化。生命周期管理是 C Agent 最容易出问题的地方。一个任务从创建到销毁可能经历队列等待、执行、回调、清理等多个阶段。我用std::shared_ptr来管理任务对象确保只要还有任何模块持有引用对象就不会被销毁。但这也带来循环引用的风险所以 Context 里持有任务引用时用的是std::weak_ptr。注意在任务回调里不要长时间持有shared_ptr否则会导致任务对象无法及时释放在高频场景下内存会持续增长。3.2 工具注册机制与类型安全调用工具调用的核心矛盾是Agent 需要动态调用未知的工具但 C 是静态类型语言。我的解决方案是两层设计。第一层是工具注册。每个工具在注册时需要提供工具名、描述、参数 schema、以及一个统一的调用函数。调用函数的签名是固定的using ToolFunction std::functionToolResult(const nlohmann::json params);所有工具都把参数包装成 JSON返回值也包装成 JSON。这样注册表里存的就是统一的函数类型不需要模板。第二层是类型安全的参数解析。工具内部从 JSON 里取参数时用辅助函数做类型检查和转换templatetypename T T getParam(const nlohmann::json params, const std::string key) { if (!params.contains(key)) { throw ToolException(Missing parameter: key); } return params.at(key).getT(); }这样如果模型传错了参数类型会在工具调用时立刻抛出明确异常而不是产生未定义行为。工具注册表本身用std::unordered_mapstd::string, ToolEntry存储读写用std::shared_mutex保护。因为工具注册通常在启动时完成运行时主要是读操作读写锁比互斥锁更合适。3.3 模型交互层的超时与重试设计和模型服务通信是 Agent 里最不可控的环节。网络抖动、服务限流、响应超时都是常态。C 里做 HTTP 请求我用的是libcurl的多接口模式而不是简单接口。原因很简单简单接口是阻塞的一个请求卡住会拖死整个线程。多接口模式允许在单线程里管理多个并发请求配合curl_multi_poll做事件循环。但这样写代码复杂度高所以我封装了一个HttpClient类对外暴露同步接口内部用多接口加条件变量实现。超时控制分三层连接超时、传输超时、总超时。连接超时设 5 秒传输超时设 30 秒总超时设 60 秒。超过总超时直接取消请求返回超时错误。重试策略我采用的是指数退避加抖动。第一次失败后等 1 秒第二次等 2 秒第三次等 4 秒每次加随机抖动避免惊群。最多重试三次三次都失败就标记任务失败。实操心得重试只对可恢复错误生效比如超时、连接失败、5xx 响应。对于 4xx 响应重试没有意义直接失败更快。3.4 并发模型选择线程池还是协程C 里做并发传统方案是线程池新方案是 C20 协程。我两个都试过最终选择了线程池加任务队列的方案。协程的优势是轻量、上下文切换开销小适合大量 IO 密集型任务。但协程的生态还不成熟调试工具少异常处理复杂而且和现有 C 库的集成需要大量适配代码。对于一个需要快速落地、稳定运行的项目来说线程池是更稳妥的选择。线程池的核心参数是线程数量。我一开始设的是 CPU 核心数但实测发现 Agent 的任务大部分时间在等 IOCPU 利用率很低。后来改成 CPU 核心数的两倍吞吐量明显提升。再往上加收益递减而且线程切换开销开始显现。任务队列我用的是std::deque加std::mutex加std::condition_variable。没有用无锁队列因为 Agent 的任务频率还没到需要无锁的程度而且无锁队列的正确性很难保证。4. 实操过程从零搭建最小可运行 Agent4.1 环境准备与依赖选型先说环境。我用的编译器是 GCC 11C 标准开到 C20。构建系统用的是 CMake 3.20 以上。依赖库有四个libcurl做 HTTPnlohmann/json做 JSON 解析spdlog做日志fmt做字符串格式化。这四个库都是 header-only 或者容易静态链接的部署时不需要额外装运行时。CMake 配置的关键是找到这些依赖。libcurl用find_package(CURL REQUIRED)nlohmann/json和spdlog可以直接用FetchContent拉取避免手动下载。cmake_minimum_required(VERSION 3.20) project(cpp_agent CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(CURL REQUIRED) include(FetchContent) FetchContent_Declare( json GIT_REPOSITORY https://github.com/nlohmann/json.git GIT_TAG v3.11.3 ) FetchContent_MakeAvailable(json) FetchContent_Declare( spdlog GIT_REPOSITORY https://github.com/gabime/spdlog.git GIT_TAG v1.13.0 ) FetchContent_MakeAvailable(spdlog) add_executable(agent main.cpp) target_link_libraries(agent PRIVATE CURL::libcurl nlohmann_json::nlohmann_json spdlog::spdlog)注意如果你在 Windows 上开发libcurl的链接可能会遇到CURL_STATICLIB的问题。解决办法是在 CMake 里加add_definitions(-DCURL_STATICLIB)并且确保链接了ws2_32和crypt32。4.2 定义 Agent 主循环与任务结构最小 Agent 的主循环其实很简单读输入、构造任务、执行任务、输出结果。但为了后续扩展我把这个循环抽象成了Agent类。class Agent { public: Agent(std::shared_ptrLLMClient llm) : llm_(std::move(llm)) {} std::string run(const std::string userInput) { auto task std::make_sharedSimpleTask(userInput); task-markRunning(); try { auto response llm_-chat(task-input()); task-markCompleted(response); return response; } catch (const std::exception e) { task-markFailed(e.what()); return Error: std::string(e.what()); } } private: std::shared_ptrLLMClient llm_; };SimpleTask继承自ITask只做最基本的状态管理。LLMClient封装了 HTTP 请求和 JSON 构造。这个版本跑起来后你可以用一句简单的输入测试int main() { auto llm std::make_sharedLLMClient(http://localhost:8080/v1/chat); Agent agent(llm); std::cout agent.run(你好介绍一下你自己) std::endl; return 0; }如果模型服务正常你会看到返回的文本。这就是最小可运行 Agent。4.3 加入工具调用让 Agent 真正干活最小版本只能聊天不能干活。要干活就得加工具。我在Agent里加了一个ToolRegistry并且在主循环里解析模型返回的工具调用请求。模型返回的格式我约定为 JSON包含tool和params两个字段。Agent 解析后查找工具、执行、把结果再喂回模型。std::string Agent::runWithTools(const std::string userInput) { auto task std::make_sharedSimpleTask(userInput); task-markRunning(); std::string currentInput userInput; for (int round 0; round maxRounds_; round) { auto response llm_-chat(currentInput); auto toolCall parseToolCall(response); if (!toolCall.has_value()) { task-markCompleted(response); return response; } auto result toolRegistry_.invoke(toolCall-name, toolCall-params); currentInput 工具 toolCall-name 返回: result.dump(); } task-markFailed(Max rounds exceeded); return Error: too many tool calls; }这里的关键是maxRounds_限制。没有这个限制模型可能会陷入无限调用工具的循环。我设的是 5 轮超过就强制终止。工具的实现示例class ReadFileTool : public ITool { public: std::string name() const override { return read_file; } ToolResult invoke(const nlohmann::json params) override { auto path getParamstd::string(params, path); std::ifstream file(path); if (!file.is_open()) { throw ToolException(Cannot open file: path); } std::string content((std::istreambuf_iteratorchar(file)), std::istreambuf_iteratorchar()); return ToolResult{{content, content}}; } };注册工具toolRegistry_.registerTool(std::make_sharedReadFileTool());这样 Agent 就能读文件了。同理可以加写文件、执行命令、发 HTTP 请求等工具。4.4 并发改造从单线程到线程池单线程版本跑通后下一步是并发。我加了一个ThreadPool类Agent 的run方法把任务提交到线程池返回std::future。class ThreadPool { public: explicit ThreadPool(size_t threads) { for (size_t i 0; i threads; i) { workers_.emplace_back([this] { while (true) { std::functionvoid() task; { std::unique_lock lock(mutex_); cv_.wait(lock, [this] { return stop_ || !tasks_.empty(); }); if (stop_ tasks_.empty()) return; task std::move(tasks_.front()); tasks_.pop(); } task(); } }); } } templatetypename F auto submit(F f) - std::futuredecltype(f()) { using ReturnType decltype(f()); auto task std::make_sharedstd::packaged_taskReturnType()(std::forwardF(f)); auto future task-get_future(); { std::unique_lock lock(mutex_); tasks_.emplace([task] { (*task)(); }); } cv_.notify_one(); return future; } ~ThreadPool() { { std::unique_lock lock(mutex_); stop_ true; } cv_.notify_all(); for (auto worker : workers_) { worker.join(); } } private: std::vectorstd::thread workers_; std::queuestd::functionvoid() tasks_; std::mutex mutex_; std::condition_variable cv_; bool stop_ false; };Agent 的调用变成auto future pool_.submit([this, input] { return runWithTools(input); }); auto result future.get();实操心得线程池的析构顺序很重要。必须先设置stop_并通知所有线程再 join。如果先 join 再设置stop_线程会一直等条件变量导致死锁。5. 常见问题与排查技巧实录5.1 编译链接阶段的典型坑C 项目最烦人的就是编译链接问题。我整理了几个高频错误和解决办法。第一个是undefined reference to curl_global_init。这是因为链接顺序不对libcurl必须放在依赖它的目标文件后面。在 CMake 里用target_link_libraries通常没问题但如果你手动写 Makefile要注意顺序。第二个是nlohmann/json的版本冲突。如果你项目里其他库也依赖 JSON可能会拉入不同版本。解决办法是用FetchContent统一管理或者用find_package指定版本。第三个是 Windows 上的LNK2019错误通常是运行时库不匹配。Debug 和 Release 的libcurl不能混用必须保持一致。错误信息原因解决办法undefined reference to curl_global_init链接顺序错误把 CURL::libcurl 放在最后LNK2019 unresolved external运行时库不匹配统一 Debug/Releasefatal error: nlohmann/json.hpp not found头文件路径未包含检查 FetchContent 或 include_directoriesmultiple definition of json头文件重复包含使用 include guard 或 inline5.2 运行时崩溃与内存问题排查C Agent 运行时崩溃最常见的原因是悬空指针和线程竞争。我遇到过几次排查过程记录如下。第一次崩溃是在工具调用返回后。工具返回了一个指向局部变量的引用Agent 拿到后继续使用结果访问了已释放的内存。解决办法是工具返回值必须是值类型或者shared_ptr不能返回引用。第二次崩溃是在并发场景下。两个线程同时修改 Context 里的同一个字段导致数据竞争。用ThreadSanitizer跑了一遍定位到问题。解决办法是 Context 的所有修改方法都加锁或者用原子类型。第三次崩溃是日志库的初始化顺序问题。spdlog的全局 logger 在静态初始化阶段被使用但此时 logger 还没创建。解决办法是把 logger 的创建放到main函数开头或者用函数内静态变量延迟初始化。注意C Agent 调试一定要开-fsanitizeaddress,undefined能提前发现大量内存问题。生产环境可以关掉但开发阶段必须开。5.3 模型交互的异常处理清单和模型服务交互异常情况比想象中多。我整理了一个速查表。异常现象可能原因处理策略连接超时服务未启动或网络不通重试三次失败则报错响应 429请求频率超限退避重试降低并发响应 500服务内部错误重试最多三次响应格式错误模型返回非 JSON记录原始响应尝试修复流式输出中断网络抖动重新发起请求或返回部分结果工具调用参数错误模型理解偏差返回错误信息给模型让其修正这里特别说一下流式输出中断。Agent 如果依赖流式结果做后续决策中断会导致状态不一致。我的做法是流式输出只用于展示不用于决策。决策依赖完整的最终响应。5.4 性能调优的实测数据最后分享一些性能调优的实测数据。测试场景是 100 个并发任务每个任务调用一次模型加一次工具。配置平均延迟吞吐量CPU 利用率单线程1200ms83 req/s15%4 线程350ms285 req/s45%8 线程220ms454 req/s70%16 线程210ms476 req/s85%32 线程230ms434 req/s90%从数据看8 线程是性价比最高的点。再往上加吞吐量增长放缓延迟反而因为线程切换而上升。这个结论和机器配置有关你的环境可能需要重新测。另外JSON 解析是热点。nlohmann/json虽然方便但解析大 JSON 时性能一般。如果 Agent 处理的数据量大可以考虑换成simdjson或者rapidjson。我实测下来simdjson在解析 1MB 以上 JSON 时比nlohmann/json快 3 到 5 倍。我个人在实际操作中的体会是C 写 Agent 最大的挑战不是语言本身而是工程化。你需要自己处理 Python 里框架帮你搞定的事情序列化、并发、错误传播、资源管理。但一旦搭起来你对整个系统的掌控力是 Python 方案给不了的。后续这个系列还会继续更新下一篇我会讲怎么把 Agent 嵌入现有 C 工程以及怎么和 Python 工具做混合调用。如果你在跟着做建议先把最小骨架跑通再逐步加功能不要一上来就追求完整架构。
企业数字化 ERP 产品动态
相关推荐
多Agent系统组织模型设计与实践指南 1. 多Agent系统组织模型设计概述在复杂问题求解领域,多Agent系统(MAS)正成为突破传统集中式架构局限的关键技术范式。这种由多个自主智能体组成的分布式网络,通过角色分工与协同机制,展现出处理开放性、动态性任务的独特优势。去年某电商平台… · 2026/11/2 22:43:01
Higress 云原生网关实战:Envoy+Istio+Wasm 架构解析与性能调优 1. 从 Envoy 到 Higress:一个云原生网关的诞生逻辑第一次接触 Higress 是在一个内部服务治理项目里,当时我们面临一个很典型的问题:K8s 集群里跑着几十个微服务,南北向流量用 Nginx Ingress,东西向用 Istio,… · 2026/11/2 22:43:01
多体系统预测控制原理与工业应用实践 1. 多体系统预测控制概述多体系统预测控制(Multibody Predictive Control)是近年来控制工程领域备受关注的前沿方向。我在工业机器人轨迹规划项目中首次接触这一方法时,就被其独特的优势所吸引——它能够同时处理多个相互关联的动力学系统的控… · 2026/11/2 22:42:01
Java FRQ 001 - If condition /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 23:26:30
Folia 全屏歌词播放器 10 分钟安装跑通:Windows / macOS / Linux 全路线 Folia 全屏歌词播放器 10 分钟安装跑通:Windows / macOS / Linux 全路线 【免费下载链接】folia-major 专注于绚丽的歌词动画效果的本地音乐/navidrome/第三方多平台在线音乐播放器 项目地址: https://gitcode.com/GitHub_Trending/fo/folia-major
你在国内网… · 2026/11/2 23:25:30
PLFM雷达原理与10.5GHz相控阵开源实现 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 23:23:29
24GHz FMCW雷达室内人员检测:从波形参数到CFAR信号处理全流程 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 23:23:29
可编程线性调频雷达信号仿真框架PLFM_RADAR设计与实现 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 23:23:29
基于MAX14431与PIC32的工业SPI隔离通信方案详解 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 23:23:29
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18
雷达信号调制技术解析:从LFM到伪线性调频原理与应用 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 1:01:15
基于MAX14431与TM4C123的工业SPI隔离电路设计全攻略 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 1:01:15
PLFM_RADAR:相位线性调频雷达信号处理框架 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 1:01:15
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18