如果我们写个排序方法, 用c语言实现的话,由于没有通用的类型,也没有c++的模板,没有java的泛化,这样不同的数据类型,我们就要实现多个排序方法,比如实现个int的排序方法,实现double类型的排序方法,但是这样重复的代码很多,其实可以用宏来实现。
同样有个需求要我们写个list,如果里面保存的数据类型不同,就要实现多个,这种实现方法会有太多代码重复,用宏实现是个不错的思路,用宏来实现相关函数还可以提升性能, 当然宏也有明显的缺点,比如不利于调试,写不好也容易在展开的时候出错。
这个list 可以看作是双向链表,具体的实现如下:
- #ifndef B4EDBD16_2948_4595_9135_6BBBAF9B6341
- #define B4EDBD16_2948_4595_9135_6BBBAF9B6341
- /*
- * Tail queue definitions.
- */
- #define _TAILQ_HEAD(name, type, qual) \
- struct name { \
- qual type *tqh_first; /* first element */ \
- qual type *qual *tqh_last; /* addr of last next element */ \
- }
- #define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,)
-
- #define TAILQ_HEAD_INITIALIZER(head) \
- { NULL, &(head).tqh_first }
-
- #define _TAILQ_ENTRY(type, qual) \
- struct { \
- qual type *tqe_next; /* next element */ \
- qual type *qual *tqe_prev; /* address of previous next element */\
- }
- #define TAILQ_ENTRY(type) _TAILQ_ENTRY(struct type,)
- #define TAILQ_END(head) NULL
- #define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
- for((var) = TAILQ_FIRST(head), \
- (tvar) = TAILQ_FIRST(head) ? TAILQ_NEXT(TAILQ_FIRST(head), field): NULL ; \
- (var) != TAILQ_END(head); \
- (var = tvar), (tvar) = var ? TAILQ_NEXT(var, field): NULL)
- /*
- * Tail queue functions.
- */
- #define TAILQ_INIT(head) do { \
- (head)->tqh_first = NULL; \
- (head)->tqh_last = &(head)->tqh_first; \
- } while (/*CONSTCOND*/0)
-
- #define TAILQ_INSERT_HEAD(head, elm, field) do { \
- if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \
- (head)->tqh_first->field.tqe_prev = \
- &(elm)->field.tqe_next; \
- else \
- (head)->tqh_last = &(elm)->field.tqe_next; \
- (head)->tqh_first = (elm); \
- (elm)->field.tqe_prev = &(head)->tqh_first; \
- } while (/*CONSTCOND*/0)
-
- #define TAILQ_INSERT_TAIL(head, elm, field) do { \
- (elm)->field.tqe_next = NULL; \
- (elm)->field.tqe_prev = (head)->tqh_last; \
- *(head)->tqh_last = (elm); \
- (head)->tqh_last = &(elm)->field.tqe_next; \
- } while (/*CONSTCOND*/0)
-
- #define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
- if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\
- (elm)->field.tqe_next->field.tqe_prev = \
- &(elm)->field.tqe_next; \
- else \
- (head)->tqh_last = &(elm)->field.tqe_next; \
- (listelm)->field.tqe_next = (elm); \
- (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \
- } while (/*CONSTCOND*/0)
-
- #define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
- (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
- (elm)->field.tqe_next = (listelm); \
- *(listelm)->field.tqe_prev = (elm); \
- (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \
- } while (/*CONSTCOND*/0)
-
- #define TAILQ_REMOVE(head, elm, field) do { \
- if (((elm)->field.tqe_next) != NULL) \
- (elm)->field.tqe_next->field.tqe_prev = \
- (elm)->field.tqe_prev; \
- else \
- (head)->tqh_last = (elm)->field.tqe_prev; \
- *(elm)->field.tqe_prev = (elm)->field.tqe_next; \
- } while (/*CONSTCOND*/0)
- #define TAILQ_REPLACE(head, elm, elm2, field) do { \
- if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \
- (elm2)->field.tqe_next->field.tqe_prev = \
- &(elm2)->field.tqe_next; \
- else \
- (head)->tqh_last = &(elm2)->field.tqe_next; \
- (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \
- *(elm2)->field.tqe_prev = (elm2); \
- _Q_INVALIDATE((elm)->field.tqe_prev); \
- _Q_INVALIDATE((elm)->field.tqe_next); \
- } while (0)
- #define TAILQ_FOREACH(var, head, field) \
- for ((var) = ((head)->tqh_first); \
- (var); \
- (var) = ((var)->field.tqe_next))
-
- #define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
- for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last)); \
- (var); \
- (var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last)))
-
- #define TAILQ_CONCAT(head1, head2, field) do { \
- if (!TAILQ_EMPTY(head2)) { \
- *(head1)->tqh_last = (head2)->tqh_first; \
- (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
- (head1)->tqh_last = (head2)->tqh_last; \
- TAILQ_INIT((head2)); \
- } \
- } while (/*CONSTCOND*/0)
-
- /*
- * Tail queue access methods.
- */
- #define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
- #define TAILQ_FIRST(head) ((head)->tqh_first)
- #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
-
- #define TAILQ_LAST(head, headname) \
- (*(((struct headname *)((head)->tqh_last))->tqh_last))
- #define TAILQ_PREV(elm, headname, field) \
- (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
- #endif /* B4EDBD16_2948_4595_9135_6BBBAF9B6341 */
利用这个宏实现的小例子:
- typedef struct _stu {
- char name[54];
- int age;
- // 在链表成员中的使用
- TAILQ_ENTRY(_stu) next;
- } stu;
-
- typedef struct _sch {
- // 链表
- TAILQ_HEAD(_stu_head, _stu) stu_list;
- } sch;
-
-
- int main(int argc, char **argv)
- {
- sch *psch = (sch *)calloc(1, sizeof(sch));
- // 链表初始化
- TAILQ_INIT(& (psch->stu_list));
-
- stu s1 = {.name = "s1", .age = 1};
- stu s2 = {.name = "s2", .age = 2};
- stu s3 = {.name = "s3", .age = 3};
- stu s4 = {.name = "s4", .age = 4};
- stu s5 = {.name = "s5", .age = 5};
- stu s6 = {.name = "s6", .age = 6};
-
- // 在list前面插入
- TAILQ_INSERT_HEAD(&(psch->stu_list), &s1, next);
- TAILQ_INSERT_HEAD(&(psch->stu_list), &s2, next);
-
- // 在list的队尾插入
- TAILQ_INSERT_TAIL(&(psch->stu_list), &s3, next);
- TAILQ_INSERT_TAIL(&(psch->stu_list), &s5, next);
-
- // 在特定元素前面插入
- TAILQ_INSERT_BEFORE(&s5, &s4, next);
- TAILQ_INSERT_TAIL(&(psch->stu_list), &s6, next);
- printf("queue_head:%p, first:%p, last:%p\n", (void*)&(psch->stu_list), (void*)(psch->stu_list).tqh_first, (void*)(psch->stu_list).tqh_last);
-
- stu *p = NULL;
- // 正向遍历
- TAILQ_FOREACH(p, &(psch->stu_list), next) {
- printf("item:%p, next:%p, &next:%p, prev:%p, *prev:%p\n", p,p->next.tqe_next, &p->next.tqe_next,p->next.tqe_prev,& p->next.tqe_prev);
- printf("name:%s\t age:%d\n", p->name, p->age);
- }
- printf("---------------------------------\n");
-
- // 反向遍历
- TAILQ_FOREACH_REVERSE(p, &(psch->stu_list), _stu_head, next) {
- printf("name:%s\t age:%d\n", p->name, p->age);
- }
- free(psch);
- return 0;
- }
上述链表的示意图如下:
注意这个链表和我们一般自己在初学c语言的链表不一样,这个里面的tqe_prev指向的不是前一个元素,而是指向前一个元素的tqe_next, 注意这个tqe_next是在stu里面的next的成员里面的。
这个宏有个比较难以理解的地方,就是得到最后一个元素和得到前一个元素:
- #define TAILQ_LAST(head, headname) \
- (*(((struct headname *)((head)->tqh_last))->tqh_last))
- #define TAILQ_PREV(elm, headname, field) \
- (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
我们拿一个来解释TAILQ_LAST,如下图展示: 假如有如下的list:
获取最后一个元素的示意图如下:
TAILQ_LAST分为两步:
- 1. 获取tqh_last 指针,即指到最后一个元素的tqe_next;
- 2. 通过最后一个元素的tqe_next 获取到指向最后一个元素即绿色的stu的地址。
第一步很容易理解,通过(head)->tqh_last 获取到最后一个元素的tqe_next. 第二步很难理解,因为它把这个地址强制转成队列,成员怎么转成队列那,那是因为两个都是指针,指针的大小都是一样的,而且都是两个指针变量,所以可以互转,转了之后可以看到如上图的对应关系,tqe_next 变成了队列的tqh_first 了、同样tqe_prev也就变成了tqh_last,而tqh_last 即tqe_prev指向黄色元素的tqe_next了,然后通过* 取值即变成了绿色的stu的地址,即最后一个元素的地址了。
- #define TAILQ_INSERT_TAIL(head, elm, field) do { \
- (elm)->field.tqe_next = NULL; \
- (elm)->field.tqe_prev = (head)->tqh_last; \
- *(head)->tqh_last = (elm); \
- (head)->tqh_last = &(elm)->field.tqe_next; \
- } while (/*CONSTCOND*/0)
再画个插入的示意图:
插入一个元素:
线条上的数字即插入的顺序。
再次插入一个元素:
最终形成的链表:
宏在调试之前,我们可以先通过gcc命令进行展开,可以获取到展开宏的源码。
gcc -E -P main.c > main.expand.c
在gdb调试的时候,可以通过:
gdb) macro expand 宏
gcc编译的时候、添加-g3 便于调试宏
gcc -g3
- https://www.361shipin.com/blog/1534986025145729024
- https://www.getdami.com/questions/wei-dui-lie-dai-ma-zhong-de-tailq_lastzen-yao-li-jie-89811/