<< C程序设计语言第2版 >> 练习 1-23 删除C语言程序中所有的注释语句

article/2025/7/12 18:44:51

1. 前言

本篇文章介绍的是实现删除C语言源文件中所有注释的功能.希望可以给C语言初学者一点参考.代码测试并不充分, 所以肯定还有bug, 有兴趣的同学可以改进.

原题目是:

练习1-23 编写一个删除C语言程序中所有的注释语句. 要正确处理带引号的字符串与字符常量. 在C语言中, 注释不允许嵌套.

2. 关于C语言注释

C语言在K&C和c89/c90时还不支持单行注释, 只有多行注释, 单行注释是以C99标准引入, 从C++那里借鉴的, 另外提一下C语言的4个标准  

K&R C    这实际是C语言第1个标准, 就是C程序设计语言这本书的第1个版本, 虽不是正式标准但是实际上的第1个标准, 好好读这本书吧, 第2版加入了ANSI C的一些特性, 如函数原型声明.

C89        这是C语言的首个正式标准, 1989年由美国国家标准委员会ANSI制定, 也称为ANSI C, 其实就是美国的国家标准C.                        

C90        国际标准化组织ISO采纳了ANSI C标准,于1990年发布,也称为C90, 与ANSI C差异不大.

C99        ISO于1999年发布, 对C进行进行了重大革新,如新数据类型long long, _Bool等, 单行注释也从C++而借鉴.

所以如果编译器老到C99都不支持,那就无法识别单行注释,但网络如此发达的今天随便下一个IDE或编译器应该都支持.

3. 注意事项

在完成这个编程作业前, 有几点需要注意.

3.1  c语言中有两种注释,一种是单行注释即以//开头到换行符为止,  还有就是多行注释, 这两种情况都需要处理;

3.2  注释(多行)不允许嵌套. 原题目有提到注释不允许嵌套, 因此这里假定待处理的文件中不包含不合法的注释, 本程序不检查注释的合法性;

3.3  带引号的字符串内的注释无效, 不能当做注释删除;

例如 printf("hello, /* test */ world");  这行代码中的 /* test */ 不能被当作注释删除, 因为它属于字符串的一部分, 不是注释.

3.4  要注意处理字符常量. 比如双引号是字符常量, 不可当做字符串标记;

例如: char c = '\"';   这里双引号只是一个字符常量, 不能当作字符串开始或结束标记.

但这里有个关键问题, 怎么实现删除操作呢? 如果简单点, 可以直接输入一些C代码, 手动加注释, 然后输入完成, 把原有输入中的注释屏蔽掉就删除了, 即不输出注释部分就可以了. 但这样手动输入太麻烦, 也不实用. 所以我们用C语言源代码文件为输入, 另外创建一个新的C源代码文件, 并将原文件中所有注释都不输出到新的C源代码文件就可以了. 这样编好程序还有一定的实用性, 可以用来删除C源代码中注释很多的文件. 只是文件操作超出了这本书第1章的内容.

4. 代码讲解

    /* 初始状态不在字符串和注释中 */instrs = incomm_s = incomm_m = FALSE;  prec = c = '\0';prec = getc(fr);c = getc(fr);while (prec != EOF) {if (instrs == FALSE) {    /* 检查进入或脱离注释 */if (prec == '/' && c == '*') {incomm_m = TRUE;} else if (prec == '/' && c == '/') {incomm_s = TRUE;} if (incomm_m == TRUE && prec == '*' && c == '/') {incomm_m = FALSE;c = getc(fr);prec = c;c = getc(fr);} else if (incomm_s == TRUE && prec == '\n') {incomm_s = FALSE;}}/* 检查进入或脱离字符串中 */if (c == '"' && prec != '\\' && instrs == FALSE) {instrs = TRUE;} else if (c == '"' && prec != '\\' && instrs == TRUE) {instrs = FALSE;}/* 处理删除注释 */if (instrs == FALSE && (incomm_m == TRUE || incomm_s == TRUE)) {prec = c;c = getc(fr);} else {putc(prec, fw);prec = c;c = getc(fr);}}

文件打开和写入部分没啥可讲的, 直接执行打开和写入就行了. 主要的处理逻辑全在这个while循环中. prec表示前一个字符, c是当前字符. incomm_m表示是否在多行注释中, incomm_s表示是否在单行注释中. 分成三块:

第一部分处理是否在注释中.

第二部分处理是否在字符串中.

