OOAD & OOP Basics
4 Pillars of OOP
Encapsulation β data hiding via access specifiers.
Abstraction β show only essential details.
Inheritance β reuse base class properties.
Polymorphism β same interface, different behavior.
Class vs Object
A class is a blueprint/template.
An object is a real-world instance of that class.
Class does not occupy memory; objects do.
Access Specifiers
private β accessible only within the class (default for class).
protected β accessible in class + derived classes.
public β accessible everywhere (default for struct).
UML & History
UML = Unified Modeling Language (Booch, Rumbaugh, Jacobson).
Alan Kay coined "Object-Oriented Programming".
Simula 67 was the first OOP language.
- UML stands for Unified Modeling Language.
- Alan Kay developed the concept of Object-Oriented Programming.
- Encapsulation hides data using access specifiers.
- Default access in
classβ private; instructβ public. - A class definition must end with
};(semicolon is mandatory). - Scope resolution operator is
::β used to define functions outside the class.
#include <iostream>
#include <string>
using namespace std;
class BankAccount {
private:
string owner;
double balance;
public:
// Parameterized Constructor
BankAccount(string name, double bal) : owner(name), balance(bal) {}
void deposit(double amt) {
if (amt > 0) balance += amt;
}
void withdraw(double amt) {
if (amt <= balance) balance -= amt;
else cout << "[ERROR] Insufficient funds for " << owner << endl;
}
void display() const {
cout << "Account: " << owner
<< " | Balance: Rs." << balance << endl;
}
}; // <-- SEMICOLON IS MANDATORY
int main() {
BankAccount accounts[3] = {
BankAccount("Alice", 5000),
BankAccount("Bob", 3000),
BankAccount("Carol", 7500)
};
accounts[0].deposit(1500);
accounts[1].withdraw(500);
accounts[2].withdraw(10000); // triggers error
for (int i = 0; i < 3; i++)
accounts[i].display();
return 0;
}
[ERROR] Insufficient funds for Carol
Account: Alice | Balance: Rs.6500
Account: Bob | Balance: Rs.2500
Account: Carol | Balance: Rs.7500::#include <iostream>
using namespace std;
class Student {
private:
string name;
int rollNo;
float marks;
public:
void setData(string n, int r, float m); // declaration only
void display();
char getGrade();
};
// Definition OUTSIDE the class β uses :: (scope resolution)
void Student::setData(string n, int r, float m) {
name = n; rollNo = r; marks = m;
}
void Student::display() {
cout << "Name: " << name
<< " | Roll: " << rollNo
<< " | Marks: " << marks
<< " | Grade: " << getGrade() << endl;
}
char Student::getGrade() {
if (marks >= 90) return 'A';
if (marks >= 75) return 'B';
if (marks >= 60) return 'C';
return 'F';
}
int main() {
Student s1, s2;
s1.setData("Dharaksh", 101, 92.5);
s2.setData("Riya", 102, 74.0);
s1.display();
s2.display();
return 0;
}
Name: Dharaksh | Roll: 101 | Marks: 92.5 | Grade: A
Name: Riya | Roll: 102 | Marks: 74 | Grade: Cthis Pointer + Static Members#include <iostream>
using namespace std;
class Employee {
private:
string name;
int id;
static int count; // shared by ALL objects β declared here
public:
Employee(string n, int i) : name(n), id(i) {
count++; // increments for every new object
}
// 'this' pointer: points to the calling object
Employee& setName(string n) {
this->name = n; // 'this->name' disambiguates from param 'n'
return *this; // enables method chaining
}
void display() const {
cout << "ID: " << id << " | Name: " << name << endl;
}
// Static function β can only access static members
static void showCount() {
cout << "Total Employees: " << count << endl;
}
};
// Static member MUST be defined outside the class
int Employee::count = 0;
int main() {
Employee e1("Alice", 1);
Employee e2("Bob", 2);
Employee e3("Carol", 3);
e1.setName("Alicia"); // using this pointer internally
e1.display();
e2.display();
Employee::showCount(); // called via class name, not object
return 0;
}
ID: 1 | Name: Alicia
ID: 2 | Name: Bob
Total Employees: 3- Forgetting
};after the class closing brace β causes a wall of confusing compile errors. - Forgetting
ClassName::when defining functions outside the class. - Accessing a
privatemember directly frommain()β only public methods can be called outside. - Static member must be defined outside the class:
int ClassName::count = 0;β forgetting this causes a linker error. - Static member functions cannot use
thispointer β they have no object context.
Constructors, Destructors & Operator Overloading
Types of Constructors
Default β no parameters; called automatically.
Parameterized β accepts arguments at creation.
Copy β ClassName(const ClassName &obj); copies another object.
Destructor Rules
Name = ~ClassName().
Takes no arguments, returns nothing.
Called automatically when object goes out of scope.
Only one destructor per class.
Non-Overloadable Operators
:: Scope Resolution
. Member Access
.* Pointer to Member
?: Ternary / Conditional
sizeof and typeid also cannot be overloaded.
Operator Overloading Rule
For +, -, * etc. the function must return a new object, not void.
Syntax: RetType operator+(const ClassName &)
- Operators
::,.,.*,?:CANNOT be overloaded. - Destructor has no arguments, no return type, name =
~ClassName. - Copy constructor parameter must be a const reference to avoid infinite recursion.
- Constructor is called at object creation; destructor at object destruction.
thispointer β points to the current object inside member functions.
#include <iostream>
using namespace std;
class Complex {
private:
float real, imag;
public:
// 1. Default Constructor
Complex() : real(0), imag(0) {
cout << "Default constructor called\n";
}
// 2. Parameterized Constructor
Complex(float r, float i) : real(r), imag(i) {
cout << "Parameterized constructor called\n";
}
// 3. Copy Constructor
Complex(const Complex &c) : real(c.real), imag(c.imag) {
cout << "Copy constructor called\n";
}
// Destructor
~Complex() {
cout << "Destructor called for ("
<< real << "+" << imag << "i)\n";
}
void display() const {
cout << real << " + " << imag << "i\n";
}
};
int main() {
Complex c1; // Default
Complex c2(3.0, 4.0); // Parameterized
Complex c3(c2); // Copy
cout << "c2: "; c2.display();
cout << "c3: "; c3.display();
return 0;
// Destructors called in REVERSE order of creation: c3, c2, c1
}
Default constructor called
Parameterized constructor called
Copy constructor called
c2: 3 + 4i
c3: 3 + 4i
Destructor called for (3+4i)
Destructor called for (3+4i)
Destructor called for (0+0i)+, -, ==)#include <iostream>
using namespace std;
class Complex {
public:
float real, imag;
Complex(float r = 0, float i = 0) : real(r), imag(i) {}
// Overload + : returns a NEW Complex object (NOT void)
Complex operator+(const Complex &c) const {
return Complex(real + c.real, imag + c.imag);
}
// Overload -
Complex operator-(const Complex &c) const {
return Complex(real - c.real, imag - c.imag);
}
// Overload == (returns bool)
bool operator==(const Complex &c) const {
return (real == c.real && imag == c.imag);
}
void display() const {
cout << real << " + " << imag << "i\n";
}
};
int main() {
Complex a(3, 4), b(1, 2);
Complex sum = a + b;
Complex diff = a - b;
cout << "Sum: "; sum.display();
cout << "Diff: "; diff.display();
cout << "Equal? " << (a == b ? "Yes" : "No") << endl;
return 0;
}
Sum: 4 + 6i
Diff: 2 + 2i
Equal? No++ prefix & postfix)#include <iostream>
using namespace std;
class Counter {
int count;
public:
Counter(int c = 0) : count(c) {}
// Prefix ++c : no int parameter
Counter& operator++() {
++count; return *this;
}
// Postfix c++ : dummy int parameter
Counter operator++(int) {
Counter tmp(*this);
count++; return tmp;
}
void show() const { cout << "Count = " << count << endl; }
};
int main() {
Counter c(5);
++c; c.show(); // prefix
c++; c.show(); // postfix
return 0;
}
Count = 6
Count = 7new/delete) + Friend Function#include <iostream>
using namespace std;
class Box {
private:
double length, width, height;
public:
Box(double l, double w, double h)
: length(l), width(w), height(h) {}
// Friend function β NOT a member, but can access private data
friend double getVolume(Box b);
void display() const {
cout << "Box(" << length << "x"
<< width << "x" << height << ")" << endl;
}
};
// Defined outside, but has private access because of 'friend'
double getVolume(Box b) {
return b.length * b.width * b.height;
}
int main() {
// --- Static allocation ---
Box b1(3.0, 4.0, 5.0);
b1.display();
cout << "Volume: " << getVolume(b1) << endl;
// --- Dynamic allocation with new/delete ---
Box* b2 = new Box(2.0, 2.0, 2.0);
b2->display();
cout << "Volume: " << getVolume(*b2) << endl;
delete b2; // MUST free heap memory
// --- Dynamic array ---
int n = 3;
int* arr = new int[n];
for (int i = 0; i < n; i++) arr[i] = (i + 1) * 10;
for (int i = 0; i < n; i++) cout << arr[i] << " ";
cout << endl;
delete[] arr; // use delete[] for arrays
return 0;
}
Box(3x4x5)
Volume: 60
Box(2x2x2)
Volume: 8
10 20 30- Returning
voidfromoperator+β must return a new object for expressions likea+b+cto work. - Writing copy constructor without
&reference β causes infinite recursion (endless copying). - Destructors are called in reverse order of construction β last created = first destroyed.
- Using
deleteon an array allocated withnew[]β must usedelete[], notdelete, to avoid UB. - Friend function is declared inside the class with
friendkeyword but defined outside without any class prefix.
Inheritance & Polymorphism
Types of Inheritance
Single β one base, one derived.
Multiple β many bases, one derived.
Multilevel β AβBβC chain.
Hierarchical β one base, many derived.
Hybrid β combination (causes diamond problem).
Virtual Functions
virtual enables runtime (dynamic) polymorphism.
Without virtual, base pointer calls base version.
With virtual, base pointer calls derived version via vtable.
Abstract Class
Contains at least one pure virtual function: virtual void f() = 0;
Cannot be instantiated β compile error if you try.
Forces derived classes to implement the function.
Diamond Problem
Occurs in Hybrid Inheritance when two paths lead to the same base class.
Fix: use virtual base class β only one copy of base is maintained.
- Abstract classes cannot be instantiated β compile-time error.
virtualkeyword enables runtime polymorphism.- Pure virtual:
virtual void func() = 0; - Virtual destructor in base class is essential when using base pointers to derived objects.
- Overriding = same signature in derived class. Overloading = different parameters in same class.
#include <iostream>
using namespace std;
class Student {
public:
int rollNo;
Student(int r = 0) : rollNo(r) {}
};
// virtual inheritance prevents duplicate Student in Result
class Marks : virtual public Student {
public:
float marks;
Marks(int r = 0, float m = 0) : Student(r), marks(m) {}
};
class Sports : virtual public Student {
public:
float sportsScore;
Sports(int r = 0, float s = 0) : Student(r), sportsScore(s) {}
};
class Result : public Marks, public Sports {
public:
Result(int r, float m, float s)
: Student(r), Marks(r, m), Sports(r, s) {}
void display() {
cout << "Roll No : " << rollNo << endl;
cout << "Marks : " << marks << endl;
cout << "Sports : " << sportsScore << endl;
cout << "Total : " << (marks + sportsScore) << endl;
}
};
int main() {
Result r(101, 85.5, 9.0);
r.display();
return 0;
}
Roll No : 101
Marks : 85.5
Sports : 9
Total : 94.5#include <iostream>
using namespace std;
class Shape {
public:
virtual void area() { // virtual β runtime dispatch
cout << "Area of generic shape\n";
}
virtual ~Shape() {} // virtual destructor β ALWAYS add this
};
class Circle : public Shape {
float r;
public:
Circle(float r) : r(r) {}
void area() override { // 'override' is good practice
cout << "Circle Area = " << 3.14 * r * r << endl;
}
};
class Rectangle : public Shape {
float l, w;
public:
Rectangle(float l, float w) : l(l), w(w) {}
void area() override {
cout << "Rectangle Area = " << l * w << endl;
}
};
int main() {
Shape* shapes[2]; // base class POINTERS
shapes[0] = new Circle(5);
shapes[1] = new Rectangle(4, 6);
for (int i = 0; i < 2; i++)
shapes[i]->area(); // calls DERIVED version at runtime
for (int i = 0; i < 2; i++)
delete shapes[i];
return 0;
}
Circle Area = 78.5
Rectangle Area = 24#include <iostream>
using namespace std;
class Animal { // Abstract class β cannot instantiate directly
public:
virtual void sound() = 0; // pure virtual function
void breathe() {
cout << "Breathing...\n";
}
virtual ~Animal() {}
};
class Dog : public Animal {
public:
void sound() override { cout << "Dog says: Woof!\n"; }
};
class Cat : public Animal {
public:
void sound() override { cout << "Cat says: Meow!\n"; }
};
int main() {
// Animal a; // ERROR: cannot instantiate abstract class
Animal* a1 = new Dog();
Animal* a2 = new Cat();
a1->sound();
a1->breathe();
a2->sound();
delete a1; delete a2;
return 0;
}
Dog says: Woof!
Breathing...
Cat says: Meow!#include <iostream>
using namespace std;
// ββ MULTILEVEL: A β B β C ββ
class A {
public:
void showA() { cout << "Class A\n"; }
};
class B : public A {
public:
void showB() { cout << "Class B\n"; }
};
class C : public B { // C inherits B which inherits A
public:
void showC() { cout << "Class C\n"; }
};
// ββ HIERARCHICAL: Vehicle β Car, Bike ββ
class Vehicle {
public:
int speed;
Vehicle(int s) : speed(s) {}
void show() { cout << "Speed: " << speed << endl; }
};
class Car : public Vehicle {
public:
int doors;
Car(int s, int d) : Vehicle(s), doors(d) {}
void display() { cout << "Car | Doors: " << doors << " | "; show(); }
};
class Bike : public Vehicle {
public:
Bike(int s) : Vehicle(s) {}
void display() { cout << "Bike | "; show(); }
};
int main() {
// Multilevel
C obj;
obj.showA(); obj.showB(); obj.showC(); // C has all three
cout << "---\n";
// Hierarchical
Car car(120, 4);
Bike bike(80);
car.display();
bike.display();
return 0;
}
Class A
Class B
Class C
---
Car | Doors: 4 | Speed: 120
Bike | Speed: 80- No
virtualdestructor in base class β derived destructor never runs β memory leak. - Forgetting
virtualon base function β base pointer always calls base version, not derived (static binding). - No
virtualon base classes in diamond inheritance β ambiguity β two copies of the base. - In multilevel inheritance, constructors run top-down (AβBβC); destructors run bottom-up (CβBβA).
- Using
privateinheritance hides ALL base members from further subclasses β usepublicfor normal IS-A relationships.
Templates & Exception Handling
Function vs Class Templates
Function template β generic function for any type.
Syntax: template <typename T> before the function.
Class template β entire class parameterized by type.
Instantiation: Calculator<int> c;
Exception Handling
try β block that might throw.
throw β raises an exception.
catch β handles the exception.
catch(...) β catches everything; must be last.
Exception Hierarchy
You can throw and catch: int, float, string, or objects.
Standard: std::exception β runtime_error, logic_error, etc.
Use e.what() to get the error message string.
Rethrowing
Use bare throw; inside a catch block to rethrow the current exception up the call stack.
catch(...)catches all exceptions β must always be last.- Function templates allow generic, type-independent programming.
- Keyword
typenameandclassare interchangeable insidetemplate<>. - If no matching catch is found,
std::terminate()is called. throw;(no argument) rethrows the current exception.
#include <iostream>
using namespace std;
template <typename T>
class Calculator {
public:
T add (T a, T b) { return a + b; }
T subtract(T a, T b) { return a - b; }
T multiply(T a, T b) { return a * b; }
T divide(T a, T b) {
if (b == 0) throw runtime_error("Division by zero!");
return a / b;
}
};
int main() {
Calculator<int> ic;
Calculator<double> dc;
cout << "Add (int) : " << ic.add(10, 5) << endl;
cout << "Mul (double) : " << dc.multiply(3.5, 2.0) << endl;
cout << "Div (double) : " << dc.divide(7.5, 2.5) << endl;
try {
cout << ic.divide(10, 0); // will throw
}
catch (const runtime_error &e) { // specific β FIRST
cout << "Caught: " << e.what() << endl;
}
catch (...) { // catch-all β LAST
cout << "Unknown error!\n";
}
return 0;
}
Add (int) : 15
Mul (double) : 7
Div (double) : 3
Caught: Division by zero!#include <iostream>
using namespace std;
template <typename T>
T largest(T a, T b) {
return (a > b) ? a : b;
}
template <typename T>
void swapValues(T &a, T &b) {
T tmp = a; a = b; b = tmp;
}
int main() {
cout << "Largest int : " << largest(12, 45) << endl;
cout << "Largest double : " << largest(3.14, 2.71) << endl;
cout << "Largest char : " << largest('Z', 'A') << endl;
int x = 10, y = 20;
swapValues(x, y);
cout << "After swap: x=" << x << " y=" << y << endl;
return 0;
}
Largest int : 45
Largest double : 3.14
Largest char : Z
After swap: x=20 y=10
#include <iostream>
using namespace std;
// Custom exception class
class AgeException : public exception {
public:
const char* what() const noexcept override {
return "Age cannot be negative!";
}
};
void checkAge(int age) {
if (age < 0) throw AgeException();
if (age < 18) throw string("Underage: access denied");
cout << "Access granted. Age: " << age << endl;
}
int main() {
int ages[] = {25, 15, -3};
for (int age : ages) {
try {
checkAge(age);
}
catch (const AgeException &e) { // most specific first
cout << "AgeException: " << e.what() << endl;
}
catch (const string &s) {
cout << "String error: " << s << endl;
}
catch (...) { // always LAST
cout << "Unknown exception!\n";
}
}
return 0;
}
Access granted. Age: 25
String error: Underage: access denied
AgeException: Age cannot be negative!#include <iostream>
using namespace std;
class StackUnderflow : public exception {
public:
const char* what() const noexcept override {
return "Stack Underflow: pop on empty stack!";
}
};
template <typename T, int SIZE = 5> // non-type template param!
class Stack {
private:
T data[SIZE];
int top;
public:
Stack() : top(-1) {}
void push(T val) {
if (top == SIZE - 1) { cout << "Stack Full!\n"; return; }
data[++top] = val;
}
T pop() {
if (top == -1) throw StackUnderflow();
return data[top--];
}
T peek() const {
if (top == -1) throw StackUnderflow();
return data[top];
}
bool isEmpty() const { return top == -1; }
int size() const { return top + 1; }
};
int main() {
Stack<int> s;
s.push(10); s.push(20); s.push(30);
cout << "Peek: " << s.peek() << endl;
cout << "Pop: " << s.pop() << endl;
cout << "Size: " << s.size() << endl;
try {
Stack<int> empty;
empty.pop(); // throws!
} catch (const StackUnderflow& e) {
cout << "Caught: " << e.what() << endl;
}
// Stack of strings β same template, different type
Stack<string, 3> ss;
ss.push("Hello"); ss.push("World");
cout << "String peek: " << ss.peek() << endl;
return 0;
}
Peek: 30
Pop: 30
Size: 2
Caught: Stack Underflow: pop on empty stack!
String peek: World- Placing
catch(...)before specific catch blocks β it swallows everything; specific blocks become dead code. - Forgetting
template <typename T>before each function definition when defined outside the class. - Catching by value when you should catch by const reference β expensive copy + slicing for class exceptions.
- Templates are not compiled until instantiated β syntax errors may not appear until you create a
Stack<int>. noexceptonwhat()β std::exception's signature requires it; omitting causes a warning or compiler mismatch.
STL β Standard Template Library
3 Components of STL
Containers β store data (vector, list, map, set, stack, queue).
Algorithms β process data (sort, find, count, reverse).
Iterators β access elements like generalized pointers.
Container Quick Reference
vector β dynamic array, random access O(1).
list β doubly linked list, no random access.
map β sorted key-value pairs, unique keys.
set β sorted unique elements.
stack/queue β LIFO / FIFO adapters.
sort() Constraint
std::sort() needs random-access iterators.
Works on: vector, array, deque.
Does NOT work on: list, map, set.
For list: use lst.sort() (member function).
Iterator Types
begin() β iterator to first element.
end() β iterator past the last element.
auto keyword simplifies iterator declarations.
Range-for: for(auto x : container)
mapstores key-value pairs in sorted key order; all keys unique.vectoris a dynamic array β grows automatically; supports random access.- STL = Containers + Algorithms + Iterators.
std::sort()requires random-access iterators β cannot be used directly onlist.push_back()β adds to end;push_front()β adds to front (list/deque only).
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v = {50, 10, 40, 20, 30};
v.push_back(60); // add to end
v.insert(v.begin() + 2, 99); // insert 99 at index 2
cout << "Before sort: ";
for (int x : v) cout << x << " ";
sort(v.begin(), v.end()); // sort ascending
cout << "\nAfter sort: ";
for (int x : v) cout << x << " ";
v.erase(v.begin() + 1); // erase element at index 1
cout << "\nAfter erase: ";
for (int x : v) cout << x << " ";
cout << "\nSize: " << v.size() << endl;
return 0;
}
Before sort: 50 10 99 40 20 30 60
After sort: 10 20 30 40 50 60 99
After erase: 10 30 40 50 60 99
Size: 6.sort() member) + Map (key-value)#include <iostream>
#include <list>
#include <map>
using namespace std;
int main() {
// ---- LIST ----
list<int> lst = {5, 2, 8, 1, 9};
lst.push_back(3);
lst.push_front(0);
lst.sort(); // member function β std::sort() won't work on list!
cout << "List (sorted): ";
for (int x : lst) cout << x << " ";
cout << endl;
lst.remove(5); // remove all occurrences of 5
cout << "After remove(5): ";
for (int x : lst) cout << x << " ";
cout << endl;
// ---- MAP ----
map<string, int> scores;
scores["Alice"] = 95;
scores["Bob"] = 88;
scores["Carol"] = 91;
scores.insert({"Dave", 78});
cout << "\nMap (sorted by key):\n";
for (auto &p : scores)
cout << " " << p.first << " -> " << p.second << endl;
// Check if key exists
if (scores.find("Bob") != scores.end())
cout << "Bob's score: " << scores["Bob"] << endl;
return 0;
}
List (sorted): 0 1 2 3 5 8 9
After remove(5): 0 1 2 3 8 9
Map (sorted by key):
Alice -> 95
Bob -> 88
Carol -> 91
Dave -> 78
Bob's score: 88#include <iostream>
#include <vector>
#include <set>
#include <algorithm>
using namespace std;
int main() {
// SET: unique + sorted automatically
set<int> s = {5, 1, 3, 5, 2, 1}; // duplicates dropped
cout << "Set: ";
for (int x : s) cout << x << " ";
cout << endl;
// VECTOR ALGORITHMS
vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
// find
auto it = find(v.begin(), v.end(), 5);
if (it != v.end())
cout << "Found 5 at index: " << (it - v.begin()) << endl;
// count
cout << "Count of 1: " << count(v.begin(), v.end(), 1) << endl;
// reverse
reverse(v.begin(), v.end());
cout << "Reversed: ";
for (int x : v) cout << x << " ";
cout << endl;
return 0;
}
Set: 1 2 3 5
Found 5 at index: 4
Count of 1: 2
Reversed: 6 2 9 5 1 4 1 3accumulate, max_element)#include <iostream>
#include <stack>
#include <queue>
#include <vector>
#include <numeric> // for accumulate
#include <algorithm>
using namespace std;
int main() {
// ββ STACK (LIFO) ββ
stack<int> st;
st.push(1); st.push(2); st.push(3);
cout << "Stack (LIFO): ";
while (!st.empty()) {
cout << st.top() << " ";
st.pop();
}
cout << endl;
// ββ QUEUE (FIFO) ββ
queue<int> q;
q.push(10); q.push(20); q.push(30);
cout << "Queue (FIFO): ";
while (!q.empty()) {
cout << q.front() << " ";
q.pop();
}
cout << endl;
// ββ NUMERIC ALGORITHMS ββ
vector<int> v = {5, 3, 8, 1, 9, 2};
int sum = accumulate(v.begin(), v.end(), 0);
auto maxIt = max_element(v.begin(), v.end());
auto minIt = min_element(v.begin(), v.end());
cout << "Sum: " << sum << endl;
cout << "Max: " << *maxIt << endl;
cout << "Min: " << *minIt << endl;
// Sort descending using comparator lambda
sort(v.begin(), v.end(), [](
int a, int b){ return a > b; });
cout << "Sorted desc: ";
for (int x : v) cout << x << " ";
cout << endl;
return 0;
}
Stack (LIFO): 3 2 1
Queue (FIFO): 10 20 30
Sum: 28
Max: 9
Min: 1
Sorted desc: 9 8 5 3 2 1std::sort(lst.begin(), lst.end())on alistwill not compile β lists have no random-access iterators. Always uselst.sort().- Accessing a
mapkey with[]that doesn't exist creates that key with default value 0 β use.find()to safely check. - Forgetting headers: include
<vector>,<list>,<map>,<set>,<algorithm>separately. stackuses.top()to peek;queueuses.front(). Mixing them up is the #1 runtime mistake.accumulateis in<numeric>, NOT<algorithm>β forgetting this causes a compile error.
File I/O β fstream, ifstream, ofstream, stringstream
ifstream β Read
Input file stream β reads from file.
ifstream fin("file.txt");
fin >> var; or getline(fin, str);
ofstream β Write
Output file stream β writes to file (creates if absent, overwrites by default).
ofstream fout("out.txt");
fout << "Hello";
fstream β Read+Write
Combines both. Must specify mode:
fstream f("a.txt", ios::in | ios::out);
Modes: ios::app (append), ios::trunc (overwrite), ios::binary
stringstream
String-based stream β useful for type conversion and parsing.
stringstream ss;
ss << 42; string s = ss.str();
int to string, string to int β no atoi needed.
ifstreamis used for reading;ofstreamfor writing;fstreamfor both.ios::appopens file in append mode β does NOT erase existing content.eof()returns true when end-of-file is reached.getline(fin, str)reads an entire line including spaces;fin >> xstops at whitespace.- Always call
close()after file operations to flush and release the resource.
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
// ββ WRITE to file ββ
ofstream fout("students.txt");
if (!fout) { cout << "Cannot open file!\n"; return 1; }
fout << "Alice 92\n";
fout << "Bob 85\n";
fout << "Carol 78\n";
fout.close();
cout << "File written.\n";
// ββ READ from file ββ
ifstream fin("students.txt");
string line;
cout << "--- File Contents ---\n";
while (getline(fin, line)) // reads line by line
cout << line << endl;
fin.close();
// ββ APPEND to file ββ
ofstream fapp("students.txt", ios::app);
fapp << "Dave 91\n";
fapp.close();
// ββ READ again to verify ββ
ifstream fin2("students.txt");
cout << "--- After Append ---\n";
while (getline(fin2, line))
cout << line << endl;
fin2.close();
return 0;
}
File written.
--- File Contents ---
Alice 92
Bob 85
Carol 78
--- After Append ---
Alice 92
Bob 85
Carol 78
Dave 91stringstream: Type Conversion + Parsing#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main() {
// int β string
int num = 42;
stringstream ss;
ss << num;
string s = ss.str();
cout << "int to string: \"" << s << "\"\n";
// string β int
string token = "2024";
stringstream ss2(token);
int year;
ss2 >> year;
cout << "string to int: " << year + 1 << endl;
// Parsing CSV-like string
string data = "Alice 95 A";
stringstream ss3(data);
string name, grade;
int marks;
ss3 >> name >> marks >> grade;
cout << "Name: " << name
<< " Marks: " << marks
<< " Grade: " << grade << endl;
return 0;
}
int to string: "42"
string to int: 2025
Name: Alice Marks: 95 Grade: A- Opening a file without checking
if (!fin)β if the file doesn't exist, all reads silently fail and produce garbage. - Using
fin >> strto read a line with spaces β it stops at whitespace. Usegetline(fin, str)for full lines. - Forgetting
fout.close()β buffer may not flush; last few writes can be lost. ofstreamwith default mode truncates existing file. Useios::appto preserve content.
Type Casting β C++ Style Casts (Exam Favourite)
static_cast
Compile-time cast. Safe for related types.
double d = static_cast<double>(5);
Use for: intβdouble, baseβderived (when safe), enumβint.
dynamic_cast
Runtime cast. For polymorphic (virtual) hierarchies only.
D* d = dynamic_cast<D*>(bPtr);
Returns nullptr on failure (pointers) or throws on references. Requires RTTI.
const_cast
Adds or removes const qualifier.
const_cast<int*>(cptr);
Only legal cast to remove const β modifying original const object = UB.
reinterpret_cast
Low-level bit-level reinterpretation. Unsafe.
int* p = reinterpret_cast<int*>(addr);
Use for: pointerβinteger, casting to void*. Avoid unless doing hardware/system programming.
static_castis checked at compile time;dynamic_castat runtime.dynamic_castworks only with polymorphic classes (at least one virtual function).const_castis the only cast that can removeconst.- Old C-style cast
(int)xis equivalent to a combination of all four β avoid in C++. dynamic_castto pointer returnsnullptron failure; to reference throwsstd::bad_cast.
#include <iostream>
using namespace std;
class Base { public: virtual void f() { cout << "Base\n"; } };
class Derived : public Base { public: void f() { cout << "Derived\n"; } };
int main() {
// 1. static_cast β compile-time, safe related types
double pi = 3.14159;
int iPi = static_cast<int>(pi); // truncates decimal
cout << "static_cast: " << iPi << endl;
// 2. dynamic_cast β runtime, polymorphic classes only
Base* b = new Derived();
Derived* d = dynamic_cast<Derived*>(b);
if (d) cout << "dynamic_cast: success β "; d->f();
Base* b2 = new Base();
Derived* d2 = dynamic_cast<Derived*>(b2);
cout << "dynamic_cast fail: " << (d2 ? "ok" : "nullptr") << endl;
// 3. const_cast β remove const qualifier
const int cx = 100;
int* px = const_cast<int*>(&cx);
cout << "const_cast: " << *px << endl; // reading is OK
// 4. reinterpret_cast β low-level pointer interpretation
long addr = reinterpret_cast<long>(b);
cout << "reinterpret_cast addr: " << addr << endl;
delete b; delete b2;
return 0;
}
static_cast: 3
dynamic_cast: success β Derived
dynamic_cast fail: nullptr
const_cast: 100
reinterpret_cast addr: [some memory address]- Using
dynamic_caston a class with no virtual functions β compile error. The base must be polymorphic. static_castbetween unrelated classes compiles but causes undefined behavior at runtime.- Modifying a truly
constvariable viaconst_castβ undefined behavior (only use const_cast to pass const data to legacy non-const APIs).
Advanced OOP β Default Args, Shallow/Deep Copy, explicit, const, mutable
Default Arguments
Parameters with default values β must be rightmost.
void f(int a, int b = 10, int c = 20);
f(5); β a=5, b=10, c=20.
Default values go in the declaration, not definition.
Shallow vs Deep Copy
Shallow Copy (default) β copies pointer values; both objects share the same heap memory β double-free bug on destruction.
Deep Copy β allocates new memory and copies actual data. Must write a custom copy constructor when class has pointer members.
explicit Keyword
Prevents implicit type conversion via single-argument constructors.
Without explicit: MyClass obj = 5; compiles silently.
With explicit: must write MyClass obj(5);
const & mutable
const member function: void f() const {} β cannot modify any member variable; can be called on const objects.
mutable: mutable int count; β allows modification even inside a const function. Used for caching/logging.
- Default arguments must always be on the rightmost parameters β putting them on the left is a compile error.
- Shallow copy uses the default copy constructor; causes issues when the class has pointer members.
- Deep copy requires a custom copy constructor that allocates new memory.
explicitkeyword on a constructor disables implicit conversion β forces direct initialization.- A
constmember function cannot call a non-const member function on the same object. mutablemembers can be modified even inconstfunctions β exception to the const rule.
#include <iostream>
#include <cstring>
using namespace std;
class ShallowDemo {
public:
int* data;
ShallowDemo(int v) { data = new int(v); }
// Default copy constructor = shallow: both share same pointer!
~ShallowDemo() { delete data; } // double-free bug!
};
class DeepDemo {
public:
int* data;
DeepDemo(int v) { data = new int(v); }
// Deep copy constructor β allocates NEW memory
DeepDemo(const DeepDemo& other) {
data = new int(*other.data); // new allocation, copied value
cout << "Deep copy performed\n";
}
~DeepDemo() { delete data; } // safe: each object owns its memory
};
int main() {
DeepDemo d1(99);
DeepDemo d2(d1); // triggers deep copy
*d1.data = 200; // modify d1
cout << "d1: " << *d1.data << endl; // 200
cout << "d2: " << *d2.data << endl; // still 99 β independent!
return 0;
}
Deep copy performed
d1: 200
d2: 99explicit + const + mutable#include <iostream>
using namespace std;
class Config {
private:
string host;
int port;
mutable int accessCount; // modifiable even in const functions
public:
// Default arguments β rightmost MUST have defaults
explicit Config(string h, int p = 8080)
: host(h), port(p), accessCount(0) {}
// const member function β cannot modify host/port
void display() const {
accessCount++; // OK: mutable
cout << host << ":" << port
<< " [accessed " << accessCount << " time(s)]\n";
}
void setPort(int p) { port = p; } // non-const: can modify
};
int main() {
// explicit prevents: Config c = "localhost"; (implicit conversion)
Config c1("localhost"); // port defaults to 8080
Config c2("example.com", 443); // explicit port
c1.display(); c1.display();
c2.display();
const Config cc("secure.io", 9000);
cc.display(); // const object β can only call const functions
// cc.setPort(1234); // ERROR: can't call non-const on const object
return 0;
}
localhost:8080 [accessed 1 time(s)]
localhost:8080 [accessed 2 time(s)]
example.com:443 [accessed 1 time(s)]
secure.io:9000 [accessed 1 time(s)]- Shallow copy with pointer members β destructor of one object frees memory still used by the other β crash / undefined behavior.
- Default arguments in the definition (not declaration) β if prototype is in header, defaults must be in the header.
- Calling a non-const member function from a
constfunction β compile error. Mark helper functionsconsttoo. - Forgetting
expliciton single-arg constructors β silent implicit conversion can cause very subtle bugs.
Advanced STL β priority_queue, unordered_map, lower_bound, make_pair, Template Specialization
priority_queue
Max-heap by default β top() always gives the largest element.
priority_queue<int> pq;
Min-heap: priority_queue<int, vector<int>, greater<int>> pq;
unordered_map
Hash-based map β O(1) average lookup (vs map's O(log n)).
No guaranteed order of keys.
unordered_map<string, int> um;
lower_bound / upper_bound
Binary search on sorted containers.
lower_bound(v.begin(), v.end(), x) β iterator to first element β₯ x.
upper_bound(..., x) β iterator to first element > x.
make_pair & Template Specialization
make_pair(a, b) creates a pair<T1,T2> β used in maps, sorting tuples.
Template Specialization: provide a specific implementation for one type.
template<> void f<int>() { ... }
priority_queueis a max-heap by default β largest element attop().unordered_mapgives O(1) average access;mapgives O(log n).lower_boundrequires the container to be sorted first β returns iterator to first element β₯ key.pairstores two values: accessed via.firstand.second.- Template specialization uses
template<>(empty angle brackets) before the function/class.
priority_queue + unordered_map + make_pair#include <iostream>
#include <queue>
#include <unordered_map>
#include <vector>
#include <utility> // make_pair
using namespace std;
int main() {
// ββ MAX-HEAP priority_queue ββ
priority_queue<int> pq;
pq.push(30); pq.push(10); pq.push(50); pq.push(20);
cout << "Max-heap order: ";
while (!pq.empty()) { cout << pq.top() << " "; pq.pop(); }
cout << endl;
// ββ MIN-HEAP ββ
priority_queue<int, vector<int>, greater<int>> minPQ;
minPQ.push(30); minPQ.push(10); minPQ.push(50);
cout << "Min-heap order: ";
while (!minPQ.empty()) { cout << minPQ.top() << " "; minPQ.pop(); }
cout << endl;
// ββ UNORDERED_MAP (O(1) lookup) ββ
unordered_map<string, int> freq;
string words[] = {"apple", "banana", "apple", "cherry", "banana", "apple"};
for (auto& w : words) freq[w]++;
cout << "Word frequencies:\n";
for (auto& p : freq)
cout << " " << p.first << ": " << p.second << endl;
// ββ MAKE_PAIR ββ
vector<pair<int, string>> students;
students.push_back(make_pair(92, "Alice"));
students.push_back(make_pair(85, "Bob"));
students.push_back({97, "Carol"}); // brace init also works
sort(students.begin(), students.end()); // sorts by first (score)
cout << "Sorted students: ";
for (auto& s : students)
cout << s.second << "(" << s.first << ") ";
cout << endl;
return 0;
}
Max-heap order: 50 30 20 10
Min-heap order: 10 30 50
Word frequencies:
cherry: 1
banana: 2
apple: 3
Sorted students: Bob(85) Alice(92) Carol(97)lower_bound / upper_bound + Template Specialization#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// ββ TEMPLATE: generic print ββ
template <typename T>
void printInfo(T val) {
cout << "Generic: " << val << endl;
}
// ββ TEMPLATE SPECIALIZATION for bool ββ
template<>
void printInfo<bool>(bool val) {
cout << "Bool specialization: " << (val ? "TRUE" : "FALSE") << endl;
}
int main() {
// Template specialization demo
printInfo(42); // uses generic
printInfo(3.14); // uses generic
printInfo(true); // uses bool specialization
// lower_bound / upper_bound (requires SORTED vector)
vector<int> v = {10, 20, 30, 40, 50, 60};
auto lo = lower_bound(v.begin(), v.end(), 30); // first >= 30
auto hi = upper_bound(v.begin(), v.end(), 30); // first > 30
cout << "lower_bound(30) index: " << (lo - v.begin()) << endl;
cout << "upper_bound(30) index: " << (hi - v.begin()) << endl;
cout << "Value at lower: " << *lo << endl;
// Binary search
bool found = binary_search(v.begin(), v.end(), 40);
cout << "40 in vector: " << (found ? "Yes" : "No") << endl;
return 0;
}
Generic: 42
Generic: 3.14
Bool specialization: TRUE
lower_bound(30) index: 2
upper_bound(30) index: 3
Value at lower: 30
40 in vector: Yeslower_bound/upper_bound/binary_searchonly work correctly on sorted containers β calling on unsorted data gives wrong results without error.priority_queuedoes NOT havebegin()/end()β you can only accesstop()and mustpop()to iterate.unordered_maphas no guaranteed key order β if the question says "sorted output", usemapinstead.- Template specialization uses
template<>with empty brackets β missing them or writingtemplate<bool>is wrong.
π High-Value Comparison Tables
| Feature | class | struct | union |
|---|---|---|---|
| Default Access | private | public | public |
| Inheritance | β Supported | β Supported | β Not supported |
| Memory | Sum of members | Sum of members | Largest member only |
| Use case | OOP with data hiding | Simple data grouping | Memory-saving type pun |
| Feature | Compile-time (Static) | Runtime (Dynamic) |
|---|---|---|
| Mechanism | Function / Operator Overloading | Virtual Functions + Pointers |
| Resolved at | Compile time | Runtime (via vtable) |
| Speed | Faster | Slight overhead |
| Keyword | None needed | virtual |
| Flexibility | Less flexible | More flexible |
| Container | Access | Ordered? | Duplicates? | Use when |
|---|---|---|---|---|
vector | Random O(1) | Insertion order | β Yes | Dynamic array, most common |
list | Sequential | Insertion order | β Yes | Frequent insert/delete at middle |
map | Key lookup O(log n) | Sorted by key | β Unique keys | Key-value store |
set | Key lookup O(log n) | Sorted | β No | Unique sorted elements |
stack | Top only | LIFO | β Yes | Undo, recursion simulation |
queue | Front/back | FIFO | β Yes | Task scheduling, BFS |
| Type | Signature | Called when |
|---|---|---|
| Default | Name() | Name obj; |
| Parameterized | Name(int x) | Name obj(5); |
| Copy | Name(const Name& o) | Name b = a; or passing by value |
| Destructor | ~Name() | Object goes out of scope / delete |
| Base Member | public inheritance | protected inheritance | private inheritance |
|---|---|---|---|
| public in Base | public in Derived | protected in Derived | private in Derived |
| protected in Base | protected in Derived | protected in Derived | private in Derived |
| private in Base | β Not accessible | β Not accessible | β Not accessible |
| Feature | map | unordered_map |
|---|---|---|
| Ordering | Sorted by key | No order guaranteed |
| Lookup | O(log n) | O(1) average |
| Implementation | Red-Black Tree | Hash Table |
| Key requirement | Comparable (<) | Hashable |
| Use when | Need sorted keys | Speed is priority |
| Cast | When to use | Checked at | Safety |
|---|---|---|---|
static_cast | Related types (intβdouble) | Compile | β Safe |
dynamic_cast | Polymorphic class hierarchies | Runtime | β Safe (nullptr on fail) |
const_cast | Remove/add const qualifier | Compile | β οΈ UB if original is const |
reinterpret_cast | Low-level pointer reinterpret | Compile | β Unsafe |
| Feature | Shallow Copy | Deep Copy |
|---|---|---|
| How | Default copy constructor | Custom copy constructor |
| What's copied | Pointer address (same memory) | New allocation + data copied |
| Problem | Double-free crash on destruction | No issue |
| When needed | No pointer members (safe) | Class has pointer/heap members |
π― Predicted Exam Q&A β MCQ + Short + Long
:: (scope), . (member access), .* (pointer to member), ?: (ternary), sizeof, typeid.Memory trick: "SD???" β Scope, Dot, pointer-to-member, ternary, Sizeof.
virtual ~Base() {}.catch(...) do, and where must it appear?catch(...) catches all exception types regardless of what was thrown. It must appear last in a sequence of catch blocks β placing it before specific handlers makes them unreachable.std::sort() be used on std::list?std::sort() requires random-access iterators (ability to jump to any element in O(1)). std::list only provides bidirectional iterators. Use lst.sort() instead β it's a member function that uses a merge sort internally.Overriding: A derived class redefines a base class virtual function with the same signature. Resolved at runtime via vtable (dynamic polymorphism).
Name b(a); or Name b = a; (at declaration).Assignment Operator (
=) β copies data into an already existing object: b = a; (b was created earlier).Signature:
Name& operator=(const Name& o) { ... return *this; }
virtual keyword in the base class. When called through a base class pointer or reference, it ensures the derived class version is invoked at runtime (dynamic dispatch via vtable).Without it:
Base* p = new Derived(); p->f(); calls Base::f().With it: same code calls Derived::f(). This is the foundation of runtime polymorphism.
Fix: Declare B and C as
virtual public A. Virtual inheritance ensures only one shared copy of A exists in D, eliminating the ambiguity.
Function Template:
template <typename T> T largest(T a, T b) { return (a > b) ? a : b; }Works for int, double, char β one definition covers all.
Class Template:
template <typename T> class Calc { T add(T a, T b) { return a+b; } };Instantiated as
Calc<int> c; or Calc<double> c;Advantages: Code reuse, type safety, no code duplication, basis of entire STL.
Keywords:
try β wraps risky code. throw β raises an exception. catch β handles it.Flow: Code in
try executes β if throw is reached, execution jumps immediately to the matching catch β remaining try code is skipped β if no match, std::terminate() is called.Rules to remember:
1. Most specific
catch first.2.
catch(...) always last.3. Catch by
const reference to avoid slicing.4. Custom exceptions should inherit from
std::exception and override what().
1. Containers β store data:
vector, list, map, set, stack, queue, deque2. Algorithms β process data (work via iterators):
sort(), find(), count(), reverse(), accumulate(), max_element()3. Iterators β abstract pointers to elements:
begin(), end(), auto it = v.begin(); ++it;Iterators decouple algorithms from containers β
sort() works on any container that provides random-access iterators without knowing the container type.
map and unordered_map?map when you need sorted keys; use unordered_map for raw speed.explicit keyword? Why use it?explicit prevents a constructor from being used for implicit type conversion. Without it, MyClass obj = 5; silently converts 5 into a MyClass object, which can cause subtle bugs. With explicit, only direct initialization MyClass obj(5); is allowed. Best practice: mark all single-argument constructors explicit unless conversion is intentional.<fstream> header with three classes:ifstream β input (read) from file.
ifstream fin("a.txt"); fin >> x;ofstream β output (write) to file.
ofstream fout("b.txt"); fout << x;fstream β both read and write.
Key file modes:
ios::in, ios::out, ios::app (append), ios::binary.Always call
close() after operations to flush the buffer.
dynamic_cast β runtime cast for polymorphic hierarchies; returns
nullptr on failure.const_cast β only cast that can remove
const; UB if you write to a truly const object.reinterpret_cast β low-level bit reinterpretation; unsafe, used for hardware-level code.
Full specialization syntax:
template<> (empty angle brackets) before the function.Example: A generic
printInfo<T> prints any type, but the bool specialization prints "TRUE"/"FALSE" instead of 1/0.Why useful: Lets you optimize or correct behavior for specific types without writing entirely separate functions or classes. The entire STL uses specialization heavily (e.g.,
vector<bool> is a specialization that packs bits).
Real-world analogy: A car's engine is hidden under the hood. You interact with it only via the steering wheel, pedals, and gear β you can't directly touch the pistons. The interface (public methods) is exposed; the implementation (private data) is hidden.
Implementation: Declare members
private; provide public getter/setter methods.Advantages:
1. Data security β prevents unauthorized direct access.
2. Maintainability β internal implementation can change without affecting outside code.
3. Controlled access β validation logic inside setters.
4. Modularity β class is a self-contained unit.
4 Pillars first. Write Encapsulation, Abstraction, Inheritance, Polymorphism at the top of every theory answer.
Semicolon }; after every class. Non-negotiable. One missing semicolon = cascade of errors.
virtual = runtime polymorphism. Base pointer + derived object + virtual function = correct derived method called.
Virtual destructor ALWAYS. If you have a base class with pointers to derived, make the destructor virtual.
catch(...) is LAST. Specific catches come first. catch-all is always the final safety net.
lst.sort() NOT std::sort(lst). Lists lack random access β they must use their own member sort.
Operator+ must return object, not void. Return type is the class itself so chaining works.
template <typename T> before EVERY function. When defined outside the class, repeat it for each function.
Shallow copy = danger with pointers. Any class with a raw pointer member MUST have a custom copy constructor doing deep copy.
File I/O: always check + always close. if (!fin) after opening. fin.close() at the end. Use getline() for lines with spaces.
dynamic_cast needs virtual functions. No virtual function in base = compile error. It returns nullptr on failure β always null-check it.
Default args: rightmost only. f(int a, int b=5) is valid. f(int a=5, int b) is a compile error.
priority_queue top = largest. For min-heap use greater<int> as third template arg. Never use begin()/end() on it.
lower_bound needs sorted data. Always sort before calling lower_bound, upper_bound, or binary_search.