C++从零实现分数类:精确四则运算与边界处理
1. 从整数除法的一个坑说起为什么需要自己实现分数类很多刚接触C的朋友都写过类似这样的代码计算1/2 1/3然后发现结果输出是0。原因很简单1/2在C里执行的是整数除法结果是0而不是0.5。就算换成double1.0/2.0 1.0/3.0得到的是0.833333精度丢失的问题依然存在而且你没法直接判断两个分数是否严格相等。这就是分数类存在的意义。它把分子和分母分开存储所有运算都在整数域内完成结果永远精确。你拿它算1/2 1/3输出就是5/6干净利落没有任何浮点误差。这篇文章要聊的就是怎么用C从零实现一个能跑四则运算的分数类。我会把设计思路、约分逻辑、运算符重载、边界处理这些环节全部拆开讲代码可以直接抄去用。适合已经学过C基础语法、想通过一个完整小项目来巩固类设计、运算符重载和异常处理的朋友。如果你正在准备课程设计或者想找一个练手项目这个题目非常合适——它足够小能在一两天内吃透又足够完整涵盖了面向对象编程的大部分核心知识点。我见过不少人写分数类写完发现负数运算结果不对、分母为零直接崩溃、连续运算时中间结果溢出。这些问题不是靠“多测试几组数据”就能解决的得从设计层面就想清楚。下面我按实际开发中会遇到的顺序一步步来拆。2. 分数类的骨架设计数据成员与构造逻辑2.1 分子分母怎么存符号放哪里分数类的数据成员看起来很简单两个整数就够了class Fraction { private: long long numerator; // 分子 long long denominator; // 分母 };但这里有几个决策点需要想明白。为什么用long long而不是int分数运算中分母会相乘比如1/100 1/99通分后分母变成9900还在int范围内。但如果连续做多次加法分母会指数级增长。用long long能把可用范围扩大一大截代价只是多占几个字节的内存。实测下来用int做五六次连续分数加法就可能溢出换成long long能撑到十几次。符号统一放在分子上。这是分数类设计的一个基本约定分母永远保持正数负号由分子携带。比如-1/2存成numerator -1, denominator 2而不是numerator 1, denominator -2。这样做的好处是约分和比较的逻辑会简单很多——你不需要在每次运算时都判断分母的符号。构造函数里就要把这个规则落实Fraction(long long num, long long den) { if (den 0) { throw std::invalid_argument(分母不能为零); } if (den 0) { num -num; den -den; } numerator num; denominator den; reduce(); // 构造时立即约分 }注意这里在构造函数里就调用了reduce()。这个选择很关键保持对象始终处于最简形式后续所有运算都不需要再担心结果是不是最简分数。代价是每次构造都有一次约分开销但换来的逻辑简洁性完全值得。2.2 约分函数的实现细节约分的核心是求最大公约数。C17 标准库里有std::gcd直接用就行#include numeric void reduce() { long long g std::gcd(std::abs(numerator), denominator); numerator / g; denominator / g; }如果你的编译器不支持C17手写一个欧几里得算法也很简单long long gcd(long long a, long long b) { while (b ! 0) { long long t b; b a % b; a t; } return a; }这里有个容易忽略的点对分子取绝对值再求gcd。因为分子可能是负数而std::gcd在C17中要求参数是非负整数实际上传入负数行为未定义。取绝对值保证结果正确约分后再由分子携带符号。还有一个边界情况分子为0时gcd(0, denominator)返回denominator约分后变成0/1。这正是我们想要的——零就是零不需要保留分母信息。2.3 默认构造与整数隐式转换有时候你会希望Fraction f 3;这种写法能工作也就是让整数自动变成分数。这需要提供一个单参数构造函数Fraction(long long num 0) : numerator(num), denominator(1) {}但要注意单参数构造函数如果不加explicit编译器会在你意想不到的地方做隐式转换。比如你写了一个接受Fraction参数的函数传个int进去它也能编译通过。大多数情况下这是好事但如果你不希望这种隐式转换发生就加上explicit关键字。我的建议是分数类保留隐式转换。因为数学上整数本来就是分母为1的分数让Fraction f 2;能工作符合直觉用起来也方便。3. 四则运算的运算符重载从通分到结果化简3.1 加法和减法通分是核心分数加法的公式很直接a/b c/d (a*d c*b) / (b*d)。但直接这样写会有两个问题中间结果可能溢出以及结果可能不是最简形式。先看基本实现Fraction operator(const Fraction other) const { long long num numerator * other.denominator other.numerator * denominator; long long den denominator * other.denominator; return Fraction(num, den); // 构造函数会自动约分 }这里num和den都是long long乘法结果也在long long范围内。但如果两个分数的分母都接近long long的上限乘积就会溢出。实际使用中这种情况很少见但如果你要写一个健壮的库可以考虑用__int128GCC/Clang支持或者提前检查溢出。减法的实现和加法几乎一样只是中间的加号变成减号Fraction operator-(const Fraction other) const { long long num numerator * other.denominator - other.numerator * denominator; long long den denominator * other.denominator; return Fraction(num, den); }一个值得注意的优化在通分之前先求两个分母的最小公倍数而不是直接相乘。这样可以减小中间结果的规模降低溢出风险。比如1/6 1/4直接相乘分母是24用最小公倍数则是12。实现上可以先求gcd(denominator, other.denominator)然后long long g std::gcd(denominator, other.denominator); long long lcm denominator / g * other.denominator; long long num numerator * (lcm / denominator) other.numerator * (lcm / other.denominator); return Fraction(num, lcm);这个版本比直接相乘多了一次gcd调用但中间结果更小。对于需要做大量连续运算的场景这个优化很有意义。3.2 乘法和除法约分时机的选择乘法最简单分子乘分子分母乘分母。Fraction operator*(const Fraction other) const { return Fraction(numerator * other.numerator, denominator * other.denominator); }但这里有个可以优化的地方在相乘之前先交叉约分。比如2/3 * 3/4如果直接相乘得到6/12再约分中间结果是6和12。如果先交叉约分——2和4有公因子23和3有公因子3——变成1/1 * 1/2 1/2中间结果小得多。Fraction operator*(const Fraction other) const { long long g1 std::gcd(std::abs(numerator), other.denominator); long long g2 std::gcd(std::abs(other.numerator), denominator); return Fraction((numerator / g1) * (other.numerator / g2), (denominator / g2) * (other.denominator / g1)); }这个写法看起来复杂但能显著降低溢出的概率。我在处理连续乘法时踩过坑用直接相乘的版本做七八次乘法后分子就溢出了换成先约分的版本同样的运算序列能顺利完成。除法就是乘以倒数Fraction operator/(const Fraction other) const { if (other.numerator 0) { throw std::invalid_argument(除数不能为零); } return *this * Fraction(other.denominator, other.numerator); }注意这里要检查除数是否为零。分数除以零在数学上没有定义程序里必须显式处理。我选择抛异常而不是返回一个特殊值因为调用方需要知道出错了而不是拿到一个看似正常的结果继续算下去。3.3 复合赋值运算符的复用技巧有了、-、*、/四个基本运算符复合赋值运算符、-、*、/可以直接复用Fraction operator(const Fraction other) { *this *this other; return *this; }这种写法虽然多了一次拷贝构造和赋值但代码量少不容易出错。如果追求性能可以单独实现复合赋值避免创建临时对象。但对于分数类这种轻量级对象编译器优化后差异微乎其微没必要为了那点性能牺牲代码可读性。4. 比较、输入输出与类型转换的完整配套4.1 比较运算符交叉相乘的陷阱判断两个分数是否相等最直接的方法是交叉相乘a/b c/d等价于a*d c*b。但这里有个陷阱——如果分母是负数交叉相乘的结果会反过来。因为我们已经在构造函数里保证了分母永远为正所以这个问题不存在。比较逻辑可以放心写bool operator(const Fraction other) const { return numerator * other.denominator other.numerator * denominator; } bool operator(const Fraction other) const { return numerator * other.denominator other.numerator * denominator; }其他比较运算符!、、、都可以基于和来实现C20 甚至可以用运算符一次性生成所有比较。但如果你的项目还在用 C17 或更早的标准手动写全六个比较运算符也不麻烦。一个容易忽略的细节交叉相乘时乘法可能溢出。如果两个分数的分母都很大numerator * other.denominator可能超出long long范围。对于一般教学项目这个问题可以忽略但如果要写生产级代码需要做溢出检查或者用更大范围的整数类型。4.2 输出格式假分数还是带分数分数的输出有两种常见格式假分数如7/3和带分数如2 1/3。大多数场景下假分数更通用因为它是计算机内部表示的直接映射。带分数更适合人类阅读但解析起来麻烦。我通常提供两个输出方式operator输出假分数另外提供一个toMixedString()方法输出带分数。std::ostream operator(std::ostream os, const Fraction f) { if (f.denominator 1) { os f.numerator; } else { os f.numerator / f.denominator; } return os; }注意这里对分母为1的情况做了特殊处理直接输出整数。这样Fraction(4, 2)会输出2而不是2/1更符合阅读习惯。输入运算符需要处理多种格式3/4、5、-2/3都应该能正确解析。实现时可以先读一个整数作为分子然后看下一个字符是不是/如果是就继续读分母。std::istream operator(std::istream is, Fraction f) { long long num, den 1; is num; if (is.peek() /) { is.ignore(); is den; } f Fraction(num, den); return is; }4.3 与浮点数的互转有时候你需要把分数转成double来做近似计算或者输出小数形式double toDouble() const { return static_castdouble(numerator) / denominator; }反过来把double转成分数需要连分数展开算法实现起来复杂得多而且浮点数本身就有精度限制转成分数后可能得到一个分子分母都很大的结果。对于教学项目通常不需要这个功能如果确实需要可以限制分母的最大值用近似算法来逼近。5. 那些只有实际写过才会遇到的边界问题5.1 分母为零构造时拦截还是运算时检查分母为零是分数类最核心的约束。我的做法是在构造函数里就抛异常这样任何试图创建非法分数的操作都会立即失败而不是等到运算时才暴露问题。Fraction(long long num, long long den) { if (den 0) { throw std::invalid_argument(分母不能为零); } // ... }但这里有个连锁反应如果构造函数抛异常那么所有可能产生零分母的运算都需要处理这个异常。比如除法运算中如果除数的分子为零倒数运算就会触发分母为零的异常。所以我在除法运算符里提前检查了other.numerator 0给出更明确的错误信息。5.2 连续运算中的溢出问题前面提到过分数连续运算时分母会快速增长。我做过一个测试从1/1开始每次加上1/nn从2递增做20次加法后分母会变成多大的数实际跑下来分母在第10次左右就超过了int的范围第15次超过了long long的范围。这意味着如果你要支持任意长度的连续运算long long也不够用。解决方案有几个一是用大整数库如 GMP但这就超出了“从零实现”的范围二是每次运算后检查是否溢出溢出时抛异常三是限制运算次数或者提供溢出检测接口。对于教学项目我建议至少加一个溢出检查long long safeMultiply(long long a, long long b) { if (a 0 || b 0) return 0; if (std::abs(a) std::numeric_limitslong long::max() / std::abs(b)) { throw std::overflow_error(乘法溢出); } return a * b; }然后在所有乘法操作前调用这个函数。虽然会增加一些开销但能避免程序在溢出后产生错误结果却不自知。5.3 负数约分的符号处理前面说过符号统一放在分子上但约分时如果分子是负数std::gcd的行为需要特别注意。C17 标准规定std::gcd的参数必须是非负整数传入负数行为未定义。所以约分时要先取绝对值void reduce() { if (numerator 0) { denominator 1; return; } long long g std::gcd(std::abs(numerator), denominator); numerator / g; denominator / g; }注意这里对分子为零的情况做了特殊处理直接把分母设为1。因为gcd(0, x) x如果不特殊处理0/5约分后会变成0/1结果是对的但多了一次不必要的计算。直接返回更高效。6. 完整代码与测试用例把上面的所有片段整合起来得到一个完整的分数类#include iostream #include numeric #include stdexcept #include cstdlib class Fraction { private: long long numerator; long long denominator; void reduce() { if (numerator 0) { denominator 1; return; } long long g std::gcd(std::abs(numerator), denominator); numerator / g; denominator / g; } public: Fraction(long long num 0, long long den 1) { if (den 0) { throw std::invalid_argument(分母不能为零); } if (den 0) { num -num; den -den; } numerator num; denominator den; reduce(); } long long num() const { return numerator; } long long den() const { return denominator; } Fraction operator(const Fraction o) const { return Fraction(numerator * o.denominator o.numerator * denominator, denominator * o.denominator); } Fraction operator-(const Fraction o) const { return Fraction(numerator * o.denominator - o.numerator * denominator, denominator * o.denominator); } Fraction operator*(const Fraction o) const { long long g1 std::gcd(std::abs(numerator), o.denominator); long long g2 std::gcd(std::abs(o.numerator), denominator); return Fraction((numerator / g1) * (o.numerator / g2), (denominator / g2) * (o.denominator / g1)); } Fraction operator/(const Fraction o) const { if (o.numerator 0) { throw std::invalid_argument(除数不能为零); } return *this * Fraction(o.denominator, o.numerator); } Fraction operator(const Fraction o) { *this *this o; return *this; } Fraction operator-(const Fraction o) { *this *this - o; return *this; } Fraction operator*(const Fraction o) { *this *this * o; return *this; } Fraction operator/(const Fraction o) { *this *this / o; return *this; } bool operator(const Fraction o) const { return numerator * o.denominator o.numerator * denominator; } bool operator!(const Fraction o) const { return !(*this o); } bool operator(const Fraction o) const { return numerator * o.denominator o.numerator * denominator; } bool operator(const Fraction o) const { return o *this; } bool operator(const Fraction o) const { return !(o *this); } bool operator(const Fraction o) const { return !(*this o); } double toDouble() const { return static_castdouble(numerator) / denominator; } friend std::ostream operator(std::ostream os, const Fraction f); friend std::istream operator(std::istream is, Fraction f); }; std::ostream operator(std::ostream os, const Fraction f) { if (f.denominator 1) { os f.numerator; } else { os f.numerator / f.denominator; } return os; } std::istream operator(std::istream is, Fraction f) { long long num, den 1; is num; if (is.peek() /) { is.ignore(); is den; } f Fraction(num, den); return is; }测试用例覆盖各种情况int main() { Fraction a(1, 2), b(1, 3); std::cout a b std::endl; // 5/6 std::cout a - b std::endl; // 1/6 std::cout a * b std::endl; // 1/6 std::cout a / b std::endl; // 3/2 Fraction c(-1, 2), d(1, -3); std::cout c d std::endl; // -5/6 std::cout c * d std::endl; // 1/6 Fraction e(4, 2); std::cout e std::endl; // 2 Fraction f(0, 5); std::cout f std::endl; // 0 std::cout (a Fraction(2, 4)) std::endl; // 1 std::cout (a b) std::endl; // 0 std::cout (b a) std::endl; // 1 try { Fraction g(1, 0); } catch (const std::exception ex) { std::cout ex.what() std::endl; // 分母不能为零 } return 0; }7. 几个可以继续深挖的扩展方向这个分数类已经能正确处理四则运算和比较但如果你想继续打磨有几个方向值得尝试。第一个是支持任意精度整数。把long long换成自己实现的大整数类或者引入成熟的大整数库这样连续运算就不会溢出了。代价是实现复杂度大幅上升但如果你在做数学相关的项目这个投入是值得的。第二个是增加幂运算和开方。分数的整数次幂很容易实现——分子分母分别求幂即可。但分数次幂就涉及到开方结果可能不是有理数需要返回浮点数或者抛出异常。第三个是性能优化。目前的实现每次运算都会创建临时对象并约分。如果要做大量运算可以考虑惰性约分——只在需要输出或比较时才约分中间运算保持未约分状态。但这会让溢出风险增加需要权衡。第四个是异常安全。目前的代码在溢出时行为未定义。可以加入溢出检测在检测到溢出时抛出std::overflow_error让调用方有机会处理。我在实际使用这个分数类时最常遇到的场景是处理比例和概率计算。比如计算3/7和5/11的加权平均用分数类可以保证结果精确不会出现浮点数那种0.9999999的问题。如果你也有类似的需求这个类可以直接拿去用改改类型定义和异常处理策略就能适配大部分场景。

相关新闻

图片翻译六步流程:从OCR识别到译文回填的完整实战指南

图片翻译六步流程:从OCR识别到译文回填的完整实战指南

图片里夹着文字这件事,放在几年前还不太折腾人,大不了照着键盘一个字母一个字母敲。可现在谁手机里没有几十张截图、合同照片、PPT翻拍?尤其做外贸、读论文、追外漫、玩外服游戏的时候,满屏蝌蚪文真是让人头大。你去搜“图片翻译”…

2026/10/13 13:31:51 阅读更多 →
6.4k Stars!用Claude Code写论文的全套流水线,有人打包开源了:TaoToken统一Key接入实操

6.4k Stars!用Claude Code写论文的全套流水线,有人打包开源了: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/13 13:31:06 阅读更多 →
Python 读取数据库 BLOB 字段并落地文件:从连接配置到本地存储的完整实践

Python 读取数据库 BLOB 字段并落地文件:从连接配置到本地存储的完整实践

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

2026/10/13 13:31:10 阅读更多 →

最新新闻

基于JavaWeb的图书馆管理系统:从数据库设计到答辩全流程拆解

基于JavaWeb的图书馆管理系统:从数据库设计到答辩全流程拆解

每年我的邮箱和私信都会被同一个问题塞满:“老师,有没有现成的毕设项目?最好简单点、能跑通、好答辩。”今年那批600套毕设分享出来之后,我翻了一圈,发现图书馆管理系统仍然是出现频率最高的题目之一。说句实话&#x…

2026/10/13 15:59:05 阅读更多 →
VirtualBox VMX源码深度解析:VMCS结构与硬件虚拟化实现

VirtualBox VMX源码深度解析:VMCS结构与硬件虚拟化实现

1. 为什么从VMX切入看VirtualBox源码——不是选最炫的,而是选最“痛”的很多人一看到“VirtualBox源码分析”,第一反应是去翻src/VBox/Devices里那些网卡、显卡模拟器的代码,觉得“看得见摸得着”。我试过,也这么干过——结果两周…

2026/10/13 15:59:05 阅读更多 →
精品水果线上销售网站设计与实现:Spring Boot+Vue全栈电商实战

精品水果线上销售网站设计与实现:Spring Boot+Vue全栈电商实战

1. 内容整体设计与思路拆解做毕业设计选“精品水果线上销售网站”,这个题目乍一看平平无奇,但真正动手做起来,你会发现它其实是把前端展示、后端业务、数据库设计、支付流程、权限控制全串起来的一个完整闭环。我见过太多人选电商类题目最后做…

2026/10/13 15:59:05 阅读更多 →
基于Spring Boot+Vue的精品水果销售网站设计与实现全解析

基于Spring Boot+Vue的精品水果销售网站设计与实现全解析

这段时间正好在帮一位学弟审他的毕业设计,题目就是“精品水果线上销售网站的设计与实现”。说实话,看到这个题目的时候我愣了一下——现在网上商城类的毕设选题太多了,图书、服饰、数码应有尽有,但“精品水果”这个切入点有点意思…

2026/10/13 15:59:05 阅读更多 →
LiteLLM实战:统一大模型API调用与代理网关搭建指南

LiteLLM实战:统一大模型API调用与代理网关搭建指南

如果你和我一样,每天要跟三四个不同的大模型API打交道,可能早就被这一堆SDK和鉴权方式搞得头大了。我一开始是拿LiteLLM当“偷懒工具”用的:它能把OpenAI、Anthropic、Gemini这些五花八门的接口统一成一个Python调用,后来才发现这…

2026/10/13 15:59:05 阅读更多 →
STM32电源闭环控制:ADC触发+双环PID+硬件保护实战

STM32电源闭环控制:ADC触发+双环PID+硬件保护实战

简介:本资源为电子设计大赛(电赛)电源类专项程序代码合集,面向高校电子类专业学生、电赛备赛团队及嵌入式电源系统开发者,聚焦电源管理、闭环控制与硬件协同等核心难点,助力快速构建稳定可靠的电源子系统。…

2026/10/13 15:58:04 阅读更多 →

日新闻

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

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

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

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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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 阅读更多 →