Lecture 2 - Data Structures
Lecture 2 - Data Structures
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.
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.
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:
Specifier Visibility
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;
}
Destructor
// 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:
Types of Inheritance
int main() {
Dog d;
d.makeSound(); // Inherited from Animal
d.bark();
return 0;
}
class B {
public:
void showB() { cout << "Class B" << endl; }
};
int main() {
C obj;
obj.showA();
obj.showB();
obj.showC();
return 0;
}
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.
int main() {
Car c1, *ptr;
ptr = &c1; // Pointer stores address of object
return 0;
}
8. Summary
Key Takeaways
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.
private ❌ No ❌ No
When a class is inherited, the inheritance mode modifies these access levels.
● 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>
class Base {
public:
int publicVar = 1;
protected:
int protectedVar = 2;
private:
void show() {
};
int main() {
Derived obj;
obj.show();
cout << "Accessing from main: " << obj.publicVar << endl; // ✅
Accessible
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;
};
public:
void show() {
cout << "PublicVar: " << publicVar << endl; // ✅ Became
private in Derived
};
int main() {
Derived obj;
obj.show();
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.
● 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;
};
public:
void show() {
};
int main() {
Derived obj;
obj.show();
Key Takeaways:
5. Comparison Table
CopyEdit
class Employee {
public:
string name;
void work() { cout << name << " is working." << endl; }
};
public:
void makeDecision() { cout << name << " makes a decision." <<
endl; }
};
public:
void learn() { cout << name << " is learning." << endl; }
};
public:
};
int main() {
Manager m;
Intern i;
ThirdPartyContractor c;
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.
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.
cpp
CopyEdit
#include <iostream>
#include <stdexcept>
public:
// Constructor
Vector() : size(0), capacity(2) {
arr = new T[capacity];
}
// Destructor
~Vector() {
delete[] arr;
}
std::cout << "Capacity: " << v.getCapacity() << ", Size: " <<
v.getSize() << "\n";
std::cout << "Finding index of 99: " << v.find(99) << "\n";
return 0;
}
Inserting 99 at index 2:
[ 1 2 99 3 4 ]
Shrinking to fit:
Capacity after shrinking: 3
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:
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;
}
int main() {
int n;
std::cout << "Enter size of array: ";
std::cin >> n;
// Initialize array
for (int i = 0; i < n; i++) {
arr[i] = i + 1;
}
return 0;
}
Comparison Table
Feature Static Array (int Dynamic Array (int* arr = new
arr[5]) int[n])
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:
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.
This would return a copy of the value at index, meaning modifications would not be reflected
in the original array.
Example Implementation
Let's implement this in a custom Vector class:
cpp
CopyEdit
#include <iostream>
public:
// Constructor
Vector(size_t s) : size(s) {
arr = new T[size];
}
// Destructor
~Vector() {
delete[] arr;
}
};
int main() {
Vector<int> v(5);
// 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;
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
}
Summary
Code Explanation
Would you like me to add support for dynamic resizing in this class? 🚀
4o
You said:
what does operator overloading mean?
ChatGPT said:
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.
cpp
CopyEdit
Point add(Point a, Point b) {
return Point(a.x + b.x, a.y + b.y);
}
cpp
CopyEdit
Point p3 = p1 + p2;
This makes the code more natural and readable.
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 +
How it Works?
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
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);
Operator Reason
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;
// Constructor
Node(T value) {
data = value;
next = nullptr;
}
};
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;
}
int main() {
LinkedList<int> list;
list.insertAtHead(10);
list.insertAtTail(20);
list.insertAtTail(30);
list.insertAtPosition(25, 2);
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:
🚀
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;
}
}
return 0;
}