关于队列的 宏定义实现

引子

模块中用到队列,先自己实现了一个:
//队列所要包含的元素类型(简化)
typedef struct packet_s
{
       int data;
       struct packet_s *next;   //链表方式链接各个元素结点
} packet_t;
//队列类型
typedef struct
{
       struct packet_s *first; //指向第一个元素结点
       struct packet_s *tail;   //指向最后一个元素结点
} packetQueue_t;
//队列定义
  packetQueue_t packetQueue;
//队列访问接口,实现都比较简单,修改指针就行。略。
void packetQueue_init(packetQueue_t *pktQueue); //队列初始化
void packetQueue_push(packetQueue_t *pktQueue, packet_t *pkt); //入队
packet_t packetQueue_pop(packetQueue_t *pktQueue); //出队
上面的队列用起来还顺手,不过,没过多久模块需要加入新功能。新功能需要用到新的元素类型及队列,但由于元素类型不同,上面的队列访问函数代码不能重用。重新针对新元素重复实现队列不是好的软件工程实践。
那么,存在能容纳各种元素类型的队列吗?如果使用C++的话,STL中的queue能做到,因为C++有模版。但对于C语言来说,就需要使用宏的技巧。最近也在看libevent的源码,刚好发现它里面的队列实现完全采用宏,队列操作代码能够重用。有牛人的代码在,那就好办了。下面就解析下libevent中队列的接口和实现,记录下来以备项目中使用。

libevent中的队列代码其实用的是OpenBSD内核源码中的queue.h。Linux(/usr/include/sys)也有这个文件,使用man queue能了解queue.h中接口如何使用。不过相对于libevent中的queue.h,linux下的queue.h有些接口没有,man queue也就看不到。所以下面简述libevent的queue.h。

接口

先通过样例体会下queue.h中队列的接口的使用,详细的接口参考queue.h文件。在下面的附录也列出了queue.h中关于队列的代码。
#include <stdio.h>
#include <stdlib.h>

#include "queue.h" 

//struct element要放入队列,就在其定义中加入
//TAILQ_ENTRY(element) entries;
typedef struct element_s {
       int data;

       TAILQ_ENTRY(element_s) entries;
} element_t;

int main()
{
       element_t *p = NULL;
       element_t *q = NULL;
       element_t *curr = NULL;
     
       //定义队列
       TAILQ_HEAD(queue_s, element_s) queueHead;

       //队列初始化
       TAILQ_INIT(&queueHead);
      
       //尾部插入,入队
       p = malloc(sizeof(element_t));
       p->data = 1;
       TAILQ_INSERT_TAIL(&queueHead, p, entries); 

       q = malloc(sizeof(element_t));
       q->data = 2;
       TAILQ_INSERT_TAIL(&queueHead, q, entries);

       //遍历队列,输出数据
       TAILQ_FOREACH(curr, &queueHead, entries) {
             printf("%d\n", curr->data);
       }    

       //从头部删除,出队
       p = TAILQ_FIRST(&queueHead);
       TAILQ_REMOVE(&queueHead, p, entries);

       //输出队列尾部元素
       p = TAILQ_LAST(&queueHead, queue_s) {
       printf("%d\n", p->data);

       exit(EXIT_SUCCESS);
}

下面看看上面的宏是如何实现的。

实现

先看加入元素结构体定义中的TAILQ_ENTRY,它的作用是在元素中加入两个指针,链接上前后两个元素。
#define TAILQ_ENTRY(type)                   \
struct {                                             \
        struct type *tqe_next;   \  
        struct type **tqe_prev; \  
                                                
}
注意的地方是:第二个元素是指针的指针。这与一般数据结构书中的惯例不同。一般数据结构书中第二个元素是指针,不是指针的指针。使用指针的指针很巧妙,到TAILQ_INSERT_TAIL代码时说明为什么这么设计。

TAILQ_HEAD和TAILQ_INIT的实现比较简单,前者定义了由两个指针组成的结构体(即队列类型),后者初始化。
#define TAILQ_HEAD(name, type)                        \
struct name {                                                 \
       struct type *tqh_first;          \
       struct type **tqh_last;          \  
}
#define TAILQ_INIT(head) do {                         \  
        (head)->tqh_first= NULL;                         \
        (head)->tqh_last= &(head)->tqh_first;\
} while (0)

