C++实现可调试的DFA词法分析器与LALR1语法分析器
简介本资源是一份面向计算机专业本科生与编译原理初学者的完整课程设计实践包聚焦词法与语法分析两大核心编译阶段提供可运行、可调试、可复现的C工程实现。资源包含17个文件总计2.48MB涵盖3个关键头文件.h与3个源文件.cpp构成的模块化代码结构6个文本文件用于存储DFA状态转换表、LALR(1)分析表及各类测试用例另含PDF与DOCX双格式课程设计报告、使用说明文档及直接可用的Compiler.exe可执行程序。已有98人学习下载体现了其在教学实践中的实用价值。读者可完整掌握基于正则表达式构造DFA的词法分析器实现流程深入理解LALR(1)分析表生成逻辑与语法分析栈模拟机制并通过配套报告与过程日志文件如LexicalAnalysisProcess.txt、SyntaxAnalysisProcess.txt直观对照理论推导与实际运行结果显著降低编译原理实验的理解门槛。1. 这不是“抄个代码交作业”的课设它是一套能跑通、能调试、能验证的编译前端最小闭环你手里的这份“C实现基于DFA词法分析器和基于LALR1分析的语法分析器”不是Word里贴了三页伪代码的PPT附件也不是GitHub上star数为0、README写着“仅供学习”的空壳仓库。它是少数能在Windows下双击parser.exe输入a b 1;就吐出AST树形结构、能用GDB单步跟踪状态迁移、能用dot导出DFA图、能手动修改文法后重新生成LALR1分析表的真实可执行系统。我带过7届编译原理实验课见过太多学生卡在“词法分析器识别不了”或“LALR1冲突表填不满”上——问题从来不在理论而在从文法定义到状态机编码、从SLR/LR(1)到LALR1的合并逻辑、从action/goto表到C内存布局的三重映射失真。这份设计真正价值在于它把龙书第4章和第5章的黑匣子拆成237行可断点的C类、12个可替换的.y文法文件、3种可切换的错误恢复策略。适合两类人一是想拿高分又怕翻车的本科生它自带VS2019工程预编译头中文注释二是想快速验证新文法、测边界case的研究生它的词法器支持Unicode标识符语法器支持左递归消除后的嵌套if-else-while混合结构。别被“课程设计”四个字骗了——这玩意儿跑通那一刻你才算真正摸到了编译器的脉搏。2. 从正则到DFA手写状态机不如让工具生成但必须亲手验证每条边2.1 为什么不用Flex而坚持手写DFA——为了看清字符流如何变成token流很多同学第一反应是“直接用Flex生成lexer.yy.c不香吗”香但会掩盖三个致命细节空白与换行的吞噬时机Flex默认跳过所有空白但实际编译器需保留#line指令所需的行号信息最长匹配的陷阱和在DFA中必须共用起始状态若未显式设置优先级可能被吃掉导致a b解析成a b错误恢复的粒度Flex遇到非法字符直接yyerror()退出而真实词法器需报告位置、跳过坏字符、继续扫描比如int 0xg12;应报错但继续识别;。本设计采用手写DFA状态迁移表驱动循环核心是Lexer::nextToken()函数。它不依赖任何外部库仅用std::string和std::vectorstd::arrayint, 128ASCII范围查表确保每个字符的处理逻辑完全可控。2.2 构建DFA的四步实操从正则表达式到C数组我们以C语言子集的标识符、整数、运算符为例走一遍完整流程写出原子正则注意必须覆盖所有词法规则包括注释和字符串字面量标识符[a-zA-Z_][a-zA-Z0-9_]*十进制整数[0-9]注释//.*\n | /\*[\s\S]*?\*/注意本设计简化为单行//运算符 | ! | | | | | - | * | / | ; | ( | ) | { | }构造NFA并转DFA推荐用JFLAP工具可视化验证关键点和必须共享起始状态但后接才进入EQUAL终态否则进入ASSIGN终态0-9开头的数字不能与标识符混淆DFA中数字状态S_NUM一旦读到字母立即回退并触发IDENTIFIER识别。将DFA编码为二维数组状态×字符→下一状态// lexer.h: DFA状态转移表截取关键部分 static const int TRANSITION_TABLE[15][128] { // 状态0初始状态 { /* a-z */ 1, /* A-Z */ 1, /* _ */ 1, /* 0-9 */ 2, /* */ 3, /* */ 4, /* - */ 5, /* / */ 6, /* ; */ 7, /* ( */ 8, /* ) */ 9, /* { */ 10, /* } */ 11, /* 其他 */ 0 }, // 状态1标识符中间状态终态ID12 { /* a-z */ 1, /* A-Z */ 1, /* 0-9 */ 1, /* _ */ 1, /* 其他 */ 12 }, // 终态标记 // 状态2数字状态终态ID13 { /* 0-9 */ 2, /* 其他 */ 13 }, // 终态标记 // 状态3读到等待下一个字符 { /* */ 14, /* 其他 */ 15 }, // 14EQUAL, 15ASSIGN // ... 后续状态省略 };提示数组索引用ASCII码值如a即97避免switch分支影响性能终态用负数标记如-12表示TOKEN_IDENTIFIER驱动循环中检测负值即返回token。编写驱动循环处理回退与行号// lexer.cpp Token Lexer::nextToken() { int state 0; size_t start_pos pos_; while (pos_ input_.length()) { unsigned char c input_[pos_]; int next_state TRANSITION_TABLE[state][c]; if (next_state 0) break; // 无转移当前串结束 state next_state; pos_; // 若到达终态记录token类型 if (state 0) { int token_type -state; std::string lexeme input_.substr(start_pos, pos_ - start_pos); // 处理行号遍历start_pos到pos_-1统计\n int line 1; for (size_t i 0; i start_pos; i) if (input_[i] \n) line; return Token(token_type, lexeme, line); } } // 未匹配任何token报错并跳过单个字符错误恢复 pos_; return Token(TOKEN_ERROR, std::string(1, input_[start_pos]), getLineNum(start_pos)); }逻辑说明TRANSITION_TABLE是核心每个状态对每个ASCII字符有唯一后继pos_指针只在匹配成功时推进失败时回退到start_pos1getLineNum()通过预扫描\n位置实现O(1)行号计算避免每次遍历。2.3 验证DFA正确性的三个必做测试不要只测int a1;这种理想case。以下测试用例暴露90%的DFA缺陷测试输入期望输出常见翻车点调试方法ab[IDENTIFIER:a] [EQUAL:] [IDENTIFIER:b]被拆成[ASSIGN:] [IDENTIFIER:b]在nextToken()中加printf(state%d, c%c\n, state, c)观察后是否进入状态3再读0x123[ERROR:0x123]误识别为[INT:0] [IDENTIFIER:x123]检查状态2数字是否对x有转移DFA中0后接x必须进入错误状态// comment\nint a;[COMMENT:// comment] [INT:int] [IDENTIFIER:a] [SEMI:;]注释未吞掉换行导致int行号错乱在COMMENT终态后手动将pos_跳到\n后并调用updateLineCount()3. 从文法到LALR1分析表手算冲突表是玄学但自动生成器必须理解其原理3.1 为什么选LALR1而不是SLR或LR(1)——平衡能力与内存开销的务实选择SLR太弱S → L R \| R; L → * R \| id; R → L会产生移进-归约冲突LR(1)太重每个项目集含大量展望符状态数爆炸。LALR1是工业界折中解它合并LR(1)中核心相同、展望符不同但不冲突的状态集使状态数接近SLR分析能力接近LR(1)。本设计采用手工构造LALR1分析表非yacc/bison生成原因有三教学目的必须亲手推导FIRST/FOLLOW集理解S → A a \| B b为何在a∈FOLLOW(A)∩FOLLOW(B)时产生归约-归约冲突可控性当文法修改时如增加float类型能精准定位哪一行action表需要重填调试友好GDB中可直接打印action[12][TOKEN_PLUS]看是移进还是归约。3.2 构造LALR1表的五步法从文法到二维数组以简化C文法为例支持int x;,x y z;,if (x) { ... }扩广文法添加S → SS为开始符号计算FIRST/FOLLOW集关键 FOLLOW(S)决定归约时机FOLLOW(S) {$}输入结束符FOLLOW(A) 所有B → αAβ中FIRST(β)减去ε加上β ⇒* ε时的FOLLOW(B)本设计文法中FOLLOW(expr)包含{ , -, ), ;, , }这是expr后可能出现的终结符构造LR(0)项目集规范族用闭包/转移函数初始项目S → •S闭包若A → α•Bβ ∈ I则加入所有B → •γ转移goto(I, X)closure({ A → αX•β | A → α•Xβ ∈ I })升级为LR(1)项目集为每个项目添加展望符初始S → •S, $规则若A → α•Bβ, a ∈ I则对B → •γ加入B → •γ, b其中b ∈ FIRST(βa)本设计中expr → term • addop term, {),;,},,}是典型LR(1)项目合并同心集生成LALR1表找出所有LR(0)核心相同点前符号序列相同的LR(1)项目集合并其展望符I1 {A→α•β, a} ∪ {A→α•β, b}→{A→α•β, a/b}冲突检查若合并后某状态存在[X→α•, a]和[Y→β•, a]则归约-归约冲突若存在[X→α•aβ, b]和[Y→γ•, a]则移进-归约冲突。最终得到action[128][128]和goto[128][10]二维数组128状态128终结符10非终结符。3.3 C中实现LALR1分析器的核心数据结构// parser.h class Parser { private: std::vectorint stack_; // 状态栈 std::vectorNode* value_stack_; // 语法树节点栈 Lexer lexer_; // LALR1分析表简化版实际为128x128 static const int ACTION_TABLE[128][128]; // action[state][token] 0:err, 0:shift, 0:reduce(-n) static const int GOTO_TABLE[128][10]; // goto[state][nonterminal] next_state public: Node* parse(); private: Node* reduce(int rule_id); // 根据语法规则ID归约构造AST节点 void shift(int state, Token token); // 压栈状态和token值 };// parser.cpp Node* Parser::parse() { stack_.push_back(0); // 初始状态0 Token token lexer_.nextToken(); while (true) { int state stack_.back(); int action ACTION_TABLE[state][token.type()]; if (action 0) { // 移进 stack_.push_back(action); value_stack_.push_back(new LeafNode(token)); // 叶子节点存token token lexer_.nextToken(); } else if (action 0) { // 归约 int rule_id -action; Node* node reduce(rule_id); // 构造内部节点 int nonterm getLHS(rule_id); // 规则左部非终结符 int goto_state GOTO_TABLE[stack_.back()][nonterm]; stack_.resize(stack_.size() - getRHSLength(rule_id)); // 弹出对应状态数 stack_.push_back(goto_state); value_stack_.push_back(node); } else if (action 0) { // 错误 handleError(token); token lexer_.recover(); // 跳过直到同步记号 } else if (token.type() TOKEN_EOF state 1) { // 接受 return value_stack_.back(); } } }参数说明ACTION_TABLE中正值为移进目标状态负值绝对值为归约规则编号GOTO_TABLE索引为当前状态和非终结符ID如0program,1stmt,2exprreduce()根据规则长度弹出value_stack_中对应数量的节点构造父节点。4. 避坑词法与语法分析器集成时的5个血泪经验4.1 现象词法分析器识别hello为STRING但语法分析器报错unexpected token STRING原因词法器返回TOKEN_STRING但LALR1表中action[当前状态][TOKEN_STRING] 0未定义因为文法未声明string_literal规则或STRING未加入终结符集。解决检查parser.y或手写文法文件中是否包含literal : STRING | NUMBER | IDENTIFIER ;并在生成ACTION_TABLE前确认TOKEN_STRING的枚举值已映射到表的列索引。4.2 现象输入if (x) yz;时yz;被忽略只解析到)原因FOLLOW(stmt)未包含;导致if (expr) stmt归约后action[状态X][SEMI]为0错误分析器丢弃;并尝试用其他规则匹配y。解决重新计算FOLLOW(stmt)——stmt出现在if (expr) •stmt和while (expr) •stmt中故FOLLOW(stmt)应包含{;, }, $}在ACTION_TABLE中为这些状态的SEMI列填入归约动作。4.3 现象编译通过但parser.exe运行时报Access violation reading location 0x00000000原因value_stack_在归约时弹出节点数错误。例如规则expr → expr term长度为3但reduce()中只弹出2个节点导致value_stack_.back()访问空指针。解决为每条规则硬编码长度RULE_LENGTH[rule_id] 3在reduce()开头加断言assert(value_stack_.size() RULE_LENGTH[rule_id]);。4.4 现象中文注释// 中文导致词法器卡死或乱码原因DFA表按ASCII 0-127构建但UTF-8中文字符如中为0xE4 0xB8 0xAD超出范围TRANSITION_TABLE[state][0xE4]越界访问。解决方案一推荐教学词法器预处理将UTF-8多字节序列转义为\u4E2D再分析方案二实用限定输入为ASCIIlexer_构造时检测input_[i] 127并报错。4.5 现象VS2019编译通过但双击parser.exe提示“缺少vcruntime140.dll”原因可执行文件依赖Microsoft Visual C 2015-2022 Redistributable而目标机器未安装。解决开发时项目属性 → C/C → 代码生成 → 运行库 →/MT静态链接CRT生成独立exe或打包时将vcruntime140.dll、msvcp140.dll同目录分发需确认许可证允许绝对不要让用户自行下载“Microsoft Visual C Redistributable”——这是安全风险点。5. 让分析器真正可用AST可视化、错误定位与文法热替换5.1 用Graphviz导出DFA图一眼揪出状态设计缺陷DFA的正确性肉眼难验但图形化后漏洞立现。本设计提供dump_dfa_dot()函数生成DOT文件// utils.cpp void dumpDfaDot(const std::string filename) { std::ofstream f(filename); f digraph DFA {\n; f rankdirLR;\n; f node [shape circle];\n; f 0 [shape doublecircle];\n; // 初始状态 for (int s 0; s 15; s) { if (s 0) continue; // 跳过终态标记 for (int c 32; c 127; c) { // 只画可打印字符 int next TRANSITION_TABLE[s][c]; if (next ! 0 next 0) { f s - next [label\ (char)c \];\n; } } if (s 12 || s 13 || s 14 || s 15) // 终态 f s [shape doublecircle];\n; } f }\n; }生成dfa.dot后命令行执行dot -Tpng dfa.dot -o dfa.png查看图片若发现的路径0-3-14与的路径0-3-15未分离或数字状态2对字母有转移则DFA设计错误。5.2 语法错误的精准定位不只是行号还要列号和上下文原生Lexer::nextToken()只返回行号但IDE级体验需列号。改造如下struct Position { int line, col; Position(int l, int c) : line(l), col(c) {} }; Position Lexer::getPosition(size_t pos) { int line 1, col 1; for (size_t i 0; i pos; i) { if (input_[i] \n) { line; col 1; } else { col; } } return Position(line, col); } Token Lexer::nextToken() { // ... 匹配逻辑同前 Position pos getPosition(start_pos); return Token(token_type, lexeme, pos.line, pos.col); }错误报告示例Error at line 3, column 12: expected ; before int int a 1 ^5.3 文法热替换不重编译改.y文件即可更新分析器本设计预留GrammarLoader模块支持运行时加载文法// grammar_loader.h class GrammarLoader { public: static bool loadFromYFile(const std::string filename, std::vectorstd::vectorint action_table, std::vectorstd::vectorint goto_table); private: static void parseYFile(const std::string content); };.y文件格式简化%token INT IDENTIFIER SEMI LPAREN RPAREN %left - %% program: stmt_list ; stmt_list: stmt | stmt_list stmt ; stmt: INT IDENTIFIER SEMI ; %%loadFromYFile()解析此文件调用computeFirstFollow()和buildLalr1Table()动态生成新表。学生可修改stmt规则添加if语句无需碰C代码。5.4 AST的JSON导出对接现代前端可视化为方便调试Node类实现toJson()方法std::string Node::toJson(int indent 0) { std::string pad(indent, ); if (isLeaf()) { return pad { \type\: \ type_ \, \value\: \ value_ \ }; } else { std::string res pad { \type\: \ type_ \, \children\: [\n; for (size_t i 0; i children_.size(); i) { res children_[i]-toJson(indent 2); if (i children_.size() - 1) res ,; res \n; } res pad ] }; return res; } }调用std::cout root-toJson() std::endl;输出{ type: program, children: [ { type: stmt, children: [ { type: INT, value: int }, { type: IDENTIFIER, value: a }, { type: SEMI, value: ; } ] } ] }粘贴到https://jsoneditoronline.org/即可交互式查看树结构。我带学生做这个课设时最常强调的一句话是“编译器不是写出来就完事而是跑起来、断点进去、看到状态栈在动、看到AST在长才算真正活了。”这份C实现的价值不在它多精巧而在它每一行代码都经得起GDB单步——当你在action[42][TOKEN_PLUS]处停下看到它返回-5归约规则5再跳进reduce(5)看到expr → expr term的三个子节点被拼成新节点那一刻龙书上的铅字就变成了你指尖的电流。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

