☰
C语言词法分析器与语法分析器课设指南:从Token到语法树
2026/10/3 9:04:14 网站建设 项目流程

简介:面向编译原理课程设计的C语言词法分析器与C-语言语法分析器报告书,适合计算机相关专业学生、备考期末或课程设计答辩的读者参考。报告系统梳理C语言词法特点,涵盖保留字、符号、标识符、数字、字符与字符串等Token定义及Token类型代码,并给出基于正则表达式和DFA的完整词法分析器设计思路,状态转移与分类判断一目了然;同时说明语法分析器的工作原理,可帮助理解编译器前端从字符流到语法树的处理过程。资源为单一doc文档,共1个文件,体积379KB,内容包含DFA状态图、TokenType枚举、词法规则、界面与实现流程等关键细节,便于直接查阅、修改或用于报告扩展。已有274人学习下载,适合需要完成同类课程设计、撰写实验报告或快速掌握词法/语法分析要点的读者。

1. 拿到课设先别动手写代码:先想清楚这门“C语言词法分析器”到底要交什么

C语言词法分析器和C语言语法分析器编译原理课程设计报告书——这个标题看起来是“两个程序加一份文档”,实际上是一条完整的交付链条:字符流进词法分析器,出来 Token 流;Token 流进语法分析器,得出“合法或不合法”的结论。很多人第一步就跑偏,花两天从网上凑一个能跑的 C 语言词法分析器,结果报告里的流程图跟代码对不上,答辩时被一个问题问穿。这门课设真正考的不是“能不能跑通”,而是“能不能把一个字符串变成 Token,再把 Token 变成一棵语法树,并用文档把每一步解释得足够诚实”。下面按我自己做课设和帮别人改课设的路线来讲:先拆需求,再分别实现词法和语法,最后用测试用例表和文档把代码焊成一份能过答辩的报告。适合正在写这份课设、以及想把编译原理前端彻底搞懂的读者。

2. 词法分析器从字符流到 Token:用 C 语言写一个带缓冲区扫描的状态机

2.1 先定 Token 集合:词法分析器的输出协议

词法分析器要做的事,是把源代码这个长长的 C 语言字符串,切成一个个有意义的词法单元,也就是 Token。第一步不是写代码,而是先定义 Token 有哪些类型。这个类型表就是词法分析器和语法分析器之间的接口协议,语法分析器只认这个表里的东西。