TAILQ_INSERT_TAIL的作用是在队列的尾部插入元素,其实现代码:
#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 (0)
上面代码没有任何if判断操作,这就体现出TAILQ_ENTRY和TAIL_HEAD中第二个字段使用指针的指针的作用了。假如像一般数据结构书的惯例一样,都使用指针。因为队列头结点与队列元素结点的类型不同,那么对于队列中第一个元素结点的prev指针就要设为NULL,以及队列类型中的last赋值为NULL表示空队列。如果这么设计,实现队列尾部插入时就肯定有个if判断操作,也就是类似于如下代码:
#define TAILQ_INSERT_TAIL(head,elm, field) do {       \
       (elm)->field.tqe_next= NULL;                              \
       (elm)->field.tqe_prev= (head)->tqh_last;            \
       if((head)->tqh_last == NULL) \
          (head)->tqh_first = elem;   \
       else                                                                  \
          (head)->tqh_last->next =elem;                         \
} while (0)
对于最基本的数据结构中的最经常用到的尾部插入操作(入队)而言,使用没有if判断的实现代码效率高一些。
可以比较体会下严蔚敏的《数据结构》中的队列的实现,它之中使用了一个多余的元素头结点,也使得入队的操作没有if判断操作。

TAILQ_INSERT_HEAD的作用是在队列的第一个元素前插入新元素,其实现如下,比较简单,无非是指针的操作。
#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 (0)
 
TAILQ_FOREACH和TAILQ_FIRST的实现代码略。详细的看下面的附录中的代码,比较简单
 
TAILQ_LAST的作用是计算出队列最后一个元素的地址,它的实现就有点难懂了,用到了TAILQ_ENTRY和TAILQ_HEAD内存布局一样的知识点:
#define TAILQ_LAST(head,headname)   \  
             (*(((struct headname *)((head)->tqh_last))->tqh_last))                
队列中的tqh_last字段的值是队列最后一个元素的tqe_next的地址,不是最后一个元素的地址。怎么计算出最后一个元素的地址呢?(structheadname *)(head)->tqh_last获得最后一个元素的tqe_next的地址,并强制转换成队列指针类型,再对其用->tqh_last就相当于获得了最后一个元素的tqe_prev地址(因为TAILQ_ENTRY和TAILQ_HEAD内存布局一样),然后解引用就得到了最后一个元素的地址。很巧妙!
 
其它接口的实现请看queue.h文件,略。有了上面的知识,就很简单了。

总结

开源软件中有很多相当好的代码,特别是一些基本数据结构都有比较高效的实现。有时间要多看和体会,用到自己的代码中,能帮助提高开发效率。

附录

queue.h文件中tailqueue相关代码如下。完整的queue.h代码可以先下载libevent库代码,然后在其compat/sys目录下找到。
#define TAILQ_HEAD(name, type)                                                          \
struct name {                                                                                                                \
             structtype *tqh_first;                                           \
             structtype **tqh_last;                                  \
}
 
#defineTAILQ_HEAD_INITIALIZER(head)                                                 \
             {NULL, &(head).tqh_first }
 
#define TAILQ_ENTRY(type)                                                                  \
struct {                                                                                                            \
             structtype *tqe_next;                              \
             structtype **tqe_prev;             \
}
 
#define TAILQ_FIRST(head)                     ((head)->tqh_first)
#define TAILQ_END(head)                        NULL
#define TAILQ_NEXT(elm, field)             ((elm)->field.tqe_next)
#define TAILQ_LAST(head,headname)                                                    \
             (*(((structheadname *)((head)->tqh_last))->tqh_last))
#define TAILQ_PREV(elm, headname,field)                                           \
             (*(((structheadname *)((elm)->field.tqe_prev))->tqh_last))
#define TAILQ_EMPTY(head)                                                                  \
             (TAILQ_FIRST(head)== TAILQ_END(head))
 
