C 语言综合练习五:文本文件统计工具
更新时间:2026-08-26。本文是
languages/c/主题入门层第 76 篇。做一个像wc的小工具:统计文本文件的行数、单词数、字符数。它把文件读取(fgetc)、字符分类(ctype.h)、状态机思维(在单词中/不在单词中)串在一起——"边界判断"是这个练习的核心训练。代码已入库demos/c-beginner-practice/text_stats.c。
本文要回答的问题
- 怎么流式统计一个大文件而不占内存?
- "单词"的边界怎么判断?
- 命令行传入文件名怎么做?
一、需求拆解
$ ./text_stats file.txt
12 行, 87 单词, 523 字符| 统计项 | 方法 |
|---|---|
| 字符数 | 读一个数一个 |
| 行数 | 遇到 \n +1 |
| 单词数 | 状态机:空白 → 非空白算新单词开始 |
单词定义:连续的非空白字符序列(isalpha/isdigit 的并集,这里简化为 !isspace)。
二、完整代码
c
#include <stdio.h>
#include <ctype.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "用法: %s 文件名\n", argv[0]);
return 1;
}
FILE *fp = fopen(argv[1], "r");
if (fp == NULL) {
perror("打开失败");
return 1;
}
long chars = 0, lines = 0, words = 0;
int in_word = 0; // 状态:是否正处在单词里
int c;
while ((c = fgetc(fp)) != EOF) {
chars++;
if (c == '\n') lines++;
if (isspace(c)) {
in_word = 0; // 离开单词
} else if (!in_word) {
in_word = 1; // 进入新单词
words++;
}
}
fclose(fp);
printf("%ld 行, %ld 单词, %ld 字符\n", lines, words, chars);
return 0;
}三、关键设计点
1. 流式读取,O(1) 内存:
c
while ((c = fgetc(fp)) != EOF) { ... }逐字符读,不管文件多大都只占固定内存——不用把整个文件装进数组(字符与行读取 讲过)。
2. 单词计数用状态机:
空白 → 非空白 = 新单词开始(words++)
非空白 → 空白 = 单词结束(in_word=0)
非空白 → 非空白 = 还在单词里(不计数)in_word 记录"是否在单词里",只在从空白进入非空白的瞬间计数——连续空格不会重复计数。
3. 文件打不开用 perror:直接告诉用户为什么(文件错误处理)。
四、运行演示
$ cat sample.txt
hello world
c language$ make run FILE=sample.txt
2 行, 4 单词, 24 字符(Linux 下 wc sample.txt 输出 2 4 24,二者一致,可交叉验证。)
五、扩展练习
- 支持多文件(
./text_stats a.txt b.txt)并输出合计; - 统计字母/数字/空白分别多少(用 ctype 分类);
- 输出出现次数最多的单词(需要哈希表,进阶)。
六、一句话总结
c
/*
* text_stats.c —— C 语言入门综合练习五:文本文件统计工具
* 文档: languages/c/beginner/76-practice-text-stats.md
* 编译: make && ./text_stats sample.txt
*/
#include <stdio.h>
#include <ctype.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "用法: %s 文件名\n", argv[0]);
return 1;
}
FILE *fp = fopen(argv[1], "r");
if (fp == NULL) {
perror("打开失败");
return 1;
}
long chars = 0, lines = 0, words = 0;
int in_word = 0; /* 状态:是否正处在单词里 */
int c;
while ((c = fgetc(fp)) != EOF) {
chars++;
if (c == '\n') lines++;
if (isspace(c)) {
in_word = 0; /* 离开单词 */
} else if (!in_word) {
in_word = 1; /* 进入新单词 */
words++;
}
}
fclose(fp);
printf("%ld 行, %ld 单词, %ld 字符\n", lines, words, chars);
return 0;
}与本站主线衔接
- 逐字符读取与 EOF,见字符与行读取;
- 字符分类函数,见ctype.h 字符分类;
- 文件错误处理,见文件错误处理。