C++单位安全编程:用编译期维度分析杜绝数值计算错误
1. 项目概述为什么我们需要一个“带单位的变量”在嵌入式开发、物理仿真、游戏引擎或者任何涉及数值计算的C/C项目中我们每天都在和数字打交道。比如你写下一行代码float distance 100.0;然后调用一个函数calculate_time(distance, speed)。看起来没问题对吧但这里隐藏着一个巨大的隐患distance的单位是什么米公里英尺speed的单位又是什么米每秒公里每小时如果调用者误传了一个以“公里”为单位的距离而函数内部期待的是“米”那么计算结果将相差一千倍。这种错误在编译期和运行时都极难被发现往往要到测试阶段甚至产品上线后才会因为一个离谱的物理现象或系统崩溃而被追查出来代价巨大。这就是“维度分析”和“单位安全”要解决的问题。它不是一个新概念在科学计算领域早已是共识。简单说就是让编译器在编译阶段就能帮我们检查单位的一致性防止“米”和“秒”相加这类荒谬操作。CUnits这类库的目标就是为C/C这种静态类型语言引入一套编译期或零开销运行时的单位系统。它让你能定义像Length distance 100.0_m;这样的变量当你试图Time t distance / speed;时编译器会自动推导结果的单位例如米/米/秒秒并在单位不匹配时比如试图将长度赋值给时间直接报错。最近的热搜词如“c盘清理”、“vscode配置c环境”反映了大量开发者正深入使用C/C进行开发而“c面试题”、“c八股文”则说明行业对代码质量和安全性的要求越来越高。一个能从根本上杜绝一类隐蔽错误的工具其价值不言而喻。CUnits正是这样一把利器它不是简单地做数值转换而是将单位作为类型系统的一部分从根本上提升代码的健壮性和可读性。2. 核心设计思路将单位“编译”进类型实现一个单位库核心挑战在于如何在不引入运行时开销的前提下将单位信息嵌入到C的类型系统中。CUnits或类似库如Boost.Units通常采用基于模板元编程的“维度系统”设计。其思路可以分解为以下几个层次2.1 维度系统物理量的基石首先我们需要定义最基本的物理维度。国际单位制SI有七个基本量长度L、质量M、时间T、电流I、热力学温度Θ、物质的量N和发光强度J。在库中我们通常用不同的类型标签来代表它们。// 概念性代码表示维度标签 struct LengthDim {}; struct MassDim {}; struct TimeDim {}; // ... 其他基本维度一个物理量如速度是这些基本维度的幂次组合。例如速度的维度是[L][T]^-1。在模板元编程中我们可以用一个编译时常数列表如std::ratio或自定义的维度类来编码这些幂次。2.2 单位类型维度的具体化身维度定义了量的种类单位则是其具体的测量标准。例如同属于长度维度我们有米(m)、千米(km)、英尺(ft)等不同单位。库的设计需要将单位也作为类型的一部分。一种常见的做法是定义一个模板类UnitDimension, Scale其中Dimension描述维度如LengthDimScale是一个编译时常数如std::ratio1, 1000表示毫米相对于米的缩放比例。但更实用的设计是直接定义具体的单位类型如Meter,KiloMeter,Second。2.3 量值类型带单位的数值这是用户直接交互的类例如QuantityMeter, double表示一个以米为单位、用double存储的数值。这个类重载了所有的算术运算符,-,*,/,等并在编译时进行单位运算和检查。加法/减法要求操作数的单位完全相同。5_m 3_m合法5_m 3_s在编译期报错。乘法/除法会产生新的单位类型。编译器会自动计算结果的维度。例如QuantityMeter, double * QuantitySecond, double结果的单位类型会是Meter * Second面积时间这通常无物理意义但库允许而QuantityMeter, double / QuantitySecond, double结果的单位类型会是Meter / Second速度。2.4 用户字面量优雅的语法糖为了让代码更直观库会利用C11的用户定义字面量功能。你可以这样写auto distance 100.0_m; // 类型是 QuantityMeter, double auto duration 30.0_s; // 类型是 QuantitySecond, double auto speed distance / duration; // 类型自动推导为 QuantityMeterPerSecond, double这比QuantityMeter, double distance(100.0);要优雅得多也是此类库用户体验的关键。3. 核心实现细节与实操要点理解了设计思路我们来看看如何动手实现一个简易但功能核心的CUnits库。我们将聚焦于长度、时间和速度这三个维度以阐明所有关键技术点。3.1 定义维度与比例系统首先我们需要一个能在编译时表示比例的系统。std::ratio是绝佳的选择它能表示任意有理数分数且所有计算都在编译时完成。#include ratio // 使用 std::ratio 定义常用比例 using Milli std::ratio1, 1000; // 1/1000 毫 using Centi std::ratio1, 100; // 1/100 厘 using Kilo std::ratio1000, 1; // 1000/1 千接下来定义维度。我们用一个空结构体作为标签并用一个整数列表或std::integer_sequence来编码七个基本SI维度上的指数。这里为了简化我们手动定义组合维度。// 基本维度标签简化版只包含L, M, T struct BaseDimensionLength {}; struct BaseDimensionMass {}; struct BaseDimensionTime {}; // 一个表示维度的模板类L的指数M的指数T的指数 template int L, int M, int T struct Dimension { static constexpr int length L; static constexpr int mass M; static constexpr int time T; // 可以扩展 I, Θ, N, J }; // 常用维度别名 using DimLength Dimension1, 0, 0; // [L] using DimMass Dimension0, 1, 0; // [M] using DimTime Dimension0, 0, 1; // [T] using DimSpeed Dimension1, 0, -1; // [L][T]^-1 using DimAcceleration Dimension1, 0, -2; // [L][T]^-23.2 实现单位与量值类型现在实现核心的Unit和Quantity类。Unit关联一个维度和一个比例相对于该维度的基本单位。template typename Dim, typename Scale std::ratio1 struct Unit { using dimension Dim; using scale Scale; }; // 具体单位定义 using Meter UnitDimLength, std::ratio1; // 基本单位 using KiloMeter UnitDimLength, Kilo; // 1000倍基本单位 using Second UnitDimTime, std::ratio1; using MeterPerSecond UnitDimSpeed, std::ratio1; // 速度单位Quantity类组合了数值、单位并重载运算符。template typename UnitT, typename ValueT double class Quantity { public: using Unit UnitT; using ValueType ValueT; constexpr Quantity() : value_{} {} explicit constexpr Quantity(ValueType val) : value_(val) {} // 获取原始值谨慎使用通常应避免 ValueType value() const { return value_; } // 单位转换转换为目标单位类型的Quantity template typename TargetUnit constexpr QuantityTargetUnit, ValueType convert() const { // 关键检查维度是否相同。如果不同static_assert会触发编译错误。 static_assert(std::is_same_vtypename Unit::dimension, typename TargetUnit::dimension, Cannot convert between quantities of different dimensions!); // 计算比例因子从当前单位到目标单位。 // 例如从 Kilometer (scaleKilo) 到 Meter (scale1)因子 1 / (1000/1) 1/1000? 不对。 // 正确逻辑数值 * (当前单位比例 / 目标单位比例) // 由于std::ratio是编译时分数需要将其转换为运行时的浮点数或整数。 using SourceScale typename Unit::scale; using TargetScale typename TargetUnit::scale; // ratio_divide 和 ratio_multiply 用于编译时比例计算 using ConversionRatio std::ratio_divideSourceScale, TargetScale; constexpr ValueType conversion_factor ValueType(ConversionRatio::num) / ValueType(ConversionRatio::den); return QuantityTargetUnit, ValueType(value_ * conversion_factor); } // 赋值运算符要求单位严格一致 template typename OtherUnit constexpr Quantity operator(const QuantityOtherUnit, ValueType other) { static_assert(std::is_same_vUnit, OtherUnit, Cannot assign quantities with different units); value_ other.value(); return *this; } private: ValueType value_; }; // 重载算术运算符以加法为例要求单位相同 template typename U, typename T constexpr QuantityU, T operator(const QuantityU, T lhs, const QuantityU, T rhs) { return QuantityU, T(lhs.value() rhs.value()); } template typename U, typename T constexpr QuantityU, T operator-(const QuantityU, T lhs, const QuantityU, T rhs) { return QuantityU, T(lhs.value() - rhs.value()); } // 乘法产生新单位 template typename U1, typename U2, typename T constexpr auto operator*(const QuantityU1, T lhs, const QuantityU2, T rhs) { // 合并维度新维度的指数是两者之和 using NewDim DimensionU1::dimension::length U2::dimension::length, U1::dimension::mass U2::dimension::mass, U1::dimension::time U2::dimension::time; // 合并比例新比例是两者之积 using NewScale std::ratio_multiplytypename U1::scale, typename U2::scale; using NewUnit UnitNewDim, NewScale; return QuantityNewUnit, T(lhs.value() * rhs.value()); } // 除法产生新单位 template typename U1, typename U2, typename T constexpr auto operator/(const QuantityU1, T lhs, const QuantityU2, T rhs) { // 合并维度新维度的指数是前者减后者 using NewDim DimensionU1::dimension::length - U2::dimension::length, U1::dimension::mass - U2::dimension::mass, U1::dimension::time - U2::dimension::time; // 合并比例新比例是前者除以后者 using NewScale std::ratio_dividetypename U1::scale, typename U2::scale; using NewUnit UnitNewDim, NewScale; return QuantityNewUnit, T(lhs.value() / rhs.value()); }注意上述operator*和operator/的实现返回了一个全新的、合并后的单位类型。这对于实现自动推导至关重要但实际库中如Boost.Units会做更多处理比如简化单位将Meter/Second标准化为MeterPerSecond。3.3 实现用户定义字面量这是提升易用性的关键。我们需要为常用的单位定义字面量运算符。// 字面量运算符必须放在独立的命名空间以免污染全局 namespace units_literals { // 长度单位 constexpr QuantityMeter, long double operator _m(long double val) { return QuantityMeter, long double(static_castlong double(val)); } constexpr QuantityMeter, unsigned long long operator _m(unsigned long long val) { return QuantityMeter, unsigned long long(val); } constexpr QuantityKiloMeter, long double operator _km(long double val) { return QuantityKiloMeter, long double(static_castlong double(val)); } // 时间单位 constexpr QuantitySecond, long double operator _s(long double val) { return QuantitySecond, long double(static_castlong double(val)); } constexpr QuantitySecond, unsigned long long operator _s(unsigned long long val) { return QuantitySecond, unsigned long long(val); } } // namespace units_literals using namespace units_literals; // 在需要使用字面量的代码中引入3.4 实战示例计算平均速度现在我们可以像下面这样安全地编写代码#include iostream int main() { using namespace units_literals; // 清晰且安全的变量声明 auto distance 150.0_km; // 类型: QuantityKiloMeter, double auto time 2.0_h; // 假设我们已经定义了小时单位 // 单位转换公里 - 米 auto distance_in_meters distance.convertMeter(); std::cout Distance in meters: distance_in_meters.value() std::endl; // 计算速度距离 / 时间 auto speed distance / time; // 类型自动推导例如 QuantityKiloMeterPerHour, double std::cout Speed: speed.value() km/h std::endl; // 尝试错误的操作将在编译期报错 // auto nonsense distance time; // 编译错误无法将长度与时间相加 // 与原始数值交互需要显式转换提醒开发者注意单位 double raw_distance distance.value(); // 获取原始数值 // 从原始数值创建必须明确单位 auto another_distance QuantityKiloMeter, double(raw_distance * 2); return 0; }4. 高级特性与工程化考量一个工业级的单位库远不止上述基础功能。在实际项目中引入时你需要考虑以下方面。4.1 单位简化与标准化当我们计算5_m / 2_s时根据之前的乘法/除法规则会得到一个类型为UnitDimension1,0,-1, std::ratio1的量这不够直观。一个成熟的库应该能将其“简化”或“标准化”为预定义的MeterPerSecond类型。这通常需要一个编译时的“单位映射表”和匹配逻辑利用模板特化来实现。// 单位简化模板 template typename Dim, typename Scale struct simplify_unit { using type UnitDim, Scale; // 默认不简化 }; // 特例将 Dimension1,0,-1 映射为 Speed 维度并关联 MeterPerSecond 单位 template struct simplify_unitDimension1,0,-1, std::ratio1 { using type MeterPerSecond; }; // 在 operator/ 中使用简化 template typename U1, typename U2, typename T constexpr auto operator/(const QuantityU1, T lhs, const QuantityU2, T rhs) { using RawDim DimensionU1::dimension::length - U2::dimension::length, ...; using RawScale std::ratio_dividetypename U1::scale, typename U2::scale; using SimplifiedUnit typename simplify_unitRawDim, RawScale::type; return QuantitySimplifiedUnit, T(lhs.value() / rhs.value()); }4.2 与第三方库和遗留代码的集成你的项目可能大量使用了纯double类型的接口。CUnits库必须提供安全的交互方式。显式提取数值提供.value()成员函数但必须在命名上或文档中强调其危险性。quantity_cast提供一个转换函数类似于static_cast让开发者明确意识到正在“剥离”单位信息。template typename T, typename U, typename V T quantity_cast(const QuantityU, V q) { return static_castT(q.value()); } double raw quantity_castdouble(speed); // 明确抛弃单位信息为第三方函数封装接口为你常用的数学库如cmath函数提供重载版本。template typename U, typename T QuantityU, T sqrt(const QuantityU, T q) { // 检查维度sqrt要求维度的所有指数都是偶数 // 实际上sqrt会改变维度指数都除以2这需要更复杂的维度运算。 // 这展示了单位库与复杂数学函数集成的难度。 return QuantityU, T(std::sqrt(q.value())); }4.3 性能与零开销抽象这是C单位库的立身之本。通过上述实现可以看到所有的单位信息维度、比例都是类型系统的一部分在编译期完成所有检查和运算。Quantity类在内存中只包含一个value_成员与原始的double或float大小完全相同。算术运算符在优化后开启-O2会直接编译为对底层数值的机器指令没有任何运行时判断或函数调用开销。你可以通过查看生成的汇编代码来验证这一点。4.4 自定义单位与非标准维度库必须允许用户轻松定义自己的单位。例如在游戏开发中定义“像素”(px)或“游戏刻”(tick)。// 自定义“像素”单位通常视为无量纲长度但我们可以将其归于一个自定义维度 struct PixelUnitTag {}; using Pixel UnitDimLength, std::ratio1; // 假设1像素对应某个物理长度这里比例设为1 constexpr QuantityPixel, double operator _px(long double val) { return QuantityPixel, double(val); } // 使用 auto screenWidth 1920.0_px; auto screenHeight 1080.0_px;对于“美元”、“个”等非物理维度你需要定义全新的基本维度。struct CurrencyDim {}; using DimCurrency Dimension0,0,0,0,0,0,0; // 在扩展的7维系统中可以占用一个未用的位置或创建新的维度类 using USD UnitDimCurrency, std::ratio1; auto price QuantityUSD, double(19.99);5. 常见问题、排查技巧与选型建议在实际引入和使用单位库的过程中你会遇到一些典型问题。5.1 编译错误排查最常见的错误是单位不匹配。编译器错误信息可能非常冗长因为其中包含了完整的模板实例化链和类型信息。问题error: no match for operator (operand types are QuantityUnitDimension1, 0, 0, std::ratio1, 1 , double and QuantityUnitDimension0, 0, 1, std::ratio1, 1 , double)解读你试图将一个长度Dimension1,0,0和一个时间Dimension0,0,1相加。错误信息直接告诉了你操作数的完整类型。技巧使用IDE的代码提示功能。当你在写distance 时IDE应该只提示能与distance相加的变量同单位类型。这能预防很多错误。5.2 与数值型API的兼容性当你调用一个接受double的第三方API时需要显式转换。痛点legacy_function(velocity.value());如果velocity单位是km/h而函数期待m/s则传入错误数据。解决方案在调用点进行转换并添加醒目注释。// 第三方函数期待以 m/s 为单位的速度值 extern void set_speed(double speed_ms); auto car_speed 120.0_km / 1.0_h; // 120 km/h // 正确做法在调用前转换并注释 set_speed(car_speed.convertMeterPerSecond().value()); // 将 120 km/h 转换为 ~33.33 m/s5.3 单位库选型自己造轮子还是用现有的自己实现教学/轻量级项目如上所示实现核心安全特性加减乘除、字面量大约需要500行代码。适合学习模板元编程或项目约束极紧无法引入大型库的情况。但会缺少标准化、简化、大量预定义单位、数学函数集成等高级功能。使用Boost.Units这是C领域最强大、最成熟的单位库没有之一。它提供了完整的SI和CGS单位系统、编译期检查、零开销、以及与Boost数学库的集成。缺点是Boost库体积较大编译时间可能增加且语法有时较为复杂。使用其他轻量级库如units(https://github.com/nholthaus/units) 是一个仅头文件的现代C库API更简洁同样提供编译期安全和零开销。这是许多新项目的优先选择。选型建议对于严肃的工程项目强烈建议直接使用成熟的第三方库如units或Boost.Units。它们经过了广泛测试覆盖了你能想到的所有单位和场景能节省大量开发和调试时间。自己实现的库更适合作为理解其原理的练习。5.4 调试与日志输出打印一个Quantity对象时你通常希望看到数值和单位。template typename U, typename T std::ostream operator(std::ostream os, const QuantityU, T q) { // 这里需要一种将单位类型U转换为字符串的方法。 // 这通常需要为每个单位类型特化一个 traits 类。 os q.value() [单位表示]; // 例如: 33.3333 [m/s] return os; }实现完整的单位字符串化需要额外的类型到字符串的映射这可能会稍微增加库的复杂度但对于调试非常有用。将单位作为类型系统的一部分是提升C/C数值计算代码安全性和可维护性的强有力手段。它通过编译期检查将一类潜在的运行时错误彻底消除。虽然初期需要适应新的类型系统并处理与遗留代码的交互但其带来的长期收益——更清晰的代码意图、更少的bug、更自信的重构——是显而易见的。下次当你写下double speed时不妨停下来想一想如果它是QuantityMeterPerSecond, double speed你的代码会不会变得更可靠

