0


c语言开源库之uthash用法

(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可以为这个结构体中的任意一种成员。

  1. #include "uthash.h"
  2. #define NAME_LEN 10
  3. typedef struct {
  4. int id;
  5. int age;
  6. int grade;
  7. char name[NAME_LEN];
  8. UT_hash_handle hh;
  9. } Hash;

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

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

  1. Hash *hashHead = NULL;

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

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

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

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

假设key类型为int,用HASH_ADD_INT

  1. Hash *data = (Hash *)malloc(sizeof(Hash));
  2. data->id = 1;
  3. data->age = 18;
  4. data->grade = 100;
  5. strcpy(data->name, "tom");
  6. HASH_ADD_INT(hashHead, id, data);

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

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

假设key类型为int,用HASH_FIND_INT

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

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

5.获取键值对个数

用HASH_COUNT

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

6.排序

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

  1. int CmpFunc(Hash *a, Hash *b)
  2. {
  3. /* 排序比较函数:
  4. 返回值小于0表示不交换a和b,即升序
  5. 返回值小于0表示交换a和b,即降序
  6. */
  7. if (a->grade != b->grade) {
  8. return b->grade - a->grade;
  9. }
  10. if (a->age != b->age) {
  11. return a->age - b->age;
  12. }
  13. return a->id - b->id;
  14. }
  15. HASH_SORT(hashHead, CmpFunc);

7.遍历

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

  1. Hash *curr = NULL;
  2. Hash *next = NULL;
  3. HASH_ITER(hh, hashHead, curr, next) {
  4. if (curr != NULL) {
  5. printf("curr grade=%d\n", curr->grade);
  6. }
  7. if (next != NULL) {
  8. printf("next grade=%d\n", next->grade);
  9. }
  10. }

curr和next这里是传出参数。

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

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

8.删除

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

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

  1. Hash *value = NULL;
  2. int boy_id = 1;
  3. HASH_FIND_INT(hashHead, &boy_id, value);
  4. if (value != NULL) {
  5. HASH_DEL(hashHead, value);
  6. free(value);
  7. }

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

  1. Hash *curr = NULL;
  2. Hash *next = NULL;
  3. HASH_ITER(hh, hashHead, curr, next) {
  4. HASH_DEL(hashHead, curr);
  5. free(curr);
  6. }

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

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

1.字符串数组作为key

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

查找:HASH_FIND_STR

添加:HASH_ADD_STR

  1. // 设计一个数据
  2. Hash *data = (Hash *)malloc(sizeof(Hash));
  3. data->id = 1;
  4. data->age = 18;
  5. data->grade = 100;
  6. strcpy(data->name, "tom");
  7. // 以name作为key进行插入
  8. HASH_ADD_STR(hashHead, name, data);
  9. // 查找
  10. Hash *findRet = NULL;
  11. char boy_name[] = "tom";
  12. HASH_FIND_STR(hashHead, boy_name, findRet);
  13. if (findRet != NULL) {
  14. printf("%s\n", findRet->name);
  15. }

2.指针作为key

查找:HASH_FIND_PTR

添加:HASH_ADD_PTR

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

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include "uthash.h"
  4. #define NAME_LEN 10
  5. typedef struct {
  6. void *key;
  7. int id;
  8. int age;
  9. int grade;
  10. char name[NAME_LEN];
  11. UT_hash_handle hh;
  12. } Hash;
  13. Hash *hashHead = NULL;
  14. int main()
  15. {
  16. // 某个值
  17. int *index = (int *)malloc(sizeof(int));
  18. *index = 99;
  19. // 设计一个数据
  20. Hash *data = (Hash *)malloc(sizeof(Hash));
  21. data->key = index;
  22. data->id = 1;
  23. data->age = 18;
  24. data->grade = 100;
  25. strcpy(data->name, "tom");
  26. // 以指针作为key进行插入
  27. HASH_ADD_PTR(hashHead, key, data);
  28. // 查找
  29. Hash *findRet = NULL;
  30. HASH_FIND_PTR(hashHead, &index, findRet); // 注意这里需要 &index
  31. if (findRet != NULL) {
  32. printf("%s\n", findRet->name);
  33. }
  34. return 0;
  35. }

3.结构体作为key

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

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

示例代码如下

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include "uthash.h"
  4. #define NAME_LEN 10
  5. typedef struct {
  6. int id;
  7. int age;
  8. int grade;
  9. } KeyStruct;
  10. typedef struct {
  11. KeyStruct key;
  12. char name[NAME_LEN];
  13. UT_hash_handle hh;
  14. } Hash;
  15. Hash *hashHead = NULL;
  16. int main()
  17. {
  18. // 设计一个数据
  19. Hash *data = (Hash *)malloc(sizeof(Hash));
  20. data->key.age = 18;
  21. data->key.grade = 100;
  22. data->key.id = 1;
  23. strcpy(data->name, "tom");
  24. // 以结构体作为key进行插入
  25. HASH_ADD(hh, hashHead, key, sizeof(KeyStruct), data);
  26. // 查找
  27. Hash *findRet = NULL;
  28. HASH_FIND(hh, hashHead, data, sizeof(KeyStruct), findRet);
  29. if (findRet != NULL) {
  30. printf("%s\n", findRet->name);
  31. }
  32. return 0;
  33. }

4.char作为key

查找:HASH_FIND

插入:HASH_ADD

  1. #include "uthash.h"
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. typedef struct {
  5. char key;
  6. int value;
  7. UT_hash_handle hh;
  8. } Hash;
  9. // 查找函数
  10. Hash *FindChar(Hash **head, char keyValue)
  11. {
  12. Hash *s;
  13. // 注意这里使用HASH_FIND_CHAR宏
  14. HASH_FIND(hh, *head, &keyValue, sizeof(char), s);
  15. return s;
  16. }
  17. // 插入或更新函数
  18. void AddChar(Hash **head, char keyValue, int value)
  19. {
  20. Hash *s;
  21. HASH_FIND(hh, *head, &keyValue, sizeof(char), s);
  22. if (s == NULL) {
  23. s = (Hash*)malloc(sizeof(Hash));
  24. s->key = keyValue;
  25. s->value = value;
  26. HASH_ADD(hh, *head, key, sizeof(char), s);
  27. } else {
  28. s->value = value;
  29. }
  30. }
  31. // 删除函数(可选)
  32. void DeleteChar(Hash **head, char keyValue) {
  33. Hash *s;
  34. HASH_FIND(hh, *head, &keyValue, sizeof(char), s);
  35. if (s != NULL) {
  36. HASH_DEL(*head, s);
  37. free(s);
  38. }
  39. }

(4)注意事项

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

当使用

  1. HASH_ADD_INT

(或类似的

  1. HASH_ADD

宏,但指定了整数类型作为键)向

  1. uthash

哈希表中添加元素时,如果哈希表中已经存在一个具有相同键(key)的元素,那么新元素不会替换旧元素,而是会导致未定义行为(undefined behavior, UB)。

  1. uthash

库没有提供直接的机制来更新已存在的键的值,它主要设计用于插入新元素和查找元素。如果你尝试使用相同的键插入一个新元素,而该键已经存在于哈希表中,

  1. uthash

不会自动合并或更新旧元素。相反,它可能会破坏哈希表的内部结构,导致数据损坏、内存泄漏或程序崩溃等问题。

如果你需要更新哈希表中已存在元素的值,你应该首先使用

  1. HASH_FIND_INT

(或相应的查找宏)来查找该元素。如果找到了,你可以直接修改该元素的值。如果没有找到,你可以使用

  1. HASH_ADD_INT

来插入新元素。

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

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include "uthash.h"
  5. typedef struct {
  6. int id; // 假设这是我们的键
  7. int value; // 我们要更新的值
  8. UT_hash_handle hh;
  9. } HashElement;
  10. HashElement *hashHead = NULL;
  11. void update_or_add_element(int id, int newValue) {
  12. HashElement *element;
  13. HASH_FIND_INT(hashHead, &id, element); // 注意这里是形参的地址
  14. if (element != NULL) {
  15. // 如果元素已存在,则更新其值
  16. element->value = newValue;
  17. } else {
  18. // 如果元素不存在,则创建并插入新元素
  19. element = malloc(sizeof(HashElement));
  20. if (element == NULL) {
  21. fprintf(stderr, "Memory allocation failed\n");
  22. exit(EXIT_FAILURE);
  23. }
  24. element->id = id;
  25. element->value = newValue;
  26. HASH_ADD_INT(hashHead, id, element); // 注意这里是结构体中key的名字
  27. }
  28. }
  29. void free_hash_table()
  30. {
  31. HashElement *curr = NULL;
  32. HashElement *next = NULL;
  33. HASH_ITER(hh, hashHead, curr, next) {
  34. HASH_DEL(hashHead, curr);
  35. free(curr);
  36. }
  37. }
  38. int main()
  39. {
  40. // 插入一些初始元素
  41. update_or_add_element(1, 100);
  42. update_or_add_element(2, 200);
  43. // 尝试更新已存在的元素
  44. // 将id为1的元素的值更新为150
  45. update_or_add_element(1, 150);
  46. // 查找并打印更新后的值
  47. HashElement *element;
  48. int target_key = 1;
  49. HASH_FIND_INT(hashHead, &target_key, element);
  50. if (element != NULL) {
  51. printf("Value for id 1: %d\n", element->value); // 应该输出 150
  52. }
  53. // 释放哈希表中的所有元素
  54. free_hash_table();
  55. return 0;
  56. }

删除数据时注意事项

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

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

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

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

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include "uthash.h"
  4. typedef struct {
  5. int id;
  6. char name[50];
  7. UT_hash_handle hh;
  8. } HashElement;
  9. HashElement *hashHead = NULL;
  10. void delete_element(int id) {
  11. HashElement *element;
  12. HASH_FIND_INT(hashHead, &id, element);
  13. if (element != NULL) {
  14. HASH_DEL(hashHead, element);
  15. free(element); // 不要忘记释放内存
  16. }
  17. }

end

标签: c语言 开发语言

本文转载自: https://blog.csdn.net/hutaotaotao/article/details/141233376
版权归原作者 hutaotaotao 所有, 如有侵权,请联系我们删除。

“c语言开源库之uthash用法”的评论:

还没有评论