编译原理第二章:用文法为语言装上解剖刀
1. 这章不是背定义而是给语言装上“解剖刀”很多人翻开《编译原理》第二章看到“文法”“产生式”“终结符”“非终结符”这些词第一反应是又来一堆抽象符号不就是教人怎么写BNF吗背完考试就扔。我带过几届某高校编译课程实验班发现一个特别扎心的现象80%的学生能默写出S → aSb | ε的推导过程但当真实遇到一段嵌套括号的JSON片段解析失败时却完全想不到——这根本不是正则表达式能搞定的事而正是本章文法理论在现实中的“显形时刻”。这章真正的价值从来不是让你记住乔姆斯基分类的四层结构图而是给你一把可操作的“语言解剖刀”。它教会你如何把任何看似混沌的输入一段配置、一条SQL、甚至用户写的简易脚本拆解成有层次、可验证、能生成的结构单元。比如某跨平台系统里前端需要动态渲染一个由后端下发的“条件表达式DSL”格式类似user.age 18 AND (user.city sh OR user.city bj)。开发初期用正则硬匹配结果一加括号优先级就崩后来改用手写递归下降又因运算符结合性搞错导致逻辑翻车。最后回过头重读第二章才真正理解这不是代码问题是文法建模缺失——你连这个DSL的上下文无关文法都没定义清楚后面所有实现都是在流沙上盖楼。关键词里虽然没填但这一章的锚点非常明确形式化描述能力。它不解决“怎么写代码”而是解决“怎么精准说清‘正确’长什么样”。就像建筑师画施工图前必须先定好建筑规范程序员在写解析器前也得先用文法把语言的骨架画出来。本章所有定义、例子、习题本质上都在训练一种思维转换从“我感觉这样写是对的”切换到“我能用有限条规则穷举所有合法形态”。所以别急着抄产生式先问自己三个问题我正在处理的输入有没有可能被误认为“合法”比如把if x1 then y2 else z3中的当作赋值而非比较它的嵌套结构是否会产生歧义比如a b * c是(ab)*c还是a(b*c)如果要让别人或另一个程序也识别这种输入我能否用一张纸写完所有判断依据这三个问题的答案直接决定了你后续是写个能跑的demo还是造出一个真正鲁棒的解析引擎。而第二章就是专门训练你回答这三个问题的肌肉记忆。2. 文法不是数学游戏是语言设计的“最小可行协议”很多初学者卡在“为什么非要用S→aSb|ε这种奇怪写法”觉得像在玩符号拼图。其实这背后藏着一个极朴素的工程原则用最少的规则覆盖最全的合法形态。我们拿一个真实案例切入——某实验室开发的传感器数据上报协议要求设备发送的报文必须是形如DATA:001,25.6,72%,OK的字符串其中字段数固定为4第三字段必须带%号末尾必须是OK或ERR。如果不用文法思维你会怎么校验写正则^DATA:[0-9]{3},[0-9]\.[0-9],[0-9]%,(OK|ERR)$。看起来没问题但很快会暴露缺陷当字段分隔符,被误写成中文逗号时正则直接失效但错误提示只会说“格式错误”无法定位是分隔符问题还是数值格式问题如果需求变成“第三字段允许72或72%”正则就得改成[0-9]%?但此时72%%也会被接受——因为正则无法表达“百分号至多出现一次且必须在数字末尾”这种结构性约束。而用文法你可以这样定义report → DATA: id , temp , humidity , status id → digitdigitdigit digit → 0 | 1 | ... | 9 temp → int . frac int → digit | digitint frac → digitdigit humidity → int % | int status → OK | ERR注意这里的关键设计点humidity规则明确将“带%”和“不带%”作为两个互斥分支杜绝了72%%这种非法组合int用递归定义digitint天然支持任意长度数字比正则里的[0-9]{3}更具扩展性每个非终结符如temp都对应一个语义明确的子结构后续写递归下降解析器时parseTemp()函数的职责就非常清晰。提示文法中的每个非终结符本质上都是一个待实现的解析函数接口。你定义temp的方式直接决定了parseTemp()函数的输入输出契约。这是文法与工程落地最直接的连接点。再看一个更典型的例子JSON数组。它的文法片段是array → [ elements ] elements → ε | value | value , elements value → string | number | object | array | true | false | null这个定义精妙在哪它用三条规则就穷尽了所有合法数组形态空数组[]、单元素[1]、多元素[1,2,a]且天然保证了逗号只出现在元素之间不会出现在开头或结尾。更重要的是value的递归引用让[1,[2,[3]]]这种深度嵌套成为文法的自然推导结果无需额外编码处理。所以文法的本质是语言设计者与实现者之间的一份最小可行协议。它不规定你怎么写代码但明确规定了“什么算对、什么算错、错在哪里”。当你在团队中定义一个新的配置文件格式时一份清晰的文法比十页Word文档更能减少歧义。3. 从文法到语言四层分类不是考试重点而是选型决策树乔姆斯基分类0型到3型常被当作考点死记但实际工作中它是你面对新需求时的第一道技术选型决策树。某公司曾为IoT设备开发固件升级指令集初期用正则匹配UPGRADE:V2.1.0;SHA256:abc123...后来需求增加“支持回滚到上一版本”指令变成UPGRADE:V2.1.0;ROLLBACK:V2.0.0;SHA256:...。开发人员试图用更复杂的正则解决结果测试时发现UPGRADE:V2.1.0;ROLLBACK:V2.0.0;ROLLBACK:V1.9.0这种非法重复指令也能通过——因为正则无法验证“ROLLBACK最多出现一次”这种全局约束。这时就需要启动乔姆斯基分类决策3型文法正则文法适合线性扫描、无状态的模式匹配如日志行过滤、简单token切分。但一旦涉及计数、嵌套、跨位置依赖立刻失效。2型文法上下文无关文法能处理嵌套和递归如JSON、XML、算术表达式。但无法表达“某符号出现次数等于另一符号”这类约束如a^n b^n。1型文法上下文有关文法能处理计数和局部上下文依赖如a^n b^n c^n但解析复杂度高O(n^6)工程中极少直接使用。0型文法无限制文法理论上能描述任何可计算语言但等价于图灵机无法保证停机——这已经不是“解析”问题而是“判定”问题了。回到固件指令案例ROLLBACK最多出现一次本质是全局计数约束超出了2型文法能力。但真要上1型文法大材小用且得不偿失。更务实的方案是用2型文法定义主干结构cmd → UPGRADE: ver ; rollback? sha再在解析后的AST上做二次语义检查统计rollback节点数量。这就是工程中的经典分层文法管结构合法性语义分析管业务规则合法性。我们用表格对比四类文法在真实场景中的取舍文法类型典型应用场景解析算法工程优势工程陷阱3型正则日志关键字提取、URL路径匹配、简单token识别DFA/NFA极速、内存占用低、工具链成熟无法处理嵌套、无法验证跨字段关系2型CFG编程语言解析、配置文件YAML/INI、查询语句SQL子集LL(1)/LR(1)、递归下降结构清晰、工具丰富ANTLR/Yacc、易于调试二义性需手动消除如悬空else、左递归需改写1型CSG自然语言语法校验、某些密码学协议格式、生物序列分析通用图灵机模拟理论上能表达任意约束解析时间不可控、无实用解析器、调试地狱0型无限制停机问题判定、某些形式化验证场景无有效算法理论完备性工程中完全不可用注意所谓“LL(1)文法”核心约束是“对任一非终结符其所有产生式的FIRST集互不相交且若含ε产生式则FIRST集与FOLLOW集不相交”。这听起来很绕但实操中就一句话确保你看到下一个输入符号时能唯一确定该走哪条产生式。比如S → aA | aB就违反LL(1)因为看到a时无法决定选A还是B而S → aA | bB就符合因为a和b天然分流。某导师在指导学生做DSL设计时总强调一个口诀“先画树再写文最后选算法”。意思是先在纸上画出你期望的语法树形状比如if语句的树一定是IfNode包含cond、then、else三个子节点再反推需要哪些非终结符和产生式最后根据树的结构特征是否有左递归是否有公共前缀选择解析策略。这个过程比死记乔姆斯基分类有用一百倍。4. 手撕一个计算器文法从定义到可运行的完整闭环光讲理论容易飘我们用一个贯穿始终的实战项目——支持四则运算、括号、负号的计算器——把第二章所有知识点串起来。这不是玩具Demo而是某图像处理Demo中嵌入的实时参数计算模块用户可输入2*(34)-(-5)^2这类表达式动态调整滤镜参数。4.1 第一步定义目标语言的“合法长相”先明确需求边界支持 - * / ^^为幂运算右结合支持()控制优先级支持负号-如-5、2*(-3)数字支持整数和小数如3.14关键约束-既是减号又是负号需区分5-3vs5*(-3)很多初学者直接写E → E T | E - T | T结果发现5--3会被解析为5 - (-3)但需求可能不允许双负号。所以我们先画一棵理想语法树E /|\ E - T / /|\ T F ^ T / / /|\ F ( E ) F ^ T | / | /|\ 2 T 3 F ^ T /| | | F ^ T 2 ε | | 3 ε从树反推E表达式应包含加减T项包含乘除F因子包含幂和括号P幂处理右结合的^。负号属于F层因为-(34)是一个完整的因子。4.2 第二步写出无二义性、无左递归的文法标准写法已消除左递归E → T E // E 处理加减 E → T E | - T E | ε T → F T // T 处理乘除 T → * F T | / F T | ε F → P F // F 处理幂右结合故F在左 F → ^ F | ε P → ( E ) | - P | NUMBER | IDENTIFIER这里的关键设计F → ^ F而不是F → F ^ P因为^右结合2^3^4应为2^(3^4)而非(2^3)^4负号-放在P层最底层确保--5被解析为-(-5)符合数学直觉NUMBER和IDENTIFIER是终结符后续词法分析产出。4.3 第三步验证文法是否覆盖所有需求我们推导2*(34)E ⇒ T E ⇒ F T E ⇒ P F T E ⇒ NUMBER F T E ⇒ 2 F T ET ⇒ * F T ⇒ * P F T ⇒ * ( E ) F TE ⇒ T E ⇒ F T E ⇒ P F T E ⇒ NUMBER F T E ⇒ 3 F T EE ⇒ T E ⇒ F T E ⇒ NUMBER F T E ⇒ 4 F T E回溯合并最终得到完整树。再验证易错点2*-32是NUMBER→P→F→T→E*触发T→* F T-3是P因-在P层故F ⇒ P F ⇒ - P F ⇒ -3完美覆盖。4.4 第四步手写递归下降解析器Python伪码class Parser: def __init__(self, tokens): self.tokens tokens self.pos 0 def parse(self): return self.parse_E() def parse_E(self): left self.parse_T() while self.current_token() in [, -]: op self.consume() right self.parse_T() left BinaryOp(op, left, right) return left def parse_T(self): left self.parse_F() while self.current_token() in [*, /]: op self.consume() right self.parse_F() left BinaryOp(op, left, right) return left def parse_F(self): left self.parse_P() if self.current_token() ^: op self.consume() right self.parse_F() # 注意这里是 parse_F不是 parse_P实现右结合 left BinaryOp(op, left, right) return left def parse_P(self): if self.current_token() (: self.consume() # ( expr self.parse_E() self.consume() # ) return expr elif self.current_token() -: self.consume() # - return UnaryOp(-, self.parse_P()) # 递归调用 parse_P 处理 --5 elif self.current_token().type NUMBER: return Number(self.consume().value) else: raise SyntaxError(fUnexpected token {self.current_token()})实操心得parse_F中right self.parse_F()这一行是右结合的关键。如果是左结合如这里应是self.parse_P()。很多初学者在这里栽跟头以为所有运算符处理方式一样其实结合性直接决定递归调用的层级。4.5 第五步运行与调试——文法即文档当解析2^3^4时实际调用栈是parse_E → parse_T → parse_F → parse_P → ... → parse_F → parse_P最终构建的树是2^(3^4)符合预期。如果某天产品提出新需求“支持!阶乘且!优先级高于^”你只需在P层加一条规则P → ( E ) | - P | NUMBER ! | NUMBER | IDENTIFIER然后在parse_P中增加if token !: ...分支。整个修改范围清晰可控因为文法已为你划定了职责边界。这才是第二章的终极价值它让你的代码具备可预测的演进能力。当需求变化时你不是在现有代码上打补丁而是回到文法层像修改接口定义一样调整产生式再同步更新对应的解析函数。这种开发范式远比“写完再改”高效可靠。5. 那些课本没写的血泪教训文法设计的五个反模式在多个项目中踩过坑后我总结出文法设计中最常见的五个反模式。它们不会让你挂科但会让你在深夜调试时怀疑人生。5.1 反模式一把语义检查塞进文法典型表现为了验证“变量必须先声明后使用”在文法里硬加规则如stmt → int id expr ; | id expr ;并幻想这样就能捕获未声明变量。问题在哪文法只管结构不管符号表。id是终结符文法无法知道x是否在前面声明过。强行在文法层做语义检查会导致文法爆炸式膨胀为每个作用域写不同规则二义性剧增xyz在不同作用域含义不同解析器失去通用性同一文法无法用于语法高亮和执行。正确做法文法只定义assign → id expr ;语义分析阶段维护符号表在遍历AST时检查每个id节点是否已在作用域中声明。某跨平台系统正是因此重构将编译流程从“语法语义混合”拆分为清晰的两阶段bug率下降70%。5.2 反模式二忽略词法与语法的职责边界典型表现用文法处理空格、注释、换行如ws → | \t | \n | wsws并在每个产生式里插入ws。问题在哪词法分析器Lexer的本职就是吞掉空白和注释只把有意义的tokenNUMBER、、ID交给语法分析器。把词法职责塞进文法导致文法臃肿难读每个规则都要考虑空白解析效率暴跌大量无意义的ws推导无法复用标准Lexer如flex被迫手写词法器。正确做法在词法分析阶段将// comment、/* */、空格、制表符全部过滤只输出TOKEN_ID、TOKEN_NUMBER等。文法中所有规则默认忽略空白。某导师曾让学生对比两种写法带ws的文法平均解析耗时是干净文法的3.2倍。5.3 反模式三用文法模拟执行逻辑典型表现为支持if (x0) {y1} else {y2}写出S → if ( E ) { S } else { S }并幻想这样就能执行分支。问题在哪文法定义的是静态结构不是运行时行为。S是非终结符代表“一条语句”但if的语义是“根据E的值选择执行哪个S”这需要在语义分析或代码生成阶段处理。把执行逻辑塞进文法造成严重二义性if (a) if (b) s1; else s2;的悬空else问题使文法失去可验证性无法用数学方法证明其完备性后续无法生成中间代码因为文法里没有“跳转”概念。正确做法文法只定义IfStmt → if ( Expr ) Stmt (else Stmt)?执行逻辑由解释器或编译器后端处理。某图像处理Demo早期用文法模拟执行结果在添加循环时彻底崩溃重构后才稳定。5.4 反模式四过度追求“数学完美”忽视工程可实现性典型表现坚持用LR(1)文法拒绝任何改写即使这意味着要写200行冲突解决代码。问题在哪编译原理教材偏爱LR文法因其理论强大。但工程中LR文法调试极其困难冲突信息晦涩工具链复杂需Bison/Yacc学习成本高很多需求用递归下降更直观如处理宏展开、预处理器指令。正确做法优先选择LL(1)或递归下降必要时用“语义动作”如ANTLR的{...}块处理局部歧义。某公司DSL项目初期强上LR三个月未交付改用递归下降后两周完成MVP。记住能跑的解析器比完美的文法重要一万倍。5.5 反模式五文法与实现脱节成为“考古文物”典型表现文法写在Word里代码是另一套两者长期不同步。某次需求变更开发改了代码但忘了更新文法文档导致新成员按旧文法写测试用例全部失败。问题在哪文法最大的价值是作为活的接口文档。当它与代码分离新人无法快速理解语言结构测试用例无法自动生成重构时缺乏权威参考。正确做法用ANTLR等工具让文法文件.g4直接生成解析器代码。文法即代码代码即文法。某实验室项目采用此方案后新人上手时间从2周缩短至2天因为所有语言规则都在一个文件里且IDE能实时语法高亮。最后分享一个技巧每次写完文法用它推导3个典型用例和1个边界用例如空输入、超长嵌套、非法字符并手动画出语法树。这比看十遍定义都管用。因为文法不是用来背的是用来“推”的——推导过程就是你和语言建立直觉的过程。

