在C中创建带有结构的队列

时间:2016-08-19 20:28:56

标签: c

我有一个实现循环队列系统的代码(在本文末尾)。一切都很完美,但是在函数createQueue中可以看到,队列只针对整数实现。我想修改此代码以接受用户通知的结构。

我可以创建一个已知的结构并用整数替换所有站点,但这样我就会将代码耦合到一个已知的结构。坏主意......

如何在不需要先知道结构的情况下,将函数createQueue传递给内存分配结构?结构队列也应该改变,在 int * elements 中,值应该从integer变为void?

#include <stdio.h>
#include <stdlib.h>

typedef struct Queue
{
        int capacity;
        int size;
        int front;
        int rear;
        int *elements;
}Queue;

Queue * createQueue(int maxElements)
{
        /* Create a Queue */
        Queue *Q;
        Q = (Queue *)malloc(sizeof(Queue));
        /* Initialise its properties */
        Q->elements = (int *)malloc(sizeof(int)*maxElements);
        Q->size = 0;
        Q->capacity = maxElements;
        Q->front = 0;
        Q->rear = -1;
        /* Return the pointer */
        return Q;
}
void Dequeue(Queue *Q)
{
        /* If Queue size is zero then it is empty. So we cannot pop */
        if(Q->size==0)
        {
                printf("Queue is Empty\n");
                return;
        }
        /* Removing an element is equivalent to incrementing index of front by one */
        else
        {
                Q->size--;
                Q->front++;
                /* As we fill elements in circular fashion */
                if(Q->front==Q->capacity)
                {
                        Q->front=0;
                }
        }
        return;
}
int front(Queue *Q)
{
        if(Q->size==0)
        {
                printf("Queue is Empty\n");
                exit(0);
        }
        /* Return the element which is at the front*/
        return Q->elements[Q->front];
}
void Enqueue(Queue *Q,int element)
{
        /* If the Queue is full, we cannot push an element into it as there is no space for it.*/
        if(Q->size == Q->capacity)
        {
                printf("Queue is Full\n");
        }
        else
        {
                Q->size++;
                Q->rear = Q->rear + 1;
                /* As we fill the queue in circular fashion */
                if(Q->rear == Q->capacity)
                {
                        Q->rear = 0;
                }
                /* Insert the element in its rear side */ 
                Q->elements[Q->rear] = element;
        }
        return;
}
int main()
{
        Queue *Q = createQueue(5);
        Enqueue(Q,1);
        Enqueue(Q,2);
        Enqueue(Q,3);
        Enqueue(Q,4);
        printf("Front element is %d\n",front(Q));
        Enqueue(Q,5);
        Dequeue(Q);
        Enqueue(Q,6);
        printf("Front element is %d\n",front(Q));
}

2 个答案:

答案 0 :(得分:9)

即使你不是C ++的好朋友,你也可以创建一个伪模板:

#include <stdio.h>
#include <stdlib.h>

