Linux自学day22-实现简单的单词及单词含义查询的功能

一 查询单词及单词含义

这里有一个单词表,以下是单词表的一部分:

由单词和单词后的释义构成,中间用空格隔开。

根据单词表中的内容,完成让用户输入单词就获得单词含义的功能。

二 方法一 链表

dict.c代码:

复制代码
#include "dict.h"
#include "public.h"

// 加载字典文件,返回链表头指针
struct list_head *load_dict_list(char *pfilename)
{
    // 定义链表头指针,初始化为 NULL
    struct list_head *phead = NULL;
    // 定义文件指针,初始化为 NULL
    FILE *fp = NULL;
    // 定义临时节点指针,初始化为 NULL
    word_t *ptmpnode = NULL;
    // 定义临时字符指针,初始化为 NULL
    char *ptmp = NULL;
    // 定义临时缓冲区,初始化为 0
    char tmpbuff[4096] = {0};
    // 定义 fgets 函数的返回值指针,初始化为 NULL
    char *pret = NULL;

    // 为链表头分配内存
    phead = malloc(sizeof(struct list_head));
    if (NULL == phead)
    {
        // 内存分配失败,输出错误信息
        ERR_MSG("fail to malloc");
        return NULL;
    }

    // 初始化链表头
    INIT_LIST_HEAD(phead);

    // 以只读方式打开字典文件
    fp = fopen(pfilename, "r");
    if (NULL == fp)
    {
        // 文件打开失败,输出错误信息
        ERR_MSG("fail to fopen");
        return NULL;
    }

    // 循环读取文件中的每一行
    while (1)
    {
        // 读取一行数据到临时缓冲区
        pret = fgets(tmpbuff, sizeof(tmpbuff), fp);
        if (NULL == pret)
        {
            // 读取到文件末尾,退出循环
            break;
        }

        // 将临时字符指针指向临时缓冲区
        ptmp = tmpbuff;
        // 找到第一个空格或字符串结束符
        while (*ptmp != ' ' && *ptmp != '\0')
        {
            ptmp++;
        }
        // 将空格替换为字符串结束符
        *ptmp = '\0';
        // 跳过空格
        ptmp++;
        while (*ptmp == ' ')
        {
            ptmp++;
        }

        // 为临时节点分配内存
        ptmpnode = malloc(sizeof(word_t));
        if (NULL == ptmpnode)
        {
            // 内存分配失败,输出错误信息
            ERR_MSG("fail to malloc");
            return NULL;
        }

        // 将单词复制到临时节点的 word 字段
        strcpy(ptmpnode->word, tmpbuff);
        // 将含义复制到临时节点的 mean 字段
        strcpy(ptmpnode->mean, ptmp);

        // 将临时节点添加到链表尾部
        list_add_tail(&ptmpnode->node, phead);
    }
    
    // 关闭文件
    fclose(fp);

    // 返回链表头指针
    return phead;
}

// 查找单词的含义,返回含义的指针
char *search_word_mean(struct list_head *phead, char *pword)
{
    // 定义临时节点指针,初始化为 NULL
    word_t *ptmpnode = NULL;

    // 遍历链表中的每个节点
    list_for_each_entry(ptmpnode, phead, node)
    {
        // 比较节点中的单词和要查找的单词是否相等
        if (0 == strcmp(ptmpnode->word, pword))
        {
            // 找到匹配的单词,返回含义的指针
            return ptmpnode->mean;
        }
    }

    // 没有找到匹配的单词,返回 NULL
    return NULL;
}

// 释放字典链表占用的内存
int free_dict_list(struct list_head *phead)
{
    // 定义两个临时节点指针,初始化为 NULL
    word_t *ptmpnode1 = NULL;
    word_t *ptmpnode2 = NULL;

    // 安全地遍历链表中的每个节点
    list_for_each_entry_safe(ptmpnode1, ptmpnode2, phead, node)
    {
        // 从链表中删除当前节点
        list_del(&ptmpnode1->node);
        // 释放当前节点占用的内存
        free(ptmpnode1);
    }
    // 释放链表头占用的内存
    free(phead);

    // 函数执行成功,返回 0
    return 0;
}
  • malloc 函数用于动态分配内存,需要检查返回值是否为 NULL

  • fopen 函数用于打开文件,fclose 函数用于关闭文件。

  • fgets 函数用于从文件中读取一行数据。

  • strcpy 函数用于复制字符串。

  • list_add_tail 函数用于将节点添加到链表尾部。

  • list_for_each_entry 宏用于遍历链表中的每个节点。

  • list_for_each_entry_safe 宏用于安全地遍历链表中的每个节点,避免在删除节点时出现错误。

