从词法分析到三地址码:手写Pascal子集编译器核心实现全解析
简介这是一份面向编译原理课程设计的小型Pascal子集编译器设计报告适合计算机专业学生、课程设计小组及自学编译器基础的读者参考。资源以单个doc文档形式打包共1个文件压缩包约952KB内容涵盖词法分析、语法分析、语义分析、中间代码生成与目标代码生成等完整设计环节。文档源自北京邮电大学课程设计报告包含题目与要求、总体设计说明、详细设计说明并针对词法分析器接口、符号表、分析表、翻译模式、四元式中间代码等给出具体实现思路还附有团队成员分工与测试安排便于对照自己的项目查漏补缺。词法部分对行号列号记录、注释与间隔符号处理作了说明层次清晰方便直接复用其设计思路。已有240人学习浏览适合需要快速把握Pascal子集编译器整体架构、撰写课程设计报告或完成阶段性模块设计的读者。1. 小型Pascal子集编译器为什么说它不是一个玩具一个Pascal子集编译器听起来像是编译原理课上用来应付答辩的作业但这套从词法分析、语法分析、语义分析到中间代码生成、全部用C/C手工实现、不依赖YACC等任何生成器的设计报告把教科书里分散在不同章节的知识点拧成了一条可运行的链路。它的核心产出很明确词法分析输出记号流和符号表语法分析基于LL(1)预测分析表构建分析树语义分析做完类型检查后生成三地址码。对你来说它最大的价值不是“能编译Pascal”而是看清一条不借助任何工具链也能走通的完整路径。这份资源适合三类人准备编译器课程设计的学生、想快速理解编译全流程的开发者、需要给团队拆解编译任务的人。2. 词法分析模块记号流、符号表与一张手工编码表2.1 接口设计为什么一个无参函数就能完成交接原文里词法分析器和语法分析器的接口非常简单只有一个函数void lexout(void); // 词法分析器主入口无参数、无返回值 // 通过填充两个全局数组与语法分析器完成数据交接配合两个全局数组Symbol Symbol_stream[1000]; // 记号流数组每个元素包含记号属性atr和记号名sname Tuple symbol_table[100]; // 符号表数组记录变量名、类型、维数、声明行、引用行等从软件工程角度看这个接口不算优雅但放在课程设计这个规模里它是最直接可用的方案。lexout一次性扫描完整个源文件把终结符和属性填进全局数组语法分析器拿到数组后按下标顺序消费。我一般会建议想往工程化方向改的人做两件事第一把lexout拆成“读一个记号返回一个记号”的生成器接口比如int get_next_token(Token* tok)让词法分析器按需扫描而不是把1000个记号全塞进内存第二将两个全局数组改成结构体指针传入传出避免多模块共享全局变量带来的隐藏耦合。往下还有一组配套判断函数每个都很短但边界要拎清int Iskeyword(char t[]); // t是识别出的字符数组关键字返回内部编码标识符返回0其他返回-1 int IsLetter(char ch[], int n); // 判断字母返回1或0 int IsDigit(char ch); // 判断数字返回1或0 int Word_insert(char t[]); // 把标识符插入符号表若已存在返回下标否则返回0 void Num_insert(char t[]); // 把常数插入常数表这套接口的分层很清楚IsLetter和IsDigit是底层字符判断Iskeyword在字母串收集完成后做关键字匹配Word_insert负责符号表写入。实际使用中有一个坑要注意Iskeyword的返回值同时承载了“是否关键字”和“具体是哪个关键字”两层语义调用方必须先判断返回值分支再决定下一步动作我见过有同学直接拿返回值当编码写入记号流结果把标识符当成关键字填了进去。2.2 符号表结构8字符上限和5次引用限制是两处硬边界原文中符号表项定义如下typedef struct table_item { char name[9]; // 变量名有效字符数为8 int address; // 目标地址语义分析阶段填充 int type; // 类型编号语义分析阶段填充 int demension; // 维数 int declare_row; // 声明行 int use_row[5]; // 引用行最多记录5次 int block; // 块索引号 } Tuple;两处参数值得展开。第一name只留9字节意味着变量名有效长度是8个字符超过部分会被截断。这是沿用早期Pascal编译器的常见简化策略在课设里没问题但如果有人拿它去编译变量名超过8个字符的源程序会出现两个不同变量被截断成同名变量的诡异现象。第二use_row[5]是真正会翻车的地方。一个变量在程序里被引用超过5次后面的引用行就记录不进去了。原文的应对方式是在词法分析外层维护一个int n[100]数组用它来跟踪每个id当前应该写到use_row的哪个位置——本质上是绕开5次限制而不是解决它。测试小程序没问题一旦写一个循环里反复操作同一个数组变量的用例这里就开始丢信息。我的建议是use_row改成动态数组或者词法阶段干脆不记引用行等语法树建完再回填。词法分析器还会生成五个txt文件符号表.txt、常数表.txt、统计信息.txt、记号流.txt、终结符.txt。这批文件在调试阶段非常有用——语法分析报错时先打开记号流.txt确认词法阶段有没有错能把问题定位时间压缩一大半。统计信息.txt里记录了行数、单词个数、错误个数是验收时展示成果的直观材料。2.3 识别主循环与内部编码表一个笔误值得警惕词法分析主循环的逻辑还原出来大致是这个样子while (i l) { // 逐个字符扫描输入缓冲区 char ch buffer[i]; if (IsLetter(ch)) { // 字母开头收集完整标识符 j 0; while (IsLetter(buffer[i]) || IsDigit(buffer[i])) { token[j] buffer[i]; } token[j] \0; int key Iskeyword(token); if (key ! -1) { // 命中关键字直接写入记号流 Symbol_stream[m].atr key; strcpy(Symbol_stream[m].sname, token); m; } else { // 普通标识符插入符号表并登记引用行 int idx Word_insert(token); Symbol_stream[m].atr 36; // id的内部编码 strcpy(Symbol_stream[m].sname, token); symbol_table[idx].use_row[n[idx]] line_count; n[idx]; m; } } else if (IsDigit(ch)) { // 数字常量收集完整数值写入常数表并写记号流 while (IsDigit(buffer[i])) { sum sum * 10 (buffer[i] - 0); i; } Symbol_stream[m].atr 35; // num的内部编码 // 这里原文还处理了小数标志state1的逻辑 m; } else { // 运算符、括号、分隔符按字符匹配 switch (ch) { case :: i; if (buffer[i] ) { Symbol_stream[m].atr 34; // assignop : i; } else { Symbol_stream[m].atr 27; // 单冒号 : } m; break; // 其他运算符分支从略 } } // 行号、字符计数在每次换行时更新 }逻辑并不复杂但有三个细节直接影响后续模块。第一关键字判断必须在标识符分支内、且在完整收集单词之后执行。用最长匹配先收完整个单词再调Iskeyword才不会出现把programme识别成关键字program的问题。第二数字常量识别要处理小数和注释嵌套原文用state1标志位记录是否遇到小数点用digit_l和digit_r记录花括号注释的层数本质是一个简化版状态机。第三词法分析器返回的记号流数组同时包含记号和属性值atr和sname语法分析器查询分析表时靠的是atr展示分析树时靠的是sname。内部编码表是词法分析的核心产出之一但原文给出的编码表里有一处明显的冲突记号编码记号编码procedure15program15原文误标应与procedure不同or14addop14与or编码冲突procedure和program编码相同、or和addop编码相同这在语法分析阶段会导致查分析表时命中错误的产生式。这不是大问题手工整理编码表时编号撞车很常见但拿到这份代码后第一步应该是核对并重新分配编码。我自己处理编码表有个习惯终结符统一分配100以下编号非终结符从100开始运算符集中在20到40区间。这样查表时能通过编码范围快速区分终结符和非终结符调试时也少很多魔法数字的困扰。3. 语法分析用FIRST集、FOLLOW集把LL(1)分析表撑起来3.1 递归求FIRST集四条规则一次讲透语法分析部分原文明确采用LL(1)方法并且要求“有求first、follow集的函数”。FIRST集的计算规则是所有推导动作的地基规则1如果X是终结符则FIRST(X) {X} 规则2如果X - ε 是一个产生式则将ε加入FIRST(X) 规则3如果X是非终结符且 X - Y1 Y2 ... Yk 是产生式 a) FIRST(Y1)中除ε以外的所有符号都加入FIRST(X) b) 若Y1...Yi-1都可以推导出ε则FIRST(Yi)中除ε以外的符号也加入FIRST(X) c) 若所有Y1...Yk都能推导出ε则将ε加入FIRST(X)对应到代码原文的思路是每个非终结符维护一个固定大小的firstgroup[15]数组配合一个count记录当前成员数class FirstGroup { public: int value; // 非终结符的内部编码 int firstgroup[15]; // FIRST集成员最多15个 int count; // 当前FIRST集元素个数 public: void insertFsg(int NT, int i, int m); // 把第i个产生式的FIRST集并入当前符号 bool existed(int sym); // 判断sym是否已在FIRST集中 }; // 调用示例GetFirstGroup(sym) 递归计算一个符号的FIRST集 bool GetFirstGroup(int sym) { // 遍历所有产生式左端为sym的产生式 // 对每个产生式右部第一个符号Y1 // 若Y1是终结符直接加入FIRST(sym) // 若Y1是非终结符递归调用GetFirstGroup(Y1)把结果并入 // 若Y1可以推导出ε则继续考察Y2递归并入 // 若右部全部可推导出ε则把ε加入FIRST(sym) return true; }这里有一个容易写错的地方insertFsg的第二个参数i是产生式编号第三个参数m是描述当前处理到右部第几个符号。合并时需要逐层判断“当前右部符号是否可推导出ε”只有当左边所有符号都能推导出ε时才允许把下一个符号的FIRST集并入。很多实现直接把右部第一个符号的FIRST集整个并入导致FIRST集多出不该有的终结符后续分析表也跟着错。另一个要注意的问题是firstgroup[15]这个固定容量。Pascal子集文法简单15个成员够用如果你要扩展语言比如增加repeat、case等结构FIRST集成员数很容易超过15数组越界是必然的。我一般会改成std::set 或者动态vector彻底消除容量上限。3.2 FOLLOW集构建与预测分析表填充两轮循环扫完FOLLOW集的构建规则相对规整规则1将输入结束符$放入FOLLOW(S)S是开始符号 规则2若存在产生式 A - αBβ则FIRST(β)中除ε以外的符号全部放入FOLLOW(B) 规则3若存在 A - αB或 A - αBβ 且 β ε 则FOLLOW(A)中所有符号放入FOLLOW(B)原文对应的实现是FollowGroup类同样用followgroup[15]固定数组存储GetFollowGroup做整体计算。规则2和规则3的处理顺序有讲究必须先把所有产生式扫描一遍处理规则2再处理规则3因为规则3会传播FOLLOW集合一轮扫描可能不够需要循环迭代直到没有新符号加入为止。原文在文档里写的是“直到每个FOLLOW集合都不能再加入任何符号或$为止”这句话就是停止条件。预测分析表的填充是语法分析的核心步骤原文的数据结构是一个二维int数组analysistable[][]// 先把表全部初始化为-1 int analysistable[NUM_NT][NUM_T]; // 非终结符行、终结符列 for (int i 0; i NUM_NT; i) for (int j 0; j NUM_T; j) analysistable[i][j] -1; // -1表示error // 逐个扫描产生式 A - ą标号为i for (int i 0; i num_generator; i) { int A generator[i][0]; // 左端非终结符 // 求FIRST(ą)对每个a属于FIRST(ą) int first_set[15]; GetFirstOfRight(generator[i], first_set, cnt); for (int k 0; k cnt; k) { int a first_set[k]; if (a ! EPSILON) { analysistable[A][a] i; // 填入产生式编号 } } // 若ε属于FIRST(ą)则对每个b属于FOLLOW(A)也填入产生式编号 if (epsilon_in_first) { for (int k 0; k follow_cnt; k) { int b followgroup[A][k]; analysistable[A][b] i; } } }这套填充逻辑完全对应编译器教材的算法。没有被赋值的区域保持-1语法分析器查到-1就报错。分析栈的结构原文也提到要有明确的数据结构实际实现就是一个int数组加栈顶指针栈里存的是非终结符和终结符的内部编码。需要注意的是表格填充的顺序敏感点如果两个产生式在同一个[A][a]位置上都填写了不同的产生式编号说明文法不是LL(1)的存在first冲突或first/follow冲突。我遇到这种情况会先把有公共前缀的产生式做提取左因子或者直接改文法描述而不是硬调表结构。LL(1)不代表所有Pascal子集的合法文法都满足原文的实现只覆盖了Sub_P的语法改文法就得重新算FIRST和FOLLOW集。3.3 分析树输出改变源程序分析树必须跟着变原文要求“能够以直观的方式输出分析树”并特别注明“改变源程序分析树将发生变化”。这其实是验收时最容易被追问的点。分析树的输出实现不复杂但有一个细节容易被忽略需要在语法分析器执行产生式归约或推导的同时记录树节点的父子关系。原文的做法是在listening栈里同步维护一个树节点数组每展开一个非终结符就创建节点并把当前栈顶节点当作父节点。输出阶段用缩进或者括号嵌套的形式打印。打印格式上我见过几种方案一是缩进式每个非终结符按深度缩进子节点缩进更多直观但占行二是括号嵌套式类似LISP风格每个节点用括号包住其子节点紧凑且方便对比。对课设来说缩进式更容易被验收老师看懂。核心验证方法是准备两个只有细微差别的Pascal源文件比如给声明部分多增加一个变量跑一遍语法分析输出两棵分析树diff一下确认差异集中在声明子树而不是扩散到整个树。树结构若大面积变动多半是分析表或符号表复用出了问题。4. 语义分析与中间代码生成从分析树到三地址码的翻译动作4.1 符号表用栈组织作用域管理的地基语义分析部分原文设计了一个值得留意的机制用堆栈组织符号表。接口如下SqStack* mktable(); // 创建一个新的符号表返回指针 void enter(SqStack* tableptr, char* name, int type, int addr); // 向tableptr指向的符号表插入变量或过程name为名字type为类型编号addr为偏移量 char* lookahead(int k); // 查找节点k的最左子节点返回对应的字符串用栈组织符号表的意义在于处理作用域每进入一个过程或函数就push一个新的符号表每退出一个过程或函数就pop掉整个表所有局部变量自动失效。原文在翻译方案里写得很直白program → { t : mktable(nil); push(t, tableptr); push(0, offset); ... subprogram_head → function id { t : mktable(top(tableptr)); push(t, tableptr); push(0, offset); ... }注意这里的细节函数声明时先基于当前外层表创建一个新的子表push到栈顶然后参数的enter操作都作用在栈顶表上。这样同一个变量名在外层函数和内层函数可以共存互不干扰。课程设计要求里明确写了“不包含嵌套过程”所以栈深度最大也就是两层到三层实现起来不需要处理多层回溯。一个我实际遇到过的问题如果符号表栈没有对应地在过程退出时pop那么后面的函数里查符号表会查到前一个函数的局部变量类型检查直接错乱。这个我在避坑章里细说。4.2 类型检查与类型转换inttoreal是重头戏原文的类型检查覆盖四类场景赋值语句检查等号两端类型是否相同判断语句检查表达式类型是否为boolean算术运算检查两个操作数是否同类型必要时做类型转换数组下标检查是否为整数。这套规则在翻译方案中体现得非常具体以简单表达式的加减法为例// simple_expression - addop term simple_expression1 // 两个操作数都是integer if (t1 INTEGER t2 INTEGER) { temp newtemp(); emit(temp : t1 t2); // 生成三地址码 result_type INTEGER; } // 两个操作数都是real else if (t1 REAL t2 REAL) { temp newtemp(); emit(temp : t1 t2); result_type REAL; } // integer real先把integer转成real再运算 else if (t1 INTEGER t2 REAL) { u newtemp(); emit(u : inttoreal t1); // 插入类型转换指令 temp newtemp(); emit(temp : u t2); result_type REAL; } // real integer同样转real else { u newtemp(); emit(u : inttoreal t2); temp newtemp(); emit(temp : t1 u); result_type REAL; }这段翻译动作的核心逻辑是一旦两个操作数类型不一致生成一个新的临时变量先emit一条inttoreal转换指令再执行真正的加法。这里有一个常见的实现错误——很多初学实现直接把int操作数和real操作数用运算符连接不给inttoreal指令分配独立的临时变量导致后续引用转换结果时找不到对应的变量地址。类型的传播方向也要注意。表达式的类型是由叶子节点向上推导的每个节点的类型属性在语义分析遍历分析树时计算。原文通过递归函数如expression、simple_expression、term、factor每个函数返回该节点对应的类型值。类型检查嵌在递归下降的过程中遇到不匹配就报错但不会中断整棵树的分析——错误收集到一个数组里最后统一打印这个处理很实用。4.3 三地址码的种类赋值、数组、指针、函数调用全覆盖中间代码部分原文选择三地址码作为中间表示并要求覆盖赋值、数组、指针、函数四类功能。这个覆盖面比较扎实不是简单地做一个表达式求值器。代码种类归纳下来如下表指令类型形式覆盖场景赋值指令x : y 或 x : y op z赋值语句、表达式计算数组相关x : a[i] 或 a[i] : x数组读写指针相关x : y / x : *p / *p : x指针操作函数调用BEGIN CALL f / PARAM x / END CALL函数定义与调用跳转指令GOTO 标签 / IF x GOTO 标签if、while语句翻译数组和指针的三地址码展开有一些细节。以数组赋值a[i] : x为例需要先算出数组基地址和下标偏移量结果是生成一条以变址方式访问内存的三地址指令。原文的翻译方案里数组类型定义要记录下标范围digits1..digits2并计算width作为偏移量乘积因子type → array [ digits1 .. digits2 ] of standard_type { type.t : array(digits1..digits2, standard_type.t); type.width : (digits2.value - digits1.value) * standard_type.width }这个width计算决定了数组元素在内存中的步长integer类型width4real类型width8都是按4字节对齐设的。关于指针原文的翻译方案里提到了指针相关三地址码包括取地址和间接访问操作。函数调用的参数传递机制原文要求“在文档中描述采用何种参数传递机制”。从翻译方案看采用的策略是函数调用前先按顺序把实参表达式求值结果写入形参对应的地址参数多的情况下用临时变量存储。另外要注意的是函数符号表在调用结束后要被回收原文通过pop(tableptr)实现这样递归调用的每一层都拿到独立的参数副本。5. 手工实现编译器的五个常见坑现象、原因、解决5.1 现象:被拆成了:和赋值语句直接解析失败词法分析器扫到冒号后只取了一个字符就结束把赋值运算符拆成了两个独立记号语法分析器在分析assignment时查不到assignop编码。原因很简单字符扫描时缺少向前看一位的逻辑特别是冒号这种可能是单字符也可能是双字符的情况必须做最长匹配。解决方式是在冒号分支里先保存当前位置读下一个字符判断是不是等号。我处理这个问题的习惯是所有“可能变长”的运算符统一走“先取下一位能匹配双字符就用双字符否则回退”的公共逻辑而不是在每个case里单独写。从那以后我每次拿到词法分析代码第一件事就是找冒号、小于号、大于号这三个最常被拆错的字符。5.2 现象FIRST集计算死循环程序跑不出结果文法里包含间接左递归比如A的产生式右部以B开头B的产生式右部又回到A。GetFirstGroup递归调用时反复进入A和B没有访问标记导致无限递归。另一方面如果文法里有多个非终结符互相依赖且都能推导出ε合并FIRST集的操作也会不断重复。解决方法是引入一个visited数组每个符号在递归入口先检查是否已经计算过计算中暂存结果递归返回时直接把暂存结果合并给调用方。这里的顺序是先标记visited再递归子目标最后回溯时清理部分标记。另一种更稳的做法是改用不动点迭代反复扫描所有产生式把FIRST集合并操作一直执行到没有新成员产生为止。后者的代码量更大但不会踩递归深度的坑。5.3 现象内层函数能“看见”外层函数的局部变量类型检查错乱符号表栈在进入子过程时push了新表但退出时忘记pop导致后续函数的enter操作全写进了前一个函数留下来的表里。具体表现是两个不相干的函数里声明同名变量语义分析认为它们是同一个变量类型检查直接串台。原因就是栈生命周期管理不严格。解决方式是在翻译方案的每个compound_statement或subprogram_declaration的结束位置强制配对pop。我给学生的建议是在代码里把push和pop写成对称的注释块每次push后立即写对应的pop注释实现时对照注释逐条补全。不要等整个模块写完再检查那会儿你已经分不清哪个push没配对了。5.4 现象integer和real混合运算结果值不对精度被截断简单表达式里一个操作数是integer、另一个是real时先执行了运算再转换类型或者把real隐式截断成integer。原因是没有按翻译方案先生成inttoreal转换指令而是把两个不同类型的操作数直接放进同一个三地址指令。解决方式是严格按翻译方案的四个分支走integer和integer走原生整数运算real和real走浮点运算混合类型必须先emit一条inttoreal用临时变量保存转换结果再参与运算。验证方法也简单编译一段含a : 1 2.5的测试程序正确的中间代码应该出现inttoreal 1和一个临时变量而不是直接emit 1 2.5。如果你在中间代码里看到两个不同类型的操作数出现在同一条三地址指令里类型转换模块基本可以断定没写对。5.5 现象while循环生成了错误的跳转标签编号运行结果缺失循环体while语句的翻译方案有几条固定的跳转动作先输出循环开始标签L_i条件不满足时跳到L_i2满足时进入循环体L_i1循环体结束跳回L_i。如果标签计数器在if语句里已经消耗了编号却没同步到while里或者递归生成代码时标签变量被重复赋值最终跳转目标就会错位。原因往往是标签编号生成器是全局变量但在递归下降过程中被多个语义动作重复修改。解决方式把标签编号生成封装成一个独立函数newlabel()每次调用返回一个新的编号并自增计数器所有emit跳转指令都只使用newlabel()的返回值绝不在翻译动作里手工写死L0、L1这样的常量。这也是为什么原文在翻译方案里反复用i:i2、i:i3来推进编号——它本质上是手动管理newlabel。6. 验证编译器正确性三查法加一个测试程序习惯拿到一个手工实现的编译器怎么确认它不是“表面能跑”我的做法是固定的三查法第一查词法用记号流.txt和终结符.txt对比源程序确认每个标识符的类型编号和关键字编码都对得上第二查语法构造至少五个不同结构的Pascal子集测试程序覆盖声明、赋值、数组、if-else、while五类场景观察分析树是否随源程序变化而变化第三查语义在中间代码里找inttoreal指令确认混合类型运算被正确处理再检查函数调用的PARAM指令序列和形参声明顺序是否一一对应。最后一个习惯是从那次做课设养成的每写完一个模块先留一个小尺寸的测试程序专门打这个模块的边界而不是等全链路跑通再测。词法阶段测注释嵌套和长标识符截断语法阶段测FIRST集冲突和分析表空字段语义阶段测类型不匹配报错和类型转换生成。完整的Pascal子集编译程序本身是黑匣子但拆开模块测每个部件都能单独验证。这样最后联调时出问题的地方只可能在模块交接处而不是埋在某个已经无法定位的旧代码里。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