产线数据采集上位机选型与运维:IPC-510 4U工控机实战解析

产线数据采集上位机选型与运维:IPC-510 4U工控机实战解析

两年前接了一个产线改造项目,甲方主管反复强调一句话:监控电脑可以慢,但不能死;数据可以后补,但绝对不能丢。那时候我在选型表里列了三个方案:普通商用台式机、无风扇嵌入式工控箱、还有一台研华 IPC-510 这…

2026/10/10 14:06:48 阅读更多 →
通达信公式编写核心原理与四大类型避坑指南

通达信公式编写核心原理与四大类型避坑指南

简介:本资源是一份面向股票量化分析初学者与通达信用户的技术指标开发入门教程,系统讲解如何在通达信平台编写四类核心公式:技术指标(如MA、KDJ)、条件选股(如“股价低于每股净资产”)、交易系统…

2026/10/10 14:06:48 阅读更多 →
pywebview 开发者指南:环境搭建、协作工作流、测试体系与 Ruff/pre-commit 代码规范

pywebview 开发者指南:环境搭建、协作工作流、测试体系与 Ruff/pre-commit 代码规范

桌面应用前端 【免费下载链接】pywebview Build GUI for your Python program with JavaScript, HTML, and CSS 项目地址: https://gitcode.com/gh_mirrors/py/pywebview 点击查看 免费下载 本文是一份面向 pywebview 贡献者的开发指南,围绕 docs/contr…