第三部分处理删除注释.

第一部分首先要搞清楚当前是否在注释中, 但在处理是否在注释中之前首先要明确如果当前没在字符串之中, 才能切换是否在注释中的状态. 因为如果当前在字符串中, 是不可以改变在注释中或出注释状态的, 在字符串中所有字符都要原样输出到新文件不能删除任何字符. 哪怕字符串中有/* */这样的字符存在也不行. 

另外由于判断是否在注释中时需要关注两个字符, 即多行的 /* 或 */ , 单行的 // , 所以我们从源代码文件中获取字符一次获取两个.所以程序开头一次读取两个字符:

prec = getc(fr);
c = getc(fr);

并且在出注释 (多行) 时即发现 */ 连续出现时, 要继续多读取两个字符, 不然这里将注释状态置为出注释后, 后面就立刻将最后的字符写入到新文件, 而这最后的那个仍然是注释内. 所以这里一旦发现出注释时, 要多读取两个字符:

if (incomm_m == TRUE && prec == '*' && c == '/') {incomm_m = FALSE;c = getc(fr);prec = c;c = getc(fr);
}

第二部分检查是否在字符串中中需要说明的是, 当发现 " 双引号时, 双引号之前的字符不可以为 \ 才能切换字符串状态. 因为 \ 表明这里双引号只是普通字符, 不能当作字符串开始或结束的标记.所以才有 prec != '\\' 这个判断.

第三部分就是说如果不在字符串中, 在合法有效的单行或多行注释中就不写入新的文件中, 即删除注释. 因为如果在字符串中全部字符都要写入新文件中的. 其它所有情况(包括在字符串中)依次写入前一个字符 prec, 然后读取一个新字符c.

5. 完整代码

/* C程序设计语言第2版 练习1-23 编写删除C语言中所有注释的程序 */
/* 注意事项: 要正确处理带引号的字符串和字符常量,并且C语言中  */
/* 注释不允许嵌套 */#include <stdio.h>
#include <stdlib.h>
#define TRUE 1        
#define FALSE 0int main(int argc, char *argv[])
{FILE *fr, *fw;   int prec, c;    /* instrs表示是否在字符串中 *//* incomm_s和incomm_m表示是否在单行多行注释中  */int instrs, incomm_s, incomm_m; if (argc < 3) {       /* 命令行语法检查 */printf("Usage: %s file1 file2\n", argv[0]);exit(EXIT_FAILURE);}printf("This program removes all comments from file1 and then ");printf("outputs to file2.\n");if ((fr = fopen(argv[1], "r")) == NULL) {   /* 检查打开文件1错误 */printf("Can't open the C source file %s.\n", argv[1]);exit(EXIT_FAILURE);}if ((fw = fopen(argv[2], "w")) == NULL) {  /* 检查打开文件2错误 */printf("Can't open the new file2 %s.\n", argv[2]);exit(EXIT_FAILURE);}/* 初始状态不在字符串和注释中 */instrs = incomm_s = incomm_m = FALSE;  prec = c = '\0';prec = getc(fr);c = getc(fr);while (prec != EOF) {if (instrs == FALSE) {    /* 检查进入或脱离注释 */if (prec == '/' && c == '*') {incomm_m = TRUE;} else if (prec == '/' && c == '/') {incomm_s = TRUE;} if (incomm_m == TRUE && prec == '*' && c == '/') {incomm_m = FALSE;c = getc(fr);prec = c;c = getc(fr);} else if (incomm_s == TRUE && prec == '\n') {incomm_s = FALSE;}}/* 检查进入或脱离字符串中 */if (c == '"' && prec != '\\' && instrs == FALSE) {instrs = TRUE;} else if (c == '"' && prec != '\\' && instrs == TRUE) {instrs = FALSE;}/* 处理删除注释 */if (instrs == FALSE && (incomm_m == TRUE || incomm_s == TRUE)) {prec = c;c = getc(fr);} else {putc(prec, fw);prec = c;c = getc(fr);}}
}

6. 运行结果

我这里将另一个练习1-13的C源文件作为输入, 测试运行结果.

执行:

执行结果, 左边是含注释的输入源代码文件1_13.c , 右边是删除注释后的源代码文件1_13_2.c :

然后我又重新编译了一下 1_13_2.c 也就是删除注释后的文件, 能通过编译, 但不完全肯定有没有删除正常字符. 大致看上去只删除了正常合法注释.

7. 结语