#define QUEUE_TEMPLATE(ELTTYPE) \
typedef struct \
{\
        int capacity;\
        int size;\
        int front;\
        int rear;\
        ELTTYPE *elements;\
}Queue_##ELTTYPE;\
\
Queue_##ELTTYPE * createQueue_##ELTTYPE(int maxElements)\
{\
        /* Create a Queue */\
        Queue_##ELTTYPE *Q;\
        Q = (Queue_##ELTTYPE *)malloc(sizeof(Queue_##ELTTYPE));\
        /* Initialise its properties */\
        Q->elements = malloc(sizeof(ELTTYPE)*maxElements);\
        Q->size = 0;\
        Q->capacity = maxElements;\
        Q->front = 0;\
        Q->rear = -1;\
        /* Return the pointer */\
        return Q;\
}\
void Dequeue_##ELTTYPE(Queue_##ELTTYPE *Q)\
{\
        /* If Queue size is zero then it is empty. So we cannot pop */\
        if(Q->size==0)\
        {\
                printf("Queue is Empty\n");\
                return;\
        }\
        /* Removing an element is equivalent to incrementing index of front by one */\
        else\
        {\
                Q->size--;\
                Q->front++;\
                /* As we fill elements in circular fashion */\
                if(Q->front==Q->capacity)\
                {\
                        Q->front=0;\
                }\
        }\
        return;\
}\
ELTTYPE front_##ELTTYPE(Queue_##ELTTYPE *Q)\
{\
        if(Q->size==0)\
        {\
                printf("Queue is Empty\n");\
                exit(0);\
        }\
        /* Return the element which is at the front*/\
        return Q->elements[Q->front];\
}\
void Enqueue_##ELTTYPE(Queue_##ELTTYPE *Q,ELTTYPE element)\
{\
        /* If the Queue is full, we cannot push an element into it as there is no space for it.*/\
        if(Q->size == Q->capacity)\
        {\
                printf("Queue is Full\n");\
        }\
        else\
        {\
                Q->size++;\
                Q->rear++;\
                /* As we fill the queue in circular fashion */\
                if(Q->rear == Q->capacity)\
                {\
                        Q->rear = 0;\
                }\
                /* Insert the element in its rear side */ \
                Q->elements[Q->rear] = element;\
        }\
        return;\
}

QUEUE_TEMPLATE(int);
QUEUE_TEMPLATE(float);

int main()
{
        Queue_int *Q = createQueue_int(5);
        Queue_float *QF = createQueue_float(5);
        Enqueue_int(Q,1);
        Enqueue_int(Q,2);
        Enqueue_int(Q,3);
        Enqueue_int(Q,4);
        printf("Front element is %d\n",front_int(Q));
        Enqueue_int(Q,5);
        Dequeue_int(Q);
        Enqueue_int(Q,6);
        printf("Front element is %d\n",front_int(Q));

        Enqueue_float(QF,1);
        Enqueue_float(QF,2);
        Enqueue_float(QF,3);
        Enqueue_float(QF,4);
        printf("Front element is %f\n",front_float(QF));
        Enqueue_float(QF,5);
        Dequeue_float(QF);
        Enqueue_float(QF,6);
        printf("Front element is %f\n",front_float(QF));
}

我添加了2种不同类型的实例。输出是:

Front element is 1
Front element is 2
Front element is 1.000000
Front element is 2.000000

此方法的缺点:宏代码上的编译错误可能很难追踪。 您可以使用已知类型创建代码,调试/改进它,然后使用sed过滤器生成宏代码,将类型替换为ELTTYPE并添加#define和尾随反斜杠

答案 1 :(得分:4)

你喜欢多少

Jean-FrançoisFabre的方法很好,并且是使用C ++模板的公平近似。我有另一个避免使用预处理器,但它是一个很多更多的工作,并且是一个差到中等近似的子类。我更喜欢这种方法用于预处理器方法,主要是因为我脑部受损。

我们从您的基本Queue类型开始,但不存储int数组,而是存储void *数组:

typedef struct Queue
{
        int capacity;
        int size;
        int front;
        int rear;
        void **elements; 
        ...
}Queue;

意味着您的Enqueue原型看起来像

void Enqueue( Queue *Q, void *element )

现在,只要我们使用void *,就会出现 问题 - 我们已经将窗口中的类型安全性抛出,副本(我们不能只将element写入elements列表。我们也无法使用void *来存储intfloat值等标量。所以我们需要至少添加以下类型感知辅助函数:

  • copy - 分配并将输入的副本分配给Enqueue
  • destroy - 销毁从队列中删除的对象

因此我们将这些函数添加到Queue类型中,如下所示:

typedef struct Queue
{
        int capacity;
        int size;
        int front;
        int rear;
        void *elements;
        void *(*copy)(const void *);
        void (*destroy)(const void *);
}Queue;

所以,假设我们要创建一个存储float的队列。我们创建了以下辅助函数:

void *copyFloat( const void *f )
{
  float *p = malloc( sizeof *p );
  if ( p )
  {
    *p = *(float *) f;
  }
  return p;
}

void destroyFloat( const void *f )
{
  free( f );
}

然后创建一个使用它们的新Queue对象:

Queue * createQueue(int maxElements, 
                    void *(*copy)(const void *), 
                    void (*destroy)(const void *),
{
  /* Create a Queue */
  Queue *Q;
  Q = malloc( sizeof *Q );
  /* Initialise its properties */
  Q->elements = malloc(sizeof *Q->elements * maxElements);
  Q->size = 0;
  Q->capacity = maxElements;
  Q->front = 0;
  Q->rear = -1;

  Q->copy = copy;
  Q->destroy = destroy;

  /* Return the pointer */
  return Q;
}
...
Queue *floatQueue = CreateQueue( 100, copyFloat, destroyFloat );

您的Enqueue功能现在看起来像

void Enqueue(Queue *Q, void *element)
{
  /* If the Queue is full, we cannot push an element into it as there is no space for it.*/
  if(Q->size == Q->capacity)
  {
    printf("Queue is Full\n");
  }
  else
  {
    Q->size++;
    Q->rear = Q->rear + 1;
    /* As we fill the queue in circular fashion */
    if(Q->rear == Q->capacity)
    {
      Q->rear = 0;
    }
    /* Insert the element in its rear side */ 
    Q->elements[Q->rear] = Q->copy( element ); // <--- create a copy of the input
  }
  return;
}

为什么我们需要创建副本?想象一下,如果你像下面这样调用Enqueue

float f;
...
f = get_new_value();
Enqueue( &queue, &f );

如果我们只是将输入参数element复制到队列中,我们会将相同的地址写入队列的每个元素 - 变量f的地址。因此,队列的每个元素都指向相同(无效)的东西。

相反,当我们将某个对象出列时,我们现在必须确保清理该内存:

void Dequeue(Queue *Q)
{
  /* If Queue size is zero then it is empty. So we cannot pop */
  if(Q->size==0)
  {
    printf("Queue is Empty\n");
    return;
  }
  /* Removing an element is equivalent to incrementing index of front by one */
  else
  {
    Q->destroy( Q->elements[Q->front] ); // deallocate the dequeued object
    Q->size--;
    Q->front++;
    /* As we fill elements in circular fashion */
    if(Q->front==Q->capacity)
    {
      Q->front=0;
    }
  }
  return;
}

您的front函数现在返回void *

void *front(Queue *Q)
{
  if(Q->size==0)
  {
    printf("Queue is Empty\n");
    exit(0);
  }
  /* Return the element which is at the front*/
  return Q->elements[Q->front];
}

我们还没有完成 - 为了完成这项工作,我们仍然需要一个类型感知的前端:

void EnqueueFloat( Queue *floatQueue, float f )
{
  Enqueue( floatQueue, &f );
}

float frontFloat( Queue *floatQueue )
{
  float *p = front( floatQueue );
  return *p;
}
真是一团糟。

但是...

一旦你有了基本的队列机制,你需要做的就是为你想要使用的每个新类型实现新的copydestroy函数,以及一个新的类型感知前端结束。您不必为每种数据类型创建全新的Queue_XXX数据类型,也不需要创建Enqueue_XXXfront_XXX以及Dequeue_XXX的全新副本;你只需要为每个实现一个瘦的,类型感知的包装器。

毫无疑问,有很多方法可以改善我所写的内容;有办法避免为每个输入分配一个deallocating副本,但是然后索引到队列变得不那么干净了。

无论如何,这只是思考的问题。