【C++/Qt 进阶】设计模式系列(5):适配器模式——给“不兼容的接口“贴一层翻译膜

系列文章:

  1. 单例模式:【C++/Qt 进阶】设计模式系列(1):单例模式——饿汉式内存泄漏真相、指针单例存在的意义、五种实现终极对比-CSDN博客
  2. 工厂模式与抽象工厂模式:【C++/Qt 进阶】设计模式系列(2):工厂模式——从游戏角色到披萨店-CSDN博客
  3. 建造者模式:【C++/Qt 进阶】设计模式系列(3): 建造者模式——把“复杂对象的组装“从业务代码里剥离出来-CSDN博客
  4. 原型模式:【C++/Qt 进阶】设计模式系列(4):原型模式——用“克隆“代替“重新构造“-CSDN博客
  5. 适配器模式(本篇)
  6. 桥接模式(持续更新中)

写在前面

⚠️ 注意:前四篇(单例 / 工厂 / 建造者 / 原型)均为创建型模式;从本篇起正式进入结构型模式的第一站——适配器。

上一讲我们通过原型模式解决了“复制当前对象”的问题。但在真实的 Qt 项目中,更常见的挑战是:

“我手头有一个现成的类(可能是第三方库,也可能是遗留代码),它的接口与系统需求不匹配,且无法修改源码。该怎么办?”

举个典型的 Qt 工程师都遇到过的场景——日志系统迁移:

// 旧代码:全量使用 qDebug() void SomeService::doWork() { qDebug() << "step 1"; // 输出到 stderr // ... 大量业务逻辑 ... qDebug() << "done"; }

如今需要替换为企业级日志库(如 spdlog、QsLog,或自研 logger)。但项目中有 200 多个.cpp文件都直接调用qDebug(),逐个修改?恐怕要改到明年。

另一个常见场景——第三方 SDK 接口风格与项目规范不符:

// 第三方相机接口(错误码风格) class VendorCamera { public: int Open(const char* dev); int Capture(unsigned char* buf, int* size); void Close(); }; // 项目统一接口(Qt 风格) class ICamera { public: virtual ~ICamera() = default; virtual bool open() = 0; virtual QImage grab() = 0; virtual void close() = 0; };

若直接使用VendorCamera,所有调用方都得重复编写if (rc != 0) handle_error(...)的样板代码。适配器模式正是为此类“接口不兼容”问题提供一层“翻译层”。

记忆口诀:“两头不对,中间一塞”——目标接口、适配者、客户端各司其职,适配器作为中间翻译者,打通沟通壁垒。


一、30 秒看懂适配器模式

维度工厂模式(造新车)适配器模式(贴翻译膜)
解决什么创建对象接口转换
关注重点选哪种类型让“不兼容”的类能协作
适用时机字段少、构造简单接入老系统 / 第三方 SDK
典型应用产品族 / 套件日志封装 / 协议转换 / SDK 包装

一句话总结:不修改现有类,让它能被新系统“翻译”着使用。在 C++ 中,通常采用对象适配(组合被适配者指针),少数情况下使用类适配(公有继承被适配者并实现目标接口)。


二、UML 类图与角色

以下是基于纯 C++/Qt 视角的适配器模式结构图,采用 Qt 容器、std::unique_ptr与虚函数声明的典型写法。

┌──────────────────────────────┐ │ class ICamera │ ← 目标接口(Target) │ { │ 客户端期望的统一接口 │ public: │ │ virtual ~ICamera() = default; │ virtual bool open() │ │ = 0; │ │ virtual QImage grab() │ │ = 0; │ │ virtual void close() = 0; │ │ }; │ └───────────────┬──────────────┘ │ implements / 继承 + 实现 ▼ ┌──────────────────────────────┐ │ class VendorCameraAdapter : │ ← 适配器(Adapter) │ public ICamera │ │ { │ │ public: │ │ explicit VendorCameraAdapter( │ VendorCamera* src) │ 构造时注入被适配者 │ : m_src(src) {} │ │ bool open() override; │ 翻译:错误码 → bool │ QImage grab() override; │ 翻译:原始数据 → QImage │ void close() override; │ 翻译:返回值 → void │ private: │ │ std::unique_ptr<VendorCamera> m_src; │ ← 持有被适配者(组合) │ }; │ └───────────────┬──────────────┘ │ 持有 ▼ ┌──────────────────────────────┐ │ class VendorCamera │ ← 适配者(Adaptee) │ { │ 已有或第三方类,接口不兼容 │ public: │ │ int Open(const char* dev); │ │ int Capture(unsigned char*, int* size); │ void Close(); │ │ }; │ └──────────────────────────────┘ ▲ │ 调用 ┌───────────────┴──────────────┐ │ class CameraClient │ ← 客户端(Client) │ { │ 只依赖 ICamera 接口 │ void shoot(ICamera* cam); │ │ }; │ └──────────────────────────────┘

