Lesson 4
Introduction to Queues in C++
Introduction to Queues

Hello there! Today, we will unveil Queues in coding, likening them to a line in a coffee shop or a queue of print requests. Queues in computer science are First-In, First-Out (FIFO) structures. Consider this example: you're at a theme park — the first person in line for the roller coaster gets on first. Today's lesson revolves around this straightforward yet powerful concept. So, let's dive in!

Implementing a Queue in C++

Let's explore the implementation of Queues in C++. An array or a linked list is ideal for implementing a Queue, but we'll showcase a simple array-based implementation for clarity. Let's define the Queue:

C++
1#include <iostream> 2using namespace std; 3 4class Queue { 5 int front, rear, size, capacity; 6 int* array; 7 8public: 9 Queue(int capacity) { 10 this->capacity = capacity; // Set the max size 11 front = size = 0; // Initialize front and size 12 rear = capacity - 1; // Initialize rear 13 array = new int[this->capacity]; 14 } 15 16 // Destructor to clean up resources 17 ~Queue() { 18 delete[] array; 19 } 20 21 // Will return true if the Queue is full 22 bool isFull() { 23 return (size == capacity); 24 } 25 26 // Will return true if the Queue is empty 27 bool isEmpty() { 28 return (size == 0); 29 } 30};

In the Queue class above, the isFull() method checks if our queue is already at maximum capacity, and isEmpty() checks if the queue is empty.

Rule of Three for Queues

When implementing classes that manage dynamic memory in C++, the Rule of Three becomes crucial. This rule indicates that if a class requires a custom destructor, it likely also requires a user-defined copy constructor and copy assignment operator. This trio of special member functions ensures that objects manage their resources correctly, preventing memory leaks and undefined behavior during object copying or assignment.

Here's how the Rule of Three applies to our Queue implementation:

C++
1Queue(const Queue& other) : front(other.front), rear(other.rear), size(other.size), capacity(other.capacity) { 2 array = new int[capacity]; 3 std::copy(other.array, other.array + capacity, array); 4} 5 6Queue& operator=(const Queue& other) { 7 if (this != &other) { 8 delete[] array; 9 front = other.front; 10 rear = other.rear; 11 size = other.size; 12 capacity = other.capacity; 13 array = new int[capacity]; 14 std::copy(other.array, other.array + capacity, array); 15 } 16 return *this; 17}

Copy Constructor Queue(const Queue& other): This constructor initializes a new Queue object using an existing Queue, copying all properties: front, rear, size, and capacity, as well as dynamically allocating a new array and copying the data from the original queue. This ensures that both queues maintain their data integrity independently without sharing memory.

Copy Assignment Operator Queue& operator=(const Queue& other): It checks for self-assignment before proceeding. The operator deletes the existing array memory of the target object, then copies the attributes and data from another Queue. Memory allocation and element copying ensure that the assigning queue's contents replace the existing queue's data safely and efficiently.

By implementing the Rule of Three, our Queue class avoids potential pitfalls associated with resource management, such as memory leaks or dangling pointers, and ensures that each Queue instance can be independently managed and safely destroyed.

Queue Enqueue Operation

Enqueue, a fancy term, denotes adding an item to the queue — the item lines up at the rear. Here's how it plays out in our Queue class:

C++
1void enqueue(int item) { 2 if (isFull()) { // Check if the queue is full 3 cout << "Queue is full\n"; 4 return; 5 } 6 rear = (rear + 1) % capacity; // Move rear 7 array[rear] = item; // Add item at rear position 8 size = size + 1; // increment size 9 cout << item << " enqueued to queue\n"; 10}

rear = (rear + 1) % capacity uses the modulo operator to calculate the new position for the rear pointer, ensuring it wraps around to the start of the queue when it surpasses the maximum index, thereby maintaining a circular behavior.

Queue Dequeue Operation

Just as Enqueue adds an element to our queue, Dequeue removes it. It extracts the element at the queue's front, reducing its size. However, we encounter an underflow condition if there are no elements to remove.

C++
1int dequeue() { 2 if (isEmpty()) { // Check if the queue is empty 3 cout << "Queue is empty\n"; 4 return INT_MIN; 5 } 6 7 int item = array[front]; // Item at front 8 front = (front + 1) % capacity; // Move front 9 size = size - 1; // decrement size 10 return item; // return removed item 11}

The dequeue() method checks for emptiness before fetching the item.

Using C++'s std::queue Class

While it is essential to understand the queue's inner structure, we won't implement it ourselves in most cases. Instead, we will use C++'s built-in std::queue class. Think of any system that needs to handle requests in a fair, first-come-first-served order. These are all excellent candidates for using a queue. Here's how you can declare, initialize, and utilize a std::queue using C++:

C++
1#include <iostream> 2#include <queue> 3 4int main() { 5 std::queue<int> queue; 6 7 // Add elements {0, 1, 2, 3, 4} to queue 8 for (int i = 0; i < 5; i++) 9 queue.push(i); 10 11 // Display and remove contents of the queue. 12 std::cout << "Elements of queue: "; 13 while (!queue.empty()) { 14 std::cout << queue.front() << " "; 15 queue.pop(); 16 } 17 std::cout << std::endl; 18 19 return 0; 20}

In the above example, we create an instance of std::queue<int> using C++. Key Queue operations include:

  • push(): Inserts the specified element into the Queue.
  • pop(): Removes the head of the Queue.
  • front(): Retrieves the element at the head of the Queue without removing it.
  • empty(): Checks if the Queue is empty.

C++'s std::queue class is versatile and can accommodate diverse data types.

In Summary: Queues

We've learned about the Queue, its operations, and its implementation in C++. Furthermore, these techniques are fundamental for smooth functioning in daily life. They are invaluable and versatile in various applications, from data buffering in hardware to process scheduling in operating systems.

With your newfound knowledge of the Queue data structure using C++, it's time to level up! Next are some practice problems to enhance your understanding of these concepts. Let's dive in!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.