C 语言本身没有内置的字典或哈希表这种数据结构,像 Python、JavaScript 等高级语言中的字典是语言内置的,但 C 语言的核心语言只提供了基本的数据类型(如 int, char, float)和几种复合数据类型(如数组、结构体、联合体)。

(图片来源网络,侵删)
在 C 语言中实现“字典”,我们通常需要借助第三方库或者自己动手实现,最常用、最标准的第三方库就是 uthash。
下面,我将围绕 uthash 这个库来详细解释如何使用它,特别是如何处理字典的“参数”(即键值对)。
为什么需要字典?字典的核心概念
字典是一种键值对集合,它允许你通过一个唯一的“键”来快速查找、添加、删除和修改一个“值”,这个查找操作的平均时间复杂度是 O(1),非常高效。
- 键:通常是唯一的、不可变的值,用于标识数据,可以是字符串、整数等。
- 值:与键关联的数据,可以是任意类型,如整数、字符串、结构体,甚至是指向复杂对象的指针。
生活中的例子:

(图片来源网络,侵删)
- 字典:单词是“键”,解释是“值”。
- 电话簿:姓名是“键”,电话号码是“值”。
- 学生档案:学号是“键”,学生信息(姓名、班级等)是“值”。
uthash 库简介
uthash 是一个轻量级的、单文件的 C 语言哈希表库,它的优点是:
- 极其简单:只需要将
uthash.h这个头文件包含到你的项目中即可,无需复杂的安装或编译步骤。 - 功能强大:支持添加、查找、删除、替换、排序等常用操作。
- 性能优异:实现了高效的哈希算法。
如何使用 uthash:
- 从 官网 下载
uthash.h文件。 - 将
uthash.h放在你的项目目录下。 - 在你的 C 源文件中包含它:
#include "uthash.h"
使用 uthash 创建和操作字典(核心内容)
uthash 的核心思想是将哈希表的功能“注入”到一个普通的 C 结构体中,这个结构体代表字典中的一个条目。
1 定义字典条目结构体
你需要定义一个结构体来表示一个键值对,这个结构体必须:

