当前位置: 首页 > news >正文

统计文件内容:统计一个文本文件中字符、单词、行数。

涉及的知识点

  1. 文件 I/O

    • FILE *fopen/fclosefgetc

    • 处理 EOF 结束条件。

  2. 字符分类

    • <ctype.h> 提供 isspaceisalpha 等函数。

    • 通过 isspace 判断空白字符,便于单词分隔。

  3. 有限状态机

    • in_word 状态跟踪是否在单词内部,实现准确的单词计数。

  4. 命令行参数

    • argc/argv 的使用,以提高程序的通用性。

  5. 错误处理

    • 检查 fopen 是否成功,用 perror 输出具体系统错误;

    • 在读写循环中也可择机检查 ferror

  6. 边界情况

    • 是否把最后一行未以 \n 结尾也计作一行;

    • 空文件或只有空白字符的文件处理。

题目描述
编写一个 C 程序,统计一个文本文件中 字符数单词数行数(类似于 Unix 下的 wc 命令),要求:

  1. 从命令行接收一个文件名作为参数;

  2. 以只读模式打开该文件,字符逐个读取;

  3. 用有限状态机方法识别“单词”边界(单词由空白字符分隔);

  4. 输出统计结果并做充分的错误检查;

  5. 代码要有详细注释,并在最后总结涉及的知识点。

参考代码:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>int main(int argc, char *argv[])
{if(argc != 2){fprintf(stderr,"用法:%s<文本文件名>\n",argv[0]);return EXIT_FAILURE;}const char *filename = argv[1];FILE *fp = fopen(filename,"r");if(!fp){perror("打开文件失败");return EXIT_FAILURE;}long char_count = 0;//字符计数long word_count = 0;//单词计数long line_count = 0;//行计数int in_word = 0;//单词状态机:0=在空白外,1=在单词内int ch;//逐字符读取直到EOFwhile((ch = fgetc(fp)) != EOF){char_count++;//每输入一个字符就加1if(ch == '\n'){line_count++;//遇到换行符就算作一行}//单词边界检测if(isspace(ch)){//如果当前是空白,切换到“非单词”状态in_word = 0;}else if (!in_word){//如果当前不是空白,且之前不在单词中,这是一个新的单词的开始in_word = 1;word_count++;}}//如果最后一行没有以‘\n’结尾,也应记为一行if(char_count>0 &&ch !='\n'){line_count++;}fclose(fp);//输出结果printf("统计结果——文件:%s\n",filename);printf("行数:%ld",line_count);printf("单词数:%ld",word_count);printf("字符数:%ld",char_count);return EXIT_SUCCESS;
}

注:空格和换行符也统计在内了

代码讲解与注释

  1. 命令行参数检查

    if (argc != 2) { fprintf(stderr, "用法: %s <文本文件名>\n", argv[0]); return EXIT_FAILURE; }

    • 确保用户传入了一个文件名,否则打印使用说明并退出。

  2. 打开文件并检查

    FILE *fp = fopen(filename, "r"); if (!fp) { perror("打开文件失败"); return EXIT_FAILURE; }

    • "r" 模式打开只读文件;

    • perror 会打印系统错误原因(如“权限不足”)。

  3. 初始化计数器与状态

    long char_count = 0, word_count = 0, line_count = 0; int in_word = 0; int ch;

    • in_word 用于跟踪是否处在一个单词内部:0 = 否,1 = 是。

  4. 逐字符读取

    while ((ch = fgetc(fp)) != EOF) { char_count++; if (ch == '\n') line_count++; ... }

    • fgetc 返回下一个字符(作为 int),或到达 EOF 时返回 EOF

    • 每读取一个字符即增加 char_count

    • 碰到换行符 \nline_count++

  5. 单词识别状态机

    if (isspace(ch)) { in_word = 0; } else if (!in_word) { in_word = 1; word_count++; }

    • isspace(ch) 判断是否为空白(空格、换行、制表等);

    • 当遇到空白时,重置 in_word

    • 当遇到非空白且刚好从空白转入,这就是新单词的开头,word_count++

  6. 文件关闭和结果输出

    fclose(fp); printf("行数: %ld, 单词数: %ld, 字符数: %ld\n", line_count, word_count, char_count);

    • fclose 关闭文件流;

    • 打印最终统计结果。