相关新闻

为什么有时候选择手搓Agent?

为什么有时候选择手搓Agent?

这是 Agent 工程里面一个很常见的架构选择问题。很多公司会问:为什么不用 LangChain、AutoGen、CrewAI 这些成熟框架,而要自己实现 Agent?答案不是“自己写一定更好”,而是业务需求、可控性、性能、复杂度决定是否手搓。一 、什么…

2026/10/12 23:05:43 阅读更多 →
C++并发安全HashMap设计:锁分段、读写锁与无锁编程实战

C++并发安全HashMap设计:锁分段、读写锁与无锁编程实战

1. 项目概述与核心价值最近在社区里看到不少关于C并发编程的讨论,尤其是涉及到高性能容器时,大家普遍对标准库的std::unordered_map在并发环境下的表现感到头疼。自己动手实现一个线程安全的哈希表,似乎成了每个想深入理解C并发与数据结构的开…

2026/10/2 2:59:53 阅读更多 →
太原电梯综合改造工程

太原电梯综合改造工程

近年来,随着太原城市更新步伐加快,老旧小区与商用建筑的电梯综合改造工程成为民生热点与行业焦点。大量运行超过15年的电梯设备面临老化、故障频发、安全标准滞后等问题,亟待系统性升级。本文将从行业痛点、技术方案与本地化服务三个维度&…

