0% found this document useful (0 votes)
2 views

Lecture 2 - Data Structures

This document provides a comprehensive review of C++ classes, covering key concepts such as access specifiers, constructors, destructors, inheritance types, memory management, and pointers. It also includes a section on dynamic arrays implemented as vectors in the Standard Template Library (STL), highlighting their features and a custom implementation of a vector class. Key takeaways emphasize encapsulation, memory management, and the importance of choosing the right inheritance mode.

Uploaded by

helmyelshahawy
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views

Lecture 2 - Data Structures

This document provides a comprehensive review of C++ classes, covering key concepts such as access specifiers, constructors, destructors, inheritance types, memory management, and pointers. It also includes a section on dynamic arrays implemented as vectors in the Standard Template Library (STL), highlighting their features and a custom implementation of a vector class. Key takeaways emphasize encapsulation, memory management, and the importance of choosing the right inheritance mode.

Uploaded by

helmyelshahawy
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 43

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;
}

You might also like