2026/10/10 14:05:48 阅读更多 →

最新新闻

AnyPS5串流实战:跨平台远程游玩PS5的延迟优化与搭建指南

AnyPS5串流实战:跨平台远程游玩PS5的延迟优化与搭建指南

1. 从“AnyPS5”这个标题说起:它到底想解决什么问题第一次看到“AnyPS5”这个标题,我脑子里蹦出来的第一反应是:这大概率是一个围绕“跨平台游戏串流”或者“远程游玩”方向的项目。为什么这么判断?因为“Any”这个前缀在技术圈里…

2026/10/10 15:40:13 阅读更多 →
效率工具盘点:从写作记录到文件管理的实用工作流指南

效率工具盘点:从写作记录到文件管理的实用工作流指南

1. 内容整体设计与思路拆解1.1 为什么值得做一次工具盘点最近整理工作流的时候,发现自己这些年陆陆续续攒下了不少顺手的小工具。有的一用就是五六年,有的装完三天就卸载,真正能留下来的,都是在某个具体场景里帮我省下过真金白银时…

2026/10/10 15:40:13 阅读更多 →
不再背八股:JS 面试必考的闭包、事件循环与原理解析

不再背八股:JS 面试必考的闭包、事件循环与原理解析

JavaScript 面试题在圈子里有个刻板印象:背八股。但真正当过面试官的人都知道,同样是"说说闭包"这一道题,有人能聊到内存管理、模块化设计、工具链原理,有人只能背出"函数套函数、返回函数"六个字。差在哪&am…