JCache CacheLoader:load与loadAll如何配合getAll实现批量回源?

JCache CacheLoader:load与loadAll如何配合getAll实现批量回源?

几年前的面试,大家喜欢问“缓存穿透怎么解决”“Redis雪崩怎么处理”,这两年风向明显变了,越来越多的团队开始抠规范本身,比如今天要聊的这道题:JCache(JSR-107)中 loadAll 和 load 的用途是什么…

2026/10/13 21:31:26 阅读更多 →
PCL点云圆柱体分割实战:RANSAC原理、参数调优与工业应用

PCL点云圆柱体分割实战:RANSAC原理、参数调优与工业应用

做点云处理这几年,我有个特别深的体会:几乎每个项目里需要找的几何元素,翻来覆去就那么几种——平面、球、圆柱。平面最简单,一个法线加一个距离就描述完;球也不难,中心加半径一共四个参数;唯独…

2026/10/13 21:30:25 阅读更多 →
PHP+UniApp开发宠物饲养交流系统实战

PHP+UniApp开发宠物饲养交流系统实战

1. 项目概述最近完成了一个宠物饲养交流系统的开发项目,采用PHP后端UniApp前端的混合开发模式。这个系统包含APP和小程序双端应用,主要解决宠物主人在饲养过程中遇到的交流、知识分享和日常管理需求。从技术选型到功能实现,整个项目走下来积累…

