> 技术文档 > c语言开源库之uthash用法

c语言开源库之uthash用法

目录

(1)uthash介绍和下载地址

(2)uthash基本用法

1.定义自己要使用的哈希表结构

2.初始化哈希表的头指针

3.插入数据(不同key类型对应不同函数)

4.查找数据(不同key类型对应不同函数)

5.获取键值对个数

6.排序

7.遍历

8.删除

(3)不同类型key值的uthash用法

1.字符串数组作为key

2.指针作为key

3.结构体作为key

4.char作为key

(4)注意事项

插入元素时是重复key怎么办

删除数据时注意事项


(1)uthash介绍和下载地址

uthash是C的比较优秀的开源代码,它实现了常见的hash操作函数,例如查找、插入、删除等。该套开源代码采用宏的方式实现了hash函数的相关功能,支持c语言的任意数据结构作为key值(可以是基本类型,也可以是自定义的struct),甚至可以采用多个值作为key。

uthash使用所需文件:由于该代码采用宏的方式实现,所有的实现代码都在uthash.h文件中,因此只需要在自己的代码中包含该头文件即可。

uthash头文件下载地址:https://github.com/troydhanson/uthash

(2)uthash基本用法

1.定义自己要使用的哈希表结构体

如下所示,必须是结构体。

结构体可以有任意个成员,比如这里有4个成员。

变量名字也可以随便取。结构体名字也可以随便取,比如这里为Hash。

但是要包含UT_hash_handle hh这个成员,必须要有。不用对它赋值,就像下面这样就行。

后面我们会理解到,value就是这个结构体,key可以为这个结构体中的任意一种成员。

#include \"uthash.h\"#define NAME_LEN 10typedef struct { int id; int age; int grade; char name[NAME_LEN]; UT_hash_handle hh;} Hash;

2.初始化哈希表的头指针

初始化自己需要使用的哈希表,像下面这样(推荐用全局变量)

Hash *hashHead = NULL;

注意必须初始化为NULL,UTHash会根据指针是否为NULL进行判断是否需要初始化。

这里的hashHead指针叫做哈希表指针,类似于链表的头指针。

hashHead => key-value结构体 => key-value结构体 => key-value结构体

3.插入数据(不同key类型对应不同函数)