#define TAILQ_FOREACH(var, head,field)                                                       \
             for((var)= TAILQ_FIRST(head);                                              \
                   (var) != TAILQ_END(head);                                                \
                   (var) = TAILQ_NEXT(var, field))
 
#defineTAILQ_FOREACH_REVERSE(var, head, headname, field)                  \
             for((var)= TAILQ_LAST(head, headname);                                             \
                   (var) != TAILQ_END(head);                                                \
                   (var) = TAILQ_PREV(var, headname, field))
 
#define TAILQ_INIT(head) do {                                                            \
             (head)->tqh_first= NULL;                                                      \
             (head)->tqh_last= &(head)->tqh_first;                                              \
} while (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 (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 (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 (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 (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 (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);                                          \
} while (0)

转自 http://blog.sina.com.cn/s/blog_603e7371010120ck.html
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是基于数组实现队列的 C 语言代码: ```c #include <stdio.h> #include <stdlib.h> #define MAX_SIZE 10 int queue[MAX_SIZE]; int front = 0; int rear = -1; int itemCount = 0; // 创建队列 void createQueue() { front = 0; rear = -1; itemCount = 0; } // 入队列 void enqueue(int data) { if(itemCount >= MAX_SIZE) { printf("队列已满,无法插入元素\n"); return; } if(rear == MAX_SIZE - 1) { rear = -1; } queue[++rear] = data; itemCount++; } // 出队列 int dequeue() { if(itemCount == 0) { printf("队列为空,无法删除元素\n"); return -1; } int data = queue[front++]; if(front == MAX_SIZE) { front = 0; } itemCount--; return data; } // 队列是否为空 int isEmpty() { return itemCount == 0; } // 队列是否已满 int isFull() { return itemCount == MAX_SIZE; } // 获取队首元素 int peek() { return queue[front]; } // 获取队尾元素 int rearPeek() { return queue[rear]; } int main() { createQueue(); enqueue(10); enqueue(20); enqueue(30); enqueue(40); enqueue(50); printf("队首元素为:%d\n", peek()); printf("队尾元素为:%d\n", rearPeek()); printf("出队列元素为:%d\n", dequeue()); printf("出队列元素为:%d\n", dequeue()); printf("队列是否为空:%d\n", isEmpty()); printf("队列是否已满:%d\n", isFull()); return 0; } ``` 在该代码中,我们使用 `MAX_SIZE` 宏定义来表示队列的最大容量,使用 `queue` 数组来存储队列元素,使用 `front` 和 `rear` 分别表示队列的队首和队尾的下标,使用 `itemCount` 来记录队列中元素的个数。 `createQueue()` 函数用于创建队列,将 `front`、`rear` 和 `itemCount` 初始化为初始值。 `enqueue(int data)` 函数用于入队列,首先检查队列是否已满,如果已满则输出错误信息并返回;如果 `rear` 已经达到数组的最大下标,则将其重置为 `-1`,然后将 `data` 插入到 `queue[++rear]` 中,最后将 `itemCount` 加一。 `dequeue()` 函数用于出队列,首先检查队列是否为空,如果为空则输出错误信息并返回 `-1`;否则将队首元素 `queue[front++]` 返回,将 `front` 加一,如果 `front` 已经达到数组的最大下标,则将其重置为 `0`,最后将 `itemCount` 减一。 `isEmpty()` 函数用于判断队列是否为空,如果队列中元素个数为 `0`,则返回 `1`,否则返回 `0`。 `isFull()` 函数用于判断队列是否已满,如果队列中元素个数等于 `MAX_SIZE`,则返回 `1`,否则返回 `0`。 `peek()` 函数用于获取队首元素,直接返回 `queue[front]` 即可。 `rearPeek()` 函数用于获取队尾元素,直接返回 `queue[rear]` 即可。 在 `main()` 函数中,我们先调用 `createQueue()` 函数创建队列,然后使用 `enqueue()` 函数向队列中插入元素,使用 `peek()` 和 `rearPeek()` 函数分别获取队首和队尾元素,使用 `dequeue()` 函数出队列,最后使用 `isEmpty()` 和 `isFull()` 函数分别判断队列是否为空和已满,输出结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值