Section 1:
Here’s a well-structured review lecture on Classes in C++, covering the fundamentals, access
specifiers, constructors, destructors, inheritance, memory management with new and delete,
and pointers. It includes clear explanations and examples for each topic.
Review Lecture: Classes in C++
1. Introduction to Classes and Objects
Definition:
A class in C++ is a user-defined data type that groups data members (variables) and
member functions (methods) together. An object is an instance of a class.
Example: Basic Class Structure
cpp
CopyEdit
#include <iostream>
using namespace std;
class Car {
public: // Access specifier
string brand;
int year;
void display() {
cout << "Brand: " << brand << ", Year: " << year << endl;
}
};
int main() {
Car car1; // Object of class Car
car1.brand = "Toyota";
car1.year = 2020;
car1.display();
return 0;
}
Key Concepts:
● class keyword defines a class.
● public: makes members accessible outside the class.
● The object car1 is created from Car, and its properties are assigned values.
2. Access Specifiers: public, private, protected
Access specifiers control visibility of class members.
Specifier Visibility
public Accessible from anywhere
private Accessible only within the class
protect Accessible in derived classes (used in
ed inheritance)
Example: private Members
cpp
CopyEdit
class BankAccount {
private:
double balance; // Private member
public:
void setBalance(double b) { balance = b; }
double getBalance() { return balance; }
};
int main() {
BankAccount acc;
// acc.balance = 1000; // ❌ ERROR: 'balance' is private
acc.setBalance(1000);
cout << "Balance: " << acc.getBalance() << endl;
return 0;
}
🔹 Why use private? To enforce encapsulation and prevent direct modification.
3. Constructors and Destructors
Constructor
● A constructor is a special function that is automatically called when an object is
created.
● It has the same name as the class and no return type.
Destructor
● A destructor is called when an object goes out of scope.
● It has the same name as the class but starts with ~.
Example: Constructor and Destructor
cpp
CopyEdit
class Student {
public:
string name;
// Constructor
Student(string n) {
name = n;
cout << "Constructor called for " << name << endl;
}
// Destructor
~Student() {
cout << "Destructor called for " << name << endl;
}
};
int main() {
Student s1("John"); // Constructor called automatically
return 0; // Destructor called automatically
}
Key Points:
● The constructor initializes the object.
● The destructor cleans up before the object is destroyed.
4. Inheritance (Single, Multiple, Multilevel, Hierarchical)
Inheritance allows a new class (child class) to acquire properties of an existing class
(parent class).
Types of Inheritance
1. Single Inheritance - One class inherits from another.
2. Multiple Inheritance - A class inherits from multiple classes.
3. Multilevel Inheritance - A class inherits from another, which in turn inherits from
another.
4. Hierarchical Inheritance - One base class has multiple derived classes.
Example: Single Inheritance
cpp
CopyEdit
class Animal {
public:
void makeSound() {
cout << "Animal makes sound" << endl;
}
};
// Derived class
class Dog : public Animal {
public:
void bark() {
cout << "Dog barks" << endl;
}
};
int main() {
Dog d;
d.makeSound(); // Inherited from Animal
d.bark();
return 0;
}
🔹 The Dog class inherits from Animal, so it can access makeSound().
Example: Multiple Inheritance
cpp
CopyEdit
class A {
public:
void showA() { cout << "Class A" << endl; }
};
class B {
public:
void showB() { cout << "Class B" << endl; }
};
class C : public A, public B {
public:
void showC() { cout << "Class C" << endl; }
};
int main() {
C obj;
obj.showA();
obj.showB();
obj.showC();
return 0;
}
🔹 Class C inherits from both A and B.
5. Memory Management with new and delete
In C++, dynamic memory allocation is done using new and delete.
Example: Using new and delete
cpp
CopyEdit
class Person {
public:
string name;
Person(string n) {
name = n;
cout << name << " created!" << endl;
}
~Person() {
cout << name << " destroyed!" << endl;
}
};
int main() {
Person *p = new Person("Alice"); // Dynamically allocated
delete p; // Free memory
return 0;
}
Key Points:
● new allocates memory on the heap.
● delete releases memory to prevent memory leaks.
6. Using Pointers with Objects
Example: Pointer to Object
cpp
CopyEdit
class Car {
public:
string model;
void setModel(string m) { model = m; }
void display() { cout << "Model: " << model << endl; }
};
int main() {
Car c1, *ptr;
ptr = &c1; // Pointer stores address of object
ptr->setModel("Tesla"); // Using pointer
ptr->display();
return 0;
}
🔹 Use -> (arrow operator) when accessing members through a pointer.
8. Summary
Key Takeaways
✅ Classes & Objects - Group data and functions.
✅ Access Specifiers - public, private, protected.
✅ Constructors & Destructors - Special functions for object lifecycle.
✅ Inheritance - Enables reusability.
✅ Dynamic Memory (new/delete) - Efficient memory management.
✅ Pointers & Objects - Use -> for pointer access.
Understanding Public, Private, and Protected Inheritance in C++
In C++, inheritance allows a class (derived class) to acquire the properties and behaviors of
another class (base class). The mode of inheritance—public, private, or
protected—determines how the members of the base class are inherited by the derived
class.
1. Access Control Recap
Before diving into inheritance modes, let’s recall the access specifiers in a base class:
Specifier Accessible in Derived Accessible in Outside
Class? Code?
public ✅ Yes ✅ Yes
protect ✅ Yes ❌ No
ed
private ❌ No ❌ No
When a class is inherited, the inheritance mode modifies these access levels.
2. Public Inheritance (public)
👉 "IS-A" relationship: The derived class is a specialized version of the base class.
Behavior:
● public members of the base class remain public in the derived class.
● protected members remain protected.
● private members are not accessible in the derived class.
Example:
cpp
CopyEdit
#include <iostream>
using namespace std;
class Base {
public:
int publicVar = 1;
protected:
int protectedVar = 2;
private:
int privateVar = 3; // ❌ Not accessible in derived class
};
class Derived : public Base {
public:
void show() {
cout << "PublicVar: " << publicVar << endl; // ✅ Accessible
(public)
cout << "ProtectedVar: " << protectedVar << endl; // ✅
Accessible (protected)
// cout << "PrivateVar: " << privateVar; // ❌ ERROR: Not
accessible
};
int main() {
Derived obj;
obj.show();
cout << "Accessing from main: " << obj.publicVar << endl; // ✅
Accessible
// cout << obj.protectedVar; // ❌ ERROR: Still protected
return 0;
Key Takeaways:
✔ Public inheritance maintains the original access levels of public and protected
members.
✔ This is the most common inheritance type.
✔ Private members of the base class remain inaccessible.
3. Private Inheritance (private)
👉 "HAS-A" relationship: The derived class implements the base class, but it is not
conceptually the same.
Behavior:
● public members of the base class become private in the derived class.
● protected members also become private.
● private members are still not accessible in the derived class.
Example:
cpp
CopyEdit
class Base {
public:
int publicVar = 1;
protected:
int protectedVar = 2;
private:
int privateVar = 3;
};
class Derived : private Base {
public:
void show() {
cout << "PublicVar: " << publicVar << endl; // ✅ Became
private in Derived
cout << "ProtectedVar: " << protectedVar << endl; // ✅ Became
private in Derived
// cout << "PrivateVar: " << privateVar; // ❌ ERROR: Not
accessible
};
int main() {
Derived obj;
obj.show();
// cout << obj.publicVar; // ❌ ERROR: Now private!
return 0;
Key Takeaways:
✔ Everything from the base class becomes private in the derived class.
✔ Even public members of the base class cannot be accessed directly from outside.
✔ Useful when you want to reuse the base class implementation but not expose it
publicly.
4. Protected Inheritance (protected)
👉 "IS-A but restricted" relationship: The derived class is a more limited version of the
base class.
Behavior:
● public members of the base class become protected in the derived class.
● protected members remain protected.
● private members are still not accessible in the derived class.
Example:
cpp
CopyEdit
class Base {
public:
int publicVar = 1;
protected:
int protectedVar = 2;
private:
int privateVar = 3;
};
class Derived : protected Base {
public:
void show() {
cout << "PublicVar: " << publicVar << endl; // ✅ Became
protected
cout << "ProtectedVar: " << protectedVar << endl; // ✅
Accessible
};
int main() {
Derived obj;
obj.show();
// cout << obj.publicVar; // ❌ ERROR: Now protected!
return 0;
Key Takeaways:
✔ Public members become protected, preventing direct access from outside.
✔ Useful when you want derived classes to have full access but not expose the base
class publicly.
✔ Mostly used when designing base classes intended only for inheritance.
5. Comparison Table
Base Class Public Inheritance Private Inheritance Protected Inheritance
Member
public ✅ Remains public 🔄 Becomes 🔄 Becomes
private protected
protected ✅ Remains 🔄 Becomes ✅ Remains
protected private protected
private ❌ Not accessible ❌ Not accessible ❌ Not accessible
6. Choosing the Right Inheritance Mode
✅ When to use public inheritance
● When the derived class truly "IS-A" the base class.
● When you want to preserve the base class's interface.
✅ When to use private inheritance
● When the derived class only "USES" the base class but should not expose it.
● When you don’t want outside access to the base class members.
✅ When to use protected inheritance
● When you want future derived classes to still have access but not outside users.
● When designing frameworks where only derived classes should use the base class
members.
7. Real-World Example: Access Control in a Company
Imagine you are designing a company system where:
● Employee (Base class): Contains general employee data.
● Manager (Derived from Employee, public): Can access employee details publicly.
● Intern (Derived from Employee, protected): Can only be accessed by future
interns.
● ThirdPartyContractor (Derived from Employee, private): Uses Employee's
implementation but should not expose it.
cpp
CopyEdit
class Employee {
public:
string name;
void work() { cout << name << " is working." << endl; }
};
class Manager : public Employee {
public:
void makeDecision() { cout << name << " makes a decision." <<
endl; }
};
class Intern : protected Employee {
public:
void learn() { cout << name << " is learning." << endl; }
};
class ThirdPartyContractor : private Employee {
public:
void contractWork() { work(); } // Can use Employee's work() but
no direct access
};
int main() {
Manager m;
m.name = "Alice"; // ✅ Allowed (public inheritance)
m.makeDecision();
Intern i;
// i.name = "Bob"; // ❌ ERROR: name is protected
ThirdPartyContractor c;
// c.name = "Charlie"; // ❌ ERROR: name is private
return 0;
Final Thoughts
● Use public when the derived class IS-A base class.
● Use private when the derived class USES the base class but should not expose it.
● Use protected when only derived classes should have access.
🚀
This ensures proper encapsulation and logical class hierarchies in object-oriented
designs!
Section 2
Dynamic arrays in C++ are implemented as vectors in the Standard Template Library (STL).
Unlike static arrays, vectors can grow and shrink dynamically as elements are inserted or
removed. They allocate memory dynamically and manage resizing automatically when needed.
Key Features of std::vector
● Dynamic resizing: Automatically grows when needed.
● Contiguous memory: Elements are stored in contiguous memory locations.
● Efficient access: Supports random access using indexing.
● Insertion & Deletion: Efficient at the end (O(1)) but expensive in the middle (O(n)).
● Capacity & Size: Has a capacity that increases exponentially (typically doubles) when it
grows beyond its current capacity.
How Vectors Grow and Shrink
When a vector exceeds its allocated capacity:
1. A new, larger memory block is allocated (usually double the current size).
2. Existing elements are copied to the new block.
3. The old memory is deallocated.
When elements are removed:
1. The size decreases.
2. The capacity does not decrease automatically.
3. The shrink_to_fit() method can be used to reduce memory usage.
Implementation of a Custom Dynamic Array Class
(Vector-like)
Below is a custom implementation of a Vector class in C++ that mimics std::vector with
key operations.
cpp
CopyEdit
#include <iostream>
#include <stdexcept>
template <typename T>
class Vector {
private:
T* arr; // Pointer to the dynamic array
size_t size; // Current number of elements
size_t capacity; // Total allocated space
// Function to resize when full
void resize(size_t newCapacity) {
T* newArr = new T[newCapacity];
for (size_t i = 0; i < size; i++) {
newArr[i] = arr[i];
}
delete[] arr;
arr = newArr;
capacity = newCapacity;
}
public:
// Constructor
Vector() : size(0), capacity(2) {
arr = new T[capacity];
}
// Destructor
~Vector() {
delete[] arr;
}
// Returns current size
size_t getSize() const {
return size;
}
// Returns current capacity
size_t getCapacity() const {
return capacity;
}
// Access elements with bounds checking
T& operator[](size_t index) {
if (index >= size) throw std::out_of_range("Index out of
range");
return arr[index];
}
// Add element at the end
void push_back(const T& value) {
if (size == capacity) {
resize(capacity * 2); // Double the capacity
}
arr[size++] = value;
}
// Remove last element
void pop_back() {
if (size > 0) size--;
}
// Insert at a specific position
void insert(size_t index, const T& value) {
if (index > size) throw std::out_of_range("Index out of
range");
if (size == capacity) resize(capacity * 2);
for (size_t i = size; i > index; i--) {
arr[i] = arr[i - 1];
}
arr[index] = value;
size++;
}
// Remove an element at a specific index
void erase(size_t index) {
if (index >= size) throw std::out_of_range("Index out of
range");
for (size_t i = index; i < size - 1; i++) {
arr[i] = arr[i + 1];
}
size--;
}
// Find an element and return its index (-1 if not found)
int find(const T& value) {
for (size_t i = 0; i < size; i++) {
if (arr[i] == value) return i;
}
return -1;
}
// Shrink capacity to fit size
void shrink_to_fit() {
if (size < capacity) {
resize(size);
}
}
// Print all elements
void print() const {
std::cout << "[ ";
for (size_t i = 0; i < size; i++) {
std::cout << arr[i] << " ";
}
std::cout << "]\n";
}
};
// Driver code to test the Vector class
int main() {
Vector<int> v;
std::cout << "Adding elements:\n";
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.print();
std::cout << "Capacity: " << v.getCapacity() << ", Size: " <<
v.getSize() << "\n";
std::cout << "Inserting 99 at index 2:\n";
v.insert(2, 99);
v.print();
std::cout << "Erasing element at index 1:\n";
v.erase(1);
v.print();
std::cout << "Finding index of 99: " << v.find(99) << "\n";
std::cout << "Popping last element:\n";
v.pop_back();
v.print();
std::cout << "Shrinking to fit:\n";
v.shrink_to_fit();
std::cout << "Capacity after shrinking: " << v.getCapacity() <<
"\n";
return 0;
}
Key Features in Our Implementation
✅ Dynamic Growth: resize() function doubles the capacity when needed.
✅ Efficient Insertion & Deletion: Supports inserting at any position.
✅ Find Operation: Searches for an element and returns its index.
✅ Shrink to Fit: Reduces unused allocated memory.
✅ Array-like Access: Overloaded operator[] allows easy indexing.
Output of the Program
yaml
CopyEdit
Adding elements:
[ 1 2 3 4 ]
Capacity: 4, Size: 4
Inserting 99 at index 2:
[ 1 2 99 3 4 ]
Erasing element at index 1:
[ 1 99 3 4 ]
Finding index of 99: 1
Popping last element:
[ 1 99 3 ]
Shrinking to fit:
Capacity after shrinking: 3
Comparison with std::vector
Feature Custom Vector<T> std::vector<T>
Dynamic Growth ✅ Yes (doubling) ✅ Yes (doubling)
Automatic Shrinking ❌ No (manual ❌ No (manual
shrink_to_fit()) shrink_to_fit())
Bounds Checking ✅ Yes (operator[]) ✅ Yes (at())
STL Integration ❌ No ✅ Yes
Conclusion
This Vector class mimics std::vector with essential functionalities. However, the STL
std::vector is more optimized and should be used in real applications. This implementation
is useful for understanding how dynamic arrays work internally in C++.
🚀
Would you like to add more features like iterators, range-based loops, or move semantics?
4o
You said:
explain the difference between static arrays, and dynamically allocated arrays with new and
delete
ChatGPT said:
Difference Between Static Arrays and Dynamically Allocated Arrays in C++
In C++, arrays can be created in two primary ways:
1. Static Arrays (Fixed-size arrays allocated on the stack)
2. Dynamically Allocated Arrays (Flexible-size arrays allocated on the heap using new
and delete)
1. Static Arrays (Fixed-size, Stack Allocation)
A static array is an array with a fixed size known at compile time. It is allocated on the stack,
which makes it fast but limited in size.
Example of a Static Array
cpp
CopyEdit
#include <iostream>
int main() {
int arr[5] = {1, 2, 3, 4, 5}; // Static array with size 5
std::cout << "Static Array: ";
for (int i = 0; i < 5; i++) {
std::cout << arr[i] << " ";
}
std::cout << "\n";
return 0;
}
Characteristics of Static Arrays
✅ Fast Allocation & Deallocation: Done automatically on the stack.
✅ Memory Efficiency: No overhead of dynamic allocation.
✅ Compile-time Size: The size must be known before compilation.
❌ Limited Size: Stack memory is limited (usually a few MBs).
❌ No Resizing: Cannot grow or shrink at runtime.
2. Dynamically Allocated Arrays (new and delete)
If we need an array where size is determined at runtime, we use dynamic memory
allocation with new and delete. The array is stored in the heap, which has a much larger
memory pool compared to the stack.
Example of a Dynamically Allocated Array
cpp
CopyEdit
#include <iostream>
int main() {
int n;
std::cout << "Enter size of array: ";
std::cin >> n;
int* arr = new int[n]; // Dynamically allocated array
// Initialize array
for (int i = 0; i < n; i++) {
arr[i] = i + 1;
}
std::cout << "Dynamic Array: ";
for (int i = 0; i < n; i++) {
std::cout << arr[i] << " ";
}
std::cout << "\n";
delete[] arr; // Free dynamically allocated memory
return 0;
}
Characteristics of Dynamically Allocated Arrays
✅ Flexible Size: Can be determined at runtime.
✅ Large Memory Availability: Stored in the heap, which has more space than the stack.
❌ Slower Allocation & Deallocation: new and delete operations take extra time.
❌ Manual Memory Management: You must delete[] the array to prevent memory leaks.
❌ No Automatic Resizing: If you need more space, you must manually allocate a new larger
array and copy elements over.
Comparison Table
Feature Static Array (int Dynamic Array (int* arr = new
arr[5]) int[n])
Memory Location Stack Heap
Size Flexibility Fixed at compile time Can be set at runtime
Resizing ❌ No ❌ No (requires manual copying)
Access Speed ✅ Faster (stack is faster) ❌ Slower (heap requires more steps)
Lifetime Until function exits Until delete[] is called
Memory Management Automatic Manual (new / delete[])
Risk of Memory Leak ❌ No ✅ Yes (if delete[] is missing)
When to Use Which?
✔ Use Static Arrays when:
● The size is fixed and known at compile time.
● You need fast allocation and deallocation.
● You don’t want to worry about manual memory management.
✔ Use Dynamic Arrays when:
● The size is unknown at compile time.
● You need a large array (heap has more memory than stack).
● You need flexibility to allocate memory at runtime.
Example: Common Mistake with Dynamic Arrays
(Memory Leak)
A memory leak occurs when we allocate memory but forget to deallocate it.
Memory Leak Example
cpp
CopyEdit
int* arr = new int[100]; // Allocating memory
// Forgetting to delete it will cause a memory leak
Solution: Always use delete[] when done.
cpp
CopyEdit
delete[] arr; // Free memory
Alternatively, use std::vector<int> instead of raw pointers to avoid manual memory
management.
Key Takeaways
1. Static arrays are allocated on the stack, have fixed size, and are fast.
2. Dynamic arrays use heap memory, allow runtime resizing, but require manual
memory management.
3. Use delete[] to free memory and prevent memory leaks.
4. std::vector<T> is a better alternative to raw dynamic arrays.
Would you like to see an example with std::vector to replace raw dynamic arrays? 🚀
4o
You said:
explain this line of code T& operator[](size_t index)
ChatGPT said:
Understanding the Line:
cpp
CopyEdit
T& operator[](size_t index)
This is an overloaded subscript operator ([]) in C++ that allows array-style indexing for a
custom class. It enables objects of the class to be accessed using square brackets, similar to
built-in arrays.
Breaking it Down
1. T& → Returns a reference to the element of type T at the given index.
2. operator[] → Overloads the subscript operator ([]) to provide array-like access.
3. size_t index → Takes an index (of type size_t, which is an unsigned integer) to
specify which element to return.
Why Use T& (Reference)?
● Returning a reference (T&) instead of a copy (T) allows modification of the element
directly.
● Efficient: Avoids unnecessary copying of the object.
Example Without T& (Returning by Value)
cpp
CopyEdit
T operator[](size_t index) {
return arr[index]; // Returns a copy (modifications won’t affect
the original array)
}
This would return a copy of the value at index, meaning modifications would not be reflected
in the original array.
Example With T& (Returning by Reference)
cpp
CopyEdit
T& operator[](size_t index) {
return arr[index]; // Returns a reference (modifications affect
the original array)
}
This allows direct modification of the element at the given index.
Example Implementation
Let's implement this in a custom Vector class:
cpp
CopyEdit
#include <iostream>
template <typename T>
class Vector {
private:
T* arr;
size_t size;
public:
// Constructor
Vector(size_t s) : size(s) {
arr = new T[size];
}
// Overloaded [] operator (for access)
T& operator[](size_t index) {
if (index >= size) throw std::out_of_range("Index out of
range");
return arr[index]; // Return reference
}
// Destructor
~Vector() {
delete[] arr;
}
};
int main() {
Vector<int> v(5);
// Assigning values using []
v[0] = 10;
v[1] = 20;
v[2] = 30;
// Accessing values
std::cout << "v[1]: " << v[1] << "\n"; // Output: 20
return 0;
}
Key Points in This Example
We can modify elements directly:
cpp
CopyEdit
v[0] = 10;
● Because we return a reference (T&), this modifies the actual element.
Bounds Checking (Optional):
cpp
CopyEdit
if (index >= size) throw std::out_of_range("Index out of range");
● Prevents out-of-bounds access.
Overloading operator[] for Read-Only Access
If you want read-only access (for const objects), overload another version:
cpp
CopyEdit
const T& operator[](size_t index) const {
if (index >= size) throw std::out_of_range("Index out of range");
return arr[index]; // Returns a reference but does not allow
modification
}
Usage of Read-Only Access
cpp
CopyEdit
const Vector<int> v(5);
std::cout << v[2]; // Allowed (read-only)
v[2] = 10; // ❌ Compilation error (modification not allowed)
Summary
Code Explanation
T& operator[](size_t index) Returns a reference to allow
modification.
T operator[](size_t index) Returns a copy, preventing modification.
const T& operator[](size_t Allows read-only access for const
index) const objects.
Would you like me to add support for dynamic resizing in this class? 🚀
4o
You said:
what does operator overloading mean?
ChatGPT said:
What is Operator Overloading in C++?
Operator overloading in C++ allows you to redefine how built-in operators (+, -, [], =, ==,
etc.) behave for user-defined types (like classes and structs). This enables objects of custom
classes to use operators in an intuitive way, just like built-in types.
Example: Why Use Operator Overloading?
Consider a Point class that represents a 2D point with x and y coordinates. Without operator
overloading, adding two points requires a member function like this:
cpp
CopyEdit
Point add(Point a, Point b) {
return Point(a.x + b.x, a.y + b.y);
}
But with operator overloading, we can write:
cpp
CopyEdit
Point p3 = p1 + p2;
This makes the code more natural and readable.
Syntax of Operator Overloading
cpp
CopyEdit
ReturnType operatorSymbol(Arguments) {
// Implementation
}
● operatorSymbol → The operator being overloaded (e.g., +, -, [], ==).
● Arguments → The parameters for the operator (can be this for member functions or
explicit arguments for friend functions).
● ReturnType → The type returned after performing the operation.
Types of Operator Overloading
1. Unary Operators (-, ++, --, !) → Work with one operand.
2. Binary Operators (+, -, *, /, ==, !=, >, <) → Work with two operands.
3. Special Operators ([], (), ->, =) → Used for specific tasks like subscript access,
function calls, etc.
Examples of Operator Overloading
1. Overloading + for Adding Two Objects
cpp
CopyEdit
#include <iostream>
class Point {
public:
int x, y;
Point(int x = 0, int y = 0) : x(x), y(y) {}
// Overload + operator
Point operator+(const Point& other) {
return Point(x + other.x, y + other.y);
}
void display() {
std::cout << "(" << x << ", " << y << ")\n";
}
};
int main() {
Point p1(2, 3), p2(4, 5);
Point p3 = p1 + p2; // Uses overloaded +
p3.display(); // Output: (6, 8)
return 0;
}
How it Works?
● operator+ takes another Point as input.
● Returns a new Point with the sum of x and y values.
● Allows p1 + p2 instead of p1.add(p2).
2. Overloading [] for Array-Like Access
cpp
CopyEdit
#include <iostream>
class Array {
private:
int arr[5];
public:
Array() {
for (int i = 0; i < 5; i++) arr[i] = i + 1;
}
// Overload [] operator
int& operator[](int index) {
if (index < 0 || index >= 5) throw std::out_of_range("Index
out of bounds");
return arr[index]; // Return reference to allow modification
}
};
int main() {
Array a;
std::cout << a[2] << "\n"; // Output: 3
a[2] = 10; // Modify element
std::cout << a[2] << "\n"; // Output: 10
return 0;
}
Why is int& Used?
● Returning by reference (int&) allows modification like a[2] = 10;.
● Without it, a[2] would return a copy and not modify the original array.
3. Overloading == for Comparison
cpp
CopyEdit
#include <iostream>
class Point {
public:
int x, y;
Point(int x = 0, int y = 0) : x(x), y(y) {}
// Overload == operator
bool operator==(const Point& other) {
return (x == other.x && y == other.y);
}
};
int main() {
Point p1(3, 4), p2(3, 4), p3(5, 6);
std::cout << (p1 == p2) << "\n"; // Output: 1 (true)
std::cout << (p1 == p3) << "\n"; // Output: 0 (false)
return 0;
}
Operators That Cannot Be Overloaded
While most operators can be overloaded, some cannot:
Operator Reason
:: (Scope resolution) Needed for namespace/class member
access
. (Member access) Cannot change object structure
.* Not generally useful for overloading
(Pointer-to-member)
sizeof Evaluates at compile time
typeid Determines object type
Key Takeaways
✅ Operator overloading allows custom behavior for operators on user-defined types.
✅ Improves code readability and usability (e.g., using p1 + p2 instead of p1.add(p2)).
✅ Some operators (::, sizeof) cannot be overloaded.
✅ Use T& (reference) return types for modifying objects, e.g., operator[].
Would you like an example with overloading the assignment = operator or smart pointers
(->)? 🚀
Section 3
Here is a complete C++ implementation of a linked list using templates. This implementation
includes all widely used functions like insertion (at head, tail, and position), deletion (by value
and position), searching, reversing, and displaying the list. It also includes comments to explain
different parts of the code.
#include <iostream>
using namespace std;
// Template class for Node
template <typename T>
class Node {
public:
T data;
Node* next;
// Constructor
Node(T value) {
data = value;
next = nullptr;
}
};
// Template class for LinkedList
template <typename T>
class LinkedList {
private:
Node<T>* head;
public:
// Constructor
LinkedList() {
head = nullptr;
}
// Function to insert at the head
void insertAtHead(T value) {
Node<T>* newNode = new Node<T>(value);
newNode->next = head;
head = newNode;
}
// Function to insert at the tail
void insertAtTail(T value) {
Node<T>* newNode = new Node<T>(value);
if (!head) {
head = newNode;
return;
}
Node<T>* temp = head;
while (temp->next) {
temp = temp->next;
}
temp->next = newNode;
}
// Function to insert at a specific position
void insertAtPosition(T value, int position) {
if (position <= 0) {
insertAtHead(value);
return;
}
Node<T>* newNode = new Node<T>(value);
Node<T>* temp = head;
for (int i = 1; temp != nullptr && i < position; i++) {
temp = temp->next;
}
if (temp == nullptr) {
insertAtTail(value);
return;
}
newNode->next = temp->next;
temp->next = newNode;
}
// Function to delete by value
void deleteByValue(T value) {
if (!head) return;
if (head->data == value) {
Node<T>* temp = head;
head = head->next;
delete temp;
return;
}
Node<T>* temp = head;
while (temp->next && temp->next->data != value) {
temp = temp->next;
}
if (temp->next) {
Node<T>* toDelete = temp->next;
temp->next = temp->next->next;
delete toDelete;
}
}
// Function to delete by position
void deleteByPosition(int position) {
if (!head) return;
if (position == 0) {
Node<T>* temp = head;
head = head->next;
delete temp;
return;
}
Node<T>* temp = head;
for (int i = 1; temp->next != nullptr && i < position; i++) {
temp = temp->next;
}
if (temp->next) {
Node<T>* toDelete = temp->next;
temp->next = temp->next->next;
delete toDelete;
}
}
// Function to search for an element
bool search(T value) {
Node<T>* temp = head;
while (temp) {
if (temp->data == value)
return true;
temp = temp->next;
}
return false;
}
// Function to reverse the linked list
void reverse() {
Node<T>* prev = nullptr;
Node<T>* current = head;
Node<T>* next = nullptr;
while (current) {
next = current->next;
current->next = prev;
prev = current;
current = next;
}
head = prev;
}
// Function to display the linked list
void display() {
Node<T>* temp = head;
while (temp) {
cout << temp->data << " -> ";
temp = temp->next;
}
cout << "NULL" << endl;
}
// Destructor to free memory
~LinkedList() {
Node<T>* temp;
while (head) {
temp = head;
head = head->next;
delete temp;
}
}
};
int main() {
LinkedList<int> list;
list.insertAtHead(10);
list.insertAtTail(20);
list.insertAtTail(30);
list.insertAtPosition(25, 2);
cout << "Linked List after insertions: ";
list.display();
list.deleteByValue(20);
cout << "After deleting 20: ";
list.display();
list.reverse();
cout << "After reversing: ";
list.display();
return 0;
}
This C++ program implements a singly linked list using templates. It includes:
● Insertion at head, tail, and a specific position.
● Deletion by value and position.
● Searching for an element.
● Reversing the list.
● Displaying the list.
● Destructor to free allocated memory.
🚀
This ensures a fully functional and reusable linked list that can store any data type. Let me know
if you want additional features like sorting or a doubly linked list!
Section 4:
Stacks (LIFO)
#include <iostream>
using namespace std;
class Stack {
private:
static const int max = 100;
int arr[max];
int top;
public:
Stack() { top = -1; }
bool isEmpty();
bool isFull();
void push(int x);
int pop();
int peek();
};
bool Stack::isEmpty() {
return (top < 0);
}
bool Stack::isFull() {
return (top >= max - 1);
}
void Stack::push(int x) {
if (isFull()) {
cout << "Stack Overflow" << endl;
} else {
arr[++top] = x;
cout << x << " pushed into stack\n";
}
}
int Stack::pop() {
if (isEmpty()) {
cout << "Stack Underflow" << endl;
return 0;
} else {
int x = arr[top--];
return x;
}
}
int Stack::peek() {
if (isEmpty()) {
cout << "Stack is Empty" << endl;
return 0;
} else {
int x = arr[top];
return x;
}
}
// Demonstrate the stack in action
int main() {
Stack myStack;
myStack.push(10);
myStack.push(20);
myStack.push(30);
cout << myStack.pop() << " Popped from stack\n";
// Show the top element
cout << "Top element is " << myStack.peek() << endl;
return 0;
}