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  

这一任务涉及文件操作、字符串处理、数据结构设计等多个知识点,对开发者综合能力要求较高。


代码实现步骤解析

第一步:理解需求,拆解问题

首先,需要明确三个关键步骤:

  1. 读取文件内容:将文本文件中的所有单词提取出来。
  2. 统计单词频率:记录每个单词出现的次数。
  3. 排序与输出:按频率从高到低排序并打印结果。

形象比喻
可以将这一过程类比为整理图书馆的书籍。假设我们需要统计某本书中每个关键词出现的次数。首先,我们需要把书的内容“扫描”一遍(读取文件),然后记录每个关键词出现的次数(统计频率),最后按出现次数从多到少排列关键词(排序与输出)。


第二步:实现文件读取与单词提取

使用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:内存不足或溢出

  • 现象:当输入文件过大时,静态数组 wordswc 可能不足以存储所有数据。
  • 解决方案:使用动态内存分配(malloc/realloc)或链表结构。

问题2:大小写敏感

  • 现象:当前代码将 "Hello" 和 "hello" 视为不同单词。
  • 优化:在分割单词后,统一转换为小写(如 tolower 函数)。

问题3:非字母数字字符的处理

  • 现象:如单词结尾带标点(如 "hello."),需进一步优化 split_words 函数。

扩展思考与应用

1. 性能优化

  • 使用哈希表(如 uthash 库)代替线性搜索,将统计时间复杂度从 O(n²) 降至 O(n)

2. 功能扩展

  • 添加命令行参数,支持指定输入文件名。
  • 将结果按字母顺序排序,或导出为 CSV 文件。

3. 现实场景应用

  • 日志分析:统计服务器日志中错误代码的出现频率。
  • 自然语言处理:作为文本分析的基础模块,用于关键词提取。

结论

通过 C 练习实例87 的实践,开发者不仅掌握了文件操作、字符串处理和排序算法等基础技能,还学会了如何将理论知识转化为实际代码。这一实例的复杂性在于需要整合多个知识点,但通过分步骤拆解、逐个击破,即使是编程初学者也能逐步攻克。建议读者在理解代码逻辑后,尝试修改输入文件、调整参数或添加新功能,进一步巩固学习成果。

编程是一门实践的艺术,而 C 练习实例87 正是这门艺术中的一块基石。通过不断练习与探索,开发者将能够构建更复杂、更高效的程序。

最新发布