typedef enum { TOK_IDENT, // 标识符,如 count TOK_NUMBER, // 整数常数,如 2024 TOK_KEYWORD, // 保留字,如 int、return TOK_OPERATOR, // 运算符,如 +、-、= TOK_DELIMITER, // 界符,如 ; ( ) { } TOK_EOF, // 输入结束 TOK_ERROR // 无法识别的字符 } TokenType;

Token 还要带上文本本身和所在行号。行号不是可选项,语法分析器报错时要指出第几行,报告里的测试截图也靠它。用结构体包装:

typedef struct { TokenType type; char text[128]; int line; } Token;

text 数组的长度关系到后面的缓冲区边界,先记住这个 128。Token 类型表和报告里的“详细设计”章节可以直接对应。画一张表格写进文档也行,但更重要的是让代码跟表对得上。

类型识别规则例子
TOK_IDENT字母或下划线开头,后跟字母/数字/下划线count, _tmp
TOK_NUMBER连续数字串2024, 0
TOK_KEYWORD标识符中属于保留字的那部分int, if, while, return
TOK_OPERATOR单字符运算符+ - * / = < >
TOK_DELIMITER分隔符; ( ) { }
TOK_ERROR其他不可识别字符@ # $

课程设计里通常不要求覆盖全部 C 语言运算符,单字符运算符加一两个双字符运算符(如 ==、<=)已经算完整。关键是报告里写清楚“本词法分析器覆盖 C 语言子集,具体范围见 Token 定义表”,不要含糊地说“支持 C 语言”。

2.2 用 fread 把源码装进缓冲区,再用指针逐字符扫

词法分析器最常见的翻车点,是输入方式不统一。有人用 getchar 从 stdin 一个一个读,有人用 fgets 读一行处理一行,还有人到语法分析阶段又用 scanf,结果缓冲区里的换行符、残留字符互相打架,Token 流错位。常见做法是先把整个源码一次性读进内存缓冲区,之后所有扫描都在字符串上进行,不再碰输入流。

#define SRC_BUF_SIZE 8192 #define MAX_TOKEN_LEN 127 static char src[SRC_BUF_SIZE]; static char* pos = src; // 当前扫描位置 static int line = 1; void initTokenizer(FILE* fp) { size_t n = fread(src, 1, SRC_BUF_SIZE - 1, fp); src[n] = '\0'; pos = src; line = 1; }

这里用 fread 而不是 fgets,是因为源码文件可能很长,逐行 fgets 还要处理换行符拼接,fread 一次读完更省事。如果你更习惯 fgets,逐行读入后去掉末尾的 \n 再拼到 src 后面也可以,效果一样,只是要多维护一个偏移量。

扫描位置用 char* 指针 pos 维护。pos 只往前移动,需要向前看时最多多看一个字符。遇到换行符更新 line 计数,所有 Token 都带上当前 line。这段代码不需要第三方库,纯 C 语言标准库就能跑,在 VS Code 里配好 gcc 环境后,gcc 命令行直接编译就能用。

缓冲区大小取 8192,对课设实验程序完全够用。如果测试文件更大,需要改成动态分配,但报告里可以写明“本设计面向千行以内的源码输入”,这是一个诚实的边界条件。

2.3 getNextToken:分支扫描与关键字查表

核心函数是 getNextToken。每调用一次,返回一个 Token。扫描逻辑按字符类型走分支:空白跳过、字母进标识符分支、数字进数字分支、运算符直接返回。

Token getNextToken(void) { Token tk; tk.text[0] = '\0'; tk.line = line; // 跳过空白与换行,维护行号 while (*pos && isspace((unsigned char)*pos)) { if (*pos == '\n') line++; pos++; } if (*pos == '\0') { tk.type = TOK_EOF; return tk; } // 标识符或关键字:先按标识符扫描,再查关键字表 if (isalpha((unsigned char)*pos) || *pos == '_') { char* p = tk.text; while ((isalnum((unsigned char)*pos) || *pos == '_') && (p - tk.text) < MAX_TOKEN_LEN) { *p++ = *pos++; } *p = '\0'; tk.type = lookupKeyword(tk.text) ? TOK_KEYWORD : TOK_IDENT; return tk; } // 整数常数 if (isdigit((unsigned char)*pos)) { char* p = tk.text; while (isdigit((unsigned char)*pos) && (p - tk.text) < MAX_TOKEN_LEN) { *p++ = *pos++; } *p = '\0'; tk.type = TOK_NUMBER; return tk; } // 运算符与界符:先按单字符返回,这里保留扩展双字符运算符的口子 tk.text[0] = *pos++; tk.text[1] = '\0'; tk.type = TOK_OPERATOR; return tk; }

这段代码有四个关键点。第一,isspace、isalpha 这些 ctype 函数接收的是 int,用 unsigned char 强转能避免 char 为负时出现未定义行为,这一点在报告里写出来会很专业。第二,标识符识别完整后,调用 lookupKeyword 判断是不是保留字。判断逻辑是“先识别成标识符再查表”,不是“先枚举关键字再硬分割”,否则输入 intx 会被错误拆成关键字 int 和标识符 x。第三,数字分支只处理整数。如果要支持小数、十六进制,需要扩展状态,课设一般用整数就可以。第四,运算符分支目前只处理单字符,双字符运算符的实现放在第 5 章讲,但代码里这个分支的位置已经预留好,往后面接 peek 逻辑不会伤筋动骨。

查关键字表用线性查找就够了。课设里保留字一般就 int、if、else、while、return 这几个,数量在 10 个以内,线性查找的复杂度和哈希没有可见差距,但代码简单得多:

static const char* keywords[] = { "int", "if", "else", "while", "return", "for" }; #define KEYWORD_COUNT (sizeof(keywords) / sizeof(keywords[0])) int lookupKeyword(const char* s) { for (int i = 0; i < KEYWORD_COUNT; i++) { if (strcmp(s, keywords[i]) == 0) return 1; } return 0; }

2.4 状态转移表与代码的对应关系

写报告时,词法分析器部分一般会画状态转移图或状态转移表。图我不在这里画,但状态转移表可以列,而且它和 getNextToken 的每个分支一一对应:

当前状态输入字符类别下一动作输出
初始空白/换行跳过,更新行号无
初始字母/_进入标识符扫描无
标识符字母/数字/_继续累积无
标识符其他字符查关键字表并返回TOK_IDENT / TOK_KEYWORD
初始数字进入数字扫描无
数字数字继续累积无
数字其他字符返回TOK_NUMBER
初始运算符/界符直接返回TOK_OPERATOR / TOK_DELIMITER

这张表的每一行,在 getNextToken 里都有一小段对应的分支。写报告时把这个对应关系点出来,老师一看就知道代码不是抄来的。这是整个课设最容易被忽略、也最容易拉开差距的地方。

3. 语法分析器选递归下降还是 LR:用 C 实现表达式分析与错误同步

3.1 三种主流方案,课设为什么首选递归下降

语法分析器吃掉词法分析器生成的 Token 流,按文法检查 Token 序列是否合法。主流方案有三个:递归下降、LL(1) 预测分析、LR(1) 分析。课程设计里,我几乎总是推荐递归下降。

方案实现思路代码量报告解释成本答辩风险
递归下降每个非终结符写一个 C 函数,互相调用100~200 行低低
LL(1) 预测分析算 First/Follow 集,显式预测表 + 栈模拟200 行以上中中
LR(1)构造状态族、ACTION/GOTO 表很高高高

递归下降的核心思想是用 C 语言的函数调用关系去模拟文法产生式。E 调用 T,T 调用 F,调用链就是推导过程。这个方案摆在报告里也最好讲:不需要额外引入算符和状态表,老师问任何一个函数是干什么的,你都答得出来。

LR(1) 也不是不能做,很多学校的编译原理实验要求用 Flex/Bison 生成。但那是另一个题目的故事了,标题里写的是“C语言语法分析器”,说明要靠手写 C 代码。手写 LR 状态表工作量巨大,而且报告容易变成一张大表,答辩时被问“这个状态为什么合并”很容易卡住。

3.2 先消左递归:文法里不能写 E -> E + T

用递归下降实现表达式语法分析,文法要先设计成无左递归形式。常见的错误写法是 E -> E + T,这个产生式一旦翻译成函数,expr() 第一行就调用 expr(),形成无限递归,程序直接爆栈。正确的写法是把左递归转成右递归或循环。

这里给出一套足够覆盖课设的表达式文法:

E -> T { (+|-) T } T -> F { (*|/) F } F -> NUM | ID | ( E )

花括号表示零个或多个,在代码里对应 while 循环。E 的循环读到一个 + 或 -,就再解析一个 T,这样连加连减都能处理,而且不会递归调用 E 本身。严格说这已经不属于最原始的递归下降写法,而是把 E' 的产生式折叠进了循环里,报告里可以说“用迭代代替了尾递归”,这是一个能在答辩时加分的细节。

3.3 expr、term、factor 三个函数:递归下降最小骨架

Token 流先要有一个统一的推进函数。全局变量 look 保存当前正在看的 Token,next() 取下一个。语法分析器里所有函数都是基于 look 来决策。

typedef enum { SYM_NUM, SYM_ID, SYM_PLUS, SYM_MINUS, SYM_STAR, SYM_SLASH, SYM_LPAREN, SYM_RPAREN, SYM_SEMI, SYM_END } Sym; typedef struct { Sym sym; int numVal; } Token; static Token look; static Token tokens[512]; static int pos = 0; static int tokenCount = 0; void next(void) { if (pos < tokenCount - 1) look = tokens[++pos]; else look = tokens[tokenCount - 1]; }

next() 的越界保护很重要。Token 数组末尾放一个 SYM_END 哨兵,pos 走到最后一个就不再前移,这样所有函数在读到 SYM_END 时都能安全停止,不会数组越界。这一点很多参考代码都没做,靠运气不崩,课设测试一复杂就原形毕露。

然后是实现三个核心函数:

// E -> T { (+|-) T } void expr(void) { term(); while (look.sym == SYM_PLUS || look.sym == SYM_MINUS) { next(); term(); } } // T -> F { (*|/) F } void term(void) { factor(); while (look.sym == SYM_STAR || look.sym == SYM_SLASH) { next(); factor(); } } // F -> NUM | ID | ( E ) void factor(void) { if (look.sym == SYM_NUM || look.sym == SYM_ID) { next(); } else if (look.sym == SYM_LPAREN) { next(); expr(); if (look.sym != SYM_RPAREN) { error("missing right parenthesis"); } next(); } else { error("unexpected token"); } }

这个骨架值得仔细讲一遍。expr 调用 term,term 调用 factor,factor 遇到左括号时又调用 expr,形成间接递归。每一次递归调用在 C 语言运行时里都对应一层函数调用栈。经常有人问“C语言没有堆栈吗”,其实 C 语言有调用栈,语法分析器正是靠这个调用栈来完成嵌套结构的推导。没有栈,括号嵌套就无从处理。

factor 里对 SYM_ID 只做了跳过,没有查符号表。这是刻意简化的设计:本课设的语法分析器只负责判断 Token 序列“合不合法”,不做语义检查。符号表的部分在第 4 章补上。报告里要写清楚这一层边界,否则答辩时会被人问“标识符重复声明为什么没报错”。

3.4 让分析器吃掉整个 Token 流:主循环与分号同步

前面三个函数只处理一个表达式。要分析一整段程序,还需要一个主循环,不断调用 expr,直到遇到 SYM_END。分号在这里作为语句结束的标志:

void parseProgram(void) { next(); // 预读第一个 Token while (look.sym != SYM_END) { expr(); if (look.sym == SYM_SEMI) { next(); } else { error("expected ';' after expression"); } } }

注意 next() 要先调用一次,让 look 持有第一个 Token,这是递归下降的通用约定:look 永远是“当前待处理的 Token”,所有函数消耗完自己关心的 Token 后,不主动多读,交给上层判断。这个“预读一个 Token”的做法在报告里叫 lookahead,写出来会显得你对原理有理解。

目前的错误处理是遇到问题就报错停止。第 4 章会把它升级成“报错后跳过若干 Token 继续分析”的恐慌模式,那才是报告里值得单独写一小节的错误恢复能力。

4. 符号表与错误处理:课程设计报告里值钱的两个模块

4.1 符号表用数组加线性查找就够,别急着上哈希

符号表的作用是记录源程序里出现的变量名、类型、声明行号。词法分析器只负责把标识符切出来,谁声明了谁,那是语法和语义阶段的事。课程设计里,符号表通常做得比较朴素:一个结构体数组,插入和查找都用线性扫描。

#define MAX_SYMBOLS 128 typedef struct { char name[32]; int type; // 0:int, 1:char,课设里够用 int line; // 声明所在行 } SymEntry; static SymEntry table[MAX_SYMBOLS]; static int symCount = 0; int lookup(const char* name) { for (int i = 0; i < symCount; i++) { if (strcmp(table[i].name, name) == 0) return i; } return -1; } int insert(const char* name, int type, int line) { if (symCount >= MAX_SYMBOLS) return -1; strncpy(table[symCount].name, name, 31); table[symCount].name[31] = '\0'; table[symCount].type = type; table[symCount].line = line; return symCount++; }

为什么不用哈希表?课设程序通常几十个变量顶天了,线性查找最坏情况也是几十次 strcmp,性能上没有差别。哈希表的好处要到成千上万个符号才体现出来,但它会引入哈希函数、冲突处理、装载因子一堆新概念,报告写起来长,答辩还可能被追问。把哈希表写进“改进方向”里,比写进正文更合适。

一个要注意的细节:strcpy 换成 strncpy,并手动在末尾置 \0。这是 C 语言字符串函数的经典边界坑,课设报告里出现“防止缓冲区溢出”的字样,是实打实的加分点。

4.2 语法分析器如何和符号表协作

有了符号表,语法分析器就能在遇到声明时登记、在使用时查找。最简单的做法是在 parseProgram 里识别类似“int a;”的声明结构,遇到 int 关键字后,把下一个标识符登记进符号表。

void parseDeclaration(void) { // 当前 look 是 SYM_INT,先跳过 Token name = look; if (name.sym == SYM_ID) { if (lookup(name.text) == -1) { insert(name.text, 0, name.line); } else { error("duplicate declaration"); } } next(); // 期待分号结束声明 if (look.sym == SYM_SEMI) next(); else error("expected ';' in declaration"); }

这里有个取舍要说清楚:如果你的课设只需覆盖表达式和简单变量,不需要完整的声明语句,这个函数可以不写,在报告里注明“符号登记在词法分析阶段遇到标识符时完成”。但我建议至少做一个重复声明检查,因为它是语义分析最简单的入口,能让你报告里的“符号表”章节不悬空。

在 factor 处理 SYM_ID 时,就可以加一道“变量未声明”检查:

if (look.sym == SYM_ID) { if (lookup(look.text) == -1) { error("undefined identifier"); } next(); }

课设到这个程度已经超出平均水准。但注意:一旦加上这个检查,测试用例表里必须有一条“使用未声明变量”的用例,否则逻辑就是死的。

4.3 错误恢复:从“报错即停”升级成恐慌模式

编译原理课本里讲过 panic mode 错误恢复,中文一般叫恐慌模式。思路很简单:发现语法错误后,不直接终止整个程序,而是丢弃后续 Token,直到遇到一个可靠的同步点,比如分号或右括号,再从那里继续分析。这样一次输入能报出多条错误,比“遇到第一个错就死”实用得多。

void panic(const char* msg, int line) { fprintf(stderr, "syntax error at line %d: %s\n", line, msg); while (look.sym != SYM_SEMI && look.sym != SYM_RPAREN && look.sym != SYM_END) { next(); } }

调用方式是把 factor 里的 error 换成 panic,并传入当前 Token 的行号:

} else { panic("unexpected token", look.line); }

注意看 while 循环的终止条件,SYM_END 必须在列表里,否则文件到尾还没遇到分号,next() 会一直推进到数组末尾,靠 3.3 节 next() 里的越界保护停在哨兵 Token 上,但 panic 会死循环。这是错误恢复实现里最容易忽略的边界,写报告时如果画了恐慌模式的流程图,一定要把 END 这个出口画上去。

错误信息格式也值得规范一下。词法错误、语法错误统一用“类型 + 行号 + 具体描述”输出,测试截图会非常清晰:

syntax error at line 3: expected ';' after expression

4.4 报告里这两个模块怎么摆

写报告时,符号表和错误处理不要放在最后当附赠内容,它们应该是独立的两节,放在“详细设计”里面。符号表讲数据结构、插入查找流程、重复声明处理;错误处理讲恐慌模式的同步点选择、错误信息格式、一次输入多条错误的验证结果。这两节能直接把你和只会贴两段参考代码的人区分开。

5. 课设避坑指南:缓冲区、关键字表与错误恢复,五个最容易翻车的位置

下面这些坑都是从真实课设里反复看到的问题,每条按“现象、原因、解决”讲清楚。它们有一个共同点:都是那种“自己在机器上调一晚上调不出来、别人一看就知道问题在哪”的细节。

5.1 输入与缓冲区的坑:读函数混用,Token 流一开始就是错的

现象是词法分析器吐出的第一个 Token 是空串,或者关键字后面永远多一个换行符。原因是有人用 fgets 逐行读源代码,又用 getchar 测试某个函数,两个读函数共享同一个输入流,把缓冲区里的残留字符送进了词法分析。解决方式很简单:源码一律用 fread 或 fgets 一次性读进内存字符串,词法分析只操作这个字符串,整个程序里不再出现从 stdin 逐字符读入的代码。这个约定写进报告的数据输入设计里,能省掉一半测试期的玄学问题。

补充一个环境相关的提醒:很多人在 VS Code 里配置好 C 语言环境,点运行能出结果,就把课设当成完成了。VS Code 只是编辑器,gcc 才是编译器。建议把代码拿到命令行窗口,用 gcc -Wall 直接把所有 .c 文件编一遍,把警告清零,再谈功能正确。换一个环境就翻车的代码,拿到答辩现场演示的风险很高。

5.2 词法扫描的坑:双字符运算符和关键字边界

先说一个很隐蔽的坑:报告状态图和代码对不上。现象是流程图里画了十几条状态转移箭头,代码里却是一长串 if-else,找不到对应的状态变量。原因是代码和流程图分别从两个来源拼出来的。解决方式是在写代码前先按 2.4 节那样列状态转移表,然后让代码每个分支注释对应表里的一行。这样做不是为了好看,而是为了答辩:老师指着图问“这一步怎么实现”,你能直接翻到代码里对应行,比在脑子里现编可靠得多。

然后是双字符运算符。现象是输入 a<=b,词法分析器先返回一个 <,再返回一个 =,语法分析器看到连续两个运算符直接报错。原因是运算符分支只做了单字符匹配,没有向前看一个字符。解决方式是在运算符分支做 peek:读 < 后,看一眼下一个字符是不是 =,是就合并成 <= 再返回。

if (*pos == '<') { pos++; if (*pos == '=') { tk.text[0] = '<'; tk.text[1] = '='; tk.text[2] = '\0'; pos++; } else { tk.text[0] = '<'; tk.text[1] = '\0'; } tk.type = TOK_OPERATOR; return tk; }

==、>=、!= 同理,每个双字符运算符最多多写三行。报告中把“支持双字符运算符”写进功能列表,测试用例表加一行 <=,这个坑就变成了加分项。

还有一个词法边界:关键字和标识符的切分。现象是输入 intx,程序却把 int 当关键字返回,x 单独成标识符。原因是扫描时先判断了关键字,看到 int 就立即返回。正确做法是先把完整字符串扫出来,再查关键字表。intx 应该整个是标识符,int x 才是关键字加标识符。这个用例一定要写进测试表,很多参考代码都在这里有个隐藏的小毛病。

5.3 错误处理的两个坑:exit 终止和数组哨兵

第一个坑:递归下降遇到第一个错误就 exit,报告却写“具备错误恢复能力”。现象是源文件里有两个错误,程序只报第一个就退出,截图和报告描述不符。原因是代码里直接调用了 exit(1)。解决方式是把 exit 换成 4.3 节的 panic,跳过分号后继续分析。同时报告用词要准:写“通过恐慌模式实现基本错误恢复”,不要写“完善的错误处理”,给自己留余地。

第二个坑:Token 数组末尾没有哨兵,遇到意外 EOF 直接越界。现象是输入缺少分号或括号不匹配时,程序崩溃或死循环,而不是打印错误信息。原因是 next() 里没有做数组边界保护。解决方式是在 tokens 数组末尾放一个 SYM_END 哨兵:

tokens[tokenCount].sym = SYM_END; tokenCount++;

next() 只推进到最后一个有效 Token 就不再移动。这个设计在 3.3 节已经写过,到这里应该能看出它的价值:所有错误处理路径都依赖这个哨兵稳定停在末尾。没有它,panic 里那个 while 循环可能直接读到内存垃圾。

6. 用一张测试用例表收尾:把代码、报告和答辩对齐的最后一公里

6.1 测试用例表就是报告里的“实验数据”

报告中最容易被老师翻看的部分是测试与结果分析。与其贴十几张截图,不如做一张有预期、有实际、有结论的测试用例表。表格做得好,报告能厚三页,答辩时也方便指着某一行讲。

输入片段预期输出实际输出结论
int a;KEYWORD(int), ID(a), SEMI一致通过
a = 1 + 2 * 3;ID(a), OP(=), NUM(1), OP(+), NUM(2), OP(*), NUM(3), SEMI一致通过
if (a <= b) return 1;KEYWORD(if), LPAREN, ID(a), OP(<=), ID(b), RPAREN, KEYWORD(return), NUM(1), SEMI一致通过
1 + 2 )语法错误,报告第 1 行缺左括号一致通过
intx;ID(intx), SEMI,不切成 KEYWORD(int)+ID(x)一致通过

这五行的覆盖点值得说一下:第一行验证关键字和声明;第二行验证运算符优先级,1+2*3 的结果不是重点,Token 顺序才是;第三行验证双字符运算符 <=;第四行验证错误恢复;第五行验证先识别标识符再查关键字表,这是词法分析器最常见的错误场景,一定要放。

6.2 递归嵌套太深时,C 语言的调用栈会先扛不住

递归下降分析器靠 C 语言的函数调用栈工作,这正好回答了一个常被问的问题:C 语言没有堆栈吗?有,调用栈是运行时维护的,它不体现在你写的代码里,但每个函数调用都在压栈。expr、term、factor 互相调用,括号嵌套一万层时栈会溢出,程序崩溃。

课设不需要处理一万层,但可以做一个深度保护,顺便在答辩时展示你对边界条件的思考:

static int depth = 0; #define MAX_PARSE_DEPTH 1024 void factor(void) { if (++depth > MAX_PARSE_DEPTH) { panic("nesting too deep", look.line); depth--; return; } // 原有逻辑 depth--; }

测试时写一个超过 1024 层括号的输入,程序应正常报错而不是崩溃。把这个测试用例也加进 6.1 的表格里,报告就多了一个有深度的边界测试项。

6.3 提交前最后一遍对账

报告章节可以按这个结构组织:需求分析、总体设计、详细设计、测试报告、心得体会。其中详细设计对应本文第 2 到 4 章的内容,测试报告对应 6.1 的表格,总体设计画一张词法模块到语法模块的调用关系图就够了。

我自己改课设时最常说的一句话是:代码和报告必须互相印证。对着代码里的每个函数,在报告目录里找对应的说明;报告里出现的每个功能,在代码里能翻到实现。不一致时以代码为准改报告,而不是改代码去迁就一份抄来的文档。整个过程不复杂,但对齐之后,代码、测试用例表、流程图、答辩是一体的,你不需要背报告,因为每一行都是你自己写的。希望你也能用这个方式把这份课设收得干净利落,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询