这个问题的前言是,我意识到 C 宏是一个敏感话题。很多时候,他们可以通过非宏观解决方案来实现,这种解决方案更安全,不受经典问题的影响,例如增加的参数;因此,在 C 中有一个哈希表实现,其中链接节点用于冲突。我敢肯定大多数人已经看过这一百万次,但有点像这样。
typedef struct tnode_t {
char* key; void* value; struct tnode_t* next;
} tnode_t;
typedef struct table_t {
tnode_t** nodes;
unsigned long node_count;
unsigned long iterator; // see macro below
...
}
我想提供一种迭代遍历节点的抽象方式。我考虑使用一个函数,它接受一个函数指针并将函数应用于每个节点,但我经常发现这种解决方案非常有限,所以我想出了这个宏:
#define tbleach(table, node) \
for(node=table->nodes[table->iterator=0];\
table->iterator<table->node_count;\
node=node?node->next:table->nodes[++table->iterator])\
if (node)
可以使用:
tnode_t* n;
tbleach(mytable, n) {
do_stuff_to(n->key, n->value);
}
我能看到的唯一缺点是迭代器索引是表的一部分,所以显然你不能在同一个表中同时进行两个循环。我不知道如何解决这个问题,但我不认为这是一个交易破坏者,考虑到这个小宏将有多大用处。所以我的问题。
** 已更新 **
我收集了Zack和Jens的建议,用“else”删除了问题,并在for语句中声明了迭代器。一切似乎都有效,但是visual studio抱怨使用宏时“不允许使用类型名称”。我想知道这里到底发生了什么,因为它编译并运行但我不确定迭代器的作用域。
#define tbleach(table, node) \
for(node=table->nodes[0], unsigned long i=0;\
i<table->node_count;\
node=node?node->next:table->nodes[++i])\
if (!node) {} else
这种做法是不好的形式,如果没有,有什么方法可以改善吗?
答案 0 :(得分:7)
那里唯一真正不可接受的的东西是你已经说过的 - 迭代器是表的一部分。你应该这样做:
typedef unsigned long table_iterator_t;
#define tbleach(table, iter, node) \
for ((iter) = 0, (node) = (table)->nodes[(iter)]; \
(iter) < (table)->node_count; \
(node) = ((node) && (node)->next) \
? (node)->next : (table)->nodes[++(iter)])
// use:
table_iterator_t i;
tnode_t *n;
tbleach(mytable, i, n) {
do_stuff_to(n->key, n->value);
}
我还将if
语句写入for循环表达式,因为它更安全(如果循环体的闭括号之后的下一个标记为else
,则不会发生奇怪的事情) 。请注意,与通常的约定不同,将读取数组条目table->nodes[table->node_count]
,因此您需要为其分配空间(并确保它始终为NULL)。我认为你的版本也是如此。
编辑:修正了表条目为NULL的情况的逻辑。
答案 1 :(得分:1)
除了Zack关于迭代器的回答以及可能会混淆语法的终止if
:
如果您有C99,请在for循环中使用局部变量。这将避免周围范围的变量的不良意外,这些变量将保留悬空指针。在宏中使用类似的东西:
for(nodeS node = ...
并且,末尾的_t
名称由POSIX保留。所以最好不要将它们用于自己的类型。