C 练习实例87(保姆级教程)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目:《Spring AI 项目实战》 正在持续爆肝中,基于 Spring AI + Spring Boot 3.x + JDK 21..., 点击查看 ;
- 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观
前言
在编程学习的旅程中,实践是巩固理论的最佳方式。C 练习实例87 是一个经典且富有挑战性的编程任务,它不仅考验对基础语法的掌握,还要求开发者具备一定的逻辑分析能力。无论是编程初学者还是中级开发者,通过这个实例都能获得多方面的提升。本文将从问题分析、代码实现、知识点解析和扩展思考四个维度展开,帮助读者系统性地理解和掌握这一实例的核心内容。
问题描述与目标
C 练习实例87 的具体要求通常是:编写一个程序,统计文本文件中每个单词出现的频率,并按频率从高到低输出结果。例如,输入文本为 "hello world hello C programming C",输出应为:
hello: 2
C: 2
world: 1
programming: 1
这一任务涉及文件操作、字符串处理、数据结构设计等多个知识点,对开发者综合能力要求较高。
代码实现步骤解析
第一步:理解需求,拆解问题
首先,需要明确三个关键步骤:
- 读取文件内容:将文本文件中的所有单词提取出来。
- 统计单词频率:记录每个单词出现的次数。
- 排序与输出:按频率从高到低排序并打印结果。
形象比喻:
可以将这一过程类比为整理图书馆的书籍。假设我们需要统计某本书中每个关键词出现的次数。首先,我们需要把书的内容“扫描”一遍(读取文件),然后记录每个关键词出现的次数(统计频率),最后按出现次数从多到少排列关键词(排序与输出)。
第二步:实现文件读取与单词提取
使用C语言的文件操作函数(如 fopen
, fgets
)读取文本文件。关键点在于:
- 如何逐行读取文件内容?
- 如何将一行文本分割为单词?
示例代码片段(读取与分割单词):
#include <stdio.h>
#include <string.h>
#include <ctype.h>
// 函数:将字符串分割为单词数组
void split_words(char *line, char *words[], int *count) {
char *token = strtok(line, " \t\n");
while (token != NULL) {
// 去除标点符号(如逗号、句号)
int len = strlen(token);
if (len > 0 && !isalnum(token[len-1])) {
token[len-1] = '\0';
}
words[(*count)++] = token;
token = strtok(NULL, " \t\n");
}
}
int main() {
FILE *fp = fopen("input.txt", "r");
if (!fp) {
perror("Failed to open file");
return 1;
}
char line[256];
char *words[100];
int word_count = 0;
while (fgets(line, sizeof(line), fp)) {
split_words(line, words, &word_count);
}
fclose(fp);
return 0;
}
关键知识点解释:
strtok
函数:用于分割字符串,分隔符为“空格、制表符、换行符”。- 去除标点符号:通过检查单词末尾字符是否为非字母数字,确保统计准确性。
第三步:统计单词频率
统计单词频率的核心是使用哈希表(Hash Table) 或 结构体数组 来记录每个单词及其出现次数。由于C语言本身没有内置的哈希表,这里采用结构体数组的方式:
定义结构体与数据存储逻辑:
typedef struct {
char word[50]; // 单词
int count; // 出现次数
} WordCount;
// 函数:统计单词频率
void count_words(WordCount *wc, char *words[], int total_words) {
int current = 0; // 当前已记录的单词数
for (int i = 0; i < total_words; i++) {
int found = 0;
for (int j = 0; j < current; j++) {
if (strcmp(wc[j].word, words[i]) == 0) {
wc[j].count++;
found = 1;
break;
}
}
if (!found) {
strcpy(wc[current].word, words[i]);
wc[current].count = 1;
current++;
}
}
}
逻辑分析:
- 遍历所有单词,若单词已存在结构体数组中,则计数器+1;
- 若不存在,则将新单词加入数组,并初始化计数器为1。
第四步:排序与输出结果
使用快速排序(Quick Sort) 或 冒泡排序(Bubble Sort) 对结构体数组按 count
字段降序排列:
排序函数实现:
// 比较函数:按 count 字段降序排序
int compare(const void *a, const void *b) {
WordCount *w1 = (WordCount *)a;
WordCount *w2 = (WordCount *)b;
return (w2->count - w1->count);
}
// 在 main 函数中调用排序
qsort(wc, current, sizeof(WordCount), compare);
完整代码整合与测试
以下是整合后的完整代码,包含文件读取、统计、排序和输出功能:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
typedef struct {
char word[50];
int count;
} WordCount;
void split_words(char *line, char *words[], int *count);
void count_words(WordCount *wc, char *words[], int total_words);
int main() {
FILE *fp = fopen("input.txt", "r");
if (!fp) {
perror("Failed to open file");
return 1;
}
char line[256];
char *words[100];
int word_count = 0;
while (fgets(line, sizeof(line), fp)) {
split_words(line, words, &word_count);
}
fclose(fp);
// 统计单词频率
WordCount wc[100];
count_words(wc, words, word_count);
// 排序
int current = word_count; // 实际有效单词数
qsort(wc, current, sizeof(WordCount), compare);
// 输出结果
for (int i = 0; i < current; i++) {
printf("%s: %d\n", wc[i].word, wc[i].count);
}
return 0;
}
void split_words(char *line, char *words[], int *count) {
char *token = strtok(line, " \t\n");
while (token != NULL) {
int len = strlen(token);
if (len > 0 && !isalnum(token[len-1])) {
token[len-1] = '\0';
}
words[(*count)++] = token;
token = strtok(NULL, " \t\n");
}
}
void count_words(WordCount *wc, char *words[], int total_words) {
int current = 0;
for (int i = 0; i < total_words; i++) {
int found = 0;
for (int j = 0; j < current; j++) {
if (strcmp(wc[j].word, words[i]) == 0) {
wc[j].count++;
found = 1;
break;
}
}
if (!found) {
strcpy(wc[current].word, words[i]);
wc[current].count = 1;
current++;
}
}
}
int compare(const void *a, const void *b) {
WordCount *w1 = (WordCount *)a;
WordCount *w2 = (WordCount *)b;
return (w2->count - w1->count);
}
关键知识点解析
1. 文件操作与字符串处理
fgets
函数:逐行读取文件内容,避免一次性读取过大数据量。strtok
函数:通过分隔符分割字符串,需注意其修改原字符串的特性。
2. 结构体与数组的联合使用
- 结构体设计:通过
WordCount
结构体同时存储单词和计数器,逻辑清晰。 - 动态与静态数组:此处使用静态数组
WordCount wc[100]
,需确保预估的容量足够。
3. 排序算法的选择
qsort
函数:C标准库提供快速排序,需自定义比较函数。- 时间复杂度:排序步骤的时间复杂度为
O(n log n)
,适合中等规模数据。
常见问题与优化建议
问题1:内存不足或溢出
- 现象:当输入文件过大时,静态数组
words
或wc
可能不足以存储所有数据。 - 解决方案:使用动态内存分配(
malloc
/realloc
)或链表结构。
问题2:大小写敏感
- 现象:当前代码将 "Hello" 和 "hello" 视为不同单词。
- 优化:在分割单词后,统一转换为小写(如
tolower
函数)。
问题3:非字母数字字符的处理
- 现象:如单词结尾带标点(如 "hello."),需进一步优化
split_words
函数。
扩展思考与应用
1. 性能优化
- 使用哈希表(如
uthash
库)代替线性搜索,将统计时间复杂度从O(n²)
降至O(n)
。
2. 功能扩展
- 添加命令行参数,支持指定输入文件名。
- 将结果按字母顺序排序,或导出为 CSV 文件。
3. 现实场景应用
- 日志分析:统计服务器日志中错误代码的出现频率。
- 自然语言处理:作为文本分析的基础模块,用于关键词提取。
结论
通过 C 练习实例87 的实践,开发者不仅掌握了文件操作、字符串处理和排序算法等基础技能,还学会了如何将理论知识转化为实际代码。这一实例的复杂性在于需要整合多个知识点,但通过分步骤拆解、逐个击破,即使是编程初学者也能逐步攻克。建议读者在理解代码逻辑后,尝试修改输入文件、调整参数或添加新功能,进一步巩固学习成果。
编程是一门实践的艺术,而 C 练习实例87 正是这门艺术中的一块基石。通过不断练习与探索,开发者将能够构建更复杂、更高效的程序。