main.c 代码:

复制代码
#include <stdio.h>
#include "public.h"
#include "dict.h"

// 主函数,程序的入口点
// argc 是命令行参数的数量,argv 是命令行参数的数组
int main(int argc, const char **argv)
{
    // 定义一个链表头指针,初始化为 NULL
    struct list_head *phead = NULL;
    // 定义一个字符数组,用于存储用户输入的单词,初始化为 0
    char word[256] = {0};
    // 定义一个字符指针,用于存储查找到的单词含义,初始化为 NULL
    char *pmean = NULL;

    // 调用 load_dict_list 函数加载字典文件,返回链表头指针
    phead = load_dict_list("./dict.txt");

    // 进入无限循环,等待用户输入
    while (1)
    {   
        // 提示用户输入要查询的单词
        printf("请输入要查询的单词:\n");
        // 读取用户输入的单词
        gets(word);

        // 如果用户输入的是 .quit,则退出循环
        if (0 == strcmp(word, ".quit"))
        {
            break;
        }

        // 调用 search_word_mean 函数查找单词的含义
        pmean = search_word_mean(phead, word);
        // 如果没有找到单词的含义
        if (NULL == pmean)
        {
            // 输出提示信息
            printf("臣妾查不到 %s\n", word);
        }
        else 
        {
            // 输出单词的含义
            printf("含义:%s\n", pmean);
        }
    }
    
    // 释放字典链表占用的内存
    free_dict_list(phead);
    // 将链表头指针置为 NULL
    phead = NULL;

    // 程序正常结束,返回 0
    return 0;
}

三 方法二 哈希表