三个核心角色

角色职责C++/Qt 中的体现
目标接口(Target)客户端期望的统一接口class ICamera { virtual open()/grab()/close() = 0; }
适配者(Adaptee)已存在但接口不兼容的类(第三方或旧代码)VendorCamera(int Open()风格)
适配器(Adapter)实现目标接口,内部组合适配者,完成接口翻译VendorCameraAdapter

💡C++ 特性提示:相比 Java 的extends继承方式,C++ 更倾向于使用组合而非继承来实现适配器,以避免多重继承带来的复杂性和耦合风险。因此,对象适配是主流选择。

三、基础代码实现(C++/Qt 版)

以下通过一个 Qt 工程师最熟悉的“统一日志门面”示例,将qDebug()、std::cout与假想的RemoteLogger三种日志实现统一为一个ILogger接口。

3.1 目标接口

// ilogger.h #include <QString> class ILogger { public: virtual ~ILogger() = default; virtual void info(const QString& msg) = 0; virtual void warn(const QString& msg) = 0; virtual void error(const QString& msg) = 0; };

3.2 适配者 1:Qt 内置qDebug()

// qdebuglogger.h #include <QDebug> #include "ilogger.h" // 适配者:Qt 风格,无类仅函数 // 使用空类包装,使其与其他适配者对齐 class QtDebugBackend { public: void output(QtMsgType level, const QString& msg) { qDebug().noquote() << msg; } };

3.3 适配者 2:远程日志服务

// remotelogger.h #include <QString> #include <QNetworkAccessManager> // 适配者:第三方团队提供的 HTTP 日志 SDK class RemoteLogger { public: int sendLog(const char* level, const char* msg) { return postHttp(level, msg); } private: int postHttp(const char*, const char*); };

3.4 适配器:统一前两者至ILogger

// remote_logger_adapter.h #include "ilogger.h" #include "remotelogger.h" #include <memory> // 对象适配:组合式封装 RemoteLogger class RemoteLoggerAdapter : public ILogger { public: explicit RemoteLoggerAdapter(std::unique_ptr<RemoteLogger> logger) : m_logger(std::move(logger)) {} void info(const QString& msg) override { if (m_logger->sendLog("INFO", msg.toUtf8().constData()) != 0) { // 失败兜底:降级到本地输出 qWarning() << "[fallback]" << msg; } } void warn(const QString& msg) override { m_logger->sendLog("WARN", msg.toUtf8().constData()); } void error(const QString& msg) override { m_logger->sendLog("ERROR", msg.toUtf8().constData()); } private: std::unique_ptr<RemoteLogger> m_logger; }; // 另一适配器:将 QtDebugBackend 包装为 ILogger class QtDebugAdapter : public ILogger { public: void info(const QString& msg) override { m_backend.output(QtInfoMsg, msg); } void warn(const QString& msg) override { m_backend.output(QtWarningMsg, msg); } void error(const QString& msg) override { m_backend.output(QtCriticalMsg, msg); } private: QtDebugBackend m_backend; };

3.5 客户端:完全透明无感

// client.cpp #include "ilogger.h" #include "remote_logger_adapter.h" class BusinessService { ILogger* m_log; public: explicit BusinessService(ILogger* log) : m_log(log) {} void doWork() { m_log->info("step 1"); // 客户端无需关心底层是本地还是远程 // ... 业务逻辑 ... m_log->error("failed"); // 同样透明 } }; int main() { // 运行时选择具体实现 auto remote = std::make_unique<RemoteLoggerAdapter>( std::make_unique<RemoteLogger>()); BusinessService svc(remote.get()); svc.doWork(); // 切换实现:只需更换适配器,业务代码零改动 QtDebugAdapter local; BusinessService svc2(&local); svc2.doWork(); return 0; }

编译运行命令:

g++ -std=c++17 -fPIC client.cpp -o adapter_demo $(pkg-config --cflags --libs Qt6Core) ./adapter_demo

四、对象适配 vs 类适配:如何选择