2026/10/10 15:40:13 阅读更多 →
MATLAB实现综合能源系统优化调度:含综合需求响应与阶梯碳机制的MILP建模

MATLAB实现综合能源系统优化调度:含综合需求响应与阶梯碳机制的MILP建模

搞过综合能源系统调度的人应该都有同感:论文里一句“考虑综合需求响应和阶梯型碳机制”,看着挺简单,真正动手用MATLAB复现的时候,各种问题就全涌上来了。多层能量平衡、设备耦合约束、非线性碳成本,还有动不动就上百个…

2026/10/10 15:40:13 阅读更多 →
100份文本用sklearn做批量分析更稳更快的实操指南

100份文本用sklearn做批量分析更稳更快的实操指南

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

2026/10/10 15:40:12 阅读更多 →
Euro-Office DocumentServer接入GlitchTip:3步搭建自托管错误监控系统

Euro-Office DocumentServer接入GlitchTip:3步搭建自托管错误监控系统

【免费下载链接】DocumentServer 项目地址: https://gitcode.com/gh_mirrors/doc/DocumentServer 点击查看 免费下载 本文介绍如何为 Euro-Office DocumentServer(在线文档协作服务器)接入开源错误追踪工具 GlitchTip,只需 3 步就…

2026/10/10 15:39:10 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

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

2026/10/10 11:14:25 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

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

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

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

2026/10/10 11:14: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/10 5:23: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/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练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/10 10:38:42 阅读更多 →