相关新闻

WebLogic 12.2.1.3.0.0 补丁升级实战:PSU+BP+JDK三阶加固

WebLogic 12.2.1.3.0.0 补丁升级实战:PSU+BP+JDK三阶加固

简介:本资源为Oracle WebLogic Server 12.2.1.3.0.0官方最新补丁集(Patch ID: 35247514),面向企业级Java EE应用运维工程师、中间件管理员及Linux平台Java服务部署人员,专用于解决基础版本在安全性、性能与稳定性方面的…

2026/10/14 10:55:01 阅读更多 →
Windows内核提权实战:从DLL劫持到Winlogon通知包与DSE绕过

Windows内核提权实战:从DLL劫持到Winlogon通知包与DSE绕过

1. 这不是“打游戏”,而是一次Windows内核级权限提权的完整复现很多人看到“HackTheBox”“Ethereal”“Walkthrough”这几个词,第一反应是:“哦,又一个CTF解题笔记”“估计就是改改端口、爆个密码、上传个webshell”。我最初也这…

2026/10/14 10:55:01 阅读更多 →
OpenCV轮廓检测本质:连通域拓扑解析与工业级应用指南

OpenCV轮廓检测本质:连通域拓扑解析与工业级应用指南

1. 这个函数不是“画线工具”,而是图像理解的起点很多人第一次在OpenCV教程里看到cv2.findContours,下意识就把它当成“描边工具”——输入一张二值图,输出一堆轮廓点,然后用cv2.drawContours连起来,看起来像在Photosh…