2026/10/7 22:07:36 阅读更多 →

最新新闻

C++数组指针字符串:从内存视角彻底搞懂核心难点

C++数组指针字符串:从内存视角彻底搞懂核心难点

我知道很多人第一次翻开郑莉这本《C语言程序设计》,到第六章"数组、指针和字符串"就开始心里打鼓。说实话,我当年也是这样:前面的流程控制、函数调用,照着抄总能写出来,可数组和指针一上来,什么&…

2026/10/13 11:01:04 阅读更多 →
智能PDU:从车间排插到智慧工厂电力神经中枢

智能PDU:从车间排插到智慧工厂电力神经中枢

1. 先聊聊为什么车间里要装“智能排插”前阵子去一个做汽车零部件的工厂看产线改造,走到车间角落的配电柜旁边,正好看到一位老师傅拿着红外测温枪对着一个满负载的插线板在测温度。那个插线板上插着三台设备的电源,线缆摸上去已经有些发烫了&…

2026/10/13 11:01:04 阅读更多 →
OpenClaw跨平台安装指南:让《Captain Claw》在现代系统重生

OpenClaw跨平台安装指南:让《Captain Claw》在现代系统重生

前几天整理旧硬盘,翻出一个十年前的游戏存档文件夹,里面还躺着那只猫船长的记录。我试着双击当年的老游戏程序,Win11下直接黑屏闪退,连个报错都不给。正想放弃,朋友提了一句:“有没有试过OpenClaw&#xff…

