我有一个实现循环队列系统的代码(在本文末尾)。一切都很完美,但是在函数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));
}
答案 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 *
来存储int
或float
值等标量。所以我们需要至少添加以下类型感知辅助函数:
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;
}
真是一团糟。
但是...
一旦你有了基本的队列机制,你需要做的就是为你想要使用的每个新类型实现新的copy
和destroy
函数,以及一个新的类型感知前端结束。您不必为每种数据类型创建全新的Queue_XXX
数据类型,也不需要创建Enqueue_XXX
和front_XXX
以及Dequeue_XXX
的全新副本;你只需要为每个实现一个瘦的,类型感知的包装器。
毫无疑问,有很多方法可以改善我所写的内容;有办法避免为每个输入分配一个deallocating副本,但是然后索引到队列变得不那么干净了。
无论如何,这只是思考的问题。