hashtable.c 代码:

  • 文件功能:实现哈希表的创建、插入、查找、删除和销毁等操作。

  • 代码逻辑步骤

    1. 创建哈希表 :分配包含 MAXNUM 个链表头的数组内存,使用 INIT_LIST_HEAD 初始化每个链表头,并返回该数组指针。
    2. 插入单词到哈希表
      • 为新节点分配内存,将单词和含义复制到节点中。
      • 根据单词首字母计算哈希键值,确定该节点应插入的链表。
      • 使用 list_add_order 将节点按字母升序插入到对应的链表中。
    3. 打印哈希表 :遍历每个链表,使用 list_for_each 遍历链表中的每个节点,输出节点中的单词。
    4. 查找单词 :根据单词首字母计算哈希键值,遍历对应的链表,使用 strcmp 比较节点中的单词和要查找的单词,若匹配则返回该节点指针;若遍历完链表仍未找到,则返回 NULL
    5. 删除单词 :循环调用 find_hashtable 查找匹配的节点,若找到则使用 list_del 从链表中删除该节点并释放其内存,直到未找到匹配节点为止,最后返回删除的节点数量。
    6. 销毁哈希表 :遍历每个链表,使用 list_for_each_entry_safe 安全地遍历链表,删除每个节点并释放其内存,最后释放链表头数组的内存。

    #include "hashtable.h"
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>

    // 创建哈希表函数
    // 该函数会分配一个包含MAXNUM个链表头的数组,用于存储哈希表的链表
    struct list_head *create_hashtable(void)
    {
    int i = 0;
    struct list_head *pheadlist = NULL;

    复制代码
      // 分配MAXNUM个链表头的内存空间
      pheadlist = malloc(MAXNUM * sizeof(struct list_head));
      if (NULL == pheadlist)
      {
          return NULL;  // 分配失败则返回NULL
      }
    
      // 初始化每个链表头
      for (i = 0; i < MAXNUM; i++)
      {
          INIT_LIST_HEAD(&pheadlist[i]);
      }
    
      return pheadlist;

    }

    // 比较函数,用于按字母升序比较两个节点中的单词
    // 参数pnew和pnode是两个链表节点指针
    static int asc_compare(struct list_head *pnew, struct list_head *pnode)
    {
    // 通过list_entry宏获取节点对应的hashnode_t结构体指针,再比较其中的单词
    if (strcmp(list_entry(pnew, hashnode_t, node)->word, list_entry(pnode, hashnode_t, node)->word) > 0)
    {
    return 1; // pnew的单词大于pnode的单词
    }
    else if (strcmp(list_entry(pnew, hashnode_t, node)->word, list_entry(pnode, hashnode_t, node)->word) < 0)
    {
    return -1; // pnew的单词小于pnode的单词
    }
    else
    {
    return 0; // 两个单词相等
    }
    }

    // 插入函数,将单词及其含义插入到哈希表中
    // 参数pheadlist是哈希表链表头数组,pword是要插入的单词,pmean是单词的含义
    int insert_hashtable(struct list_head *pheadlist, char *pword, char *pmean)
    {
    int key = 0;
    hashnode_t *ptmpnode = NULL;

    复制代码
      // 分配一个新的hashnode_t节点的内存空间
      ptmpnode = malloc(sizeof(hashnode_t));
      if (NULL == ptmpnode)
      {
          return -1;  // 分配失败则返回-1
      }
    
      // 复制单词和含义到新节点
      strcpy(ptmpnode->word, pword);
      strcpy(ptmpnode->mean, pmean);
    
      // 根据单词首字母计算哈希键值
      if (*pword >= 'a' && *pword <= 'z')
      {
          key = *pword - 'a';
      }
      else if (*pword >= 'A' && *pword <= 'Z')
      {
          key = *pword - 'A' + 26;
      }
      else 
      {
          key = 52;
      }
    
      // 将新节点按升序插入到对应的链表中
      list_add_order(&ptmpnode->node, &pheadlist[key], asc_compare);
    
      return 0;

    }

    // 打印哈希表函数,将哈希表中的所有单词按链表顺序打印出来
    // 参数pheadlist是哈希表链表头数组
    int show_hashtable(struct list_head *pheadlist)
    {
    int i = 0;
    struct list_head *ptmpnode = NULL;

    复制代码
      // 遍历每个链表
      for (i = 0; i < MAXNUM; i++)
      {
          printf("%d:", i);
          // 遍历链表中的每个节点
          list_for_each(ptmpnode, &pheadlist[i])
          {
              // 通过list_entry宏获取节点对应的hashnode_t结构体指针,打印其中的单词
              printf("%s ", list_entry(ptmpnode, hashnode_t, node)->word);
          }
          printf("\n");
      }
    
      return 0;

    }

    // 查找函数,根据单词查找对应的节点
    // 参数pheadlist是哈希表链表头数组,pword是要查找的单词
    hashnode_t *find_hashtable(struct list_head *pheadlist, char *pword)
    {
    int key = 0;
    struct list_head *ptmpnode = NULL;

    复制代码
      // 根据单词首字母计算哈希键值
      if (*pword >= 'a' && *pword <= 'z')
      {
          key = *pword - 'a';
      }
      else if (*pword >= 'A' && *pword <= 'Z')
      {
          key = *pword - 'A' + 26;
      }
      else 
      {
          key = 52;
      }
    
      // 遍历对应的链表
      list_for_each(ptmpnode, &pheadlist[key])
      {
          // 通过list_entry宏获取节点对应的hashnode_t结构体指针,比较其中的单词
          if (strcmp(list_entry(ptmpnode, hashnode_t, node)->word, pword) == 0)
          {
              return list_entry(ptmpnode, hashnode_t, node);  // 找到则返回该节点
          }
          else if (strcmp(list_entry(ptmpnode, hashnode_t, node)->word, pword) > 0)
          {
              break;  // 若当前节点的单词大于要查找的单词,则停止查找
          }
      }
      
      return NULL;  // 未找到则返回NULL

    }

    // 删除函数,删除哈希表中所有匹配的单词节点
    // 参数parray是哈希表链表头数组,pword是要删除的单词
    int delete_hashtable(struct list_head *parray, char *pword)
    {
    hashnode_t *ptmpnode = NULL;
    int cnt = 0;

    复制代码
      while (1)
      {
          // 查找匹配的节点
          ptmpnode = find_hashtable(parray, pword);
          if (NULL == ptmpnode)
          {
              break;  // 未找到则退出循环
          }
    
          // 从链表中删除该节点并释放内存
          list_del(&ptmpnode->node);
          free(ptmpnode);
          cnt++;
      }
      
      return cnt;  // 返回删除的节点数量

    }

    // 销毁函数,释放哈希表的所有内存
    // 参数parray是哈希表链表头数组
    int destroy_hashtable(struct list_head *parray)
    {
    int i = 0;
    hashnode_t *ptmpnode = NULL;
    hashnode_t *pnextnode = NULL;

    复制代码
      // 遍历每个链表
      for (i = 0; i < MAXNUM; i++)
      {
          // 安全地遍历链表中的每个节点并释放内存
          list_for_each_entry_safe(ptmpnode, pnextnode, &parray[i], node)
          {
              free(ptmpnode);
          }
      }
    
      // 释放链表头数组的内存
      free(parray);
    
      return 0;

    }