在 C++ 项目中,绝大多数场景应优先选用对象适配。

维度对象适配(组合)类适配(多继承)
实现方式class Adapter : public ITarget { Adaptee* m_src; }class Adapter : public ITarget, public Adaptee
灵活性✅ 高(支持运行时动态切换被适配者)❌ 低(绑定于编译期)
支持适配数量✅ 可组合多个适配者(多成员指针)❌ 仅能继承一个具体类(受限于单继承)
风险几乎无⚠️ 存在菱形继承隐患
Qt 项目推荐✅✅✅首选方案❌ 基本不使用
// ✅ 推荐:对象适配(组合) class VendorCameraAdapter : public ICamera { VendorCamera* m_src; // 组合方式,清晰安全 }; // ❌ 不推荐:类适配(多继承 + 菱形风险) class VendorCameraAdapter : public ICamera, public VendorCamera { // 若 VendorCamera 本身有基类,可能引发菱形继承问题 };

五、通用模板场景

模板 1:统一日志门面

即上文 3.4 节的实现,可抽象为通用结构:

// 所有业务代码依赖统一接口 ILogger ILogger* log = selectLoggerFromConfig("prod"); // 根据配置选择具体适配器 BusinessService(log).doWork();

核心收益:未来更换日志库时,业务代码无需修改,仅需替换适配器即可。


模板 2:协议转换(MQTT ↔ Modbus ↔ HTTP)

class IUnifiedProtocol { public: virtual QByteArray pack(const Command& cmd) = 0; virtual Command unpack(const QByteArray& raw) = 0; }; // 多个适配器实现统一接口 class MqttAdapter : public IUnifiedProtocol { /* 调用 libmosquitto */ }; class ModbusAdapter : public IUnifiedProtocol { /* 调用 libmodbus */ }; class HttpRestAdapter : public IUnifiedProtocol { /* 调用 QNetworkAccessManager */ };

核心收益:业务层仅调用IUnifiedProtocol,切换通信协议等同于更换适配器,解耦彻底。


模板 3:旧 API 兼容过渡

项目升级至 Qt6 后,QRegExp已被QRegularExpression替代。但旧代码中仍有数千处调用QRegExp::exactMatch(...)。可通过适配器提供临时兼容层:

// 过渡期兼容垫片,建议在 1-2 个版本后移除 class QRegExpAdapter { public: static bool exactMatch(const QString& str, const QString& pattern) { QRegularExpression re(pattern); return re.match(str).hasMatch(); } private: QRegularExpression m_re; }; // 老代码:QRegExpAdapter rx("^foo.*"); // rx.exactMatch("foobar"); // 依然可用

💡复用思路:上述三种模板均遵循“定义一个统一目标接口 + 实现多个适配器”的模式。读者可将ILogger、IUnifiedProtocol替换为项目中的实际统一接口名。


六、优缺点分析

6.1 优点

✅复用现有类:无需修改旧有代码(尤其在无法或不敢改动历史代码时极具价值)。
✅实现解耦:客户端仅依赖目标接口,更换适配器无侵入性。
✅统一多套接口:多个第三方 SDK → 统一接口 → 业务层只需编写一次逻辑。
✅符合开闭原则:新增 SDK 只需添加新适配器,不影响已有代码。

6.2 缺点

❌类数量膨胀:每接入一个新系统就需新增一个适配器类。
❌接口粒度难平衡:目标接口过细 → 适配器过多;过粗 → 失去适配意义。
❌滥用导致混乱:本应重写却强行适配,使系统结构复杂化。
❌依赖旧行为假设:适配器基于被适配者行为不变,一旦其变更,适配器也需同步调整。


七、与其他设计模式对比

模式关注点适用场景与适配器的区别
单例全局唯一实例日志器、配置中心解决“实例数量”问题
简单工厂快速创建对象产品少且稳定关注“选择哪个对象”
工厂方法子类决定创建逻辑产品族可扩展同上
抽象工厂一次性创建一组相关对象主题/界面套件同上
Builder分步构建复杂对象零件固定但组合多样关注“构造顺序”
Prototype克隆已有对象撤销重做、对象池关注“状态复制”
Adapter接口转换集成旧系统 / 第三方 SDK关注“接口翻译”

核心区别:前四者均为创建型模式(解决“如何创建”),而适配器属于结构型模式(解决“如何组合与转换”)。下一讲的桥接与装饰模式也同属结构型,将进一步探讨“对象组织方式”。


八、最佳实践与避坑指南