2026/10/14 10:55:01 阅读更多 →

最新新闻

浏览器多开怎么设置独立 IP?Profile、代理与核验流程

浏览器多开怎么设置独立 IP?Profile、代理与核验流程

浏览器多开不会自动让每个窗口拥有不同的公网 IP。想让不同浏览器环境使用不同出口,需要分别配置获授权的代理或网络线路,再从各个环境实际访问网络进行核验。要先分清两件事:Profile 隔离浏览器数据,代理配置影响网络出口。**它们…

2026/10/14 19:11:22 阅读更多 →
PS5 Chiaki 串流多账号切换导致控制器失灵的故障排查与解决方案

PS5 Chiaki 串流多账号切换导致控制器失灵的故障排查与解决方案

在使用 Chiaki 串流 PS5(或配合自动化脚本挂机)时,部分拥有大小号/多区服账号的玩家可能会遇到一个极其隐蔽且容易误导排查方向的控制失效问题。本文将详细复盘该故障的成因及解决思路,供遇到类似情况的玩家参考。 一、 故障现象与…

2026/10/14 19:11:22 阅读更多 →
2026企业AI办公工具选型指南:框架、产品盘点与落地思路

2026企业AI办公工具选型指南:框架、产品盘点与落地思路

2026企业AI办公工具选型指南:框架、产品盘点与落地思路企业引入AI办公工具时,很多管理者容易陷入选型误区。不少团队会把产品功能清单的丰富度作为核心评判标准,或是单纯对比订阅成本,也有部分决策仅凭品牌声量敲定方案。这类评估…