假设key类型为int,用HASH_ADD_INT

 Hash *data = (Hash *)malloc(sizeof(Hash)); data->id = 1; data->age = 18; data->grade = 100; strcpy(data->name, \"tom\"); HASH_ADD_INT(hashHead, id, data);

其中第一个参数为哈希表指针,第二个参数为哈希表结构体中的某个成员名,要将哪个成员作为key,就填哪个,第三个参数是value,为哈希表结构体指针。

4.查找数据(不同key类型对应不同函数)

假设key类型为int,用HASH_FIND_INT

 Hash *findRet = NULL; int key = 1; HASH_FIND_INT(hashHead, &key, findRet); if (findRet != NULL) { printf(\"%s\\n\", findRet->name); }

第一个参数为哈希表指针,第二个参数为待查找key的地址,第三个参数为传出参数,即查找到的value,找不到的话为NULL。

5.获取键值对个数

用HASH_COUNT

 int count = HASH_COUNT(hashHead); printf(\"%d\\n\", count);

6.排序

使用HASH_SORT,实现对哈希表中的元素排序。以下是先按照成绩降序,再年龄升序,再id升序排序的例子

int CmpFunc(Hash *a, Hash *b){ /* 排序比较函数: 返回值小于0表示不交换a和b,即升序 返回值大于0表示交换a和b,即降序 */ if (a->grade != b->grade) { return b->grade - a->grade; } if (a->age != b->age) { return a->age - b->age; } return a->id - b->id;}HASH_SORT(hashHead, CmpFunc);

7.遍历

使用HASH_ITER,实现对哈希表中元素的遍历

 Hash *curr = NULL; Hash *next = NULL; HASH_ITER(hh, hashHead, curr, next) { if (curr != NULL) { printf(\"curr grade=%d\\n\", curr->grade); } if (next != NULL) { printf(\"next grade=%d\\n\", next->grade); } }

curr和next这里是传出参数。

curr是遍历到的元素,next是curr的下一个元素。一般关注curr即可。

注意当curr为最后一个键值对时,next将会为NULL。

8.删除

使用HASH_DEL,实现对哈希表中元素的删除。

注意这里需要先找到哈希表中的目标元素,然后利用HASH_DEL进行删除,注意只是从哈希表中删除,然后再利用free进行内存回收。

 Hash *value = NULL; int boy_id = 1; HASH_FIND_INT(hashHead, &boy_id, value); if (value != NULL) { HASH_DEL(hashHead, value); free(value); }

如果要对哈希表中的全部元素删除,可遍历结合使用HASH_DEL

 Hash *curr = NULL; Hash *next = NULL; HASH_ITER(hh, hashHead, curr, next) { HASH_DEL(hashHead, curr); free(curr); }

(3)不同类型key值的uthash用法

不同类型key时,插入数据和查找数据的函数将会不同,其余基本还是相同的。

1.字符串数组作为key

比如哈希结构体Hash中成员chae name[10]

查找:HASH_FIND_STR

添加:HASH_ADD_STR

 // 设计一个数据 Hash *data = (Hash *)malloc(sizeof(Hash)); data->id = 1; data->age = 18; data->grade = 100; strcpy(data->name, \"tom\"); // 以name作为key进行插入 HASH_ADD_STR(hashHead, name, data); // 查找 Hash *findRet = NULL; char boy_name[] = \"tom\"; HASH_FIND_STR(hashHead, boy_name, findRet); if (findRet != NULL) { printf(\"%s\\n\", findRet->name); }

2.指针作为key

查找:HASH_FIND_PTR

添加:HASH_ADD_PTR

示例代码如下,注意查找时是传入指针的地址

#include  #include #include \"uthash.h\"#define NAME_LEN 10typedef struct { void *key; int id; int age; int grade; char name[NAME_LEN]; UT_hash_handle hh;} Hash;Hash *hashHead = NULL;int main() { // 某个值 int *index = (int *)malloc(sizeof(int)); *index = 99; // 设计一个数据 Hash *data = (Hash *)malloc(sizeof(Hash)); data->key = index; data->id = 1; data->age = 18; data->grade = 100; strcpy(data->name, \"tom\"); // 以指针作为key进行插入 HASH_ADD_PTR(hashHead, key, data); // 查找 Hash *findRet = NULL; HASH_FIND_PTR(hashHead, &index, findRet); // 注意这里需要 &index if (findRet != NULL) { printf(\"%s\\n\", findRet->name); } return 0; }

3.结构体作为key

插入用HASH_ADD,与之前相比,多了两个参数,一个句柄hh,一个结构体大小

查找用HASH_FIND,与之前相比,多了两个参数,一个句柄hh,一个结构体大小

示例代码如下

#include  #include #include \"uthash.h\"#define NAME_LEN 10typedef struct { int id; int age; int grade; } KeyStruct;typedef struct { KeyStruct key; char name[NAME_LEN]; UT_hash_handle hh;} Hash;Hash *hashHead = NULL;int main() { // 设计一个数据 Hash *data = (Hash *)malloc(sizeof(Hash)); data->key.age = 18; data->key.grade = 100; data->key.id = 1; strcpy(data->name, \"tom\"); // 以结构体作为key进行插入 HASH_ADD(hh, hashHead, key, sizeof(KeyStruct), data); // 查找 Hash *findRet = NULL; HASH_FIND(hh, hashHead, data, sizeof(KeyStruct), findRet); if (findRet != NULL) { printf(\"%s\\n\", findRet->name); } return 0; }

4.char作为key

查找:HASH_FIND

插入:HASH_ADD

#include \"uthash.h\"#include #include  typedef struct { char key; int value; UT_hash_handle hh;} Hash; // 查找函数Hash *FindChar(Hash **head, char keyValue){ Hash *s; // 注意这里使用HASH_FIND_CHAR宏 HASH_FIND(hh, *head, &keyValue, sizeof(char), s); return s;} // 插入或更新函数void AddChar(Hash **head, char keyValue, int value){ Hash *s; HASH_FIND(hh, *head, &keyValue, sizeof(char), s); if (s == NULL) { s = (Hash*)malloc(sizeof(Hash)); s->key = keyValue; s->value = value; HASH_ADD(hh, *head, key, sizeof(char), s); } else { s->value = value; }} // 删除函数(可选)void DeleteChar(Hash **head, char keyValue) { Hash *s; HASH_FIND(hh, *head, &keyValue, sizeof(char), s); if (s != NULL) { HASH_DEL(*head, s); free(s); }}

(4)注意事项

插入元素时是重复key怎么办

当使用 HASH_ADD_INT(或类似的 HASH_ADD 宏,但指定了整数类型作为键)向 uthash 哈希表中添加元素时,如果哈希表中已经存在一个具有相同键(key)的元素,那么新元素不会替换旧元素,而是会导致未定义行为(undefined behavior, UB)。

uthash 库没有提供直接的机制来更新已存在的键的值,它主要设计用于插入新元素和查找元素。如果你尝试使用相同的键插入一个新元素,而该键已经存在于哈希表中,uthash 不会自动合并或更新旧元素。相反,它可能会破坏哈希表的内部结构,导致数据损坏、内存泄漏或程序崩溃等问题。

如果你需要更新哈希表中已存在元素的值,你应该首先使用 HASH_FIND_INT(或相应的查找宏)来查找该元素。如果找到了,你可以直接修改该元素的值。如果没有找到,你可以使用 HASH_ADD_INT 来插入新元素。

下面是一个简单的示例,展示了如何安全地更新哈希表中已存在元素的值:

#include  #include  #include  #include \"uthash.h\" typedef struct { int id; // 假设这是我们的键 int value; // 我们要更新的值 UT_hash_handle hh; } HashElement; HashElement *hashHead = NULL; void update_or_add_element(int id, int newValue) { HashElement *element; HASH_FIND_INT(hashHead, &id, element); // 注意这里是形参的地址 if (element != NULL) { // 如果元素已存在,则更新其值 element->value = newValue; } else { // 如果元素不存在,则创建并插入新元素 element = malloc(sizeof(HashElement)); if (element == NULL) {  fprintf(stderr, \"Memory allocation failed\\n\");  exit(EXIT_FAILURE); } element->id = id; element->value = newValue; HASH_ADD_INT(hashHead, id, element); // 注意这里是结构体中key的名字 } } void free_hash_table(){ HashElement *curr = NULL; HashElement *next = NULL; HASH_ITER(hh, hashHead, curr, next) { HASH_DEL(hashHead, curr); free(curr); }} int main() { // 插入一些初始元素 update_or_add_element(1, 100); update_or_add_element(2, 200); // 尝试更新已存在的元素 // 将id为1的元素的值更新为150 update_or_add_element(1, 150); // 查找并打印更新后的值 HashElement *element; int target_key = 1; HASH_FIND_INT(hashHead, &target_key, element); if (element != NULL) { printf(\"Value for id 1: %d\\n\", element->value); // 应该输出 150 } // 释放哈希表中的所有元素 free_hash_table(); return 0; }

删除数据时注意事项

如果你尝试传入一个不是哈希表中元素或键的地址来删除数据,那么结果将是未定义的。可能导致程序崩溃、数据损坏、或者无效果等。

为了避免这些问题,你应该始终确保:

  • 使用正确的键或指向哈希表中元素的指针来执行删除操作。
  • 如果你有指向哈希表中某个元素的指针,并且想要删除它,你应该直接使用该指针(确保它是指向包含 UT_hash_handle 成员的结构体的指针)和 HASH_DEL 宏。
  • 如果你只有键的值,你应该先使用查找宏(如 HASH_FIND_INT)来找到对应的元素,然后再使用 HASH_DEL 宏来删除它。

这里是一个简单的示例,展示了如何正确地删除哈希表中的元素:

#include  #include  #include \"uthash.h\" typedef struct { int id; char name[50]; UT_hash_handle hh; } HashElement; HashElement *hashHead = NULL; void delete_element(int id) { HashElement *element; HASH_FIND_INT(hashHead, &id, element); if (element != NULL) { HASH_DEL(hashHead, element); free(element); // 不要忘记释放内存 } } 

(5)示例代码

release.21  表示发布21版本

patch.21.01 表示对21版本发布的01补丁

统计每个版本的补丁数,然后按补丁数降序输出,补丁数相同时按版本升序

#include \"stdlib.h\"#include \"string.h\"#include \"securec.h\"#include \"uthash.h\"#define KEY_LEN 11typedef struct { char versionName[11]; int patchCount; UT_hash_handle hh;} Hash;Hash *hashHead = NULL;static void GetVersionName(char *version, char *dst, int *isPatch){ if (strncmp(version, \"release\", 7) == 0) { *isPatch = 0; strncpy_s(dst, KEY_LEN, version, KEY_LEN); return; } *isPatch = 1; char temp[KEY_LEN] = \"release.\"; temp[8] = version[6]; temp[9] = version[7]; strncpy_s(dst, KEY_LEN, temp, KEY_LEN);}static void HashAddData(char *version){ char *key = (char *)malloc(KEY_LEN); if (key == NULL) { return; } int isPatch = 0; GetVersionName(version, key, &isPatch); Hash *findRet = NULL; HASH_FIND_STR(hashHead, key, findRet); if (findRet == NULL) { findRet = (Hash *)malloc(sizeof(Hash)); if (findRet == NULL) { return; } strcpy_s(findRet->versionName, KEY_LEN, key); findRet->patchCount = isPatch; HASH_ADD_STR(hashHead, versionName, findRet); } else { findRet->patchCount += isPatch; }}static int CmpFunc(Hash *a, Hash *b){ if (a->patchCount != b->patchCount) { return b->patchCount - a->patchCount; } return strcmp(a->versionName, b->versionName);}static char **GetResult(size_t *returnSize){ *returnSize = HASH_COUNT(hashHead); char **ret = (char **)malloc(*returnSize * sizeof(char *)); if (ret == NULL) { return NULL; } for (int i = 0; i versionName, curr->patchCount); strncpy_s(ret[index], KEY_LEN, curr->versionName, KEY_LEN); index++; } return ret;}static void FreeHash(){ Hash *curr = NULL; Hash *next = NULL; HASH_ITER(hh, hashHead, curr, next) { HASH_DEL(hashHead, curr); free(curr); } hashHead = NULL;}static char **ArrangeVersion(char **versions, size_t versionsSize, size_t *returnSize){ // uthash初始化 FreeHash(); // 更新uthash for (int i = 0; i < versionsSize; i++) { HashAddData(versions[i]); } // 排序 HASH_SORT(hashHead, CmpFunc); // 将排序后的内容输出 char **result = GetResult(returnSize); return result;}int main(void){ int select = 1; if (select == 1) { char *versions[] = { \"release.21\", \"release.23\", \"patch.21.01\", \"patch.23.02\", \"patch.24.05\", \"release.24\", \"release.09\", \"patch.21.11\", \"patch.23.12\", \"patch.24.01\", \"patch.24.08\" }; size_t versionsSize = 11; size_t retSize; ArrangeVersion(versions, versionsSize, &retSize); } return 0;}

end