第1章最后一个作业题是检查C语言基本语法错误的题目, 这个程序实现有难度, 而且实现到什么程度也很灵活, K老师只是没说其实就是实现一点编译器的功能, 怕吓退初学者. 但这个程序做好了还有一定的实用性, 后续找时间做一下.

喜欢就点赞收藏, 您的点赞收藏是我创作的动力.


http://www.hkcw.cn/article/onPSqdaSnD.shtml

相关文章

数据结构与算法之中缀表达式的求值

栈 栈的一个实际需求 请输入一个表达式计算式&#xff1a;[7*22-51-53-3]点击计算【如下图】 栈的介绍 栈的英文为stack(stack)。栈是一个先入后出(FILO-First In Last Out)的有序列表。栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表…

显卡3080和4060哪个强 两款游戏性能对比

对于硬核玩家而言&#xff0c;选择一款合适的显卡至关重要。在市场上&#xff0c;NVIDIA的3080和4060两款显卡备受关注。很多朋友在选择时会疑惑&#xff0c;究竟是3080更强&#xff0c;还是4060更具性价比呢&#xff1f;今天我们就来深入分析这两款显卡&#xff0c;帮助大家做…

Visual Studio 2022 发布独立的 exe 文件

我们在用 Visual Studio 2022 写好一个 exe 程序之后&#xff0c;如果想把这个拿到其他地方运行&#xff0c;需要把 exe 所在的文件夹一起拿过去。 编译出来的 exe 文件需要其他几个文件一同放在同一目录才能运行&#xff0c;原因在于默认情况下&#xff0c;Visual Studio 是把…

若依项目AI 助手代码解析

基于 Vue.js 和 Element UI 的 AI 助手组件 一、组件整体结构 这个 AI 助手组件由三部分组成&#xff1a; 悬浮按钮&#xff1a;点击后展开 / 收起对话窗口对话窗口&#xff1a;显示历史消息和输入框API 调用逻辑&#xff1a;与 AI 服务通信并处理响应 <template><…

Java源码中有哪些细节可以参考?(持续更新)

欢迎来到啾啾的博客&#x1f431;。 记录学习点滴。分享工作思考和实用技巧&#xff0c;偶尔也分享一些杂谈&#x1f4ac;。 有很多很多不足的地方&#xff0c;欢迎评论交流&#xff0c;感谢您的阅读和评论&#x1f604;。 目录 String的比较final的使用transient避免序列化 St…

Linux | Shell脚本的常用命令

一. 常用字符处理命令 1.1 连续打印字符seq seq打印数字&#xff1b;且只能正向打印&#xff0c;不可反向连续打印 设置打印步长 指定打印格式 1.2 反向打印字符tac cat 正向&#xff0c;tac 反向 1.3 打印字符printf printf "打印的内容"指定格式打印内容 换行…

【FlashRAG】本地部署与demo运行(二)

前文【FlashRAG】本地部署与demo运行&#xff08;一&#xff09; 下载必要的模型文件 完成了项目拉取和依赖下载后&#xff0c;我们需要进一步下载模型文件 Faiss&#xff08;Facebook AI Similarity Search&#xff09;是由Facebook AI团队开发的高效相似性搜索和密集向量聚…

火狐安装自动录制表单教程——仙盟自动化运营大衍灵机——仙盟创梦IDE

打开火狐插件页面 安装完成 使用 功能 录制浏览器操作 录入地址 开始操作 录制完成 在当今快速发展的软件开发生态中&#xff0c;自动化测试已从一种新兴技术手段&#xff0c;转变为保障软件质量与开发效率不可或缺的关键环节。其重要性体现在多个维度&#xff0c;同时&#x…

【目标检测】【AAAI-2022】Anchor DETR

Anchor DETR&#xff1a; Query Design for Transformer-Based Object Detection 锚点DETR&#xff1a;基于Transformer的目标检测查询设计 论文链接 代码链接 摘要 在本文中&#xff0c;我们提出了一种基于Transformer的目标检测新型查询设计。此前的Transformer检测器中&am…

zTasker一款Windows自动化软件,提升效率:大小仅有10MB,免费无广告

一、zTasker是什么&#xff1f; zTasker是一款发布于2023年9月的免费无广告工具&#xff0c;专为Windows用户打造。它以仅8MB的轻量体积、极低资源占用&#xff08;内存消耗不足10MB&#xff09;和秒级启动速度脱颖而出&#xff0c;堪称“任务计划程序的终极强化版”。无论是定…

