Let's Build a Queue
What is a queue?
Things We might Do with a Queue
Terminology. . .
An item gets into the queue at the back or tail.
The item waits and waits, while advancing towards the front or head of the queue.
The item at the head of the queue is the next one to leave the queue.
Diagrams. . .
enqueue A
Now, enqueue B
Now, enqueue C
And last, dequeue
A linked data structure works well for a queue.
Make it doubly linked for ease of implementation. . .
The Queue class appears as
/-----\
head --> | | <-- tail
| |
\-----/
member functions:
Queue objects using
call by value semantics
(There are likely lots of other reasonable member functions, but this is a simplified example.)
What are we putting into the queue?
Assume for this example that we are working
with Widget class objects.
In reality, we would use templates to implement a type-independent queue class.
An incomplete class declaration:
class Queue {
public:
Queue(); //construct an empty queue
Queue(const Queue &); //copy constructor
void enqueue(Widget *);
Widget * dequeue();
private:
??? * head;
??? * tail;
};
A typical implementation of a linked data structure
uses a struct
(not an object, just a structure!),
that is most often called a Node.
struct Node {
Widget * w;
Node * next;
Node * previous;
};
The fields are all pointers.
A complete diagram of the queue when it has
3 Widget's in it:
Therefore, the declarations of head
and tail are:
Node * head; Node * tail;
The Node structure is of use
internally to the queue class, so make it private.
A more complete declaration of the queue class:
class Queue {
public:
Queue(); //construct an empty queue
Queue(const Queue &); //copy constructor
void enqueue(Widget *);
Widget * dequeue();
private:
struct Node {
Widget * w;
Node * next;
Node * previous;
};
Node * head;
Node * tail;
};
And, place this declaration into a .h file,
using a #ifndef to avoid double "includes,"
which the compiler hates.
#ifndef QUEUE_H
#define QUEUE_H
class Queue {
public:
Queue(); //construct an empty queue
Queue(const Queue &); //copy constructor
void enqueue(Widget *);
Widget * dequeue();
private:
struct Node {
Widget * w;
Node * next;
Node * previous;
};
Node * head;
Node * tail;
};
#endif
To play with a Queue,
we must instantiate one.
Queue *pQ1; pQ1 = new Queue(); // "new" invokes constructor
Before we can enqueue a Widget,
we need one:
Widget * pW = new Widget(); (*pQ1).enqueue(pW);
Here is another Queue,
instantiated in a different way:
Queue Q2; // declaration invokes constructor
But, do not ever do this:
Q2.enqueue(pW);
Why? This gives us 2 distinct queues pointing to only 1 Widget! It makes memory management impossible!
Why discuss this seemingly silly case? Because lots of programmers accidentally make this error.
How to get another Widget instance?
Use the copy constructor, if an exact copy of that same one is needed.
Widget * pW2 = new Widget(*pW);
| Copyright © Karen Miller, 2009 |