Object-Oriented Programming Study Notes
Complete exam-focused notes covering OOP principles, C++ syntax, classes, inheritance, polymorphism, templates, STL, and exception handling.
- 1-Mark Qs
- 25+ Definitions
- 5-Mark Qs
- 15+ Explanations
- 15-Mark Qs
- 10+ Programs & theory
- PYQs
- 2021–24 Previous years
Introduction to OOP & C++ Basics
Procedural vs OOP · OOP Concepts · C++ Structure · Data Types · Operators · Control Statements · Functions
.cpp (or .C, .cc, .cxx)private access specifier.virtual functions.| Feature | Procedural (C) | OOP (C++) |
|---|---|---|
| Approach | Top-down (algorithm) | Bottom-up (data) |
| Data Security | No data protection | Access specifiers (private, protected, public) |
| Code Reuse | Functions (limited) | Inheritance (excellent) |
| Data & Functions | Separate | Combined in class |
| Polymorphism | Not supported | Function/Operator overloading |
| Examples | C, Pascal, COBOL | C++, Java, Python |
| Size | Larger programs | Modular, smaller |
| Inheritance | Not supported | Single, Multiple, Multilevel |
cin and cout are predefined stream objects for input/output.
#include <iostream>
using namespace std;
int main() {
int age;
string name;
cout << "Enter your name: "; // Output
cin >> name; // Input
cout << "Enter your age: ";
cin >> age;
cout << "Hello " << name << ", Age: " << age << endl;
return 0;
}<< → insertion operator (output)
>> → extraction operator (input)
endl → newline + flush
Arithmetic: + - * / % (addition, subtraction, multiplication, division, modulo)
Relational: < > <= >= == != (returns bool)
Logical: && || ! (AND, OR, NOT)
Bitwise: & | ^ ~ << >> (bit AND, OR, XOR, NOT, left shift, right shift)
Assignment: = += -= *= /= %=
Special: sizeof, ?:, &, *, ->
#include <iostream>
using namespace std;
// Call by Value - COPY is passed
void byValue(int x) { x = x + 10; }
// Call by Reference - ALIAS is passed
void byRef(int &x) { x = x + 10; }
// Call by Pointer - ADDRESS is passed
void byPointer(int *x) { *x = *x + 10; }
int main() {
int a = 5, b = 5, c = 5;
byValue(a); // a = 5 (unchanged)
byRef(b); // b = 15 (changed)
byPointer(&c); // c = 15 (changed)
return 0;
}Call by Value: Copy passed → original unchanged, safe but slower for large data.
Call by Reference: Alias passed → original changes, no extra memory.
Call by Pointer: Address passed → original changes, pointer arithmetic possible.
#include <iostream>
using namespace std;
// Same name, different parameters
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
int add(int a, int b, int c) { return a + b + c; }
int main() {
cout << add(2, 3) << endl; // 5 (int)
cout << add(2.5, 3.5) << endl; // 6.0 (double)
cout << add(1, 2, 3) << endl; // 6 (3 params)
return 0;
}Return type alone cannot differentiate overloaded functions.
What is OOP?
Object-Oriented Programming (OOP) is a programming paradigm based on the concept of "objects" that contain data (attributes) and code (methods/functions). OOP organizes software design around data (objects) rather than functions and logic.
Need for OOP: Procedural programming focuses on functions and procedures to operate on data. As programs grow larger, procedural code becomes difficult to maintain. OOP solves this by bundling data and functions together.
Basic Principles of OOP:
1. Object: An object is an instance of a class. It represents a real-world entity with attributes (data) and methods (behavior). Example: Car object with attributes: color, speed; methods: accelerate(), brake().
2. Class: A class is a blueprint/template that defines what an object looks like. It doesn't occupy memory until instantiated. Example: class Car { ... };
3. Abstraction: Hiding internal implementation details and showing only the interface. Users know what a function does, not how. Achieved using abstract classes and pure virtual functions.
4. Encapsulation: Wrapping data and functions together and restricting direct access using private members. Data can only be accessed through public member functions (getters/setters).
5. Inheritance: Creating a new class (derived) from an existing class (base). The derived class inherits all accessible members of the base class. Types: single, multilevel, hierarchical, multiple, hybrid.
6. Polymorphism: Ability to take many forms. Compile-time (overloading) and runtime (virtual functions). Enables writing generic code that works with multiple types.
7. Dynamic Binding: The process of linking a function call to its definition at runtime. Achieved using virtual keyword.
8. Message Passing: Objects communicate by sending messages (calling methods) to each other.
#include <iostream>
using namespace std;
class Car {
// Encapsulation: private data
private:
string brand;
float speed;
public:
// Abstraction: show only interface
void setBrand(string b) { brand = b; }
string getBrand() { return brand; }
void accelerate() {
speed += 10;
cout << brand << " accelerating. Speed: " << speed << " km/h\n";
}
};
int main() {
Car c1; // Object creation
c1.setBrand("Toyota");
c1.accelerate(); // Message passing
return 0;
}If-Else:
int marks = 75;
if (marks >= 90)
cout << "Grade A";
else if (marks >= 60)
cout << "Grade B";
else
cout << "Grade C";Switch:
int day = 3;
switch(day) {
case 1: cout << "Mon"; break;
case 2: cout << "Tue"; break;
case 3: cout << "Wed"; break;
default: cout << "Invalid";
}For Loop:
for(int i = 1; i <= 5; i++)
cout << i << " "; // 1 2 3 4 5While Loop:
int i = 1;
while(i <= 5) {
cout << i << " ";
i++;
}Do-While Loop:
int i = 1;
do {
cout << i << " ";
i++;
} while(i <= 5); // executes at least onceInline Functions: Suggests compiler to replace function call with function body to reduce call overhead. Suitable for small functions.
inline int square(int x) { return x * x; }
// Compiler replaces: cout << square(5);
// with: cout << (5 * 5);Default Arguments: Allows function to be called with fewer arguments by providing default values.
float interest(float p, float r = 5.0, int t = 2) {
return (p * r * t) / 100.0;
}
// All valid:
interest(1000); // r=5.0, t=2
interest(1000, 7.0); // t=2
interest(1000, 7.0, 3); // all argsScope Resolution Operator (::): Used to define member functions outside class, access global variables, and access static members.
class Demo {
int x;
public:
void setX(int x);
static int count;
};
// Defining outside class
void Demo::setX(int x) { this->x = x; }
int Demo::count = 0; // Initialize static memberClasses & Objects
Class Declaration · Member Functions · Objects · Memory Allocation · Static Members · Constructors · Destructors · this Pointer · Friend Functions · Nested Classes
~ and takes no parameters.this pointer?static keyword that are shared among all objects of a class. Only one copy exists in memory regardless of the number of objects.ClassName::functionName().friend keyword inside the class.friend class ClassName;ClassName(const ClassName &obj) { ... }| Constructor | Destructor |
|---|---|
| Same name as class | Same name with ~ prefix |
| No return type | No return type |
| Called at object creation | Called at object destruction |
| Can take parameters | Cannot take parameters |
| Can be overloaded | Cannot be overloaded |
| Initializes memory | Deallocates memory |
const keyword that cannot modify any member variables of the class. Syntax: void show() const;#include <iostream>
using namespace std;
class Student {
string name;
int roll;
float *marks; // Dynamic allocation
public:
// 1. Default Constructor
Student() {
name = "Unknown";
roll = 0;
marks = new float[3]{0, 0, 0};
}
// 2. Parameterized Constructor
Student(string n, int r, float m[]) {
name = n;
roll = r;
marks = new float[3];
for(int i = 0; i < 3; i++) marks[i] = m[i];
}
// 3. Copy Constructor (deep copy)
Student(const Student &s) {
name = s.name;
roll = s.roll;
marks = new float[3];
for(int i = 0; i < 3; i++) marks[i] = s.marks[i];
}
// 4. Dynamic Constructor (allocates memory)
Student(string n, int r, int nSub) {
name = n; roll = r;
marks = new float[nSub];
for(int i = 0; i < nSub; i++) marks[i] = 0;
}
void display() {
cout << name << " " << roll << " ";
for(int i = 0; i < 3; i++) cout << marks[i] << " ";
cout << endl;
}
~Student() { delete[] marks; } // Destructor
};#include <iostream>
using namespace std;
class BankAccount {
double balance; // private
public:
BankAccount(double b) { balance = b; }
// Friend function - can access private members
friend void showBalance(BankAccount &acc);
// Friend class - all its methods can access private
friend class Auditor;
};
// Friend function definition
void showBalance(BankAccount &acc) {
cout << "Balance: " << acc.balance << endl;
}
// Friend class
class Auditor {
public:
void audit(BankAccount &acc) {
cout << "Auditing account with balance: "
<< acc.balance << endl;
}
};
int main() {
BankAccount acc(5000);
showBalance(acc); // Friend function call
Auditor a;
a.audit(acc); // Friend class method
return 0;
}#include <iostream>
using namespace std;
class Counter {
int id;
static int count; // shared by ALL objects
public:
Counter(int id) {
this->id = id;
count++;
}
// Static member function
static void showCount() {
cout << "Total objects: " << count << endl;
}
void showID() {
cout << "ID: " << id
<< ", Total: " << count << endl;
}
};
// Must initialize static member outside class
int Counter::count = 0;
int main() {
Counter c1(1); c1.showID(); // ID: 1, Total: 1
Counter c2(2); c2.showID(); // ID: 2, Total: 2
Counter c3(3); c3.showID(); // ID: 3, Total: 3
Counter::showCount(); // Call without object: Total: 3
return 0;
}this pointer with example.this pointer is a hidden pointer passed as the first argument to all non-static member functions. It points to the object that invoked the function.
#include <iostream>
using namespace std;
class Box {
int length;
public:
// Using this pointer to resolve naming conflict
void setLength(int length) {
this->length = length; // this->length is member, length is param
}
// Chaining using this pointer
Box& increment() {
this->length += 10;
return *this; // returns current object
}
// Comparison
bool isLarger(Box b) {
return this->length > b.length;
}
void display() { cout << length << endl; }
};
int main() {
Box b1;
b1.setLength(50);
b1.increment().increment(); // chaining
b1.display(); // 70
return 0;
}Constructors: Special member functions invoked automatically when an object is created. Used for initialization.
Properties:
- Same name as class
- No return type (not even void)
- Can be overloaded
- Can have default arguments
- Cannot be virtual
- Cannot be inherited (but derived class calls base constructor)
Types of Constructors:
- Default Constructor: No parameters. Initializes with default values.
- Parameterized Constructor: Accepts parameters. Used to initialize with specific values.
- Copy Constructor: Takes reference to another object. Used for initialization and passing-by-value.
- Dynamic Constructor: Allocates memory dynamically using
new.
#include <iostream>
using namespace std;
class String {
char *str;
int len;
public:
// Default Constructor
String() {
len = 0;
str = new char[1];
str[0] = '\0';
}
// Parameterized Constructor
String(const char *s) {
len = strlen(s);
str = new char[len + 1];
strcpy(str, s);
}
// Copy Constructor (DEEP COPY)
String(const String &s) {
len = s.len;
str = new char[len + 1]; // NEW memory allocation
strcpy(str, s.str); // copy data
}
void display() { cout << str << endl; }
~String() { delete[] str; } // free memory
};
int main() {
String s1("Hello"); // Parameterized
String s2 = s1; // Copy constructor
String s3; // Default
s2.display(); // Hello
return 0;
}Deep vs Shallow Copy:
- Shallow Copy: Copies pointer value (both point to same memory). Dangerous — modifying one affects other.
- Deep Copy: Allocates new memory and copies content. Safe — each object has independent memory.
Destructors: Functions prefixed with ~. Called automatically when object goes out of scope. Used for cleanup (freeing memory). Only one destructor per class. Cannot be overloaded.
#include <iostream>
using namespace std;
class Engine {
int horsepower;
public:
Engine(int hp = 150) : horsepower(hp) {}
// Friend class can access private members
friend class Car;
void showSpec() {
cout << "Horsepower: " << horsepower << endl;
}
};
// Nested class
class Car {
string model;
Engine engine; // Engine as member
public:
Car(string m, int hp) : model(m), engine(hp) {}
// Nested class inside Car
class Insurance {
string provider;
int policyNo;
public:
Insurance(string p, int n) : provider(p), policyNo(n) {}
void show() {
cout << "Provider: " << provider
<< ", Policy: " << policyNo << endl;
}
};
void showCarDetails() {
cout << "Model: " << model << endl;
engine.showSpec(); // Can access Engine's public members
}
};
int main() {
Car c("Tesla Model 3", 283);
c.showCarDetails();
// Using nested class
Car::Insurance ins("ICICI Lombard", 12345);
ins.show();
return 0;
}Operator Overloading & Inheritance
Operator Overloading Rules · Unary/Binary Operators · Special Operators · Type Conversion · Inheritance Types · Ambiguity Resolution · Virtual Base Class · Abstract Classes
+, == to work with class objects.:: (scope resolution), .* (member pointer), sizeof, ?: (ternary), . (dot/member access)| Overloading | Overriding |
|---|---|
| Same function, different params | Same function, same params (in derived) |
| Compile-time polymorphism | Runtime polymorphism |
| Same class | Base & derived class |
| No virtual keyword needed | Requires virtual function |
virtual keyword that ensures only one copy of the base class exists in the derived class when using multiple inheritance (diamond problem).class A { virtual void f() = 0; };= 0 syntax. It has no implementation in the base class. All derived classes must override it. Makes the class abstract.+ and ++ operators.#include <iostream>
using namespace std;
class Complex {
float real, imag;
public:
Complex(float r = 0, float i = 0) : real(r), imag(i) {}
// Overload + (binary, non-member)
friend Complex operator+(Complex c1, Complex c2) {
return Complex(c1.real + c2.real, c1.imag + c2.imag);
}
// Overload ++ (unary, prefix - member function)
Complex operator++() {
real++; imag++;
return *this;
}
// Overload ++ (unary, postfix - member function)
Complex operator++(int) {
Complex temp = *this;
real++; imag++;
return temp; // returns old value
}
void display() {
cout << real << " + " << imag << "i" << endl;
}
};
int main() {
Complex c1(2, 3), c2(1, 4);
Complex c3 = c1 + c2; // Uses operator+
c3.display(); // 3 + 7i
++c1; // prefix: c1 = 3 + 4i
c1.display();
c1++; // postfix: old value returned
c1.display(); // 4 + 5i
return 0;
}#include <iostream>
using namespace std;
class Distance {
float meters;
public:
// Constructor for basic-to-class conversion
Distance(float m = 0) : meters(m) {}
// Operator for class-to-basic conversion
operator float() { return meters; }
void show() { cout << meters << " meters" << endl; }
};
int main() {
// Basic to Class: constructor called automatically
Distance d1 = 5.5; // implicit conversion
Distance d2(10.0); // explicit constructor call
d1.show(); // 5.5
d2.show(); // 10
// Class to Basic: conversion operator called
float m = (float)d1; // explicit
float m2 = d2; // implicit
cout << m << " " << m2 << endl; // 5.5 10
return 0;
}#include <iostream>
using namespace std;
class A {
public:
void show() { cout << "Class A\n"; }
};
class B : public A {};
class C : public A {};
// Diamond Problem: D inherits from B and C
class D : public B, public C {
public:
void show() {
B::show(); // Ambiguity! Must specify which A
C::show();
}
};
// Virtual Base Class Solution
class A2 {
public:
void show() { cout << "Virtual A\n"; }
};
class B2 : virtual public A2 {};
class C2 : virtual public A2 {};
class D2 : public B2, public C2 {
public:
void show() {
A2::show(); // Only ONE copy of A2 exists
}
};
int main() {
D d;
d.show(); // Calls both A copies
D2 d2;
d2.show(); // Calls single A2
return 0;
}
1. SINGLE: One base → one derived A → B
2. MULTILEVEL: Chain: A → B → C
3. MULTIPLE: Multiple bases → one derived A,B → C
4. HIERARCHICAL: One base → multiple derived A → B,C,D
5. HYBRID: Combination (e.g., hierarchical + multilevel)
#include <iostream>
using namespace std;
// 1. Single Inheritance
class Animal {
public: void eat() { cout << "Eating\n"; }
};
class Dog : public Animal { // Single
public: void bark() { cout << "Barking\n"; }
};
// 2. Multilevel Inheritance
class Puppy : public Dog { // Dog → Puppy (multilevel from Animal)
public: void weep() { cout << "Weeping\n"; }
};
// 3. Multiple Inheritance
class Bird {
public: void fly() { cout << "Flying\n"; }
};
class Bat : public Animal, public Bird { // Multiple
public: void hang() { cout << "Hanging\n"; }
};
// 4. Hierarchical Inheritance
class Cat : public Animal { // Same base, different derived
public: void meow() { cout << "Meowing\n"; }
};
// 5. Hybrid: Multiple + Multilevel
class Kitten : public Cat {}; // Cat→Kitten + Cat from Animal
int main() {
Dog d; d.eat(); d.bark();
Puppy p; p.eat(); p.bark(); p.weep();
Bat b; b.eat(); b.fly(); b.hang();
return 0;
}++, [], (), new/delete).Definition: Operator overloading allows C++ operators to be redefined for user-defined types. Nearly all operators can be overloaded.
Rules:
- At least one operand must be a user-defined type (class/struct).
- Cannot change the precedence, associativity, or arity of operators.
- Cannot create new operators.
::,.*,sizeof,?:,.cannot be overloaded.=,[],(),->must be overloaded as member functions.- Unary operators: prefer member overloading.
- Binary operators: prefer friend overloading (symmetry).
#include <iostream>
using namespace std;
class Matrix {
int mat[3][3];
public:
Matrix() { for(int i=0;i<3;i++) for(int j=0;j<3;j++) mat[i][j]=0; }
Matrix(int val) {
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
mat[i][j] = val + i*3 + j;
}
// Overload [] (MUST be member function)
int* operator[](int row) { return mat[row]; }
// Overload () as function call operator
Matrix operator()(int scalar) {
Matrix temp;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
temp.mat[i][j] = mat[i][j] * scalar;
return temp;
}
// Overload + (binary, friend for symmetry)
friend Matrix operator+(Matrix a, Matrix b) {
Matrix temp;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
temp.mat[i][j] = a.mat[i][j] + b.mat[i][j];
return temp;
}
// Overload == (binary, friend)
friend bool operator==(Matrix a, Matrix b) {
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
if(a.mat[i][j] != b.mat[i][j]) return false;
return true;
}
// Overload << for output (friend)
friend ostream& operator<<(ostream &out, Matrix m) {
for(int i=0;i<3;i++) {
for(int j=0;j<3;j++) out << m.mat[i][j] << "\t";
out << endl;
}
return out;
}
// Overload new/delete (static)
void* operator new(size_t size) {
cout << "Overloaded new called\n";
return malloc(size);
}
void operator delete(void* ptr) {
cout << "Overloaded delete called\n";
free(ptr);
}
};
int main() {
Matrix m1(1), m2(10);
Matrix m3 = m1 + m2;
cout << "Matrix 1:\n" << m1;
cout << "Matrix 2:\n" << m2;
cout << "Matrix 1 + 2:\n" << m3;
// [] overloading
cout << "m3[0][0] = " << m3[0][0] << endl;
// () overloading
Matrix m4 = m3(2); // Multiply by 2
cout << "Matrix 3 * 2:\n" << m4;
// new/delete overloading
Matrix *ptr = new Matrix();
cout << (*ptr == m1) << endl; // 1 (true)
delete ptr;
return 0;
}#include <iostream>
#include <cstring>
using namespace std;
class Employee {
int empID;
char name[50];
double salary;
public:
// 1. Default Constructor
Employee() {
empID = 0;
strcpy(name, "N/A");
salary = 0.0;
cout << "Default constructor called\n";
}
// 2. Parameterized Constructor (partial)
Employee(int id) {
empID = id;
strcpy(name, "New Employee");
salary = 15000.0;
cout << "Parameterized (1 arg) called for ID " << id << endl;
}
// 3. Parameterized Constructor (all args)
Employee(int id, const char *n, double s) {
empID = id;
strcpy(name, n);
salary = s;
cout << "Parameterized (3 args) called for " << n << endl;
}
// 4. Copy Constructor
Employee(const Employee &e) {
empID = e.empID;
strcpy(name, e.name);
salary = e.salary;
cout << "Copy constructor called for " << e.name << endl;
}
void display() {
cout << "ID: " << empID
<< ", Name: " << name
<< ", Salary: " << salary << endl;
}
~Employee() { cout << "Destructor called for " << name << endl; }
};
int main() {
cout << "=== Creating e1 (default) ===\n";
Employee e1; // Default constructor
e1.display();
cout << "\n=== Creating e2 (parameterized) ===\n";
Employee e2(101, "Rahul", 45000);
e2.display();
cout << "\n=== Creating e3 (copy) ===\n";
Employee e3 = e2; // Copy constructor
e3.display();
cout << "\n=== Creating e4 (partial params) ===\n";
Employee e4(102); // Parameterized (1 arg)
e4.display();
cout << "\n=== End of main ===\n";
return 0;
}Output:
Default constructor called
ID: 0, Name: N/A, Salary: 0
Parameterized (3 args) called for Rahul
ID: 101, Name: Rahul, Salary: 45000
Copy constructor called for Rahul
ID: 101, Name: Rahul, Salary: 45000
Parameterized (1 arg) called for ID 102
ID: 102, Name: New Employee, Salary: 15000
End of main
Destructor called for New Employee
Destructor called for Rahul
Destructor called for Rahul
Destructor called for N/A
+, [], () is a favorite 15-mark question. Constructor overloading with all types guaranteed. Inheritance ambiguity and virtual base class regularly tested.
Polymorphism & Virtual Functions
Compile-time Polymorphism · Runtime Polymorphism · vtable/vptr Concept · Pure Virtual Functions · Virtual Destructors · Early vs Late Binding · Interface Classes · Shape Hierarchy Example
virtual functions and vtable mechanism.virtual keyword. Ensures the correct destructor is called (derived then base) when deleting through a base pointer.| Compile-Time (Static) | Runtime (Dynamic) |
|---|---|
| Function Overloading | Virtual Functions |
| Operator Overloading | Abstract Classes |
| Resolved at compile time | Resolved at runtime |
| Fast execution | Slightly slower (vtable lookup) |
| No virtual keyword needed | Uses virtual keyword |
| Memory efficient | Requires vtable memory |
// Memory layout representation:
// vtable for Base:
// [0] → Base::show()
// [1] → Base::display()
// vtable for Derived:
// [0] → Derived::show() ← overridden
// [1] → Base::display()
// Memory for Derived object:
// ┌──────────────┐
// │ vptr │──→ points to Derived's vtable
// │ base data │
// │derived data │
// └──────────────┘
#include <iostream>
using namespace std;
class Base {
public:
virtual void show() { cout << "Base show\n"; }
void display() { cout << "Base display\n"; }
};
class Derived : public Base {
public:
void show() override { cout << "Derived show\n"; } // overrides
};
int main() {
Base *ptr;
Derived d;
ptr = &d;
ptr->show(); // "Derived show" (runtime via vtable)
ptr->display(); // "Base display" (not virtual)
return 0;
}#include <iostream>
using namespace std;
// Abstract class (cannot create objects)
class Shape {
public:
// Pure virtual function
virtual float area() = 0;
void show() { cout << "Shape class\n"; }
};
class Circle : public Shape {
float radius;
public:
Circle(float r) : radius(r) {}
float area() override { return 3.14159 * radius * radius; }
};
class Rectangle : public Shape {
float length, width;
public:
Rectangle(float l, float w) : length(l), width(w) {}
float area() override { return length * width; }
};
int main() {
// Shape s; // ERROR: abstract class
Shape *s1 = new Circle(5);
Shape *s2 = new Rectangle(4, 6);
cout << "Circle area: " << s1->area() << endl;
cout << "Rectangle area: " << s2->area() << endl;
delete s1; delete s2;
return 0;
}#include <iostream>
#include <cmath>
using namespace std;
// ==================== ABSTRACT BASE CLASS ====================
class Shape {
public:
// Pure virtual functions → makes Shape abstract
virtual float area() = 0;
virtual void display() = 0;
// Virtual destructor - essential for polymorphism
virtual ~Shape() {
cout << "Shape destructor called\n";
}
};
// ==================== CIRCLE CLASS ====================
class Circle : public Shape {
float radius;
public:
Circle(float r = 0) : radius(r) {}
// Override pure virtual functions
float area() override {
return 3.14159f * radius * radius;
}
void display() override {
cout << "Circle [r=" << radius
<< ", Area=" << area() << "]\n";
}
~Circle() { cout << "Circle destructor called\n"; }
};
// ==================== RECTANGLE CLASS ====================
class Rectangle : public Shape {
float length, width;
public:
Rectangle(float l = 0, float w = 0) : length(l), width(w) {}
float area() override { return length * width; }
void display() override {
cout << "Rectangle [l=" << length
<< ", w=" << width
<< ", Area=" << area() << "]\n";
}
~Rectangle() { cout << "Rectangle destructor called\n"; }
};
// ==================== TRIANGLE CLASS ====================
class Triangle : public Shape {
float base, height;
public:
Triangle(float b = 0, float h = 0) : base(b), height(h) {}
float area() override { return 0.5f * base * height; }
void display() override {
cout << "Triangle [b=" << base
<< ", h=" << height
<< ", Area=" << area() << "]\n";
}
~Triangle() { cout << "Triangle destructor called\n"; }
};
// ==================== MAIN ====================
int main() {
cout << "========== POLYMORPHISM DEMO ==========\n\n";
// Array of Shape pointers (polymorphic)
Shape *shapes[5];
shapes[0] = new Circle(5.0f);
shapes[1] = new Rectangle(4.0f, 6.0f);
shapes[2] = new Triangle(3.0f, 4.0f);
shapes[3] = new Circle(10.0f);
shapes[4] = new Rectangle(7.0f, 8.0f);
// Polymorphic calls - correct area() called at runtime
float totalArea = 0;
for (int i = 0; i < 5; i++) {
shapes[i]->display(); // Runtime dispatch
totalArea += shapes[i]->area(); // Runtime dispatch
}
cout << "\nTotal Area of all shapes: " << totalArea << endl;
// Cleanup - virtual destructor ensures correct order
cout << "\nDeleting shapes...\n";
for (int i = 0; i < 5; i++) {
delete shapes[i]; // Calls correct destructor via vtable
}
cout << "\n========== END ==========\n";
return 0;
}vtable Mechanism Explained:
Shape vtable: [0] ~Shape() [1] area=0 [2] display=0
Circle vtable: [0] ~Circle() [1] Circle::area() [2] Circle::display()
Rect vtable: [0] ~Rect() [1] Rect::area() [2] Rect::display()
Tri vtable: [0] ~Tri() [1] Tri::area() [2] Tri::display()
Each object layout:
Circle object: [vptr → Circle vtable] [radius]
Rect object: [vptr → Rect vtable] [length] [width]
When shapes[i]->area() is called, the vptr points to the correct vtable, which has the address of the correct area() function. This is late binding — resolved at runtime.
Advanced C++ Features
Templates · Exception Handling · STL · RTTI · File I/O · Command Line Arguments · new vs malloc
| Template | Macro |
|---|---|
| Type-safe (compiler checks) | Not type-safe (text replacement) |
| Processed by compiler | Processed by preprocessor |
| Supports overloading | No overloading |
| Debuggable | Hard to debug |
try, catch, and throw blocks, preventing program crashes.dynamic_cast, typeid, and type_info.new and malloc()?| new | malloc() |
|---|---|
| C++ operator | C library function |
| Calls constructor | Does NOT call constructor |
| Returns typed pointer | Returns void* |
| Use delete to free | Use free() to free |
| Size calculated automatically | Size must be specified manually |
[] and methods like push_back(), size(), pop_back().#include <iostream>
using namespace std;
// Function template with multiple type parameters
template <typename T1, typename T2>
T1 maxValue(T1 a, T2 b) {
return (a > b) ? a : (T1)b;
}
// Overloaded function template
template <typename T>
T maxValue(T a, T b, T c) {
return (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c);
}
int main() {
cout << maxValue(10, 20) << endl; // 20 (int)
cout << maxValue(3.5, 2.1) << endl; // 3.5 (double)
cout << maxValue(5, 10, 3) << endl; // 10 (3 params)
cout << maxValue('A', 65) << endl; // A (char vs int)
// Explicit type specification
cout << maxValue<int>(5.5, 3.2) << endl; // 5
return 0;
}#include <iostream>
#include <stdexcept>
using namespace std;
void check(int age) {
if (age < 0)
throw invalid_argument("Age cannot be negative");
if (age < 18)
throw runtime_error("Under 18 - not eligible");
if (age > 100)
throw out_of_range("Age seems unrealistic");
}
int main() {
int ages[] = {25, -5, 15, 105, 30};
for (int i = 0; i < 5; i++) {
try {
check(ages[i]);
cout << ages[i] << " - Eligible\n";
}
catch (const invalid_argument &e) {
cout << "Invalid: " << e.what() << endl;
}
catch (const runtime_error &e) {
cout << "Runtime: " << e.what() << endl;
}
catch (const out_of_range &e) {
cout << "Range: " << e.what() << endl;
}
catch (...) {
cout << "Unknown exception caught\n";
}
}
return 0;
}#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;
int main() {
// ========== VECTOR ==========
cout << "=== VECTOR ===\n";
vector<int> v;
v.push_back(10); // Add elements
v.push_back(20);
v.push_back(30);
v.push_back(40);
cout << "Size: " << v.size() << endl; // 4
cout << "Element at [2]: " << v[2] << endl; // 30
v.pop_back(); // Remove last
cout << "After pop: " << v.size() << endl; // 3
// Iterate using iterator
cout << "Vector: ";
for (auto it = v.begin(); it != v.end(); it++)
cout << *it << " "; // 10 20 30
cout << endl;
// Sort and binary search
v.push_back(15);
sort(v.begin(), v.end());
if (binary_search(v.begin(), v.end(), 20))
cout << "20 found!\n";
// ========== MAP ==========
cout << "\n=== MAP ===\n";
map<string, int> grades;
grades["Alice"] = 85;
grades["Bob"] = 92;
grades["Charlie"] = 78;
grades["Diana"] = 95;
// Find
map<string, int>::iterator it = grades.find("Bob");
if (it != grades.end())
cout << "Bob's grade: " << it->second << endl;
// Iterate (auto-sorted by key)
cout << "All grades:\n";
for (auto &p : grades)
cout << p.first << ": " << p.second << endl;
// Count elements
cout << "Students: " << grades.size() << endl;
return 0;
}#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
// Writing to file
ofstream fout("data.txt");
fout << "Name: Rahul\n";
fout << "Roll: 101\n";
fout << "Marks: 85\n";
fout.close();
// Reading from file
ifstream fin("data.txt");
string line;
while (getline(fin, line)) {
cout << line << endl;
}
fin.close();
// Append mode
ofstream fappend("data.txt", ios::app);
fappend << "Grade: A\n";
fappend.close();
// Read/write mode (fstream)
fstream file("data.txt", ios::in | ios::out);
file.seekp(0, ios::end);
file << "Department: CSE\n";
file.close();
return 0;
}File Modes:
ios::in (read), ios::out (write), ios::app (append), ios::ate (seek to end), ios::trunc (truncate), ios::binary (binary mode)
Templates enable generic programming — writing code that works with any data type. The compiler generates type-specific code at compile time.
Function Template:
#include <iostream>
using namespace std;
// Function Template with multiple parameters
template <typename T1, typename T2>
void displayPair(T1 first, T2 second) {
cout << first << " : " << second << endl;
}
// Template with default type
template <typename T = int>
T sum(T a, T b) { return a + b; }
// Overloaded function template
template <typename T>
T sum(T a, T b, T c) { return a + b + c; }
template <typename T>
T sum(T a, T b, T c, T d) { return a + b + c + d; }
// Non-template function (for specific type)
double sum(double a, double b) {
cout << "[double version] ";
return a + b;
}
int main() {
displayPair("Roll", 101); // string, int
displayPair(3.14, "pi"); // double, string
displayPair('A', 65); // char, int
cout << sum(10, 20) << endl; // 30 (int, 2 args)
cout << sum(1, 2, 3) << endl; // 6 (int, 3 args)
cout << sum(1, 2, 3, 4) << endl; // 10 (int, 4 args)
cout << sum(2.5, 3.5) << endl; // [double version] 6.0
return 0;
}Class Template:
#include <iostream>
using namespace std;
// Class Template
template <typename T>
class Calculator {
T a, b;
public:
Calculator(T x, T y) : a(x), b(y) {}
T add() { return a + b; }
T subtract() { return a - b; }
T multiply() { return a * b; }
T divide() { return a / b; }
};
// Template specialization for char*
template <>
class Calculator<char*> {
char *a, *b;
public:
Calculator(char *x, char *y) : a(x), b(y) {}
// String concatenation
void concat() {
cout << "Concatenated: ";
while(*a) cout << *a++;
while(*b) cout << *b++;
cout << endl;
}
};
// Template class with multiple type params
template <typename K, typename V>
class KeyValue {
K key;
V value;
public:
KeyValue(K k, V v) : key(k), value(v) {}
void show() {
cout << key << " = " << value << endl;
}
};
int main() {
// Generic class with int
Calculator<int> calcInt(10, 5);
cout << calcInt.add() << endl; // 15
cout << calcInt.multiply() << endl; // 50
// Generic class with double
Calculator<double> calcDbl(3.5, 2.0);
cout << calcDbl.divide() << endl; // 1.75
// Specialized version
char s1[] = "Hello", s2[] = " World";
Calculator<char*> calcStr(s1, s2);
calcStr.concat(); // Hello World
// Multiple type params
KeyValue<string, int> kv1("Age", 25);
KeyValue<string, double> kv2("Pi", 3.14);
kv1.show(); // Age = 25
kv2.show(); // Pi = 3.14
return 0;
}#include <iostream>
#include <stdexcept>
using namespace std;
// Function that divides two numbers
double divide(double a, double b) {
if (b == 0)
throw runtime_error("Division by zero!");
return a / b;
}
// Function that checks age
void validateAge(int age) {
if (age < 0)
throw invalid_argument("Age cannot be negative");
if (age < 18)
throw out_of_range("Must be 18 or older");
}
// Function with throw specification (deprecated in C++11)
void safeDivide(double a, double b) throw() {
// throw() means this function doesn't throw any exception
if (b == 0) return 0;
return a / b;
}
class BankAccount {
double balance;
public:
BankAccount(double b) : balance(b) {}
void withdraw(double amount) {
if (amount > balance)
throw overflow_error("Insufficient balance");
if (amount <= 0)
throw invalid_argument("Amount must be positive");
balance -= amount;
}
double getBalance() { return balance; }
};
int main() {
cout << "=== EXCEPTION HANDLING DEMO ===\n\n";
// Example 1: Division
try {
cout << divide(10, 2) << endl; // 5
cout << divide(10, 0) << endl; // throws
}
catch (const runtime_error &e) {
cout << "Runtime Error: " << e.what() << endl;
}
// Example 2: Age validation with multiple catch
try {
validateAge(-5); // throws invalid_argument
}
catch (const invalid_argument &e) {
cout << "Invalid Argument: " << e.what() << endl;
}
catch (const out_of_range &e) {
cout << "Out of Range: " << e.what() << endl;
}
// Example 3: BankAccount with exception
try {
BankAccount acc(1000);
cout << "Balance: " << acc.getBalance() << endl;
acc.withdraw(500);
cout << "After withdrawal: " << acc.getBalance() << endl;
acc.withdraw(600); // throws
}
catch (const overflow_error &e) {
cout << "Overflow Error: " << e.what() << endl;
}
catch (const invalid_argument &e) {
cout << "Invalid: " << e.what() << endl;
}
catch (...) {
cout << "Caught unknown exception\n";
}
cout << "\nProgram continues after handling exceptions!\n";
return 0;
}RTTI (Runtime Type Identification):
#include <iostream>
#include <typeinfo>
using namespace std;
class Base { virtual void f() {} };
class Derived : public Base {};
int main() {
Base *ptr = new Derived();
// typeid - returns type_info object
if (typeid(*ptr) == typeid(Derived))
cout << "ptr points to Derived\n";
cout << "Type name: " << typeid(*ptr).name() << endl;
// dynamic_cast - safe downcasting
Derived *d = dynamic_cast<Derived*>(ptr);
if (d != nullptr)
cout << "Cast successful\n";
delete ptr;
return 0;
}new vs malloc:
// malloc() - C style
int *p1 = (int*)malloc(sizeof(int) * 5); // Returns void*
*p1 = 10; // No constructor call
free(p1); // Must match with free()
// new - C++ style
int *p2 = new int[5]; // Returns typed pointer
*p2 = 10; // Constructor called automatically
delete[] p2; // Must match with delete[]
// new for objects
class MyClass { int x; public: MyClass() { x=0; } };
MyClass *obj = new MyClass(); // Constructor called
delete obj; // Destructor calledCommand Line Arguments:
#include <iostream>
using namespace std;
int main(int argc, char *argv[]) {
cout << "Program name: " << argv[0] << endl;
cout << "Arguments count: " << argc - 1 << endl;
for (int i = 1; i < argc; i++)
cout << "Arg " << i << ": " << argv[i] << endl;
// Usage: ./program hello 123 3.14
// argc = 4, argv[0] = "./program", argv[1] = "hello", etc.
return 0;
}Quick Reference: Comparison Tables
Essential comparison tables for last-minute revision
| Type | Base Classes | Derived Classes | Example |
|---|---|---|---|
| Single | 1 | 1 | A → B |
| Multiple | >1 | 1 | A,B → C |
| Multilevel | 1 | 1 (chain) | A → B → C |
| Hierarchical | 1 | >1 | A → B, A → C |
| Hybrid | Mixed | Mixed | A → B,C; B → D |
| Feature | Constructor | Destructor |
|---|---|---|
| Name | Same as class | ~ + class name |
| Return Type | None | None |
| Parameters | Can have | Cannot have |
| Overloading | Yes | No |
| When Called | Object creation | Object destruction |
| Purpose | Initialize | Cleanup |
| Memory | Stack or Heap | Always Stack (scope) |
| Feature | Function Template | Class Template |
|---|---|---|
| Declaration | template<typename T> | template<typename T> class |
| Instantiation | Automatic (argument deduction) | Explicit: Stack<int> |
| Overloading | Yes (with different params) | No |
| Specialization | Yes | Yes |
| Use Case | Generic functions | Generic data structures |
| Container | Type | Key Feature | Access |
|---|---|---|---|
| vector | Sequence | Dynamic array | Random [] |
| list | Sequence | Doubly linked list | Sequential |
| deque | Sequence | Double-ended queue | Random [] |
| stack | Container Adapter | LIFO | push/pop top |
| queue | Container Adapter | FIFO | push/pop front |
| map | Associative | Key-Value pairs, sorted | By key |
| set | Associative | Unique sorted elements | By value |
| multimap | Associative | Multiple values per key | By key |
| unordered_map | Hash | Key-Value, O(1) access | By key (hash) |