2026/10/13 21:30:25 阅读更多 →

最新新闻

Python超市管理系统源码实战:Flask+SQLite收银库存全流程

Python超市管理系统源码实战:Flask+SQLite收银库存全流程

简介:这是一份面向Python初学者与课程设计学习者的超市管理系统源码包,围绕员工管理、客户管理、登录验证与商品库存记录等日常运营场景展开,适合用来练习GUI界面搭建、文件持久化存储与模块化组织方式。压缩包共13个文件,约8KB&a…

2026/10/13 22:18:56 阅读更多 →
AI制图实测:10分钟生成架构图与流程图

AI制图实测:10分钟生成架构图与流程图

相信不少朋友都经历过这样的场景:刚画完一张系统架构图,产品经理又改了两版需求;刚理清一条业务主线,对接方又加了三个分支。画图这件事,听着很轻松,真正动手才发现,框要对齐、线要避开交叉、字…

2026/10/13 22:18:56 阅读更多 →
AI编程大模型免费活动的技术真相与工程实践指南

AI编程大模型免费活动的技术真相与工程实践指南

1. 项目概述:这不是“薅羊毛清单”,而是一份AI编程模型能力演进的实时观测日志“最新免费AI编程大模型活动汇总 2026年10月8日更新”——这个标题乍看像电商促销页,实则藏着当前AI工程落地最真实的脉搏。我从2023年起持续跟踪国内大模型在开发…