dict.c 代码:

  • 文件功能:将字典文件加载到链表中,并提供查找单词含义和释放链表内存的功能。

  • 代码逻辑步骤

    1. 加载字典到链表
      • 分配链表头内存并初始化。
      • 打开字典文件,若打开失败则输出错误信息并返回 NULL
      • 逐行读取文件内容,使用 strtok 分割每行内容,提取单词和含义。
      • 为每个单词和含义分配节点内存,将其复制到节点中,并使用 list_add_tail 将节点添加到链表尾部。
      • 关闭文件并返回链表头指针。
    2. 查找单词含义 :遍历链表,使用 strcmp 比较节点中的单词和要查找的单词,若匹配则返回该节点中的含义;若遍历完链表仍未找到,则返回 NULL
    3. 释放链表内存 :使用 list_for_each_entry_safe 安全地遍历链表,删除每个节点并释放其内存,最后释放链表头的内存。

    #include "dict.h"
    #include "public.h"

    // 加载字典文件到链表中
    // 参数pfilename是字典文件的文件名
    struct list_head *load_dict_list(char *pfilename)
    {
    struct list_head *phead = NULL;
    FILE *fp = NULL;
    word_t *ptmpnode = NULL;
    char *ptmp = NULL;
    char tmpbuff[4096] = {0};
    char *pret = NULL;

    复制代码
      // 分配链表头的内存空间
      phead = malloc(sizeof(struct list_head));
      if (NULL == phead)
      {
          ERR_MSG("fail to malloc");
          return NULL;
      }
    
      // 初始化链表头
      INIT_LIST_HEAD(phead);
    
      // 打开字典文件
      fp = fopen(pfilename, "r");
      if (NULL == fp)
      {
          ERR_MSG("fail to fopen");
          return NULL;
      }
    
      while (1)
      {
          // 从文件中读取一行
          pret = fgets(tmpbuff, sizeof(tmpbuff), fp);
          if (NULL == pret)
          {
              break;  // 读取到文件末尾则退出循环
          }
    
          ptmp = tmpbuff;
          // 找到单词和含义的分隔符
          while (*ptmp != ' ' && *ptmp != '\0')
          {
              ptmp++;
          }
          *ptmp = '\0';
          ptmp++;
          // 跳过多余的空格
          while (*ptmp == ' ')
          {
              ptmp++;
          }
    
          // 分配一个新的word_t节点的内存空间
          ptmpnode = malloc(sizeof(word_t));
          if (NULL == ptmpnode)
          {
              ERR_MSG("fail to malloc");
              return NULL;
          }
    
          // 复制单词和含义到新节点
          strcpy(ptmpnode->word, tmpbuff);
          strcpy(ptmpnode->mean, ptmp);
    
          // 将新节点添加到链表尾部
          list_add_tail(&ptmpnode->node, phead);
      }
      
      fclose(fp);  // 关闭文件
    
      return phead;

    }

    // 查找单词的含义
    // 参数phead是链表头指针,pword是要查找的单词
    char *search_word_mean(struct list_head *phead, char *pword)
    {
    word_t *ptmpnode = NULL;

    复制代码
      // 遍历链表
      list_for_each_entry(ptmpnode, phead, node)
      {
          // 比较单词是否匹配
          if (0 == strcmp(ptmpnode->word, pword))
          {
              return ptmpnode->mean;  // 找到则返回含义
          }
      }
    
      return NULL;  // 未找到则返回NULL

    }

    // 释放字典链表的内存
    // 参数phead是链表头指针
    int free_dict_list(struct list_head *phead)
    {
    word_t *ptmpnode1 = NULL;
    word_t *ptmpnode2 = NULL;

    复制代码
      // 安全地遍历链表并删除节点
      list_for_each_entry_safe(ptmpnode1, ptmpnode2, phead, node)
      {
          list_del(&ptmpnode1->node);
          free(ptmpnode1);
      }
      free(phead);  // 释放链表头的内存
    
      return 0;

    }