(图片来源网络,侵删)
- 包含你的“键”和“值”字段。
- 包含一个
UT_hash_handle类型的字段(这个字段由uthash内部使用,你不需要关心它的具体内容)。
示例 1:字符串键,整数值
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "uthash.h" // 包含 uthash 库
// 定义字典条目的结构体
struct my_dict {
char *key; // 键:字符串
int value; // 值:整数
UT_hash_handle hh; // uthash 需要的句柄
};
// 创建一个全局的哈希表指针,用于管理所有条目
struct my_dict *users = NULL;
示例 2:整数键,结构体指针值
#include "uthash.h"
// 定义值的结构体
struct user_info {
char name[50];
int age;
};
// 定义字典条目的结构体
struct int_dict {
int id; // 键:整数
struct user_info *info; // 值:指向结构体的指针
UT_hash_handle hh;
};
struct int_dict *user_db = NULL;
2 字典操作函数(增删改查)
uthash 提供了一系列宏来操作哈希表,我们以 struct my_dict 为例。
添加或更新条目
使用 HASH_ADD_KEYPTR 宏,如果键已存在,它会更新对应的值;如果不存在,它会添加一个新条目。
void add_user(const char *key, int value) {
struct my_dict *s;
// 1. 检查键是否已存在
HASH_FIND_STR(users, key, s); // 在 users 表中查找 key,找到后指针存入 s
if (s == NULL) {
// 2. 如果不存在,则创建一个新条目
s = (struct my_dict *)malloc(sizeof(struct my_dict));
s->key = strdup(key); // 复制字符串,因为原始 key 可能是临时变量
s->value = value;
HASH_ADD_KEYPTR(hh, users, s->key, strlen(s->key), s);
printf("Added: %s -> %d\n", key, value);
} else {
// 3. 如果存在,则更新值
s->value = value;
printf("Updated: %s -> %d\n", key, value);
}
}
HASH_FIND_STR(users, key, s):在users表中查找字符串类型的key,如果找到,s指向该条目;否则s为NULL。HASH_ADD_KEYPTR(hh, users, s->key, strlen(s->key), s):hh:UT_hash_handle字段的名字。users:哈希表指针。s->key:要添加的键的指针。strlen(s->key):键的长度。s:要添加的结构体指针。
查找条目
使用 HASH_FIND_STR 宏。
struct my_dict *find_user(const char *key) {
struct my_dict *s;
HASH_FIND_STR(users, key, s); // 查找逻辑与上面相同
return s; // 返回找到的条目指针,如果没找到则为 NULL
}
删除条目
使用 HASH_DEL 宏。注意:删除后需要手动释放内存!
void delete_user(const char *key) {
struct my_dict *s = find_user(key); // 先找到
if (s != NULL) {
HASH_DEL(users, s); // 从哈希表中移除
free(s->key); // 释放键的内存
free(s); // 释放结构体本身的内存
printf("Deleted: %s\n", key);
}
}
遍历所有条目
使用 HASH_ITER 宏。
void print_all_users() {
struct my_dict *s, *tmp;
HASH_ITER(hh, users, s, tmp) {
// s 是当前条目
// tmp 是下一个条目的备份,用于安全地删除当前条目
printf("Key: %s, Value: %d\n", s->key, s->value);
}
}
清空整个字典
void free_all_users() {
struct my_dict *current_user, *tmp;
HASH_ITER(hh, users, current_user, tmp) {
HASH_DEL(users, current_user); // 先从表中移除
free(current_user->key); // 再释放内存
free(current_user);
}
}
完整示例代码
下面是一个完整的、可运行的示例,展示了上述所有操作。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "uthash.h"
// 1. 定义字典条目结构体
struct my_dict {
char *key;
int value;
UT_hash_handle hh;
};
// 全局哈希表指针
struct my_dict *users = NULL;
// 2. 定义操作函数
void add_user(const char *key, int value) {
struct my_dict *s;
HASH_FIND_STR(users, key, s);
if (s == NULL) {
s = (struct my_dict *)malloc(sizeof(struct my_dict));
s->key = strdup(key);
s->value = value;
HASH_ADD_KEYPTR(hh, users, s->key, strlen(s->key), s);
printf("Added: %s -> %d\n", key, value);
} else {
s->value = value;
printf("Updated: %s -> %d\n", key, value);
}
}
struct my_dict *find_user(const char *key) {
struct my_dict *s;
HASH_FIND_STR(users, key, s);
return s;
}
void delete_user(const char *key) {
struct my_dict *s = find_user(key);
if (s != NULL) {
HASH_DEL(users, s);
free(s->key);
free(s);
printf("Deleted: %s\n", key);
}
}
void print_all_users() {
struct my_dict *s, *tmp;
printf("--- All Users ---\n");
HASH_ITER(hh, users, s, tmp) {
printf("Key: %s, Value: %d\n", s->key, s->value);
}
printf("-----------------\n");
}
void free_all_users() {
struct my_dict *current_user, *tmp;
HASH_ITER(hh, users, current_user, tmp) {
HASH_DEL(users, current_user);
free(current_user->key);
free(current_user);
}
}
// 3. 主函数
int main() {
// 添加/更新
add_user("Alice", 25);
add_user("Bob", 30);
add_user("Charlie", 35);
print_all_users();
// 更新
add_user("Alice", 26);
print_all_users();
// 查找
struct my_dict *found = find_user("Bob");
if (found) {
printf("Found Bob, his age is: %d\n", found->value);
} else {
printf("Bob not found.\n");
}
// 删除
delete_user("Charlie");
print_all_users();
// 查找不存在的键
struct my_dict *not_found = find_user("David");
if (!not_found) {
printf("David not found, as expected.\n");
}
// 清空内存
free_all_users();
// 验证是否已清空
print_all_users(); // 应该没有任何输出
return 0;
}
| 操作 | uthash 宏 |
关键点 |
|---|---|---|
| 定义 | struct my_dict { ... UT_hash_handle hh; }; |
必须包含 UT_hash_handle 字段 |
| 添加/更新 | HASH_ADD_KEYPTR(hh, table, key_ptr, key_len, item_ptr) |
如果键存在则更新,否则添加 |
| 查找 | HASH_FIND_STR(table, key_ptr, item_ptr_out) |
根据键查找,结果通过指针返回 |
| 删除 | HASH_DEL(table, item_ptr) |
删除后必须手动 free() 内存 |
| 遍历 | HASH_ITER(hh, table, current_item, tmp_item) |
使用临时指针 tmp 保证安全遍历 |
| 清空 | 在 HASH_ITER 循环内调用 HASH_DEL 和 free() |
遍历并释放每个条目的内存 |
通过 uthash,你就可以在 C 语言中轻松、高效地使用字典数据结构来管理参数和键值对数据。
