简介这份GDUT编译原理课程实验与课程设计资料包面向高校计算机专业正在学习编译原理的学生也适合自学编译器构造的开发者参考以广东工业大学课程实践为背景完整呈现词法分析、语法分析、语义分析、代码生成等编译器核心阶段的实验与设计实现帮助读者把抽象语法理论落到具体编码与调试中。包内共131个文件压缩包大小约3.01MB主要文件类型包括png截图、PL0源程序、java/cpp源代码、class编译结果以及md说明文档等png截图展示词法/语法分析过程的可视化结果pl0文件对应教学语言PL0的定义与实验版本java/cpp代码覆盖解释器与编译器的实现class为编译产物md报告记录实验步骤、排错心得与反思。目前已有500人学习下载通过研读这些示例和报告读者可以直观理解PL0语言从源码到目标代码的完整转换流程掌握递归下降解析、符号表管理等关键技术并借鉴课程设计的组织方式与排错思路为独立完成编译原理大作业提供扎实参考。1. 拿到这份 GDUT 编译原理实验包之后先搞清楚它值不值得学如果你正在修编译原理大概率见过这么一份叫“GDUT 编译原理课程的课内实验和课程设计含报告.zip”的压缩包里面是若干课内实验的源码、文档外加一份或几份课程设计报告。它解决的真实痛点不是“给你答案让你抄”而是把教材里 300 页的编译原理压缩到三个能动手的环节——词法分析、语法分析、中间代码生成再让你把三者串成一个能跑的课程设计。这个方向值不值得做答案很直接编译原理是少数几个“实验做完就能理解一门语言核心机制”的课而且这份课程设计质量直接影响学分绩排名。这篇文章我会从 zip 解压开始到报告提交为止把每层怎么搭、参数怎么调、坑在哪讲清楚保证新手能跟进熟手能直接拿走几个排查思路。2. 从 zip 到能跑的实验环境解压乱码、目录结构盲区与工具链选型2.1 解压就翻车GBK 编码乱码与 zip 损坏的两种表现拿到这个 zip很多人第一步就卡在解压。你用的操作系统如果是英文版或者某些精简版 Linux解压后文件名全是乱码常见表现是“ʵһ.docx”这种带问号的文件名。这不是 zip 文件损坏而是压缩包里的文件名用的是 GBK 编码而解压工具默认按 UTF-8 解释。Linux 下处理方式很简单# 用 unzip 的 -O 指定文件名字符集 unzip -O cp936 GDUT 编译原理课程的课内实验和课程设计含报告.zip -d gdut_compiler参数说明-O cp936是让 unzip 按 GBK/CP936 编码解码文件名-d gdut_compiler指定解压到独立目录避免和后续生成文件混在一起。如果你用的是 Python也可以用zipfile模块逐个文件读取再把文件名从gbk转utf-8重命名但那只适合文件数量少的情况。还有一种翻车现象是解压中途报“End-of-central-directory signature not found”这个属于 zip 文件本身损坏通常是传输中断或复制过程中被截断别浪费时间修直接重新获取一份。Windows 下如果你用的是 7-Zip右键解压时选择“使用文件名编码 → ANSI(GBK)”也行WinRAR 新版会自动识别大多数 GBK 文件名所以这问题主要集中在 Linux 和 macOS 终端。2.2 压缩包解压后的目录长什么样实验序号与报告命名规律GDUT 这门课的实验包一般按“课内实验 课程设计”两块组织。最常见的实验目录结构是三个独立 lab加一个 course_design 目录课内实验分别是词法分析、语法分析LL(1) 或 LR(1) 分析表、语义分析与四元式生成课程设计是一个简化版 C 语言编译器目标代码可以输出到虚拟机上解释执行也可以直接输出汇编。目录名有时是中文“实验一”、“实验二”有时是英文 “lab1”、“lab2”报告通常以 Word 或 PDF 格式存在根目录或 report 子目录。目录名核心内容对应知识点lab1/lexical词法分析程序正则表达式、DFA 最小化lab2/parser语法分析程序LL(1)、FIRST/FOLLOW 集lab3/semantic语义分析与四元式属性文法、中间代码生成course_design综合编译器符号表、代码生成report/实验报告与设计报告文档与过程记录拿到包第一时间不是打开代码而是先建立目录映射把每个目录对应到教材哪一章。因为很多实验源码是半成品需要你补齐函数才能跑你得知道哪个文件是主体、哪个是补全入口否则后面每个实验都像在黑匣子里翻箱倒柜。报告文件我一般建议复制一份保留原始版本因为报告里的格式和图表可以作为你后续写自己报告的排版参考但也仅限于排版参考内容上尽量不要直接复用后面避坑章节会细说。2.3 工具链选型用 flex/bison 还是手写递归下降决定你后面省不省力工具链选型是这个实验包第一个分水岭。GDUT 的课内实验通常允许两种路线一是用 flex 和 bison 这种生成器二是纯手写。我的建议是课内实验用手写、课程设计用生成器辅助理由如下选型优点缺点适用场景flex bison正则到 DFA 自动生成、语法规则声明式描述生成的 C 代码可读性差、调试需要理解生成逻辑课程设计综合项目纯手写词法分析完全可控、便于展示状态转移和算法细节代码量大、容易漏掉边界字符课内实验一递归下降手写语法分析直观、报错信息友好、不需要额外工具必须手动消除左递归课内实验二、课程设计前端JavaCC / ANTLR跨平台、有 IDE 插件引入外部依赖、报告里难解释原理不推荐本门课使用如果实验环境是 Linux安装 flex/bison 只要一条命令如果是 Windows可以装 WSL 或在本地用 win_flex/bison。这里有个热词叫“java编译原理”很多人的课程设计是用 Java 写的如果你也打算用 Java注意 flex 生成的 C 代码是不能直接用的要么换成 JFlex CUP要么课程设计整体用手写路线。我一般会更推荐 C 路线因为报告里贴状态转换图、符号表结构、四元式格式时C 的实现更贴近教科书老师看着更好给分。# Ubuntu 下安装生成器工具链 sudo apt-get update sudo apt-get install -y flex bison build-essential # 验证 flex 版本版本号 2.6.4 及以上都能满足本课需求 flex --version bison --version参数说明flex --version用于确认 flex 可用如果系统提示找不到命令说明没安装成功或 PATH 没配置。flex 版本比较老旧不影响实验——2.5.3 到 2.6.4 之间的变化主要集中在新语法特性上课内实验用到的经典正则写法完全兼容没必要追新。3. 课内实验落地词法分析的状态表怎么写、语法分析表怎么算才不被扣分3.1 词法分析的手写实现正则→DFA 的状态转移表是核心产物课内实验一通常是“设计一个识别 C 语言子集的词法分析程序”。常见要求是识别关键字、标识符、无符号整数、运算符和分隔符遇到无法识别的字符要报错。最快且最可控的实现方式是手写状态转移表把识别数字、标识符、运算符的过程各画成一个小 DFA再并成一个总表。状态转移表可以用二维数组存储行是状态编号列是输入字符分类值是跳转状态或动作编号。下方是简化后只识别关键字、整数和标识符的核心逻辑用 C 语言描述// 简化版词法分析识别关键字、整数、标识符 typedef enum { ST_START, ST_ID, ST_NUM, ST_DONE, ST_ERR } State; int classify(char ch) { if (isalpha(ch) || ch _) return 0; // 标识符字符 if (isdigit(ch)) return 1; // 数字字符 if (isspace(ch)) return 2; // 空白字符 return 3; // 其他字符运算符、分隔符 } State get_token(FILE *fp, char *buf, int *token_type) { int i 0; State st ST_START; int c; while ((c fgetc(fp)) ! EOF) { int cls classify(c); if (st ST_START) { if (cls 0) st ST_ID; else if (cls 1) st ST_NUM; else if (cls 2) continue; // 跳过前导空白 else { buf[0] c; buf[1] \0; return ST_DONE; } } else if (st ST_ID) { if (cls 0 || cls 1) buf[i] c; else { ungetc(c, fp); buf[i] \0; return ST_DONE; } } else if (st ST_NUM) { if (cls 1) buf[i] c; else { ungetc(c, fp); buf[i] \0; return ST_DONE; } } } return st; }逻辑说明classify把输入字符规约为 4 类这样状态转移表就能用 4 列覆盖所有字符不需要为每个 ASCII 码建列。get_token逐个读字符、按当前状态转移读多了一个字符时用ungetc回退给下一次调用。这里有个实验中很容易被问到的问题为什么数字后面跟字母不能识别成“数字标识符”比如123abc必须要报错或识别为非法词素因为 C 语言规范里数字后面直接跟字母不是合法词法单元。状态表体现这一点的写法是数字状态在遇到字母时跳转到错误状态而不是结束当前 token。参数说明token_type在完整实现里应该作为返回值区分关键字、整数、标识符、运算符。关键字识别有两个惯用做法一是把这些关键字字符串硬编码成一张表读出标识符后查表命中就是关键字二是在构建 DFA 时为每个关键字单独建起始状态但这会让状态表膨胀不推荐。实际验收时老师重点会看两点一是测试用例里包含3.14这样的浮点数时你的程序怎么处理二是注释//和/* */能不能正确跳过。浮点处理建议直接读到.再判定当前状态如果是数字状态就继续读小数部分否则按非法字符报错没必要一开始就为浮点数单独建状态。3.2 语法分析的 LL(1) 路线FIRST 集和 FOLLOW 集计算是必考扣分点实验二如果要求 LL(1) 分析你要产出三个东西FIRST 集、FOLLOW 集、预测分析表。我见过大量报告在这部分把 FIRST 和 FOLLOW 直接抄书上的示例这就导致一个结果老师随便换一个文法你程序里的表就对不上了。FIRST 集计算顺序是先处理所有终结符FIRST 就是自己再反复迭代非终结符直到集合不增长。FOLLOW 集计算重点是三条规则开始符号的 FOLLOW 要包含$A→αBβ 时 FOLLOW(B) 并入 FIRST(β)排除 εA→αB 或 A→αBβ 且 β→ε 时 FOLLOW(B) 并入 FOLLOW(A)。这三条规则报告里写不写得全直接决定步骤分。预测分析表构造是按行填对每条产生式 A→α对 a 属于 FIRST(α)把该产生式填入 M[A][a]如果 α 能推导出 ε则对 b 属于 FOLLOW(A)也把该产生式填入 M[A][b]这类是同步/报错条目。递归下降实现比驱动表实现简单得多考虑通用的做法是每个非终结符写一个函数遇到终结符就匹配遇到非终结符就调用对应函数。下面是一个消除左递归后的表达式文法递归下降片段// 文法E-TE, E-TE|ε, T-FT, T-*FT|ε, F-(E)|id void E(void) { T(); E_prime(); } void E_prime(void) { if (lookahead ) { match(); T(); E_prime(); // E - TE } // 否则 lookahead 属于 FOLLOW(E)直接返回对应 ε 产生式 } void T(void) { F(); T_prime(); } void F(void) { if (lookahead id) match(id); else if (lookahead () { match((); E(); match()); } else error(非法字符); }逻辑说明E先调T再调E_primeE_prime判断 lookahead 是不是是就继续推导否则默认走 ε 产生式返回。这个“默认返回”就是 LL(1) 里用 FOLLOW 集做同步的依据代码里没有显式写 FOLLOW 集但逻辑上等价。报告中需要把lookahead的维护讲清楚每次match成功就读取下一个 token提前读字符也体现在match函数里。这里有个血泪经验递归下降最隐蔽的坑是忘记处理空产生式不读取输入的情况导致死循环。E_prime在 lookahead 不是时绝不能去读下一个 token必须原样返回否则会把属于外层终结符的 token 吞掉——一般加个计数器或打印 trace 就能发现。3.3 语法分析的 SLR(1) 路线项目集族构造中的移进-归约冲突处理如果你选的实验包用 SLR或 LR(1)分析表代码实现就变成三部分项目集族或 LR(0) 状态构造、ACTION/GOTO 表生成、驱动器表驱动执行。构造项目集族时闭包函数closure和转换函数goto是核心。很多人的翻车点在“读入终结符还是非终结符时查的是 action 还是 goto 表”LR 分析驱动器规则其实很简单栈顶状态查 ACTION 表按终结符决定移进或归约按非终结符跳转查 GOTO 表。驱动器核心逻辑如下# 简化的 LR 分析驱动器栈存状态编号和符号 def run_analyzer(input_tokens, ACTION, GOTO): stack [0] # 初始状态压栈 input_pos 0 while True: state stack[-1] # 栈顶状态 token input_tokens[input_pos] act ACTION[state].get(token) # 查 ACTION 表 if act is None: raise Exception(f语法错误: 状态{state}遇到{token}) if act[0] shift: # 移进符号与状态压栈 stack.append(token) stack.append(act[1]) input_pos 1 elif act[0] reduce: # 归约按产生式弹栈 lhs, rhs_len act[1] for _ in range(rhs_len * 2): stack.pop() prev_state stack[-1] # 弹出后栈顶状态 stack.append(lhs) stack.append(GOTO[prev_state][lhs]) elif act[0] accept: return True逻辑说明栈里每两个元素是一对符号状态所以按产生式右部长度弹栈时右部有 n 个符号就要弹出 2n 个元素。归约完成后查 GOTO 表把左部非终结符和新状态压栈。这里最容易错的是把 GOTO 表也当成 ACTION 表一起查导致读到非终结符时报错。参数说明ACTION[state][token]返回三元组第一个字串指定动作类型如果想做错误恢复而不是一遇错就崩可以另外加一个 panic-mode 恢复表遇到非法 token 时连续弹栈直到能继续不过课内实验一般不要求到这一步报告里提一句“本程序采用恐慌模式恢复”反而能加分。3.4 中间代码生成的课内实验三地址码与四元式的命名规范语义分析课内实验通常以“把表达式翻译成四元式或三元式、逆波兰式”形式出现。四元式结构是 (op, arg1, arg2, result)result 通常是一个新临时变量名。如果你拿到的实验包是生成“中间代码到文件”需要明确临时变量编号规则例如t1、t2……以及跳转语句的标号管理。下面用一段生成四元式的思路说明重点在于每次翻译需要新临时变量时怎么申请编号// 四元式生成辅助函数申请新临时变量 char *new_temp(void) { static int temp_id 0; char *name (char*)malloc(16); sprintf(name, t%d, temp_id); return name; } void emit_quad(char *op, char *arg1, char *arg2, char *result) { Quad *q new_quad(); // 打开四元式表 q-op op; q-arg1 arg1; q-arg2 arg2; q-result result; append_quad(q); }参数说明new_temp里的temp_id是静态变量整个编译过程只递增不重置保证每个临时变量唯一。如果课程设计里有循环跳转语句的四元式标号要单独管理不能和临时变量编号混用。report 中附四元式输出样例时最好用一个含ab*c-d/e的表达式展示运算符优先级是怎么在生成顺序中体现的先算b*c生成 t1再算d/e生成 t2再算at1最后-t2。4. 课程设计从词法到解释器的完整架构与文件划分4.1 课程设计的总体架构前端与后端分离课程设计是课内实验的综合体常见题目是“设计一个支持变量声明、赋值、算术运算、if-else 和 while 循环的简化语言编译器并输出在虚拟机上执行的中间代码”。总架构建议按五个模块切分词法分析模块把源码转成 token 流语法分析模块递归下降构建语法树或直接在分析过程中生成代码符号表模块维护变量名到类型、地址的映射中间代码生成模块输出四元式序列解释器模块逐条执行四元式并输出变量值。模块之间用接口解耦例如 SymbolTable 结构体独立成一个文件词法和语法模块都通过接口查找变量。我见过不少人把符号表设计成全局二维数组课程设计规模小的时候可以跑但报告里解释设计合理性会吃亏因为没法讲清作用域。4.2 符号表变量名查找的作用域陷阱符号表的作用域处理是这个课程设计最容易翻车的地方。典型需求是支持块级作用域进入{压栈新一层符号表遇到变量声明写入当前层变量查找从当前层逐层向上找出了}弹栈。实现上常见做法是用链表表示作用域栈每个节点是一个哈希表。如果你选择更简单的“不区分作用域”方案也能完成基本功能但条件分支里声明同名变量会直接互相覆盖演示时容易被老师抓到。下面是一个查找变量的实现从内层往外层逐层找。// 符号表用链表模拟作用域栈head 指向当前最内层 Symbol *lookup(const char *name) { Scope *s head; while (s) { Symbol *sym find_in_scope(s, name); // 在当前作用域哈希表查找 if (sym) return sym; s s-outer; // 没找到往外层作用域找 } return NULL; // 所有层都没找到 }逻辑说明Scope是一个作用域节点成员outer指向上层作用域。进入新块时新建 Scope 并令head new_scope退出块时head head-outer。这个结构在报告里解释清楚能直接体现你理解静态作用域。这里还要提一个坑在语法分析递归下降过程中何时执行符号表压栈、弹栈必须和语义动作绑定。常见的错误是编译器先完整构造语法树再做语义分析这时作用域栈的压弹时机好控制但如果你一边语法分析一边生成四元式和维护符号表就一定要在设计递归下降函数时明确在哪个函数进入时压栈、哪个退出时弹栈否则稍复杂一点的嵌套代码就会出现变量解析错误。4.3 从语法树到四元式序列的翻译模式中间代码生成的关键不是代码量而是翻译模式写得规则化。设计好的输出结构能大幅降低后续解释器负担四元式采用以下格式op取值有 - * / j j j jj表示无条件跳转j、j、j表示条件跳转result对跳转语句来说是目标标号。if-else 和 while 的翻译要提前创建标号。下面展示 if-else 的代码生成思路// 生成 if-else 的四元式 void gen_if(Expr *cond, Stmt *then_body, Stmt *else_body) { int label_else new_label(); // 分配“进入else”的标号 int label_end new_label(); // 分配“结束if”的标号 gen_condition_jump(cond, label_else); // 条件为假跳 else gen_stmt(then_body); // 生成 then 分支代码 emit_quad(j, label_end, NULL, NULL); // then 结束跳到最后 emit_label(label_else); // else 入口 gen_stmt(else_body); emit_label(label_end); // 最后出口 }逻辑说明gen_condition_jump需要把a b这类条件翻译成四元式j a b label_else所以在生成条件代码时就要把操作数反转成四元式参数。设计时要确认j的语义是“第一个操作数小于第二个操作数时跳转”否则条件方向写反解释器执行结果就反过来了。参数说明new_label单独维护一个计数器不能和临时变量共用一个计数器因为四元式里二者靠命名区分。4.4 解释器执行四元式验证生成的中间代码是否正确课程设计如果选择解释执行解释器是最容易拿分也最容易暴露问题的部分因为一旦四元式生成有错解释器输出立刻可见。解释器核心是一个循环逐条取出四元式并模拟执行。每个变量在符号表里有运行时值临时变量直接插入符号表。下面是核心执行循环void interpret(Quad *code, int n) { int pc 0; // 程序计数器指向待执行四元式 while (pc n) { Quad *q code[pc]; switch (op_code(q-op)) { case OP_ADD: set_var(q-result, get_var(q-arg1) get_var(q-arg2)); pc; break; case OP_JMP: pc q-result; break; case OP_JLT: // 如果 arg1 arg2 则跳转 pc (get_var(q-arg1) get_var(q-arg2)) ? q-result : pc 1; break; case OP_ASSIGN: set_var(q-result, get_var(q-arg1)); pc; break; } } }逻辑说明set_var和get_var底层都走符号表查找但set_var写的是当前作用域里那个变量临时变量由于不在用户源码里出现可以直接插入最内层作用域或全局作用域不影响语义。参数说明JLT的pc变化是“跳转 or 自增 1”写错这个分号不是小问题会直接造成死循环。报告里验收测试可以用一个计算 1 到 10 求和的 while 循环输出最终变量值如果输出是 55基本能确定词法、语法、语义三段都正确。5. 避坑与排查实验包源码、报告提交与运行时的高频雷区5.1 报告中的 FIRST/FOLLOW 集表格前后不一致现象报告正文里 FIRST 集表格和程序里实际生成的 FIRST 集不一致老师只抽查一个非终结符就对不上。原因报告是从前人模板复制后改了文法但集合没按新文法重新计算程序是按另一个版本写法写的两边就分叉了。解决把程序和报告里的表格做成同一份数据——程序里加一个调试开关直接把计算结果输出成文本报告中表格贴这份输出保证无人工转录误差。5.2 zip 解压后源码带上 CRLF 行尾导致 flex 报错现象flex 编译词法定义文件时报 “Unrecognized character” 或者奇怪的\r错误gcc 编译时报 “stray ‘\r’ in program”。原因现有压缩包里文件来自 Windows 环境用 dos2unix 前所有行尾都是\r\n。解决解压后先对整个目录做行尾转换find . -name *.l -o -name *.y -o -name *.c | xargs sed -i s/\r$//注意这条命令会把目录下所有 C/flex/bison 源文件的行尾改成 Unix 格式做完后file命令显示 “ASCII text” 而不是 “with CRLF line terminators”。如果 a 文件本来就是 UTF-8 with BOMflex 可能对 BOM 也有意见用sed -i 1s/^\xEF\xBB\xBF//去掉即可这一步是跳过 BOM 的经典做法。5.3 flex 生成的分析器匹配到最长关键字相邻的标识符现象输入int main()时词法分析器把int识别成关键字没问题但输入intx时也识别成了关键字。原因flex 规则写成了int { return KEYWORD_INT; }而没有用词界int[^a-zA-Z0-9_]或int/[^a-zA-Z0-9_]。flex 默认最长匹配但intx整体也能匹配到标识符规则词法规则是[a-zA-Z_][a-zA-Z0-9_]*为什么还会错因为你可能把关键字的正则写成了太宽泛的“字符串字面量”匹配或者顺序放到了标识符规则后面。解决正确做法是关键词规则和标识符规则都要限定词界并保证标识符规则兜底flex 中写int { return KEYWORD_INT; } [a-zA-Z_][a-zA-Z0-9_]* { return IDENTIFIER; }解释两条规则在intx上做最长匹配[a-zA-Z_][a-zA-Z0-9_]*能吃掉intx全部 4 个字符而int只有 3 个字符所以最终匹配为标识符对纯int字符串关键字规则是完整匹配 3 个字符标识符规则也能匹配 3 个字符且长度相同此时 flex 优先选择前面的规则。结论是顺序和词界都要对缺一不可。5.4 递归下降分析器在空产生式上死循环现象程序在解析表达式后一直不返回CPU 占用 100%。原因某个非终结函数进入分支后没消费任何 token又直接递归调用自己形成无限递归。典型是含有左递归文法没消除干净例如E - E T | T直接照搬到递归下降就会死循环。解决先手工消除左递归和提取公共左因子再在parse_xxx函数的每个分支入口加printf跟踪日志用一个小输入看调用序列如果发现同一函数连续打印而输入位置没变就说明该分支没有消费 token属于逻辑错误而不是偶然死循环。5.5 课程设计里报告和代码版本不一致现象提交时报告描述的功能比代码多了一个“数组支持”但源码里根本没有数组相关的符号表结构。原因写报告时序在前完善代码时序在后或者反过来最后交错了版本。解决提交前tar或zip打包前只用一个目录报告放在代码同目录把 README 写清楚“代码目录结构、编译命令、测试样例”版本号用日期命名例如course_design_20250115.zip。同时把报告里描述的每个功能点划掉核对一遍报告说支持 while 就在源码里找到 while 的处理分支找不到就删掉报告里的对应段落这是最久的教训——报告叙述与源码行为不一致会被判抄袭或敷衍。6. 报告和演示的验收自查好的报告长什么样、演示怎么稳住报告读起来应该像一个工程师在记录自己修一个编译器的过程而不是习题答案的装订本。一个比较稳的报告结构按五块走需求分析你选择实现了哪些功能、支持哪种文法、设计符号表和四元式的数据结构定义、实现核心函数流程图加关键代码片段、测试3-4 组测试用例包含正常与异常码、总结遇到的问题和解决过程。测试部分是最容易被忽视也是对成绩影响最大的除了把 12*3 跑对还要放一个词法错误样例和一个语法错误样例证明你的错误处理是真实生效的不是只做了正确路径。演示环节有个高性价比习惯准备一组固定命令按顺序执行不现场临场发挥。具体做法是在 README 里写一个run.sh直接编译并运行验收用例演示时只敲bash run.sh。这里面有个第一人称的踩坑经验我当年有一次课程设计答辩现场演示前临时改了一个源文件结果别字被编译器当成新标识符直接报错后开才意识到演示用的应该是和提交一致的那个目录自己误导了自己。之后我都用一个 release 目录做演示版本开发目录另存一份做到源码一致性和演示前零修改。还有个小技巧报告里的四元式输出样例最好留一句“本程序在 Ubuntu 20.04 与 gcc 7.5 下测试通过”——环境和工具链版本写清楚老师自己复现时不会因环境不符来质疑结果。如果时间和精力允许建议把课内实验二和三的代码复用到课程设计里而不是课程设计时另起炉灶重写一个编译器。复用会让你对代码路径更熟报告里能写“课内实验的 LL(1) 分析器扩展了错误恢复后应用到课程设计的前端”这种连贯性体现出来的理解深度比堆一堆功能点更打动老师。最后再提醒一个细节提交前把压缩包重新打包成“学号_姓名_编译原理课程设计.zip”这种课所要求命名格式压缩包里面第一层不要套一层文件夹否则老师解压打开得点两层才看到代码。这个和代码本身质量无关的坑多到数不清顺手就能避免。希望这份从解压到提交的整个过程能帮到你至少让你在这些容易被扣分但完全不必要的地方少走一段弯路。如果你按这个思路把课内实验和课程设计串起来做你会发现编译原理这门课其实没那么多黑匣子每一步的输入输出都是能看见的。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?