2026/10/13 22:18:56 阅读更多 →
PyTorch目标检测实战:小黄人检测从数据准备到模型训练全流程

PyTorch目标检测实战:小黄人检测从数据准备到模型训练全流程

最近用PyTorch做图像识别练习,选了个特别有画面感的入门任务——小黄人检测。目标检测这个名字听着有点唬人,但拆开其实就两件事:先把图片里的小黄人找出来,再用矩形框把它框住。上学那会儿做这类需求,第一反应是搞滑动…

2026/10/13 22:18:56 阅读更多 →
矩阵置零:原地算法与状态管理,LeetCode 73 题全解析

矩阵置零:原地算法与状态管理,LeetCode 73 题全解析

矩阵置零这个题,我第一次在刷题平台上看到时,内心是有点不屑的:两层循环,遇到 0 就改行改列,这不是有手就行?可当我真的原地写起来,才发现完全不是那么回事。题目本身很短:给定一个m…

2026/10/13 22:18:56 阅读更多 →
利用阿里云alidns实现DDNS动态解析:公网IP变化自动更新教程

利用阿里云alidns实现DDNS动态解析:公网IP变化自动更新教程

家里的宽带每隔一段时间公网IP就会变,尤其是不满运营商最长在线时长之后强制断线重拨的那种,老域名解析记录一直是旧IP,访问家里NAS或者是远程桌面全都得靠临时记IP来连,烦得不行。后来我把域名托管到了阿里云云解析,直…

2026/10/13 22:17:56 阅读更多 →

日新闻

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

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

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

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/13 16:22:04 阅读更多 →
影刀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/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/12 18:11:17 阅读更多 →