C89混合类型哈希表:简化字面量插入宏的实现问询
C89混合类型哈希表:简化字面量插入宏的实现问询
首先,你的思路完全正确——在C89里确实没办法自动推导字面量的类型,所以显式指定keytype和valtype是必须的,4个参数的接口已经是C89能做到的最简洁的形式了,3个参数的话真的实现不了(毕竟C没有类型推导的语法糖)。
先帮你修正现有代码里的几个关键问题,这些问题会影响后续宏的正常工作:
1. 修正ht_set的逻辑与语法错误
你的ht_set里有几处明显的问题:
- 语法错误:
if语句缺少括号,C89要求if的条件必须用括号包裹 - 赋值错误:
ht->entries[i].key = &key;是把指针赋值给struct tagged类型的变量,应该直接值拷贝(key是函数参数,值传递,栈上的临时变量,拷贝后才会存在于哈希表中) - 循环条件逻辑颠倒:原
while条件会在条目为空时继续循环,这和你要找空位置或匹配key的需求相反 - 条目状态更新遗漏:插入新条目时需要把
empty设为0,而不是只增加len
修正后的ht_set如下:
void ht_set(struct tbl *ht, struct tagged key, struct tagged val) { size_t i; i = hash(&key) % ht->cap; // 查找空位置或匹配的key while (!ht->entries[i].empty && !cmp(&ht->entries[i].key, &key)) { i = (i + 1) % ht->cap; } if (ht->entries[i].empty) { // 插入新条目 ht->entries[i].key = key; ht->entries[i].val = val; ht->entries[i].empty = 0; ht->len++; } else { // 更新已有key的value ht->entries[i].val = val; } }
2. 实现HT_SET_LITERAL宏
我们可以通过类型到union成员的映射宏+临时变量初始化的方式实现这个简化接口,完全兼容C89:
首先定义辅助宏,用来把枚举类型映射到对应的union成员:
// 辅助宏:将枚举类型TAG映射到union对应的成员名 #define _TYPE_TO_MEMBER_INT i #define _TYPE_TO_MEMBER_FLOAT f #define _TYPE_TO_MEMBER_CHAR c #define _TYPE_TO_MEMBER_STR s #define TYPE_TO_MEMBER(T) _TYPE_TO_MEMBER_##T // 辅助宏:初始化一个struct tagged变量 #define INIT_TAGGED_VAR(TAGGED, TYPE, VAL) \ do { \ (TAGGED).tag = (TYPE); \ (TAGGED).obj.##TYPE_TO_MEMBER(TYPE) = (VAL); \ } while (0)
然后定义最终的HT_SET_LITERAL宏,用do-while(0)包裹确保可以在任何上下文(比如if分支后)安全使用:
#define HT_SET_LITERAL(HT, KEYTYPE, KEY, VALTYPE, VAL) \ do { \ struct tagged _tmp_key; \ struct tagged _tmp_val; \ INIT_TAGGED_VAR(_tmp_key, KEYTYPE, KEY); \ INIT_TAGGED_VAR(_tmp_val, VALTYPE, VAL); \ ht_set((HT), _tmp_key, _tmp_val); \ } while (0)
3. 使用示例
现在用户可以用非常简洁的方式插入字面量了,完全不需要手动构建struct tagged:
struct tbl my_ht; ht_init(&my_ht); // 插入字符串字面量作为key,整数作为value HT_SET_LITERAL(&my_ht, STR, "user_id", INT, 1001); // 插入浮点数作为key,字符串作为value HT_SET_LITERAL(&my_ht, FLOAT, 3.14f, STR, "pi"); // 插入字符作为key,整数作为value HT_SET_LITERAL(&my_ht, CHAR, 'a', INT, 97);
4. 针对字符串视图的补充
如果你的String类型是自定义的字符串视图(比如包含指针和长度的结构体),可以再加一个辅助宏来快速初始化字面量:
// 假设String的定义是:typedef struct { const char* ptr; size_t len; } String; #define STR_VIEW(STR) ((String){(STR), sizeof(STR)-1}) // 使用方式 HT_SET_LITERAL(&my_ht, STR, STR_VIEW("hello world"), INT, 42);
为什么不能用3个参数?
在C89里,宏无法“感知”字面量的类型——比如"hello"是char*,但你需要把它标记为STR类型;42是int,但你可能想把它作为FLOAT插入。没有显式的类型标记,宏根本不知道该初始化union的哪个成员,所以4个参数是C89下的最优解。
内容来源于stack exchange
相关产品推荐
相关产品推荐