2026/10/14 19:11:22 阅读更多 →
上海点山展示如何通过展厅设计传递企业的“前世今生”?

上海点山展示如何通过展厅设计传递企业的“前世今生”?

上海点山展示如何通过展厅设计传递企业过去现在未来?关键在时间叙事的三层结构展厅不只是物理意义上的展示空间,更是品牌时间的容器。对于深耕长三角区域的展厅设计公司而言,如何让企业的“过去、现在、未来”不再割裂为孤立的展板&#xff0…

2026/10/14 19:11:22 阅读更多 →
判断泛消费级机器人初创公司是否值得持续关注,应该看哪些连续证据

判断泛消费级机器人初创公司是否值得持续关注,应该看哪些连续证据

机器人行业经常出现“下一家黑马是谁”“某家公司值不值得持续关注”等问题。对于仍处在研发和产品验证阶段的初创公司,提前给出确定性结论并不稳妥。相比标签和短期热度,更可行的方法是建立一组能够持续更新的观察指标。以贝塔无限为例,其公…

2026/10/14 19:11:22 阅读更多 →
库存扣减的并发控制:乐观锁的应用

库存扣减的并发控制:乐观锁的应用

财务主管跨界写技术文,先交代缘由:公司商城搞限时促销,活动结束对账,账面库存和实物对不上,多卖了四十七件。排查一周,病根是库存扣减没做并发控制,两个请求同时读到同样的库存数,各…

2026/10/14 19:10:22 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/13 22:01:27 阅读更多 →
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/14 7:53:34 阅读更多 →