C语言语句分类与注释规范:从基础语法到Doxygen与乱码排查
发布时间:2026/10/6 5:05:09 作者:尧图编辑部 阅读量:1,286

写C语言的人最容易被问倒的问题往往不是“指针怎么用”而是两个看起来特别基础的东西语句分几类注释怎么写才算对。我见过不少能写出几百行代码的初学者让他说说C语言有哪几类语句第一反应就是“if、for、while那些嘛”再追问一句“return算哪类空语句是什么声明算不算语句”基本就卡住了。至于注释很多人的理解还停留在“加//就是注释呗”结果改编码、跑工具链、写项目文档的时候一块儿踩坑。这篇文章想把C语言的语句分类和注释这两个基础主题一次讲透。不是背书上的定义而是站在实际写代码、调试、维护项目、和别人协作的角度把这两块内容掰开揉碎配上可以直接照抄的示例和工具配置。适用人群很明确刚学完Hello World、正在补基础的自学者写了些代码但体系还不清晰的学生以及工作中被注释、乱码、文档生成折磨过的开发者。读完你能做到三件事看到一段C代码能准确说出每条语句的类型写注释时知道什么该写、什么不该写、怎么配合Doxygen生成项目文档遇到中文注释乱码、批量去注释误伤代码这类问题有自己的排查思路。1. 先把“语句分类”这堂课补上1.1 为什么新手总在语句上翻车很多教材讲C语言语法习惯从“数据类型”“运算符”“流程控制”这种模块切分开讲。好处是知识点清晰坏处是学生脑子里没有一个整体的“语句”概念。等到了做综合练习、看别人源码的时候到处都是“int a 10;”和“printf(“%d”, a);”混在一起老师说是语句但问“声明到底是语句吗”“a后面那个分号是干什么的”就答不上来。更深一层的原因是C语言是一门“语句驱动”的过程式语言程序执行本质上就是一条一条语句往下走。不理解语句怎么分类就没法准确理解编译器眼中的代码结构。遇到编译报错时错误信息经常是“expected ‘;’ before ‘}’”“label at end of compound statement”这类跟语句边界有关的提示看不懂分类就不知道错在哪儿。我之前带过一个小伙伴他写一个for循环总是习惯在循环体最后加一句多余的“return 0;”理由是“看见main函数里最后有return就觉得每个函数都得有”。这就是典型的没有建立“return属于跳转语句表示函数执行到此结束”的认知把语句类别混在一起了。搞清楚分类之后这种问题基本能避免一半。1.2 语句分类并不复杂一张表稳住大局C语言的语句可以从两个维度看语法形式和功能角色。语法形式就是“长什么样”是一个表达式加分号还是一堆语句被花括号包起来。功能角色就是“干什么用的”做计算、做判断、做循环还是让程序跳走。把两个维度合并一下C语言语句可以分为六类语句类别语法特征典型例子表达式语句表达式紧跟分号x 10; printf(“ok”); i;复合语句用花括号括起来的若干语句{ int a 1; a; }选择语句根据条件分支执行if、if-else、switch循环语句重复执行一段代码for、while、do-while跳转语句改变程序执行顺序break、continue、goto、return空语句只有一个分号;还有一个特别容易争论的点声明语句算不算语句。在C89/C90标准里声明和语句是分开的两类语法单位所以那时候要求变量声明必须放在块的开头不能混在语句中间。但是C99之后允许声明出现在语句序列里所以在现代C语言环境下你完全可以写int a 1; printf(%d, a); int b a 1; printf(%d, b);第二个int b a 1在C99后的编译环境下是合法代码。这也就是为什么GCC默认用GNU C标准或者C11、C17时混着声明写不报错但某些严格C89模式下会报错。所以严格来说现代C语言里声明语句也得算进去一共是七类。记忆口诀也很简单表达式语音、块、选择、循环、跳转、空、声明。1.3 用一段代码把各类语句认全光看表不直观我们直接看一段包含了多种语句的代码。猜猜这段代码里有没有错误顺便给每条语句归类#include stdio.h int main() { int i, sum 0; // 声明语句声明变量 for (i 1; i 10; i) { // for是循环语句 if (i % 2 0) { // if是选择语句 sum i; // 表达式语句 } } printf(sum %d\n, sum); // 函数调用表达式语句 while (sum 0) { // while循环语句 sum--; } if (sum 0) // 选择语句 ; else sum; // 表达式语句 return 0; // 跳转语句结束main函数 }代码里的“{”和“}”把一堆语句包起来形成复合语句。for循环后面跟的那个花括号块就是一个典型的复合语句。而if后面跟的“;”是一个空语句表示“如果sum等于0什么都不做”。这段代码本身逻辑没什么意义纯属为了演示分类。但如果让你设计一个计算5×5矩阵鞍点的程序你会发现鞍点问题的标准解法里每个环节都要用到这些语句类别外层嵌套for循环遍历矩阵内层if判断某元素是否是该行最大、该列最小找到后用return提前结束函数哪怕中间只是临时标记一下位置也得用变量声明语句和表达式语句。循环、分支、跳转、表达式、声明全部用上你会发现语句分类不是抽象概念而是你写算法时每一行都在打交道的东西。2. 语句分类里的细节才是分水岭2.1 空语句与分号陷阱最容易踩空语句是只有一个分号的语句代表“这里什么也不做”。听起来简单但坑非常多。最常见的翻车场景是while循环后面多打一个分号int i 0; while (i 10); // 注意这里有个分号 { printf(%d\n, i); i; }这段代码原意是循环打印i但因为while (i 10);后面跟了个分号编译器理解成while条件判断条件成立就执行空语句然后继续判断。i的值一直没变循环条件永远为真程序就直接死循环卡死。后面那个花括号块变成一段与while无关的复合语句根本执行不到。这种错误之所以难排查就是因为分号太小了编译不报错运行表现却是“程序没反应”或者“卡住了”。我在帮别人看代码时遇到死循环第一件事就是检查循环头后面有没有不小心加上的分号。if语句也有同样的问题if (a 0); b 1;实际执行时无论a是否大于0b都会被赋值为1因为if后面那个分号让它是空语句等于是“如果a0就什么都不干”b1是独立的后续语句。正确的复合语句和空语句使用倒是可以利用这个特性。比如某些算法里想写“当条件满足时持续等待某个状态改变”循环体本来就是空的就可以写while (flag 0) ;但这种写法一定要加注释否则别人一看就是“分号灾难”。2.2 复合语句、作用域与局部变量复合语句就是花括号包裹起来的语句序列它可以出现在任何需要一条语句的地方。它的作用不只是把多条语句“打包”更重要的是划定变量的作用域。#include stdio.h int main() { int a 10; { int b 20; printf(a%d, b%d\n, a, b); // 能访问到外部变量a } // printf(b%d\n, b); // 错误b已经不在作用域内 printf(a%d\n, a); return 0; }那个花括号块里的b生命周期从声明处开始到块结束就结束。出了块再想用b编译直接报“未声明的标识符”。循环体、函数体本质上都是复合语句。函数体是一层块里面的变量是函数级局部变量if、for、while的循环体又是一个嵌套块在循环体里声明的变量只属于那个块。这里有个容易混的点C语言在for循环里经常写for (int i 0; ...)这个i属于整个for循环语句内部的复合语句作用域。循环结束i就不可见了。如果你想循环结束后还要用i就得在for之前声明int i; for (i 0; i 10; i) { ... } printf(i %d\n, i);一个简单的生活类比复合语句就像一间临时办公室你在里面申请了一个临时工位b用完就回收了函数体是常驻工位从进公司就分配好了。临时工位的好处是不跟外面的人冲突坏处是你不能把它带到其他房间用。2.3 控制语句的细分两选择、三循环、四跳转控制语句是C语句分类里的重头戏总共九种口诀可以记为“两选择、三循环、四跳转”。两选择if和switch。if处理条件分支switch处理多路分支。很多人喜欢用if接一串else if实现多路分支其实如果判断的是同一个整数变量的多个取值switch更清晰执行效率也常常更高。但注意一点switch里的case不是语句而是语句标签所以case后面可以没有任何语句也可以多条语句直到遇到break、return或switch结束。这就是为什么case后面忘记写break会一泻千里执行下去学名“fallthrough”。三循环for、while、do-while。for适合知道循环次数的情况while适合“条件满足就继续跑”的情况do-while保证循环体至少执行一次。写菜单、读文件、键盘输入判断退出这类场景do-while比while自然得多。四跳转break、continue、goto、return。break只能跳出最近的switch或循环continue是跳过本次循环剩余部分进入下一次goto可以跳转到同一个函数内任意带标签的位置return则直接结束整个函数。这九种控制语句我在给初学者上课时要求必须做到“看到关键字马上能说出属于哪类”因为调试的时候你会发现代码逻辑混乱往往不是算法问题而是把break、continue、return混着用还说不清各自的作用边界。举个例子for (int i 0; i 10; i) { if (i 3) continue; // 跳过i3这一次i会继续变成4 if (i 7) break; // i7时整个循环退出i不会到8 printf(%d\n, i); }打印结果是0、1、2、4、5、6然后循环终止。continue只是“这次不干了”break是“这一层循环以后都不干了”。如果把break换成return那就不是退出循环而是整个main函数结束回车执行权交还给操作系统。2.4 声明语句C99前后的变化要分清声明到底算不算语句上面已经提到了标准差异。展开说一点C89的年代块的语法结构是“先全部声明再全部语句”所以老教材会写void func(void) { int a; int b; a 10; b 20; ... }源码里声明必须全部堆在块开头。到了C99标准允许“声明和语句混合出现”于是可以写成void func(void) { int a 10; printf(%d\n, a); int b a 5; // 现代C语言里合法 ... }这个变化对实际编码的影响不只是“能不能随手在中间声明变量”这种便利性问题。它反映的是一种编程思想变量的声明尽量贴近首次使用的位置让变量的作用域尽量缩小避免一个很大的函数开头堆了一堆变量看到后面都忘了哪些还在用。这也是为什么很多编码规范鼓励“变量就地声明”。不过要小心C89时代的老代码、某些嵌入式编译器默认标准不高时或者你写代码时用了“-stdc89 -pedantic”编译中间声明就会报错。如果代码库很老跟着老代码风格走可能更稳。我的建议是如果是自己主导的新项目直接用C11或更新标准混着声明没问题如果是维护老项目先看项目里默认的编译参数和代码风格别一上来就改写法。3. 注释不是给人看的是给“半年后的自己”看的3.1 两种注释写法的底层规则C语言有行注释//和块注释/* */两种。//的作用范围是从两个斜杠开始到这一行结束/* */则是从开始标记到结束标记之间不管跨多少行全部是注释。底层的处理规则是注释在预处理阶段就会被替换成空格。注意是一个空格不是空字符串。所以下面这句int/*注释*/x;经过预处理后是int x;中间短暂地出现了一个空格编译器才能把int和x分开。如果你误以为注释会被“整个删除”写成int a/*注释*/b;预处理后变成int ab;变量名就变成了ab。这种错误在宏定义里尤其隐蔽。两个细节值得记下来一是块注释不能嵌套也就是说/* 外部 /* 内部 */ 外部 */最里面的*/会把整个注释终止后面残留的外部 */会被当成代码送去编译直接报错。二是//在C89标准里是不存在的所以如果你还在用老古董编译器最好只用/* */。现在GCC、Clang、MSVC这些主流编译器在默认标准下都支持//但写跨平台可移植代码时知道这个历史没坏处。3.2 注释和代码的边界字符串、反斜杠、嵌套注释判定发生在词法分析阶段它会优先于“字符串内容”来判断吗答案是注释和字符串是同一个级别的记号编译器从源码开头逐字符扫描走到一个位置时如果看到//或/*还没进入字符串状态那它就是注释如果当前在字符串内这两个字符就是字符串内容的一部分。printf(// 这不是注释); printf(/* 这也不是注释 */);这两行里的//和/* */都只是字符串字面量的一部分编译后会被原样打印出来。反过来字符串字面量里的内容再像注释也没用。很多人忽略了换行和反斜杠的交互问题。C标准的续行规则是反斜杠换行会在预处理早期被删掉把两行拼成一行。这意味着// 注释内容 \ int a 10;你以为注释到行尾结束了不因为行尾有反斜杠反斜杠和换行被拼行下一行的int a 10;也被卷进注释里了。结果是a根本没有被声明后面用到a的代码全部报“未声明的标识符”。我排查过一次这种问题原因是某个人在注释结尾顺手打了个\想对齐注释结果把下一行代码“吃”掉了。还有一种边界情况/* */块注释里如果字符串里包含*/其实不会出事因为字符串还没开始。但如果块注释里出现单引号什么的不影响注释就是注释编译器不会去解析注释里的引号配对。可以放心在注释里写代码示例只要别嵌套块注释和别用反斜杠结尾就行。3.3 什么样的注释值得写好注释与坏注释很多新手写注释的习惯是从代码行开始往上贴语义等于“把代码翻译成人话”。这种注释的价值非常低i; // i加1谁不知道i是i加1好的注释要回答的是“为什么”和“注意什么”不是“做了什么”。我见过一段处理鞍点问题的代码注释写得特别经典// 先假设当前元素就是鞍点再通过列比较推翻它 // 注意鞍点必须是“所在行最大所在列最小” // 不能只做行最大就break否则会漏掉列方向上的矛盾这个注释把算法的核心思路、边界条件、容易出错的地方全讲清楚了。后面的人接手这份代码不需要重新跑一遍逻辑推演才能看懂。很多团队规范里要求写“文件头注释”“函数注释”“字段注释”目的就是让别人在看一个文件的时候能在30秒内知道这个文件是干什么的、函数输入什么输出什么、结构体的每个字段什么含义。这些不是形式主义。我给函数写注释通常遵循几个原则输入输出参数必须说清楚返回值要说明最好说明出错时返回什么复杂逻辑的步骤要用注释标出注释与代码更新同步。你做一次代码评审就会发现最坑的一种情况是代码是对的注释是老的描述的功能跟代码实际行为完全不一样。这种误导性注释比没有注释更糟糕因为它会让人做出错误判断。3.4 文档级注释Doxygen、字段注释与团队规范如果只靠//和/* */散装注记注释的价值会打折因为没有统一格式没法自动提取成文档。项目一大就得用工具从注释里生成文档C语言里最常用的就是Doxygen。Doxygen能识别特定格式的注释块。常见的文件头注释/** * file matrix.c * brief 矩阵相关运算包括鞍点查找、转置、求和 * version 1.0 */函数注释/** * brief 查找二维矩阵中的鞍点 * param mat 二维数组行列数为ROW和COL * param row 矩阵行数 * param col 矩阵列数 * return 存在鞍点返回1否则返回0 */ int find_saddle_point(int mat[][MAX_COL], int row, int col);字段注释typedef struct { char name[32]; /** 学生姓名不超过31个字符 */ int score; /** 分数范围0~100 */ int id; /** 学号 */ } Student;Doxygen通过brief、param、return这些标签把注释结构化。写的时候感觉多花了点时间但好处是可以用Doxygen一键生成HTML或PDF文档团队里其他人不用打开源码就能查接口说明。对一个中大型项目来说这比维护独立的Markdown文档靠谱得多因为文档和代码在一起更新代码时顺手更新注释文档就会自动同步。这也就是为什么很多公司代码规范里对“字段注释”“函数注释”“文件注释”的格式都有明确要求。热词里那些“字段注释”“文档级Doxygen注释”的背后其实是同一个诉求让注释成为可维护的、可自动处理的资产而不是可有可无的摆设。4. 工具链上的注释实战4.1 VS Code里中文注释乱码的排查与解决中文注释乱码是C语言初学者和跨平台开发者最容易撞上的问题。明明代码写得好好的编译也通过了就是一运行或者一打开文件中文注释全变成“锟斤拷”“~~~~”之类的乱码。原因只有一个文件保存的编码和读取时用的编码不一致。VS Code默认使用UTF-8编码打开文件。如果一份C源码是中文Windows下一些老编辑器保存的GBK/GB2312编码VS Code按UTF-8解码UTF-8不认识GBK的字节序列就会显示成乱码。反过来有些终端或老编译器默认按GBK读取源码你用VS Code写了一个UTF-8编码的文件编译器或终端按GBK读中文也乱码。排查步骤看VS Code右下角状态栏显示“UTF-8”还是“GBK”。如果显示UTF-8但中文乱码多半是文件本身是GBK需要重新以GBK打开。点击右下角编码区域选择“通过编码重新打开”在列表里选“GBK”或“GB2312”。这一步只是重新按指定编码读取文件本身没变。如果按GBK打开后中文正常了说明文件是GBK保存的。想统一成UTF-8就点“通过编码保存”选择“UTF-8”保存一次即可。如果用的是GCC编译源码文件是GBK而编译器期望UTF-8可能会出现“error: converting to execution character set: Invalid argument”之类的报错。解决方式是统一编码或者在编译参数里加-finput-charsetGBK让编译器按GBK解释源码。一个比较稳的项目约定是所有人统一用UTF-8保存源码文件VS Code设置files.encoding: utf8编译时也明确使用支持UTF-8的编译器配置。这样至少从源头减少乱码问题。我团队的新项目现在清一色UTF-8老项目实在动不了的就单独建一份构建说明注明“此项目源码为GBK编码请勿用UTF-8重新保存”。注意一点注释乱码一般不影响程序功能但会影响维护。有人为了省事遇到乱码直接把中文注释删了我觉得这不是好办法。宁可花两分钟把编码统一好也别让代码失去注释。4.2 配置一套顺手的函数注释模板没有好用的注释模板每次写函数注释都要重新敲格式很耗费耐心。很多编辑器都支持自定义注释模板VS Code里可以装Doxygen Documentation插件在函数上方输入/**再按回车它会自动根据函数参数生成注释骨架。也有更轻量的方式自己配置用户代码片段。我常用的Doxygen风格函数注释块是/** * brief 函数一句话说明 * param param1 参数1的说明 * param param2 参数2的说明 * return 返回值的说明失败时返回X */真正写项目时如果函数比较复杂我还会额外加一段“实现说明”/* 实现说明 * 1. 先检查参数的合法性如果为NULL直接返回错误码 * 2. 排序使用插入排序输入序列接近有序时性能较好 * 3. 注意外层循环结束后last变量里保存的是最后一个交换位置 * 后续排序可以跳过这个位置之后的所有元素。 */注释模板的意义不只是格式统一更是强迫你在写函数之前把“参数是什么、返回什么、怎么做”想清楚。经常有这种情况本来觉得函数很简单写到param和return的时候才发现返回值设计不合理、参数命名不清楚。注释写不出来的地方往往就是设计有问题的地方。4.3 批量去除源码注释的正确姿势有时候你想把网上下载的源码里的大段注释去掉或者想看看编译前的“无注释版本”到底长什么样。很多人的第一反应是用正则表达式匹配//...和/*...*/然后替换掉。这个方法在小规模、无特殊场景下能用但有致命的坑printf(// 这里的两个斜杠不能删\n); char *url http://example.com; // 比较时不能把双斜杠当注释如果正则一刀切匹配//.*会把这些出现在字符串字面量里的双斜杠也删掉代码直接报废。同样/*在字符串里也可能出现。比较稳妥的一种做法是利用编译器的预处理功能gcc -E -P file.c输出的是预处理之后的源码注释已经被编译器在预处理阶段剥离了宏也被展开。这个命令常用于快速检查“去掉注释后代码还剩什么”不过它会展开#include等指令输出会很大而且宏展开结果可能跟你预期不同。如果项目里确实需要保留代码、只去掉注释正儿八经的做法是写一个词法扫描小程序模拟编译器的状态机处理分“普通代码”“字符串”“字符常量”“注释”四种状态逐字符扫描只有在普通代码状态下看到//或/*才进入注释状态字符串里的内容原样保留。大学编译原理课程里这种词法分析练习经常出现感兴趣的可以自己用C语言实现不算复杂。还有一个建议如果只想让代码暂时“没有注释”优先用gcc -E -P这种官方能力不要用网上那些来路不明的“去注释工具”直接覆盖你的源码文件。去注释是不可逆操作一旦工具误删了字符串内容很难恢复。操作之前务必备份。4.4 把提交信息也当成一种注释注释不只是写在源码里的只要是“给人看的说明文字”都值得用注释的思维去写。版本控制里每次提交的commit message就是这个道理。git commit里的-m参数后面那段话就是代码变更的注释只不过它写在了提交记录里。好的提交信息模板通常长这样fix(parser): 修复switch缺少break导致执行串流的问题 在解析switch分支时个别case分支末尾漏掉break 导致执行流落入后续分支查了半天才定位到。 给相关case补上break并加了一行注释提示后续维护者。第一行是总结说清楚做了什么后面是正文说清楚为什么这么做。这其实跟函数注释一个逻辑总结是函数的brief正文是return和实现说明。很多初学者做课程项目提交信息随便写“update”“修改代码”“1”过一个月之后翻提交记录完全想不起来哪个提交改了哪些内容。我自己的习惯是提交前问自己一个问题如果三个月后的我看到这条提交信息能不能不看代码就知道这里发生了什么如果不能就重写。5. 常见问题与经验避坑5.1 块注释嵌套导致编译报错这个坑我在前面提过这里细说。错误示意/* * 函数说明 * 输入参数a ... /* 这是内部注释 */ */ int func(int a);词法扫描器看到第一个/*后进入注释状态然后一路扫描碰到内部的*/就认为注释结束了。结果就是最外层期望的*/变成了代码编译器发现*/前面没有对应的/*直接报错“expected expression before ‘/’ token”。这还算好查的因为报错位置就在残留的*/附近。更隐蔽的是/* 判断条件 * if (x 0) { /* 正数 */ } * 此时进入了循环分支 */如果预处理阶段在某个宏定义里用/*和*/包住了内容而内容里又出现了另一个/*很可能导致代码直接被“吃”掉一大块。解决办法只有一个不要嵌套块注释。需要“注释里的注释”时用//代替内层或者把内层注释改成一行行//。5.2 “看起来是注释其实是代码”的坑有些坑不源于注释规则而源于输入法。中文输入法状态下打注释分隔符有时候会把//打成中文全角符号比如或者//中间夹个全角空格。编译器看到不是标准注释符当普通代码处理于是“三斜杠”“半角半全角”满天飞报错还特别诡异。我自己遇到过最离谱的一次有人在代码里写了一行看起来是注释的文字开头却是“”全角分号然后后面写了大段中文描述结果编译器把这一串当成一个巨大的标识符尝试解析语法错误报了十几行。排查了半天才发现不是逻辑问题是符号输入错误。经验是如果编译突然报出一堆莫名其妙看不懂的语法错误先看一眼报错行里的符号是不是全角特别是分号、括号、引号。用VS Code之类的编辑器可以直接显示全角符号也可以通过搜索“”全角分号来定位。5.3 九种控制语句的快速记忆与辨析两选择三循环四跳转这是给控制语句画的简表关键字类别关键特点if选择可以有else分支switch选择case是标签需要break防止贯穿for循环初始、条件、增量三部分while循环先判断再执行do-while循环先执行至少一次break跳转跳出最近的一层循环或switchcontinue跳转跳过本次循环剩余部分goto跳转跳到本函数内任意标签慎用return跳转结束当前函数返回调用方辨析时最容易错的是break的“层”的概念。break只跳出最近的一层循环不是跳出所有循环。比如三层嵌套for最内层break只能跳出最内层外面两层照跑。如果你想从最内层直接跳出所有循环一个常用姿势是goto另一个是用标志变量。初学者对goto往往谈虎色变但用于“多重循环统一退出”这个场景比夹一堆flag干净得多。顺便分享一个实际编程习惯不到万不得已不写goto但写了goto也别恐慌。很多代码规范只是限制goto乱飞不是禁用。C语言的跳转语句总共就四种break和continue只能作用于循环或switchreturn只能作用于函数剩下的跨距跳转只有goto。理解了分类你会明白为什么有时候就是需要一个goto来解决三重循环退出问题。5.4 注释驱动的学习习惯最后分享一个我觉得对新手特别有用的训练方法写代码先写注释用注释把语句框架搭出来再逐步把每条注释替换成真实代码。比如要做“找5×5矩阵的鞍点”这个经典题目先用注释设计骨架int find_saddle_point(int a[][5]) { for (int row 0; row 5; row) { // 找到当前行的最大元素 // 记录这个最大元素所在的列 // 检查该列中是否有比它更大的元素 // 如果没有这就是鞍点返回1 } // 所有行都没找到返回0 return 0; }然后把每条注释逐一替换成代码。这个过程强迫你先思考算法结构再填具体实现而且天然形成了一段有注释的完整代码。好处很明显确保你的代码思路不是“边写边猜”而是先有一个清晰的规划同时这些注释留在了代码里其他人读你的作业或者项目时能一眼看出算法逻辑。很多课程里写作业喜欢直接闷头敲代码代码写完回头再补注释。我觉得效率最低的就是这种“先写代码后补注释”。后补注释你写的全是“i加一”这种废话因为实现已经写完了你没有动力去重新梳理为什么。先注释后代码注释的质量会高一个档次代码质量也会随之提高。遇到“翁恺C语言练习题”“浙大C语言题目”这类练习资源时别急着把题刷完。挑几个经典题比如九九乘法表、冒泡排序、字符串逆序、鞍点问题严格按照“先注释骨架再填代码”的方式走一遍你对语句分类的掌控、对注释的敏感度会提升得比刷几十道题还明显。分析代码里循环、分支、跳转各自承担了什么职责你面试笔试时被问“这里为什么用do-while而不是while”也能回答得更有底气。我个人现在的习惯拿到任何一段陌生代码第一件事就是按语句类别把它画一遍表达式语句在哪复合语句在哪控制语句是谁控制的跳转语句出现在哪几个关卡。等这个工作做完这段代码的逻辑基本也看懂了。注释这事儿同理它不是一个用来应付检查的动作而是一个帮助你思考的工具。把这两件事练扎实后面的指针、内存、结构体、文件操作学起来会顺得多。