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

news2025/7/21 6:07:47

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 0

int 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.coloradmin.cn/o/2392777.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

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

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

如何撰写一篇优质 Python 相关的技术文档 进阶指南

&#x1f49d;&#x1f49d;&#x1f49d;在 Python 项目开发与协作过程中&#xff0c;技术文档如同与团队沟通的桥梁&#xff0c;能极大提高工作效率。但想要打造一份真正实用且高质量的 Python 技术文档类教程&#xff0c;并非易事&#xff0c;需要在各个环节深入思考与精心打…

MiniMax V-Triune让强化学习(RL)既擅长推理也精通视觉感知

MiniMax 近日在github上分享了技术研究成果——V-Triune&#xff0c;这次MiniMax V-Triune的发布既是AI视觉技术也是应用工程上的一次“突围”&#xff0c;让强化学习&#xff08;RL&#xff09;既擅长推理也精通视觉感知&#xff0c;其实缓解了传统视觉RL“鱼和熊掌不可兼得”…

Hash 的工程优势: port range 匹配

昨天和朋友聊到 “如何匹配一个 port range”&#xff0c;觉得挺有意思&#xff0c;简单写篇散文。 回想起十多年前&#xff0c;我移植并优化了 nf-HiPAC&#xff0c;当时还看不上 ipset hash&#xff0c;后来大约七八年前&#xff0c;我又舔 nftables&#xff0c;因为用它可直…

HackMyVM-Dejavu

信息搜集 主机发现 ┌──(root㉿kali)-[~] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:39:60:4c, IPv4: 192.168.43.126 Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan) 192.168.43.1 c6:45:66:05:91:88 …

Opencv实用操作5 图像腐蚀膨胀

相关函数 腐蚀函数 img1_erosion cv2.erode(img1,kernel,iterations1) &#xff08;图片&#xff0c;卷积核&#xff0c;次数&#xff09; 膨胀函数 img_dilate cv2.dilate(img2,kernel1,iterations1) &#xff08;图片&#xff0c;卷积核&#xff0c;次数&#xff09;…

【赵渝强老师】OceanBase的部署架构

OceanBase数据库支持无共享&#xff08;Shared-Nothing&#xff0c;SN&#xff09;模式和共享存储&#xff08;Shared-Storage&#xff0c;SS&#xff09;模式两种部署架构。 一、 无共享&#xff08;Shared-Nothing&#xff0c;SN&#xff09;模式 在SN模式下&#xff0c;各…

LangChain【3】之进阶内容

文章目录 说明一 LangChain Chat Model1.1 少量示例提示(Few-Shot Prompting)1.2 Few-Shot示例代码1.3 示例选择器&#xff08;Eample selectors&#xff09;1.4 ExampleSelector 类型1.5 ExampleSelector案例代码1.6 LangServe工具1.7 LangServe安装1.8 langchain项目结构1.9 …

大规模JSON反序列化性能优化实战:Jackson vs FastJSON深度对比与定制化改造

背景&#xff1a;500KB JSON处理的性能挑战 在当今互联网复杂业务场景中&#xff0c;处理500KB以上的JSON数据已成为常态。 常规反序列化方案在CPU占用&#xff08;超30%&#xff09;和内存峰值&#xff08;超原始数据3-5倍&#xff09;方面表现堪忧。 本文通过Jackson与Fas…

AWS EC2 实例告警的创建与删除

在AWS云环境中&#xff0c;监控EC2实例的运行状态至关重要。通过CloudWatch告警&#xff0c;用户可以实时感知实例的CPU、网络、磁盘等关键指标异常。本文将详细介绍如何通过AWS控制台创建EC2实例告警&#xff0c;以及如何安全删除不再需要的告警规则&#xff0c;并附操作截图与…

STM32 搭配 嵌入式SD卡在智能皮电手环中的应用全景评测