8.1 必须遵守

🔸优先使用对象适配(组合方式),避免类适配(多重继承)。
🔸适配器持有Adaptee的指针或智能指针,而非值类型——防止拷贝时意外复制被适配对象。
🔸目标接口应保持稳定:一旦发布即不可更改,否则所有适配器需重写。
🔸适配器应保持简洁:仅负责接口翻译(参数转换、错误码转异常),禁止掺杂业务逻辑。

8.2 常见误区

❌坑 1:适配器变成“万能处理器”

// 错误示例:在适配器中嵌入业务逻辑 class CameraAdapter : public ICamera { public: QImage grab() override { if (m_src->Capture(...) != 0) { retry(); // ← 业务逻辑! log_error(); // ← 业务逻辑! fallback(); // ← 业务逻辑! } } };

✅正确做法:适配器只做翻译,错误处理与重试交由业务层:

QImage CameraAdapter::grab() override { QByteArray buf; int size = 0; int rc = m_src->Capture(reinterpret_cast<unsigned char*>(buf.data()), &size); if (rc != 0) return {}; // 失败返回空,由业务层决定后续动作 return QImage::fromData(buf.left(size)); }

❌坑 2:目标接口粒度过细

// 错误示例:每个操作都暴露为虚函数 class ICamera { public: virtual void powerOn() = 0; virtual void powerOff() = 0; virtual void setExposure() = 0; virtual void setGain() = 0; virtual void setWhiteBalance() = 0; virtual void trigger() = 0; virtual QImage grab() = 0; // ... 共 50 个虚函数 };

✅正确做法:合并为 2~3 个粗粒度接口:

class ICamera { public: virtual bool configure(const CameraConfig& cfg) = 0; // 批量配置 virtual QImage grab() = 0; // 核心业务操作 virtual void close() = 0; };

❌坑 3:适配器与被适配者生命周期不一致

// 错误示例:栈对象传入适配器,析构后悬空 int main() { VendorCamera vendor; // 栈对象 VendorCameraAdapter adapter(&vendor); // 持有指针 useAdapter(&adapter); // OK } // vendor 先析构,adapter 使用时已悬空?

✅正确做法:使用std::unique_ptr明确所有权,“谁创建,谁管理”:

int main() { auto vendor = std::make_unique<VendorCamera>(); auto adapter = std::make_unique<VendorCameraAdapter>(vendor.get()); // adapter 析构时,vendor 仍有效 // 确保 vendor 析构前,adapter 已先释放 }

❌坑 4:目标接口未声明为抽象基类

// 错误示例:目标接口是具体类,无法扩展 class Camera {}; // 无 virtual,无 = 0

✅正确做法:目标接口必须具备虚析构和纯虚函数:

class ICamera { public: virtual ~ICamera() = default; // ← 关键 virtual bool open() = 0; // ← 关键 // ... };

九、写在最后

适配器模式是应对“已有类无法直接接入新系统”问题的标准解决方案。在 Qt 项目中,任何涉及第三方 SDK 接入、老 API 兼容、多协议统一的场景,几乎都离不开它。

一句话总结:别改老代码,给它贴一层翻译膜。

如果本文对你有帮助,欢迎点赞、收藏、关注三连支持~

下期预告:桥接模式(Bridge Pattern)——更深层次的解耦:将“抽象”与“实现”拆分为两个独立变化的维度。内容已在撰写中,敬请期待!

完整可编译示例代码(含 Qt .pro 工程)已上传,回复关键词Adapter Demo即可获取。

相关新闻

【沃思智能景观照明节能控制:点亮城市夜色,守护每一度电】

【沃思智能景观照明节能控制:点亮城市夜色,守护每一度电】

亮化工程是城市夜景的点睛之笔&#xff0c;从地标建筑到滨水步道&#xff0c;从主题公园到商业街区&#xff0c;景观照明用光与影塑造着城市的夜间表情。然而&#xff0c;璀璨夺目的背后&#xff0c;往往伴随着高昂的电费账单、繁重的运维压力以及日益受关注的光污染问题。如何…

2026/10/20 2:20:27 阅读更多 →
3 种安装路径全对比:PojavLauncher 启动器让 iPhone 跑起 Minecraft Java 版

3 种安装路径全对比:PojavLauncher 启动器让 iPhone 跑起 Minecraft Java 版

3 种安装路径全对比&#xff1a;PojavLauncher 启动器让 iPhone 跑起 Minecraft Java 版 【免费下载链接】PojavLauncher_iOS A Minecraft: Java Edition Launcher for Android and iOS based on Boardwalk. Succeeded by https://github.com/AngelAuraMC/Amethyst-iOS 项目地…

2026/10/20 2:20:27 阅读更多 →
【虎鲸进化论 30】安全护栏:人类闸门、审计与爆炸半径

【虎鲸进化论 30】安全护栏:人类闸门、审计与爆炸半径

【虎鲸进化论 30】安全护栏&#xff1a;人类闸门、审计与爆炸半径 本文是「虎鲸进化论」系列第 31 篇&#xff08;正文第 30 篇&#xff09;。上一篇&#xff1a;事故复盘&#xff1b;下一篇预告&#xff1a;抽象的代价 —— 当没有人再读它的代码。 前言 上一篇的结尾说&…

2026/10/20 2:20:27 阅读更多 →

最新新闻

Spring Boot公益服务平台开发实战:从数据库建模到答辩全流程指南

Spring Boot公益服务平台开发实战:从数据库建模到答辩全流程指南

最近帮几个学弟学妹把基于Spring Boot的公益服务平台从零到一完整搭了一遍&#xff0c;整个过程走了不少弯路&#xff0c;也沉淀了不少可以直接复用的经验。这类系统在课程设计和毕业设计里出现频率很高&#xff0c;一是业务场景贴近生活&#xff0c;二是功能边界清晰——用户注…

2026/10/20 2:52:44 阅读更多 →
VS2019下编译FLTK 1.4.4 64位静态库完整指南

VS2019下编译FLTK 1.4.4 64位静态库完整指南

如果有人问我Windows下最省心的C界面库是什么&#xff0c;我的答案一直是FLTK。不是因为它功能最全&#xff0c;而是因为它是少数能把二进制体积控制在几百KB、链接完不带一堆运行库依赖的轻量级选择。最近我用VS2019把一个内部工具做成64位程序&#xff0c;决定把FLTK-1.4.4从…

2026/10/20 2:52:44 阅读更多 →
NSGA2多目标优化MATLAB实现:从原理到代码全解析

NSGA2多目标优化MATLAB实现:从原理到代码全解析

简介&#xff1a;一份MATLAB环境下的NSGA-II算法实现资料包&#xff0c;围绕非支配排序遗传算法第二代展开&#xff0c;面向需要在科研或工程场景中处理多目标优化问题的开发者与学习者。资源共20个文件&#xff0c;以MATLAB的m脚本为主&#xff08;含初始化、非支配排序、锦标…

2026/10/20 2:52:44 阅读更多 →
前端大数据表格渲染卡顿?主线程调度、Worker 与虚拟滚动的优化实践

前端大数据表格渲染卡顿?主线程调度、Worker 与虚拟滚动的优化实践

1. 一次真实的卡顿现场&#xff1a;loading 转着转着就“冻住”了1.1 现象表现与复现路径先还原一下我当时遇到的问题。项目是一个典型的管理后台报表页&#xff0c;接口返回一张大列表&#xff0c;大概 8000 到 15000 行&#xff0c;每行有七八个字段&#xff0c;还要带状态标…

2026/10/20 2:52:44 阅读更多 →
C++ eVTOL仿真工程:六自由度动力学与控制分配实战

C++ eVTOL仿真工程:六自由度动力学与控制分配实战

简介&#xff1a;eVTOL_Simulation 是一份面向 C 学习者与仿真爱好者的轻量级项目源码&#xff0c;围绕电动垂直起降飞行器的调度场景展开。程序设定 5 家公司共 20 架飞机同时起飞&#xff0c;每架飞机拥有巡航速度、电池容量、充电时间、巡航能耗及每小时故障概率等参数&…

2026/10/20 2:52:44 阅读更多 →
STM32双串口DMA互透传:零CPU干预的硬件级直通方案

STM32双串口DMA互透传:零CPU干预的硬件级直通方案

简介&#xff1a;本资源是基于STM32CubeMX与HAL库实现的双串口DMA互透传完整工程&#xff0c;面向嵌入式初学者及STM32进阶开发者&#xff0c;解决多串口高吞吐、低CPU占用场景下的实时数据转发难题&#xff0c;适用于工业通信网关、协议转换器、传感器汇聚等典型应用。压缩包共…

2026/10/20 2:51:44 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 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/19 2:54:50 阅读更多 →
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/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练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/18 12:16:16 阅读更多 →