2026/10/13 11:01:04 阅读更多 →
2026计算机职业规划:产品思维与新兴技术赛道

2026计算机职业规划:产品思维与新兴技术赛道

## 1. 计算机专业职业规划的现状与痛点2026年的计算机行业与十年前相比已经发生翻天覆地的变化。当我在某985高校计算机系担任企业导师时,发现超过70%的应届生仍在使用2015年的职业规划模板——这就像用Windows XP的操作手册来指导Windows 12的安装。最典型的认知偏…

2026/10/13 11:01:04 阅读更多 →
Java+SSM+Flask全栈实现在线奶茶店系统

Java+SSM+Flask全栈实现在线奶茶店系统

1. 项目概述:在线奶茶店系统的全栈实现这个基于JavaSSMFlask的网上奶茶店系统,是我为连锁奶茶品牌开发的一套完整的线上销售解决方案。系统采用前后端分离架构,后端使用JavaSSM框架处理核心业务逻辑,Flask则负责轻量级的API服务和…

2026/10/13 11:01:04 阅读更多 →
MySQL慢查询定位与索引设计:EXPLAIN与B+树实战全解析

MySQL慢查询定位与索引设计:EXPLAIN与B+树实战全解析

接手线上业务系统这几年,最让我头疼的问题之一就是MySQL慢查询。明明功能开发完了、测试也过了,一到流量高峰期,几个核心接口突然就卡了几秒,数据库CPU飙高,紧接着就是连接数打满、应用报错。排查下来,十有…

2026/10/13 11:00:03 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 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/12 21:05:09 阅读更多 →
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/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练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/12 18:11:17 阅读更多 →