在智能皮电手环及数据存储技术不断迭代的当下&#xff0c;主控 MCU STM32H750 与存储 SD NAND MKDV4GIL-AST 的强强联合&#xff0c;正引领行业进入全新发展阶段。二者凭借低功耗、高速读写与卓越稳定性的深度融合&#xff0c;以及高容量低成本的突出优势&#xff0c;成为大规模…

黑马点评项目01——短信登录以及登录校验的细节

1.短信登录 1.1 Session方式实现 前端点击发送验证码&#xff0c;后端生成验证码后&#xff0c;向session中存放键值对&#xff0c;键是"code"&#xff0c;值是验证码&#xff1b;然后&#xff0c;后端生成sessionID以Cookie的方式发给前端&#xff0c;前端拿到后&a…

【笔记】Windows 系统安装 Scoop 包管理工具

#工作记录 一、问题背景 在进行开源项目 Suna 部署过程中&#xff0c;执行设置向导时遭遇报错&#xff1a;❌ Supabase CLI is not installed. 根据资料检索&#xff0c;需通过 Windows 包管理工具Scoop安装 Supabase CLI。 初始尝试以管理员身份运行 PowerShell 安装 Scoop…

MySQL之约束和表的增删查改

MySQL之约束和表的增删查改 一.数据库约束1.1数据库约束的概念1.2NOT NULL 非空约束1.3DEFAULT 默认约束1.4唯一约束1.5主键约束和自增约束1.6自增约束1.7外键约束1.8CHECK约束 二.表的增删查改2.1Create创建2.2Retrieve读取2.3Update更新2.4Delete删除和Truncate截断 一.数据库…

Oracle数据库性能优化的最佳实践

原创&#xff1a;厦门微思网络 以下是 Oracle 数据库性能优化的最佳实践&#xff0c;涵盖设计、SQL 优化、索引管理、系统配置等关键维度&#xff0c;帮助提升数据库响应速度和稳定性&#xff1a; 一、SQL 语句优化 1. 避免全表扫描&#xff08;Full Table Scan&#xff09;…

汽配快车道:助力汽车零部件行业的产业重构与数字化出海

汽配快车道&#xff1a;助力汽车零部件行业的数字化升级与出海解决方案。 在当今快速发展的汽车零部件市场中&#xff0c;随着消费者对汽车性能、安全和舒适性的要求不断提高&#xff0c;汽车刹车助力系统作为汽车安全的关键部件之一&#xff0c;其市场需求也在持续增长。汽车…

Windows 11 家庭版 安装Docker教程

Windows 家庭版需要通过脚本手动安装 Hyper-V 一、前置检查 1、查看系统 快捷键【winR】&#xff0c;输入“control” 【控制面板】—>【系统和安全】—>【系统】 2、确认虚拟化 【任务管理器】—【性能】 二、安装Hyper-V 1、创建并运行安装脚本 在桌面新建一个 .…

PyQt6基础_QtCharts绘制横向柱状图

前置&#xff1a; pip install PyQt6-Charts 结果&#xff1a; 代码&#xff1a; import sysfrom PyQt6.QtCharts import (QBarCategoryAxis, QBarSet, QChart,QChartView, QValueAxis,QHorizontalBarSeries) from PyQt6.QtCore import Qt,QSize from PyQt6.QtGui import QP…

《TCP/IP 详解 卷1:协议》第2章:Internet 地址结构

基本的IP地址结构 分类寻址 早期Internet采用分类地址&#xff08;Classful Addressing&#xff09;&#xff0c;将IPv4地址划分为五类&#xff1a; A类和B类网络号通常浪费太多主机号&#xff0c;而C类网络号不能为很多站点提供足够的主机号。 子网寻址 子网&#xff08;Su…

如何通过一次需求评审,让项目效率提升50%?

想象一下&#xff0c;你的团队启动了一个新项目&#xff0c;但需求模糊不清&#xff0c;开发到一半才发现方向错了&#xff0c;返工、加班、客户投诉接踵而至……听起来像噩梦&#xff1f;一次完美的需求评审就能避免这一切&#xff01;它就像项目的“导航仪”&#xff0c;确保…