main.c 代码:

  • 文件功能:提供用户交互界面,让用户输入单词并从字典文件中查找其含义,同时记录查找耗时。

  • 代码逻辑步骤

    1. 打开字典文件 :使用 fopen 以只读模式打开 dict.txt 文件,若打开失败则输出错误信息并退出程序。
    2. 用户交互循环 :不断提示用户输入要查找的单词,当用户输入 .quit 时退出循环。
    3. 查找单词 :每次用户输入单词后,将文件指针重置到文件开头,逐行读取文件内容,使用 strtok 分割每行内容,比较单词是否匹配。若匹配则输出单词含义,并标记该单词已找到;若遍历完文件仍未找到,则输出未找到的提示信息。
    4. 记录查找耗时 :使用 GET_START_TIMEGET_END_TIME 宏记录查找开始和结束时间,通过 GET_CONST_TIME 计算并输出查找耗时。
    5. 关闭文件 :用户退出循环后,使用 fclose 关闭文件。

    #include <stdio.h>
    #include <string.h>
    #include "public.h"

    // 主函数,实现从文件中查找单词含义的功能
    int main(int argc, const char **argv)
    {
    FILE *fp = NULL;
    char *pret = NULL;
    char word[256] = {0};
    char tmpbuff[4096] = {0};
    int is_exist = 0;

    复制代码
      // 打开字典文件
      fp = fopen("dict.txt", "r");
      if (NULL == fp)
      {
          ERR_MSG("fail to fopen");
          return -1;
      }
    
      while (1)
      {
          printf("请输入要查找的单词:\n");
          gets(word);
    
          if (strcmp(word, ".quit") == 0)
          {
              break;  // 输入.quit则退出循环
          }
    
          GET_START_TIME;  // 记录开始时间
          rewind(fp);  // 将文件指针重置到文件开头
          while (1)
          {
              // 从文件中读取一行
              pret = fgets(tmpbuff, sizeof(tmpbuff), fp);
              if (NULL == pret)
              {
                  break;  // 读取到文件末尾则退出循环
              }
    
              // 查找单词是否匹配
              if (0 == strcmp(word, strtok(tmpbuff, " ")))
              {
                  printf("含义:%s\n", strtok(NULL, "\r"));
                  is_exist = 1;
                  break;
              }
          }
          GET_END_TIME;  // 记录结束时间
          
          if (!is_exist)
          {
              printf("我没有找到 %s 单词\n", word);
          }
          printf("耗时: %.3lfms\n", GET_CONST_TIME);  // 输出查找耗时
      }
          
      fclose(fp);  // 关闭文件
    
      return 0;

    }

相关推荐
千百元1 小时前
centos线程数查看
linux·运维·服务器
犽戾武2 小时前
浅谈多个虚拟机(WSL和VMWare)的网络配置
linux·网络
最后一个bug2 小时前
教你快速理解linux中的NUMA节点探测是干什么用的?
linux·c语言·开发语言·arm开发·嵌入式硬件
鲤籽鲲2 小时前
C# System.Net.IPEndPoint 使用详解
网络·microsoft·c#·.net
awei09162 小时前
Linux系统安装RabbitMQ
linux·运维·rabbitmq·ruby
快乐点吧3 小时前
【Word】批注一键导出:VBA 宏
开发语言·c#·word
linux kernel3 小时前
第八部分:进程创建退出等待和替换
linux·运维·服务器
Dm_dotnet3 小时前
为Avalonia应用添加图标
c#
awei09163 小时前
Jenkins服务器报磁盘空间不足的问题解决方案
linux·运维·jenkins
dessler3 小时前
Kubernetes(k8s)-日志(logs)和exec内部逻辑
linux·运维·kubernetes