Hello, dear student! Today, we are embarking on an exciting journey through Stacks, a powerful tool in C++. In programming, Stacks are fundamental data structures utilized in various applications. Our goal for this lesson is to understand the concept of Stacks, learn how to implement and manipulate them in C++, and explore their complexities. Let's get started!
First and foremost, let's understand what a Stack is. Imagine a stack of plates that you can only remove from the top. That's precisely what a Stack is: a Last-In, First-Out (LIFO) structure. Stacks are used in memory management, backtracking algorithms, and more. The key operations involved are Push
(adding an element to the top of the stack), Pop
(removing the topmost element), and Peek
(looking at the topmost element without removing it).
C++ provides several ways to implement Stacks, including using arrays or leveraging the Standard Library. Here, we will focus on implementing an array-based stack due to its simplicity and efficiency within a defined capacity. Let's look into creating a Stack using an array in C++:
C++1class Stack { 2 int size; 3 int top = -1; 4 int* stackArray; 5 6public: 7 Stack(int size) : size(size) { 8 stackArray = new int[size]; 9 } 10 11 ~Stack() { 12 delete[] stackArray; 13 } 14};
In this code, top
represents the index in the stackArray
that currently holds the top element of the stack. It is initialized to -1
, indicating the stack is empty, signifying no valid index for the top element.
Since we are working with dynamic memory, it is essential to deallocate it before the object is destroyed, necessitating the user-defined destructor above. The Rule of Three in C++ programming states that if a class requires a user-defined destructor, copy constructor, or copy assignment operator, it likely needs all three. This is because these special member functions manage how objects are copied and destroyed, ensuring proper resource management and avoiding memory leaks.
C++1Stack(const Stack& other) : size(other.size), top(other.top) { 2 stackArray = new int[size]; 3 std::copy(other.stackArray, other.stackArray + size, stackArray); 4} 5 6Stack& operator=(const Stack& other) { 7 if (this != &other) { 8 delete[] stackArray; 9 size = other.size; 10 top = other.top; 11 stackArray = new int[size]; 12 std::copy(other.stackArray, other.stackArray + size, stackArray); 13 } 14 15 return *this; 16}
The copy constructor Stack(const Stack& other)
creates a new Stack
object with the same size, top, and stack elements as the existing Stack
by allocating new memory and performing an element-wise copy using std::copy
. This ensures that each Stack
object has its own separate memory, preventing unintended shared data which can lead to resource management issues.
The copy assignment operator Stack& operator=(const Stack& other)
checks for self-assignment and then clears the existing memory of the object before allocating new memory and copying elements from the source Stack
. This method ensures that the existing stack is replaced smoothly with a new stack's data, managing resources properly and preventing memory leaks.
A Stack involves three operations — Push
, Pop
, and Peek
. In an array-based Stack, the Push
operation adds a new element at the top of the Stack. Here’s how we can write a push function:
C++1void Push(int data) { 2 if (top < size - 1) { 3 stackArray[++top] = data; 4 } else { 5 std::cout << "Stack Overflows" << std::endl; 6 } 7}
Before adding the element, the method checks if the stack is full by comparing top
with size - 1
. If it's less than that, we can insert the element; otherwise, it gives a "Stack Overflows" message. The ++top
operation increments the top
variable (which represents the top element index in the stack) by one and then uses this new value to add the data
element into the stackArray
.
The Pop
operation removes the topmost element from the Stack.
C++1int Pop() { 2 if (top > -1) { 3 return stackArray[top--]; 4 } else { 5 std::cout << "Stack Underflows" << std::endl; 6 return -1; 7 } 8}
This method removes and returns the top item of the stack. It checks if the stack is empty by verifying if top
is greater than -1
. If it is, top
is decremented, and the element is returned. Notice that the actual element isn't removed from the array; instead, the top
is simply adjusted.
Lastly, the Peek
operation returns the topmost element without removing it from the Stack.
C++1int Peek() { 2 if (top > -1) { 3 return stackArray[top]; 4 } else { 5 std::cout << "Stack is Empty" << std::endl; 6 return -1; 7 } 8}
Since Push
, Pop
, and Peek
operations are performed at one end of the data structure, each takes constant time, represented as O(1)
. The space complexity is directly related to the number of elements, denoted by O(n)
, where n
is the number of elements in the Stack.
While understanding the inner structure of a stack is essential, implementing it manually isn't always necessary. C++ offers the powerful utility std::stack
from the <stack>
header, which provides standard operations such as push()
, pop()
, and top()
to manage stack operations efficiently. This class is dynamic, automatically managing its own storage and resizing as needed. Here's an example of how to use std::stack
:
C++1#include <iostream> 2#include <stack> 3 4int main() { 5 std::stack<int> stack; 6 7 // Performing the push operation 8 stack.push(10); 9 stack.push(20); 10 stack.push(30); 11 12 std::cout << "Stack consists of 3 elements." << std::endl; 13 14 // Performing the pop operation 15 int popped = stack.top(); 16 stack.pop(); 17 std::cout << "Popped from stack: " << popped << std::endl; 18 19 // Performing the peek operation 20 int peeked = stack.top(); 21 std::cout << "Peeked from stack: " << peeked << std::endl; 22 23 return 0; 24}
This example demonstrates creating a Stack and using methods like push()
, pop()
, and top()
. Though slightly different in syntax, std::stack
provides similar functionality to the manual implementation with dynamic management.
Congratulations, you've completed the lesson on Stacks in C++! Next, you will embark on practice sessions utilizing the concepts learned here. These practical exercises aim to solidify your understanding and sharpen your problem-solving skills. So, get ready to roll up your sleeves and dive right into coding in C++!