手写语法分析器:从LL(1)文法到可执行解析器
简介本资源是一份面向计算机专业本科生及编译原理初学者的语法分析实践教学材料聚焦编译器核心环节——语法分析的原理理解与代码实现。内容覆盖上下文无关文法定义、LL/LR分析对比、递归下降与Yacc/Bison工具应用、抽象语法树构建及语法错误处理等关键知识点兼顾理论梳理与工程落地。压缩包共4个文件453KB含C源码语法分析.cpp用于手写解析器实现、可执行程序语法分析.exe支持快速验证、实验报告文档语法分析.docx详述设计思路与测试用例、辅助说明文本file_out.txt记录运行输出结构精炼、即下即用。已有3106人学习下载适合课程实验复现、期末项目参考或编译器开发入门实践助读者打通从文法定义到AST生成的完整技术链路。1. 为什么写“语法分析实验报告含代码”不是交作业而是练出编译器工程的底层手感你手头这份《语法分析实验报告含代码》大概率来自编译原理课的课程设计——但别急着把它当应付学分的文档扔进回收站。它其实是你第一次亲手把「文法→推导→树→动作」这条链路从纸面拽进内存的真实切口不是调用现成 parser generator而是用递归下降或 LL(1) 手写一个能读入a b * c并输出带节点标记的 AST 的解析器不是只画个分析表就收工而是让程序真正在终端里报出Error at line 3, col 5: expected ) but got ;。这类实验的核心价值从来不在“报告格式是否规范”而在于它强制你直面词法与语法的边界、冲突消解的代价、错误恢复的妥协点——这些正是你在后续做 DSL 设计、配置文件校验、前端模板编译、甚至自研规则引擎时绕不开的黑匣子。如果你正卡在“能看懂 Dragon Book 却写不出可运行的 parser”或者想把 YAML/JSON Schema 验证逻辑下沉到更可控的层级这份实验就是最薄、最硬、最不骗人的第一块垫脚石。2. 从文法定义到可执行解析器递归下降法的完整落地路径递归下降是语法分析实验中最常被选用的方法——它不依赖外部工具代码结构与文法直接对应调试直观且能自然嵌入语义动作。但“手写递归下降”绝不是照抄教材伪码就能跑通的事。下面以经典的算术表达式文法为例拆解从文法书写到可运行代码的每一步真实操作。2.1 文法设计先砍掉左递归再确认 FIRST/FOLLOW 集可用性实验中常见的错误起点是直接拿教科书上的 E → E T | T 这类左递归文法开干。这会导致无限递归必须先改写。我们采用标准消除法得到无左递归、适合递归下降的等价文法E → T E E → T E | ε T → F T T → * F T | ε F → ( E ) | id | num提示这个改写不是为了“看起来更学术”而是为后续预测分析铺路。E 和 T 的 ε 产生式意味着它们对应函数必须能接受空输入——这直接影响你代码里if lookahead in FIRST(E)的判断逻辑也决定了错误恢复点的位置。验证该文法是否满足 LL(1) 条件关键看每个非终结符的产生式右部 FIRST 集是否两两不相交且若含 ε则该非终结符的 FOLLOW 集与其它 FIRST 集也不相交。手动计算得FIRST(E) { , ε }FOLLOW(E) { ), $ }$ 表示输入结束FIRST(T) { *, ε }FOLLOW(T) { , ), $ }所有交集为空文法合法。注意这步不能跳过。很多同学后期 parser 总在某处卡死或误报错根源就是文法本身不满足 LL(1)却强行用递归下降硬套。2.2 词法扫描器Lexer用正则预编译拒绝手写状态机语法分析器的输入不是原始字符串而是 Token 流。实验里最容易被低估的环节就是词法扫描器的质量。别用str.split()或简单if s.startswith(if)——这会毁掉整个语法分析的健壮性。我一般用 Python 的re模块预编译一组带命名组的正则按优先级顺序匹配import re TOKEN_SPEC [ (NUMBER, r\d(\.\d*)?), # 整数或浮点数 (ID, r[a-zA-Z_]\w*), # 标识符 (OP_ADD, r\), (OP_MUL, r\*), (LPAREN, r\(), (RPAREN, r\)), (WS, r\s), # 空白跳过 (MISMATCH, r.), # 兜底报错用 ] # 编译为单个正则用命名组区分类型 tok_regex |.join(f(?P{name}{pattern}) for name, pattern in TOKEN_SPEC) get_token re.compile(tok_regex).match class Lexer: def __init__(self, text): self.text text self.pos 0 self.current_token None self.next_token() def next_token(self): while self.pos len(self.text): mo get_token(self.text, self.pos) if not mo: raise SyntaxError(fUnexpected character {self.text[self.pos]} at position {self.pos}) kind mo.lastgroup value mo.group() self.pos mo.end() if kind WS: # 跳过空白 continue if kind MISMATCH: raise SyntaxError(fIllegal character {value} at position {self.pos - len(value)}) self.current_token (kind, value) return self.current_token (EOF, )参数说明TOKEN_SPEC中正则顺序即匹配优先级WS放中间靠后确保不被当作123的一部分MISMATCH是兜底项捕获所有未定义字符避免静默失败next_token()内部循环跳过空白使语法分析器完全不用处理空格逻辑——这是解耦的关键。2.3 递归下降解析器函数即非终结符return 即推导成功每个非终结符对应一个函数函数名即非终结符名如parse_E,parse_T函数体即该非终结符的产生式展开。核心原则当前函数只消费它能确定的 Token绝不回溯绝不猜测。class Parser: def __init__(self, lexer): self.lexer lexer self.current_token lexer.current_token def eat(self, token_type): 消费指定类型的 Token失败则抛异常 if self.current_token[0] token_type: self.lexer.next_token() self.current_token self.lexer.current_token else: raise SyntaxError( fExpected {token_type}, got {self.current_token[0]} fat position {self.lexer.pos - len(self.current_token[1])} ) def parse_E(self): E → T E node {type: E, children: []} node[children].append(self.parse_T()) node[children].append(self.parse_Eprime()) return node def parse_Eprime(self): E → T E | ε if self.current_token[0] OP_ADD: node {type: Eprime, children: []} self.eat(OP_ADD) node[children].append(self.parse_T()) node[children].append(self.parse_Eprime()) return node else: # 匹配 ε返回空节点或 None由上层决定如何处理 return {type: Eprime, children: [], epsilon: True} def parse_T(self): T → F T node {type: T, children: []} node[children].append(self.parse_F()) node[children].append(self.parse_Tprime()) return node def parse_Tprime(self): T → * F T | ε if self.current_token[0] OP_MUL: node {type: Tprime, children: []} self.eat(OP_MUL) node[children].append(self.parse_F()) node[children].append(self.parse_Tprime()) return node else: return {type: Tprime, children: [], epsilon: True} def parse_F(self): F → ( E ) | id | num if self.current_token[0] LPAREN: self.eat(LPAREN) node self.parse_E() self.eat(RPAREN) return {type: F, children: [node], is_paren: True} elif self.current_token[0] in (ID, NUMBER): token self.current_token self.eat(token[0]) return {type: F, value: token[1], token_type: token[0]} else: raise SyntaxError(fExpected ID or NUMBER or LPAREN, got {self.current_token[0]}) # 使用示例 if __name__ __main__: code a 2 * (b - c) lexer Lexer(code) parser Parser(lexer) try: ast parser.parse_E() print(Parse success. AST:, ast) except SyntaxError as e: print(Parse error:, e)逻辑说明eat()是原子操作保证 Token 消费的确定性每个parse_*函数返回 AST 片段字典结构便于后续语义分析或解释执行ε 产生式用带epsilon: True的空节点表示比返回None更利于统一遍历错误信息包含精确位置self.lexer.pos - len(...)这是调试阶段救命的细节。3. LL(1) 分析表驱动法从手写函数到查表跳转的范式切换当文法变大、产生式增多递归下降的手动编码维护成本急剧上升。此时LL(1) 分析表驱动法成为更工程化的选择——它把文法逻辑从代码中抽离固化为一张二维表解析器退化为纯粹的栈查表机。这不仅是“换种写法”更是理解预测分析本质的必经之路。3.1 构建 FIRST/FOLLOW 集用集合运算代替人肉枚举对中等规模文法如含 if/while/assign 的小语言手动计算 FIRST/FOLLOW 极易出错。我习惯用 Python 写一个通用计算脚本输入文法字符串输出各非终结符的 FIRST 和 FOLLOW 集from collections import defaultdict, deque def compute_first_sets(grammar): grammar: dict, keynonterminal, valuelist of productions (each prod is list of symbols) Returns: dict mapping nonterminal - set of terminals first defaultdict(set) # 初始化终结符的 FIRST 就是自身 for nt in grammar: first[nt] set() # 终结符不参与计算假设已知所有终结符集合 terminals {id, num, , *, (, ), ;, if, then, else, while, do, $} changed True while changed: changed False for nt, productions in grammar.items(): for prod in productions: # 对每个产生式计算其 FIRST i 0 while i len(prod): sym prod[i] if sym in terminals: # 终结符加入 FIRST if sym not in first[nt]: first[nt].add(sym) changed True break elif sym in grammar: # 非终结符 # 加入 FIRST(sym)若 ε 在 FIRST(sym) 中则继续下一个符号 before len(first[nt]) first[nt].update(first[sym] - {ε}) if ε not in first[sym]: break i 1 else: break else: # 所有符号都能推出 ε加入 ε if ε not in first[nt]: first[nt].add(ε) changed True return first def compute_follow_sets(grammar, first_sets, start_symbolS): follow defaultdict(set) follow[start_symbol].add($) # 开始符号 FOLLOW 包含 $ changed True while changed: changed False for nt, productions in grammar.items(): for prod in productions: for i, sym in enumerate(prod): if sym in grammar: # sym 是非终结符 # 查找 sym 后的符号串 β beta prod[i1:] if beta: # FIRST(β) 中非 ε 元素加入 FOLLOW(sym) first_beta set() j 0 while j len(beta): b beta[j] if b in first_sets: first_beta.update(first_sets[b] - {ε}) if ε not in first_sets[b]: break else: first_beta.add(b) break j 1 else: # β 可推出 ε则 FOLLOW(nt) 加入 FOLLOW(sym) first_beta.discard(ε) if ε in first_sets.get(beta[0], set()) if beta else False: pass # 已处理 before len(follow[sym]) follow[sym].update(first_beta) if len(follow[sym]) before: changed True else: # β 为空FOLLOW(nt) 加入 FOLLOW(sym) before len(follow[sym]) follow[sym].update(follow[nt]) if len(follow[sym]) before: changed True return follow参数说明grammar输入为字典如{E: [[T, E\]], E\: [[, T, E\], [ε]]}first_sets是上一步compute_first_sets()的输出start_symbol指定文法起始符号默认S脚本自动处理 ε 推导链和 FOLLOW 传播避免人肉漏算。3.2 构造 LL(1) 分析表二维字典 冲突检测有了 FIRST/FOLLOW分析表M[A, a]就是对每个产生式A → α将α填入M[A, b]其中b ∈ FIRST(α)若α ⇒* ε则将α填入M[A, b]其中b ∈ FOLLOW(A)。def build_ll1_table(grammar, first_sets, follow_sets): table defaultdict(dict) for A, productions in grammar.items(): for prod in productions: # 计算 prod 的 FIRST first_prod set() i 0 while i len(prod): sym prod[i] if sym in first_sets: first_prod.update(first_sets[sym] - {ε}) if ε not in first_sets[sym]: break else: first_prod.add(sym) break i 1 else: # prod 可推出 ε first_prod.discard(ε) first_prod.update(follow_sets[A]) # 填表 for a in first_prod: if a in table[A]: print(fCONFLICT: M[{A}, {a}] already has {table[A][a]}, now adding {prod}) table[A][a] prod return dict(table) # 示例文法简化版 grammar { E: [[T, E\]], E\: [[, T, E\], [ε]], T: [[F, T\]], T\: [[*, F, T\], [ε]], F: [[(, E, )], [id]] } first compute_first_sets(grammar) follow compute_follow_sets(grammar, first, E) table build_ll1_table(grammar, first, follow) print(LL(1) Parsing Table:) for nt, row in table.items(): for t, prod in row.items(): print(fM[{nt}, {t}] {prod})输出示例M[E, id] [T, E] M[E, (] [T, E] M[E, ] [, T, E] M[E, )] [ε] M[E, $] [ε] ...关键点build_ll1_table()中的CONFLICT打印是血泪经验——只要出现冲突文法就不满足 LL(1)必须重构表结构用defaultdict(dict)访问table[E][]直接得产生式无需额外索引实际工程中此表可序列化为 JSON 存储实现文法与解析器逻辑分离。3.3 表驱动解析器栈查表三步完成一次规约表驱动解析器极度简洁一个符号栈、一个输入指针、一个查表动作。它不关心文法语义只忠实执行M[A, a]指令。class LL1Parser: def __init__(self, table, start_symbolE): self.table table self.start_symbol start_symbol def parse(self, tokens): tokens: list of (type, value) tuples, ending with (EOF, ) Returns: AST root node, or raises SyntaxError stack [self.start_symbol] pos 0 ast_nodes [] # 用于构建 AST此处简化为记录规约序列 while stack: top stack.pop() if top in self.table and pos len(tokens): a tokens[pos][0] # 当前输入符号类型 if a in self.table[top]: prod self.table[top][a] # 将产生式逆序压栈因栈是 LIFO for symbol in reversed(prod): if symbol ! ε: stack.append(symbol) # 记录本次规约top prod ast_nodes.append({rule: f{top} → { .join(prod)}, children: []}) else: raise SyntaxError(fNo entry in table for M[{top}, {a}] at position {pos}) elif top in (id, num, , *, (, )): # 终结符 if pos len(tokens): raise SyntaxError(fUnexpected end of input, expected {top}) if tokens[pos][0] top: pos 1 else: raise SyntaxError(fExpected {top}, got {tokens[pos][0]} at {pos}) else: raise SyntaxError(fUnknown symbol {top} on stack) if pos ! len(tokens) - 1: # tokens 末尾是 EOF应已消耗完 raise SyntaxError(fInput not fully consumed, remaining: {tokens[pos:]}) return {type: Root, children: ast_nodes} # 使用示例 tokens [(id, a), (OP_ADD, ), (id, b), (EOF, )] parser LL1Parser(table, E) try: result parser.parse(tokens) print(LL(1) parse success:, result) except SyntaxError as e: print(LL(1) error:, e)逻辑说明stack初始为[start_symbol]代表待展开的最左推导pop()得top若为非终结符查表得产生式reversed()后压栈模拟推导若top为终结符直接与tokens[pos]匹配成功则postokens必须以(EOF, )结尾确保输入耗尽判断准确此版本 AST 构建极简实际项目中可在每个prod规约时注入语义动作函数。4. 避坑指南语法分析实验里 5 个高频翻车现场与硬核解法写语法分析器不是写 Hello World90% 的时间花在调试和修复隐性缺陷上。以下是我在带学生、做内部 DSL 工具时反复踩过的坑每一条都附带现象、根因和可立即复用的解法。4.1 现象SyntaxError: Expected ID, got —— 词法扫描器吞掉了关键 Token原因Lexer 的正则顺序错误导致被更长的模式如提前匹配或空白处理逻辑遗漏使前后存在未跳过的\n导致current_token滞后。解法在Lexer.__init__()末尾加断言assert self.current_token[0] ! WS确保首个 Token 不是空白为每个 Token 类型添加长度测试print(f[DEBUG] Matched {kind}: {value} at {mo.start()}-{mo.end()})终极验证写一个dump_tokens(text)函数输出所有 Token 的(type, value, start_pos, end_pos)肉眼检查是否连续无间隙。4.2 现象递归下降 parser 在a b * c处优先级错乱生成(a b) * c的 AST原因文法未正确体现运算符结合性。E → E T | T消除左递归后若E的调用位置不对如在parse_T()内而非parse_E()内会导致乘法节点被挂到加法节点下。解法严格遵循文法映射E处理T处理*F处理原子在parse_E()中必须先parse_T()再parse_Eprime()AST 验证技巧对输入a b * c打印最终 AST 的json.dumps(ast, indent2)检查*节点是否在的children[1]即右子树内而非同级。4.3 现象LL(1) 表构造脚本输出CONFLICT但文法看起来“应该没问题”原因FIRST集计算未考虑间接 ε 推导。例如A → B C,B → ε,C → d则FIRST(A)应含d但脚本若未递归传播B的 ε就会漏算。解法修改compute_first_sets()增加迭代轮次计数直到first集不再变化添加 debug 输出print(fRound {round}: {first})观察 ε 如何逐层传播人工验证法对每个含 ε 的非终结符手动列出所有能由它推出的终结符序列与脚本输出比对。4.4 现象表驱动 parser 报No entry in table for M[T, id]但文法明明有T → F T原因FIRST(F)未正确计算。若F → id | ( E )则FIRST(F) {id, (}但若FIRST脚本漏了id因将其视为终结符未纳入计算M[T, id]就为空。解法在compute_first_sets()开头显式声明terminals {id, num, , ...}并确保所有终结符都在此集合为F单独运行print(FIRST(F) , first[F])确认输出为{id, (}防御性编程在build_ll1_table()中对每个prod打印其first_prod确认id在其中。4.5 现象错误恢复失效一个;写成:后后续所有行都报错原因eat()函数过于刚性遇到错误直接抛异常未提供跳过非法 Token 并同步到下一个合法 Token 的机制。解法在Parser类中添加sync_to(tokens)方法传入预期 Token 类型列表如[;, }, )]向前扫描直到匹配任一修改eat()为eat_or_sync(expected_types, sync_tokens)失败时自动同步最小可行恢复在parse_Eprime()开头加if self.current_token[0] not in [OP_ADD, RPAREN, EOF]: self.sync_to([OP_ADD, RPAREN, EOF])。5. 从实验报告到生产级工具三个可立即落地的进阶技巧语法分析实验的价值绝不止于交一份 PDF 报告。当你把parse_E()跑通那一刻你就已经拥有了一个可插拔的语法解析内核。下面这三个技巧是我把课堂代码真正用进项目里的血泪经验每个都经过至少三个不同场景验证。5.1 技巧一用 AST 节点绑定语义动作零成本实现计算器实验报告常止步于生成 AST但 AST 本身就是执行指令的蓝图。无需额外解释器直接在parse_F()返回节点时计算值在parse_Eprime()中执行加法def parse_F(self): if self.current_token[0] LPAREN: self.eat(LPAREN) val self.parse_E() self.eat(RPAREN) return val # val 是数值不是节点 elif self.current_token[0] NUMBER: val float(self.current_token[1]) self.eat(NUMBER) return val elif self.current_token[0] ID: # 这里可查变量表实验中暂返回 0 self.eat(ID) return 0 def parse_E(self): left self.parse_T() return self.parse_Eprime(left) def parse_Eprime(self, left): if self.current_token[0] OP_ADD: self.eat(OP_ADD) right self.parse_T() return left right else: return left # 测试 code 2 3 * 4 lexer Lexer(code) parser Parser(lexer) result parser.parse_E() print(Result:, result) # 输出 14.0关键点parse_*函数返回值类型从dict切换为float/intAST 隐式构建在调用栈中语义动作与语法结构严格对齐Eprime的动作就是left right此模式可无缝扩展ID返回变量值parse_Assign()更新变量表parse_If()控制执行流。5.2 技巧二用 Python AST 模块反向生成可执行代码验证解析正确性手写 parser 容易在细节上出错如括号匹配、运算符结合性。一个硬核验证法把你的 parser 输出的 AST用ast.unparse()转回 Python 代码再用compile()执行对比结果。import ast # 假设你的 parser 返回的是标准 Python AST 节点如 ast.BinOp def ast_to_python_code(node): 将自定义 AST 转为 Python AST再 unparse # 示例将 {type: BinOp, op: , left: ..., right: ...} 映射为 ast.BinOp if node[type] BinOp: left ast_to_python_code(node[left]) right ast_to_python_code(node[right]) op ast.Add() if node[op] else ast.Mult() return ast.BinOp(leftleft, opop, rightright) elif node[type] Num: return ast.Constant(valuenode[value]) elif node[type] Name: return ast.Name(idnode[id], ctxast.Load()) # ... 其他类型 return ast.Constant(value0) # 验证流程 my_ast parser.parse_E() # 你的 parser 输出 py_ast ast_to_python_code(my_ast) code_str ast.unparse(py_ast) print(Generated Python code:, code_str) # e.g., (2 3) * 4 compiled compile(code_str, string, eval) result eval(compiled)价值ast.unparse()是 Python 3.9 的官方工具生成的代码可直接执行是黄金验证标准若code_str与原始输入语义不等价如ab*c变成a(b*c)说明你的 AST 构建逻辑有偏差此技巧可自动化对测试集批量生成、编译、执行覆盖率远超手工检查。5.3 技巧三把 parser 封装为 CLI 工具支持-c和文件输入实验报告代码往往锁在.py文件里但真正的工程价值在于可复用。用argparse封装让它像grep一样工作import argparse import sys def main(): parser argparse.ArgumentParser(descriptionSimple expression parser) parser.add_argument(-c, --code, helpExpression to parse (e.g., 2 3 * 4)) parser.add_argument(file, nargs?, helpInput file path (if not using -c)) parser.add_argument(--ast, actionstore_true, helpPrint AST instead of result) args parser.parse_args() if args.code: text args.code elif args.file: with open(args.file) as f: text f.read() else: text sys.stdin.read() try: lexer Lexer(text) parser_obj Parser(lexer) if args.ast: ast parser_obj.parse_E() import json print(json.dumps(ast, indent2)) else: result parser_obj.parse_E() # 假设已改造为计算模式 print(result) except SyntaxError as e: print(fError: {e}, filesys.stderr) sys.exit(1) if __name__ __main__: main()使用示例# 直接计算 $ python parser.py -c 10 / 2 - 3 2.0 # 解析文件并输出 AST $ echo a * (b c) expr.txt $ python parser.py --ast expr.txt # 管道输入 $ echo 2 ** 3 | python parser.py 8.0工程意义CLI 是模块化的第一道门槛别人pip install my-parser后就能my-parser -c ...--ast开关让调试可视化比print(ast)更清晰stdin 支持让工具可融入 shell pipeline这是 DevOps 场景的刚需。我带过的实习生第一个月的任务就是把课堂 parser 改造成这样的 CLI。三个月后他用同一套解析器给公司内部的配置校验系统写了 DSL 支持——不是重写只是换了 Token 定义和语义动作。语法分析实验报告里的每一行代码都不是终点而是你亲手锻造的第一把编译器刻刀。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

面向接口编程与Spring依赖注入,解耦iPad协议SDK的实战设计

面向接口编程与Spring依赖注入,解耦iPad协议SDK的实战设计

做“个微iPad协议对接”这种后端服务的兄弟,大概率见过一个灾难现场:业务代码里到处是iPadClient对象,SDK一改,全工程跟着跳。我前几年维护过一套消息中台,就吃过这个亏,后来把整个对接层重写成面向接口编程…

2026/10/10 20:43:28 阅读更多 →
Flutter个人理财App分类列表页面开发实战:从数据模型到真机调试

Flutter个人理财App分类列表页面开发实战:从数据模型到真机调试

最近在做一款基于 Flutter 的个人理财管理 App,正好移植到某开源操作系统平台上。做了一圈下来,最让我意外的不是复杂图表,也不是多端适配,而是一个看起来毫不起眼的“分类列表页面”——就是账本里那一行行支出分类的展示列表。这…

2026/10/10 20:42:27 阅读更多 →
微信小程序健身房预约系统:数据库设计、接口开发与部署全攻略

微信小程序健身房预约系统:数据库设计、接口开发与部署全攻略

这个选题我前后带过不少同学做过,今天聊点实在的。先亮个底:基于微信小程序的健身房预约系统,在整个课设和毕设题库里属于性价比很高的一类项目,麻雀虽小但五脏俱全——前端要处理小程序登录、页面交互、状态管理;后端…

2026/10/10 20:42:27 阅读更多 →

最新新闻

把安全测试嵌进自动化流水线:DevSecOps落地实战

把安全测试嵌进自动化流水线:DevSecOps落地实战

"自动化测试"这四个字,大部分团队每天在跑;"安全测试"这四个字,大部分团队只在上线前才想起来。把它们真正揉进同一条流水线,让它跟着每次构建、每次提测自动执行,这就是DevSecOps要解决的核心问题…

2026/10/10 22:10:59 阅读更多 →
mir_client.rar源码包编译与M2引擎联调避坑指南

mir_client.rar源码包编译与M2引擎联调避坑指南

简介:一份面向Mir系列游戏M2客户端研究的C源码包,旨在帮助中高级C开发者以及游戏引擎学习者,拆解早期网游客户端的核心实现与模块组织方式。压缩包共187个文件,以87个.h头文件和78个.cpp源文件为主,同时带有工程配置、…

2026/10/10 22:10:59 阅读更多 →
H5手机相机拍照上传全攻略:capture与getUserMedia选型及实现

H5手机相机拍照上传全攻略:capture与getUserMedia选型及实现

简介:面向需要实现手机相机拍照并上传照片到后台的HTML5开发者,压缩包内含完整可运行的示例代码与配套资料。资源共22个文件、3.18MB,主要包含HTML页面、JavaScript脚本、PHP后台处理脚本,以及jpg/png演示截图、txt操作笔记和url参…

2026/10/10 22:10:59 阅读更多 →
技术科学:连接基础科学与工程技术的桥梁

技术科学:连接基础科学与工程技术的桥梁

不知道你有没有这种经历:在某个行业聚会上听到“技术科学”四个字,总觉得哪里见过,真要解释又开不了口。我最近在准备一个科普视频脚本,题目就叫《究竟什么是技术科学》。说实话,刚拿到这个题目时我也没太当回事&#…

2026/10/10 22:10:59 阅读更多 →
轻量级监控平台coolmonitor:Docker部署与告警配置实战

轻量级监控平台coolmonitor:Docker部署与告警配置实战

最近好几个朋友拿着同样的需求来问我:手上有两三台服务器、一台家用NAS,想上线监控,但一打开Prometheus和Grafana的部署文档就头皮发麻。又是exporter又是Alertmanager又是datasource,还没开始采数据,先被一堆名词劝退…

2026/10/10 22:10:59 阅读更多 →
论文降AI率实用指南:从检测原理到9款工具选型与改稿实操

论文降AI率实用指南:从检测原理到9款工具选型与改稿实操

论文查重过了,却被AIGC检测拦下,可能是近两年本科生最焦虑的时刻之一。你辛辛苦苦用AI辅助整理文献、润色语句,结果学校一查AI率,直接给你标了个40%,导师那边的解释还没想好措辞。我自己见过太多这种情况,也…

2026/10/10 22:09:58 阅读更多 →

日新闻

卫星轨道分类全解析:从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 阅读更多 →