下面我按每一段代码来说明它的作用和执行流程:

int main(int argc, char *argv[])
{
  • 程序入口argc 保存命令行参数个数,argv 是参数字符串数组。

  • argc 至少为 1(程序名本身)。

    if (argc != 2){fprintf(stderr, "用法:%s <文本文件名>\n", argv[0]);return EXIT_FAILURE;}

参数检查:程序要求恰好一个额外参数(要统计的文件名),否则打印“用法”提示并以失败状态退出。

    const char *filename = argv[1];

argv 中取出用户指定的文件名,保存到 filename

    FILE *fp = fopen(filename, "r");if (!fp){perror("打开文件失败");return EXIT_FAILURE;}
  • 打开文件:以只读模式 ("r") 打开。

  • 若返回 NULL,表示打开失败,用 perror 输出系统错误信息,然后退出。

    long char_count = 0;  // 字符计数long word_count = 0;  // 单词计数long line_count = 0;  // 行计数

三个计数器,分别用于统计字符总数单词数行数

    int in_word = 0;      // 单词状态机:0=不在单词中,1=在单词中int ch;
  • in_word 用于跟踪是否当前已经处在一个单词内部,初始为 0(不在单词中)。

  • ch 用来存 fgetc 读取到的每个字符(int 类型,以便区分 EOF)。

    // 逐字符读取直到 EOFwhile ((ch = fgetc(fp)) != EOF){char_count++;  // 每读取一个字符就 +1
  • 读取循环fgetc(fp) 每次读取一个字符,直到文件末尾返回 EOF

  • 字符计数:无论是什么字符,只要不是 EOFchar_count 就加 1。

        if (ch == '\n'){line_count++;  // 遇到换行符就 +1 行}

行计数:每次读到换行符 '\n',认为是一行结束,line_count 加 1。

        // 单词边界检测if (isspace(ch)){// 遇到任何空白字符(空格、制表、换行等),标记为“非单词”状态in_word = 0;}else if (!in_word){// 当前字符不是空白,且之前不在单词中// 说明这是一个新单词的开头in_word = 1;word_count++;}

单词计数

  • isspace(ch) 判断是否空白。

  • 如果空白,就重置状态 in_word = 0

  • 如果不是空白且 in_word == 0(此前不在单词里),说明遇到新单词,word_count++ 并置 in_word = 1

    }// 如果最后一行没有以 '\n' 结尾,也应算作一行if (char_count > 0 && ch != '\n'){line_count++;}

末尾行处理

  • 循环结束后,ch 已经是 EOF(不是 '\n')。

  • 如果文件非空且最后一个读到的字符不是换行,那么最后一行没有被前面的 if (ch=='\n') 统计过,就要再加 1 行。

    fclose(fp);

关闭文件,释放资源。

    // 输出结果printf("统计结果——文件:%s\n", filename);printf("行数:%ld\n", line_count);printf("单词数:%ld\n", word_count);printf("字符数:%ld\n", char_count);return EXIT_SUCCESS;
}
  • 打印统计:按照行、单词、字符的顺序输出,并以 EXIT_SUCCESS 正常结束程序。


涉及知识点

  1. 命令行参数argc/argv 的使用。

  2. 文件 I/Ofopenfgetcfclose 以及 EOF 判断。

  3. 字符分类isspace 判断空白字符。

  4. 有限状态机:用 in_word 控制单词边界检测。

  5. 行尾处理:对不以 '\n' 结尾的最后一行进行单独统计。

难点讲解:

下面一行一行来拆解这些最常见的命令行参数处理写法:

int main(int argc, char *argv[])
  • main 函数的签名

    • int main(...) 表示程序的入口函数,返回一个整数给操作系统(0 常表示成功,非 0 表示失败)。

    • argcargv 用来接收从命令行传给程序的参数。

  • argc(Argument Count)

    • 类型是 int,表示“参数个数”。

    • 它至少为 1,因为命令行最少有一个参数:程序本身的名字

    • 例如当你在命令行输入

      ./stats myfile.txt
      

      那么

    • argc == 2

    • 第 1 个参数 argv[0]"./stats"(程序名)

    • 第 2 个参数 argv[1]"myfile.txt"(用户输入的文件名)

  • argv(Argument Values)

    • 类型是 char *argv[],也就是“字符串指针数组”。

    • argv[i]i 是下标,从 0 开始)是第 i 个参数的 C 字符串指针。

    • 具体:

      • argv[0] 总是程序自身的名称或路径。

      • argv[1]argv[2]…依次是用户在命令行输入的各个参数。

  • 为什么用 [] 下标?

    • argv 是一个数组名,argv[i] 就是数组索引操作,取第 i 项。

    • 中括号里的 “0” 和 “1” 就是索引:

      • argv[0] → 第 0 项 → 程序名

      • argv[1] → 第 1 项 → 第一个用户参数

if (argc != 2)fprintf(stderr, "用法:%s <文本文件名>\n", argv[0]);
  • if (argc != 2)

    • 我们的程序设计成必须带一个参数(文件名)才能正常工作。

    • 如果 argc 不是 2(即没有给出,或者给了过多的参数),就进入 if 里报错并退出。

  • fprintf(stderr, "用法:%s <文本文件名>\n", argv[0]);

    • stderr 是“标准错误输出”,用于打印错误或帮助信息。

    • 格式串里的 %s 会被 argv[0] 替换,也就是程序名。

    • 这样用户看到的提示就会是:

  • 用法:./stats <文本文件名>
    

    无论程序是怎么被调用的,提示都会自动显示正确的可执行文件名。

const char *filename = argv[1];
  • filename 就指向了用户在命令行中输入的第一个真正参数(文件名),类型是 const char *,意味着“只读的 C 字符串指针”。

  • 后续你就可以用 fopen(filename, "r") 来打开这个文件,而不用再写 argv[1],让代码更具可读性。

小结

  • argc:参数总数,包括程序本身名字。

  • argv:参数值数组,argv[0] 是程序名,argv[1]…是用户实际给的参数。

  • if (argc != expected):常用来校验用户是否按预期提供了足够(也不太多)的参数。

  • argv[i]:通过数组下标取出第 i 个参数,下标从 0 开始。

http://www.lryc.cn/news/584328.html

相关文章:

  • C#中异步任务取消:CancellationToken
  • HOOK专题
  • Linux流量分析:tcpdump wireshark
  • EchoSight-Pro发布说明
  • 【网络】Linux 内核优化实战 - net.ipv4.tcp_fin_timeout
  • Android Coil 3 data加载图的Bitmap或ByteArray数据类型,Kotlin
  • 设计总监年中复盘:用Adobe XD内容识别布局,告别“手动调距”
  • 大模型在膀胱癌诊疗全流程预测及应用研究报告
  • HarmonyOS AI辅助编程工具(CodeGenie)UI生成
  • RabbitMQ 高级特性之消息分发
  • web 系统对接飞书三方登录完整步骤实战使用示例
  • 网络安全(初级)(1)
  • AI+低代码双引擎驱动:重构智能业务系统的产品逻辑
  • Fiddler中文版全面评测:功能亮点、使用场景与中文网资源整合指南
  • 深入理解机器学习
  • CPU调度调度算法
  • 链表算法之【合并两个有序链表】
  • Web后端开发工程师AI协作指南
  • 【java面试day4】redis缓存-数据持久化
  • AI赋能生活:深度解析与技术洞察
  • 【论文阅读】Decoupled Knowledge Distillation
  • Spring Boot 整合 RabbitMQ
  • 大语言模型驱动智能语音应答:技术演进与架构革新
  • Java Reference类及其实现类深度解析:原理、源码与性能优化实践
  • 聊一聊 Linux 上对函数进行 Hook 的两种方式
  • 使用EasyExcel动态合并单元格(模板方法)
  • Centos 7下使用C++使用Rdkafka库实现生产者消费者
  • Houdini 分布式解算效率瓶颈突破:渲染 101 云集群实战指南
  • 编程实践:单例模式(懒汉模式+饿汉模式)
  • 面试技术问题总结一