数学术语之源——绝对值(absolute value)(复数模?)

目录 1. 绝对值&#xff1a;(absolute value): 2. 复数尺度(复尺度)&#xff1a;(modulus): 1. 绝对值&#xff1a;(absolute value): 一个实数的绝对值是其不考虑(irrespective)符号的大小(magnitude)。在拉丁语中具有相同意思的单词是“modulus”&#xff0c;这个单词还…

USB充电检测仪-2.USB充电检测仪硬件设计

本系列文章的最终目标是制作一个USB充电检测仪&#xff0c;支持的功能&#xff1a; 显示USB充电电压、电流、功率、充电量&#xff08;单位WH&#xff09;&#xff1b;实现Typec口和USB-A口的相互转换&#xff08;仅支持USB 2.0&#xff09;&#xff1b; 当然网上有很多卖这种…

华院计算受邀参展第九届丝绸之路国际博览会暨中国东西部合作与投资贸易洽谈会

2025年5月21日至25日&#xff0c;以“丝路融通开放合作”为主题的第九届丝绸之路国际博览会暨中国东西部合作与投资贸易洽谈会在陕西西安国际会展中心隆重召开。应上海市国内合作交流服务中心和上海科创投集团的邀请&#xff0c;华院计算技术&#xff08;上海&#xff09;股份有…

智能路由革命:AI 生态系统的智能高速交警

研究和行业基准测试揭露了一个惊人的事实&#xff1a;大多数企业的 AI 系统运行效率只有 15% 到 20%。罪魁祸首是谁呢&#xff1f;就是糟糕的查询路由。 想象一下这个现实情况&#xff1a; 你所在的组织每在 AI 上花 10 块钱&#xff0c;就有 8 块钱是浪费在把简单查询发送到…

[yolov11改进系列]基于yolov11引入倒置残差块块注意力机制iEMA的python源码+训练源码

【iEMA介绍】 iRMB&#xff08;Inverted Residual Mobile Block&#xff09;的框架原理&#xff0c;是一种结合轻量级CNN和注意力机制的方法&#xff0c;用于改进移动设备上的目标检测模型。IRMB通过倒置残差块和元移动块实现高效信息处理&#xff0c;同时保持模型轻量化。本文…

深度学习实战110-基于深度学习的工业系统故障诊断技术研究(卷积网络+注意力机制模型)

大家好,我是微学AI,今天给大家介绍一下深度学习实战110-基于深度学习的工业系统故障诊断技术研究(卷积网络+注意力机制模型)。工业系统故障诊断是确保现代工业设备安全稳定运行的关键技术环节。随着工业自动化和智能化水平的不断提高,传统故障诊断方法在应对日益复杂、多变…

Fluence (FLT) 2026愿景:RWA代币化加速布局AI算力市场

2025年5月29日&#xff0c;苏黎世 - Fluence&#xff0c;企业级去中心化计算平台&#xff0c;荣幸地揭开其2026愿景的面纱&#xff0c;并宣布将于6月1日起启动四大新举措。 Fluence 成功建立、推出并商业化了其去中心化物理基础设施计算网络&#xff08;DePIN&#xff09;&…

科学智能赋能空间科学研究(2):AI4S 范式下空间科学实验的核心挑战

中国科学院空间应用工程与技术中心在空间科学实验领域的研究覆盖了多模态空间科学实验数据模式挖掘、领域知识抽取、跨学科知识融合与认知智能等研究内容&#xff0c;有效促进了空间科学实验领域的数据应用生态的体系化建设&#xff0c;相关研究成果已正式发表于权威学术期刊《…

QML 无边框窗口翻转动画

目录 引言核心组件实现无边框翻转窗口&#xff08;FlipableDemo.qml&#xff09;登录页面和设置页面&#xff08;省略&#xff09;主界面集成&#xff08;Main.qml&#xff09; 下载链接 引言 接上篇 QML 滑动与翻转效果&#xff08;Flickable与Flipable&#xff09; 。本文通…

若依框架修改模板,添加通过excel导入数据功能

版本&#xff1a;我后端使用的是RuoYi-Vue-fast版本&#xff0c;前端是RuoYi-Vue3 需求: 我需要每个侧边栏功能都需要具有导入excel功能&#xff0c;但是若依只有用户才具备&#xff0c;我需要代码生成的每个功能都拥有导入功能。​ 每次生成一个一个改实在是太麻烦了。索性…