一 查询单词及单词含义
这里有一个单词表,以下是单词表的一部分:

由单词和单词后的释义构成,中间用空格隔开。
根据单词表中的内容,完成让用户输入单词就获得单词含义的功能。
二 方法一 链表
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 代码:
-
文件功能:实现哈希表的创建、插入、查找、删除和销毁等操作。
-
代码逻辑步骤 :
- 创建哈希表 :分配包含
MAXNUM
个链表头的数组内存,使用INIT_LIST_HEAD
初始化每个链表头,并返回该数组指针。 - 插入单词到哈希表 :
- 为新节点分配内存,将单词和含义复制到节点中。
- 根据单词首字母计算哈希键值,确定该节点应插入的链表。
- 使用
list_add_order
将节点按字母升序插入到对应的链表中。
- 打印哈希表 :遍历每个链表,使用
list_for_each
遍历链表中的每个节点,输出节点中的单词。 - 查找单词 :根据单词首字母计算哈希键值,遍历对应的链表,使用
strcmp
比较节点中的单词和要查找的单词,若匹配则返回该节点指针;若遍历完链表仍未找到,则返回NULL
。 - 删除单词 :循环调用
find_hashtable
查找匹配的节点,若找到则使用list_del
从链表中删除该节点并释放其内存,直到未找到匹配节点为止,最后返回删除的节点数量。 - 销毁哈希表 :遍历每个链表,使用
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 代码:
-
文件功能:将字典文件加载到链表中,并提供查找单词含义和释放链表内存的功能。
-
代码逻辑步骤 :
- 加载字典到链表 :
- 分配链表头内存并初始化。
- 打开字典文件,若打开失败则输出错误信息并返回
NULL
。 - 逐行读取文件内容,使用
strtok
分割每行内容,提取单词和含义。 - 为每个单词和含义分配节点内存,将其复制到节点中,并使用
list_add_tail
将节点添加到链表尾部。 - 关闭文件并返回链表头指针。
- 查找单词含义 :遍历链表,使用
strcmp
比较节点中的单词和要查找的单词,若匹配则返回该节点中的含义;若遍历完链表仍未找到,则返回NULL
。 - 释放链表内存 :使用
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 代码:
-
文件功能:提供用户交互界面,让用户输入单词并从字典文件中查找其含义,同时记录查找耗时。
-
代码逻辑步骤 :
- 打开字典文件 :使用
fopen
以只读模式打开dict.txt
文件,若打开失败则输出错误信息并退出程序。 - 用户交互循环 :不断提示用户输入要查找的单词,当用户输入
.quit
时退出循环。 - 查找单词 :每次用户输入单词后,将文件指针重置到文件开头,逐行读取文件内容,使用
strtok
分割每行内容,比较单词是否匹配。若匹配则输出单词含义,并标记该单词已找到;若遍历完文件仍未找到,则输出未找到的提示信息。 - 记录查找耗时 :使用
GET_START_TIME
和GET_END_TIME
宏记录查找开始和结束时间,通过GET_CONST_TIME
计算并输出查找耗时。 - 关闭文件 :用户退出循环后,使用
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;
}
- 打开字典文件 :使用