Object-Oriented Programming
in C++ Complete Notes
Classes ยท Objects ยท Inheritance ยท Polymorphism ยท Templates โ All in Easy English
๐ Table of Contents โ 15 Units
Introduction to Object-Oriented Programming
What is OOP and why is it important?
What is OOP?
Object-Oriented Programming (OOP) is a programming paradigm (style) based on the concept of "objects". An object contains both data (attributes) and functions (methods) that operate on that data. OOP allows us to model real-world entities in our code.
Procedural vs Object-Oriented Programming
| Feature | Procedural (C) | Object-Oriented (C++) |
|---|---|---|
| Focus | Functions and procedures | Objects and classes |
| Data & Functions | Separate | Bundled together in objects |
| Data Security | Data can be accessed anywhere | Data can be hidden (encapsulation) |
| Code Reusability | Limited | High (through inheritance) |
| Real-World Modeling | Difficult | Natural and easy |
| Examples | C, Pascal, FORTRAN | C++, Java, Python |
Four Pillars of OOP
Benefits of OOP
- Modularity โ Code is organized into separate, manageable pieces
- Reusability โ Existing classes can be reused through inheritance
- Maintainability โ Easier to update and fix code
- Security โ Data hiding protects sensitive information
- Flexibility โ Polymorphism allows flexibility in code design
- Real-World Modeling โ Represents real-world scenarios naturally
Basic OOP Terminology
| Term | Meaning |
|---|---|
| Class | Blueprint or template for creating objects |
| Object | Instance of a class (actual entity) |
| Attribute/Data Member | Variables inside a class (properties) |
| Method/Member Function | Functions inside a class (behaviors) |
| Instance | A specific object created from a class |
| Message Passing | Objects communicate by calling each other's methods |
Why C++ for OOP?
C++ was designed by Bjarne Stroustrup in 1979 as an extension of C with object-oriented features. It supports both procedural and OOP paradigms, making it a versatile language.
Classes and Objects in C++
Creating blueprints and instances โ the foundation of OOP.
What is a Class?
A class is a user-defined data type that serves as a blueprint for creating objects. It defines what attributes (data) and methods (functions) an object will have.
Think of a class like an architectural blueprint โ it defines the structure, but the actual house built from it is the object.
What is an Object?
An object is an instance of a class โ the actual entity created in memory based on the class definition. One class can have multiple objects, each with its own set of attribute values.
Defining a Class in C++
// Syntax of a class class ClassName { // Access specifier public: // Data members (attributes) int attribute1; string attribute2; // Member functions (methods) void method1() { cout << "Hello from method1" << endl; } };
Creating Objects
// Example: Student class #include <iostream> using namespace std; class Student { public: string name; int rollNo; float gpa; void display() { cout << "Name: " << name << endl; cout << "Roll No: " << rollNo << endl; cout << "GPA: " << gpa << endl; } }; int main() { // Creating objects Student s1, s2; // Accessing members using dot operator s1.name = "Ali"; s1.rollNo = 101; s1.gpa = 3.8; s1.display(); // Output: // Name: Ali // Roll No: 101 // GPA: 3.8 return 0; }
Class vs Object
| Class | Object |
|---|---|
| Blueprint or template | Instance of a class |
| Logical entity | Physical entity |
| Declared once | Can be created multiple times |
| No memory allocated | Memory is allocated |
| Example: Student class | Example: s1, s2 objects |
Member Functions โ Inside vs Outside Class
Member functions can be defined inside the class or outside using the scope resolution operator ::
class Rectangle { public: int length, width; // Defined inside class int area() { return length * width; } // Declaration only (definition outside) int perimeter(); }; // Definition outside class using :: int Rectangle::perimeter() { return 2 * (length + width); }
Accessing Class Members
We use the dot operator (.) to access members of an object and the arrow operator (->) for pointers to objects.
Student s1; s1.name = "Ahmed"; // dot operator for objects Student *ptr = &s1; ptr->rollNo = 102; // arrow operator for pointers
Array of Objects
Student students[3]; // Array of 3 Student objects students[0].name = "Sara"; students[0].rollNo = 201; students[1].name = "Hassan"; students[1].rollNo = 202; for(int i=0; i<3; i++) { students[i].display(); }
Car with attributes: brand, model, year, and a method displayInfo(). Create 2 objects and display their information.Access Specifiers (Access Modifiers)
Controlling visibility โ public, private, and protected.
What are Access Specifiers?
Access specifiers (also called access modifiers) control who can access the members of a class. They enforce data hiding and encapsulation, which are key OOP principles.
Three Types of Access Specifiers
| Specifier | Accessible From | Use Case |
|---|---|---|
| public | Anywhere (inside class, outside class, derived classes) | Interface of the class โ methods users can call |
| private | Only inside the class (not in derived classes) | Internal implementation โ data that should be hidden |
| protected | Inside class + derived classes | Data that child classes need to access |
Public Access Specifier
Members declared as public can be accessed from anywhere in the program.
class Box { public: int length; // public data member void display() { // public member function cout << "Length: " << length; } }; int main() { Box b; b.length = 10; // โ Accessible โ it's public b.display(); // โ Accessible }
Private Access Specifier
Members declared as private can only be accessed from within the class. This is the default access level in C++ classes.
class BankAccount { private: double balance; // private โ hidden from outside public: void deposit(double amount) { balance += amount; // โ Can access private member inside class } double getBalance() { return balance; } }; int main() { BankAccount acc; // acc.balance = 5000; โ ERROR โ balance is private acc.deposit(5000); // โ Access through public method cout << acc.getBalance(); // โ Get value using public method }
Protected Access Specifier
Members declared as protected are like private members, but they can also be accessed by derived (child) classes.
class Parent { protected: int protectedData; // protected member }; class Child : public Parent { public: void display() { protectedData = 100; // โ Can access protected member of parent cout << protectedData; } }; int main() { Child c; // c.protectedData = 50; โ ERROR โ not accessible outside classes c.display(); // โ Works through public method }
Access Specifiers Summary Table
| Access From | public | private | protected |
|---|---|---|---|
| Same Class | โ Yes | โ Yes | โ Yes |
| Derived Class | โ Yes | โ No | โ Yes |
| Outside Class | โ Yes | โ No | โ No |
Default Access Specifier
If you don't specify an access modifier, the default is private for classes and public for structs.
Employee with private attributes (id, salary), protected attribute (department), and public methods to set and get these values. Test it in main().Constructors and Destructors
Special functions โ automatic initialization and cleanup.
What is a Constructor?
A constructor is a special member function that is automatically called when an object is created. It has the same name as the class and no return type (not even void). Constructors are used to initialize object attributes.
Types of Constructors
| Type | Description |
|---|---|
| Default Constructor | No parameters โ initializes with default values |
| Parameterized Constructor | Takes parameters to initialize with specific values |
| Copy Constructor | Creates a new object as a copy of an existing object |
Default Constructor
class Student { private: string name; int age; public: // Default constructor Student() { name = "Unknown"; age = 0; cout << "Default constructor called\n"; } void display() { cout << "Name: " << name << ", Age: " << age << endl; } }; int main() { Student s1; // Constructor automatically called s1.display(); // Output: // Default constructor called // Name: Unknown, Age: 0 }
Parameterized Constructor
class Rectangle { private: int length, width; public: // Parameterized constructor Rectangle(int l, int w) { length = l; width = w; } int area() { return length * width; } }; int main() { Rectangle r1(10, 5); // Values passed to constructor cout << "Area: " << r1.area(); // Output: Area: 50 }
Constructor Overloading
A class can have multiple constructors with different parameters. This is called constructor overloading.
class Box { private: int length, width, height; public: // Default constructor Box() { length = width = height = 1; } // Constructor with one parameter Box(int side) { length = width = height = side; } // Constructor with three parameters Box(int l, int w, int h) { length = l; width = w; height = h; } }; int main() { Box b1; // Calls default constructor Box b2(5); // Calls single parameter constructor Box b3(2, 3, 4); // Calls three parameter constructor }
Copy Constructor
A copy constructor creates a new object as a copy of an existing object. If not defined, C++ provides a default copy constructor.
class Student { private: int rollNo; string name; public: Student(int r, string n) { rollNo = r; name = n; } // Copy constructor Student(Student &s) { rollNo = s.rollNo; name = s.name; cout << "Copy constructor called\n"; } void display() { cout << rollNo << " - " << name << endl; } }; int main() { Student s1(101, "Ali"); Student s2 = s1; // Copy constructor called s2.display(); // Output: // Copy constructor called // 101 - Ali }
What is a Destructor?
A destructor is a special member function that is automatically called when an object is destroyed (goes out of scope). It has the same name as the class with a tilde (~) prefix and no parameters or return type. Destructors are used to free resources.
class Demo { public: // Constructor Demo() { cout << "Constructor called\n"; } // Destructor ~Demo() { cout << "Destructor called\n"; } }; int main() { Demo d; // Constructor called // ... code ... // Destructor automatically called when d goes out of scope return 0; }
Constructor vs Destructor
| Feature | Constructor | Destructor |
|---|---|---|
| Name | Same as class name | Same as class name with ~ prefix |
| Called When | Object is created | Object is destroyed |
| Parameters | Can have parameters | No parameters |
| Overloading | Yes โ multiple constructors | No โ only one destructor |
| Purpose | Initialize object | Clean up resources (free memory) |
Book with attributes title, author, price. Write: (1) Default constructor (2) Parameterized constructor (3) Copy constructor (4) Destructor that displays "Book destroyed".Encapsulation โ Data Hiding
Bundling data and methods, protecting internal details.
What is Encapsulation?
Encapsulation is the concept of bundling data (attributes) and methods (functions) that operate on that data into a single unit (class), and restricting direct access to some of the object's components. This is achieved using access specifiers.
Why Encapsulation?
- Data Hiding โ Sensitive data is protected from unauthorized access
- Control โ You control how data is accessed and modified
- Validation โ You can add validation logic in setter methods
- Flexibility โ Internal implementation can change without affecting users
- Maintainability โ Code is easier to maintain and debug
Implementing Encapsulation โ Getters and Setters
We make data members private and provide public getter and setter methods to access and modify them.
class Student { private: // Private data โ hidden from outside int rollNo; string name; float gpa; public: // Setter methods โ to set values with validation void setRollNo(int r) { if(r > 0) { rollNo = r; } else { cout << "Invalid roll number!\n"; } } void setName(string n) { name = n; } void setGPA(float g) { if(g >= 0.0 && g <= 4.0) { gpa = g; } else { cout << "Invalid GPA!\n"; } } // Getter methods โ to get values int getRollNo() { return rollNo; } string getName() { return name; } float getGPA() { return gpa; } }; int main() { Student s; // Cannot access private data directly // s.rollNo = 101; โ ERROR // Must use public setter methods s.setRollNo(101); s.setName("Ahmed"); s.setGPA(3.75); // Access using getter methods cout << "Roll No: " << s.getRollNo() << endl; cout << "Name: " << s.getName() << endl; cout << "GPA: " << s.getGPA() << endl; // Validation in action s.setGPA(5.0); // Output: Invalid GPA! }
Real-World Example โ Bank Account
class BankAccount { private: string accountNo; double balance; public: BankAccount(string acc, double bal) { accountNo = acc; balance = bal; } // Controlled deposit void deposit(double amount) { if(amount > 0) { balance += amount; cout << "Deposited: " << amount << endl; } } // Controlled withdrawal void withdraw(double amount) { if(amount > 0 && amount <= balance) { balance -= amount; cout << "Withdrawn: " << amount << endl; } else { cout << "Insufficient balance or invalid amount!\n"; } } // Read-only access to balance double getBalance() { return balance; } }; int main() { BankAccount acc("ACC001", 10000); acc.deposit(5000); cout << "Balance: " << acc.getBalance() << endl; acc.withdraw(3000); cout << "Balance: " << acc.getBalance() << endl; acc.withdraw(20000); // Will fail โ insufficient balance }
Benefits of Encapsulation
| Benefit | Explanation |
|---|---|
| Security | Prevents unauthorized or accidental modification of data |
| Validation | Allows you to add checks before setting values |
| Flexibility | Internal implementation can be changed without breaking code |
| Read-Only/Write-Only | Can provide only getters (read-only) or only setters (write-only) |
| Better Debugging | Easier to track where data is being modified |
Temperature with a private attribute celsius. Provide setter with validation (must be above -273.15) and getters for both Celsius and Fahrenheit values.Inheritance โ Code Reusability
Creating new classes from existing ones โ parent and child relationships.
What is Inheritance?
Inheritance is the mechanism by which one class (child/derived class) acquires the properties and behaviors of another class (parent/base class). It promotes code reusability and establishes a relationship between classes.
Why Use Inheritance?
- Code Reusability โ Reuse existing code without rewriting it
- Extensibility โ Add new features to existing classes
- Hierarchical Classification โ Models real-world relationships (IS-A relationship)
- Polymorphism โ Enables runtime polymorphism through virtual functions
- Maintainability โ Changes in base class automatically reflect in derived classes
Inheritance Syntax
// Base class (Parent class) class BaseClass { // members }; // Derived class (Child class) class DerivedClass : access_specifier BaseClass { // new members + inherited members };
Simple Inheritance Example
#include <iostream> using namespace std; // Base class class Animal { public: void eat() { cout << "Animal is eating\n"; } void sleep() { cout << "Animal is sleeping\n"; } }; // Derived class class Dog : public Animal { public: void bark() { cout << "Dog is barking\n"; } }; int main() { Dog d; // Calling inherited methods d.eat(); // From Animal class d.sleep(); // From Animal class // Calling own method d.bark(); // From Dog class // Output: // Animal is eating // Animal is sleeping // Dog is barking return 0; }
Modes of Inheritance
The access specifier used during inheritance determines how base class members are inherited in the derived class.
| Base Class Member | Public Inheritance | Protected Inheritance | Private Inheritance |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | Not inherited | Not inherited | Not inherited |
Public Inheritance (Most Common)
class Base { public: int publicVar; protected: int protectedVar; private: int privateVar; }; class Derived : public Base { // publicVar remains public // protectedVar remains protected // privateVar is NOT inherited };
Constructor and Destructor in Inheritance
When an object of derived class is created, the base class constructor is called first, then the derived class constructor. Destructors are called in reverse order.
class Base { public: Base() { cout << "Base constructor\n"; } ~Base() { cout << "Base destructor\n"; } }; class Derived : public Base { public: Derived() { cout << "Derived constructor\n"; } ~Derived() { cout << "Derived destructor\n"; } }; int main() { Derived d; // Output: // Base constructor // Derived constructor // Derived destructor // Base destructor }
Passing Parameters to Base Class Constructor
class Person { protected: string name; int age; public: Person(string n, int a) { name = n; age = a; } }; class Student : public Person { private: int rollNo; public: // Pass parameters to base class constructor Student(string n, int a, int r) : Person(n, a) { rollNo = r; } void display() { cout << "Name: " << name << endl; cout << "Age: " << age << endl; cout << "Roll No: " << rollNo << endl; } }; int main() { Student s("Ali", 20, 101); s.display(); }
Vehicle with attributes (brand, model) and a derived class Car with additional attribute (numberOfDoors). Demonstrate inheritance with proper constructors.Types of Inheritance
Single, Multiple, Multilevel, Hierarchical, and Hybrid inheritance.
Five Types of Inheritance in C++
C++ supports various types of inheritance, each with different class relationships.
1. Single Inheritance
One derived class inherits from one base class.
// Diagram: A โ B class A { public: void display() { cout << "Class A\n"; } }; class B : public A { public: void show() { cout << "Class B\n"; } }; int main() { B obj; obj.display(); // From class A obj.show(); // From class B }
2. Multiple Inheritance
One derived class inherits from multiple base classes.
// Diagram: A, B โ C class Father { public: void showFather() { cout << "I am the Father\n"; } }; class Mother { public: void showMother() { cout << "I am the Mother\n"; } }; // Child inherits from both Father and Mother class Child : public Father, public Mother { public: void showChild() { cout << "I am the Child\n"; } }; int main() { Child c; c.showFather(); // From Father c.showMother(); // From Mother c.showChild(); // From Child }
3. Multilevel Inheritance
A class is derived from a class which is also derived from another class โ forming a chain.
// Diagram: A โ B โ C class Grandfather { public: void show1() { cout << "Grandfather\n"; } }; class Father : public Grandfather { public: void show2() { cout << "Father\n"; } }; class Son : public Father { public: void show3() { cout << "Son\n"; } }; int main() { Son s; s.show1(); // From Grandfather s.show2(); // From Father s.show3(); // From Son }
4. Hierarchical Inheritance
Multiple derived classes inherit from a single base class.
// Diagram: A โ B, C, D class Animal { public: void eat() { cout << "Eating...\n"; } }; class Dog : public Animal { public: void bark() { cout << "Barking...\n"; } }; class Cat : public Animal { public: void meow() { cout << "Meowing...\n"; } }; class Bird : public Animal { public: void fly() { cout << "Flying...\n"; } }; int main() { Dog d; d.eat(); // All three classes can use eat() d.bark(); Cat c; c.eat(); c.meow(); }
5. Hybrid Inheritance
A combination of two or more types of inheritance. It can cause the Diamond Problem.
// Example: Combination of hierarchical and multiple inheritance class A { public: void displayA() { cout << "Class A\n"; } }; class B : public A { public: void displayB() { cout << "Class B\n"; } }; class C : public A { public: void displayC() { cout << "Class C\n"; } }; // D inherits from both B and C (which both inherit from A) class D : public B, public C { public: void displayD() { cout << "Class D\n"; } };
The Diamond Problem
In hybrid inheritance, if two parent classes inherit from the same grandparent class, the child class gets two copies of the grandparent's members, causing ambiguity.
Solution: Use virtual inheritance to ensure only one copy of the base class is inherited.
class A { public: int x; }; // Virtual inheritance class B : virtual public A { }; class C : virtual public A { }; class D : public B, public C { public: void display() { x = 10; // Now there's only one copy of x โ no ambiguity cout << x; } };
Inheritance Types Summary
| Type | Structure | Example |
|---|---|---|
| Single | A โ B | Animal โ Dog |
| Multiple | A, B โ C | Father, Mother โ Child |
| Multilevel | A โ B โ C | Grandfather โ Father โ Son |
| Hierarchical | A โ B, C, D | Animal โ Dog, Cat, Bird |
| Hybrid | Combination | Mix of above types |
Polymorphism โ One Interface, Multiple Forms
Compile-time and runtime polymorphism explained.
What is Polymorphism?
Polymorphism means "many forms". It allows one interface (function or operator) to be used for different types or purposes. The same function name can behave differently based on context.
Types of Polymorphism in C++
1. Compile-Time Polymorphism (Static Binding)
The function call is resolved at compile time. Also called early binding or static binding.
Types of Compile-Time Polymorphism
| Type | Description | Example |
|---|---|---|
| Function Overloading | Multiple functions with same name but different parameters | add(int, int) and add(float, float) |
| Operator Overloading | Giving special meaning to operators for user-defined types | Using + to add two objects |
2. Runtime Polymorphism (Dynamic Binding)
The function call is resolved at runtime. Also called late binding or dynamic binding. Achieved through:
- Virtual Functions โ Functions in base class marked with
virtualkeyword - Function Overriding โ Redefining base class function in derived class
- Pointer/Reference to Base Class โ Base class pointer pointing to derived class object
Simple Runtime Polymorphism Example
class Shape { public: // Virtual function virtual void draw() { cout << "Drawing Shape\n"; } }; class Circle : public Shape { public: // Override base class function void draw() { cout << "Drawing Circle\n"; } }; class Rectangle : public Shape { public: void draw() { cout << "Drawing Rectangle\n"; } }; int main() { Shape *ptr; // Base class pointer Circle c; Rectangle r; ptr = &c; // Pointing to Circle object ptr->draw(); // Output: Drawing Circle ptr = &r; // Pointing to Rectangle object ptr->draw(); // Output: Drawing Rectangle // Same pointer, different behaviors โ this is polymorphism! return 0; }
Compile-Time vs Runtime Polymorphism
| Feature | Compile-Time | Runtime |
|---|---|---|
| Binding Time | Compile time (early binding) | Runtime (late binding) |
| Achieved By | Function/operator overloading | Virtual functions, inheritance |
| Performance | Faster โ resolved at compile time | Slightly slower โ resolved at runtime |
| Flexibility | Less flexible | More flexible |
| Example | add(int, int) and add(float, float) | Base pointer calling derived function |
Real-World Polymorphism Example
class Employee { protected: string name; int id; public: Employee(string n, int i) : name(n), id(i) {} virtual void calculateSalary() { cout << "Employee salary calculation\n"; } }; class Manager : public Employee { public: Manager(string n, int i) : Employee(n, i) {} void calculateSalary() { cout << "Manager: Base + Bonus + Benefits\n"; } }; class Developer : public Employee { public: Developer(string n, int i) : Employee(n, i) {} void calculateSalary() { cout << "Developer: Base + Project Bonus\n"; } }; int main() { Employee *emp; Manager m("Ali", 101); Developer d("Sara", 102); emp = &m; emp->calculateSalary(); // Manager's version emp = &d; emp->calculateSalary(); // Developer's version }
Vehicle with virtual function speed() and derived classes Car and Bike that override it.Function Overloading
Same function name, different parameters โ compile-time polymorphism.
What is Function Overloading?
Function overloading allows multiple functions with the same name but different parameters (number, type, or order) to exist in the same scope. The compiler determines which function to call based on the arguments passed.
Ways to Overload Functions
| Method | Description | Example |
|---|---|---|
| Number of Parameters | Different number of arguments | add(int, int) and add(int, int, int) |
| Type of Parameters | Different data types | add(int, int) and add(float, float) |
| Order of Parameters | Different sequence | display(int, float) and display(float, int) |
Example 1: Different Number of Parameters
#include <iostream> using namespace std; // Function with 2 parameters int add(int a, int b) { return a + b; } // Function with 3 parameters int add(int a, int b, int c) { return a + b + c; } int main() { cout << add(5, 10) << endl; // Calls first function โ 15 cout << add(5, 10, 15) << endl; // Calls second function โ 30 return 0; }
Example 2: Different Type of Parameters
class Calculator { public: // Function with int parameters int multiply(int a, int b) { cout << "Integer multiply: "; return a * b; } // Function with float parameters float multiply(float a, float b) { cout << "Float multiply: "; return a * b; } // Function with double parameters double multiply(double a, double b) { cout << "Double multiply: "; return a * b; } }; int main() { Calculator calc; cout << calc.multiply(5, 3) << endl; // Calls int version cout << calc.multiply(2.5f, 3.2f) << endl; // Calls float version cout << calc.multiply(2.5, 3.2) << endl; // Calls double version }
Example 3: Different Order of Parameters
void display(int x, float y) { cout << "Int: " << x << ", Float: " << y << endl; } void display(float x, int y) { cout << "Float: " << x << ", Int: " << y << endl; } int main() { display(10, 3.5f); // Calls first version display(3.5f, 10); // Calls second version }
Rules for Function Overloading
- Same function name โ All overloaded functions must have the same name
- Different parameters โ Must differ in number, type, or order of parameters
- Return type alone is NOT enough โ Functions can't differ only by return type
- Must be in same scope โ All overloaded functions must be in the same class or namespace
Invalid Function Overloading
// โ ERROR: Different return type only โ NOT ALLOWED int getValue() { return 10; } float getValue() { // ERROR โ can't overload based only on return type return 3.5; }
Practical Example โ Area Calculation
class Area { public: // Area of square float calculate(float side) { return side * side; } // Area of rectangle float calculate(float length, float width) { return length * width; } // Area of circle float calculate(float radius, bool isCircle) { return 3.14159 * radius * radius; } }; int main() { Area a; cout << "Square: " << a.calculate(5.0) << endl; cout << "Rectangle: " << a.calculate(4.0, 6.0) << endl; cout << "Circle: " << a.calculate(3.0, true) << endl; }
Benefits of Function Overloading
| Benefit | Explanation |
|---|---|
| Code Clarity | Same name for similar operations makes code easier to read |
| Flexibility | Handle different data types with the same function name |
| Consistency | Logical naming โ all "add" functions actually add things |
| Less Memory | No need to remember multiple function names |
Print with overloaded function show() that can display: (1) an integer (2) a float (3) a string (4) two integers. Test all versions in main().Operator Overloading
Giving special meaning to operators for user-defined types.
What is Operator Overloading?
Operator overloading allows you to redefine how operators (+, -, *, ==, etc.) work with user-defined types (classes). It makes your code more intuitive and natural.
Syntax of Operator Overloading
return_type operator symbol (parameters) { // code }
Example โ Overloading + Operator
class Complex { private: int real, imag; public: Complex(int r = 0, int i = 0) { real = r; imag = i; } // Overload + operator Complex operator + (Complex &obj) { Complex temp; temp.real = real + obj.real; temp.imag = imag + obj.imag; return temp; } void display() { cout << real << " + " << imag << "i" << endl; } }; int main() { Complex c1(3, 4), c2(1, 2); Complex c3 = c1 + c2; // Using overloaded + operator c3.display(); // Output: 4 + 6i return 0; }
Operators That Can Be Overloaded
| Category | Operators |
|---|---|
| Arithmetic | +, -, *, /, %, ++, -- |
| Relational | ==, !=, <, >, <=, >= |
| Logical | &&, ||, ! |
| Bitwise | &, |, ^, ~, <<, >> |
| Assignment | =, +=, -=, *=, /= |
| Other | [], (), ->, <<, >>, new, delete |
Operators That CANNOT Be Overloaded
::โ Scope resolution operator.โ Member access operator.*โ Pointer to member operator?:โ Ternary operatorsizeofโ Size operator
Overloading ++ Operator (Unary)
class Counter { private: int count; public: Counter() : count(0) {} // Overload prefix ++ (++obj) void operator ++ () { ++count; } // Overload postfix ++ (obj++) void operator ++ (int) { count++; } void display() { cout << "Count: " << count << endl; } }; int main() { Counter c; ++c; // Prefix increment c++; // Postfix increment c.display(); // Output: Count: 2 }
Overloading == Operator
class Point { private: int x, y; public: Point(int a = 0, int b = 0) : x(a), y(b) {} // Overload == operator bool operator == (Point &p) { return (x == p.x && y == p.y); } }; int main() { Point p1(5, 10), p2(5, 10), p3(3, 7); if(p1 == p2) cout << "p1 and p2 are equal\n"; else cout << "p1 and p2 are not equal\n"; if(p1 == p3) cout << "p1 and p3 are equal\n"; else cout << "p1 and p3 are not equal\n"; }
Overloading << Operator (Stream Insertion)
To use cout << obj syntax, we overload the << operator as a friend function.
class Student { private: string name; int rollNo; public: Student(string n, int r) : name(n), rollNo(r) {} // Friend function for << operator friend ostream& operator << (ostream &out, Student &s); }; // Definition of << operator ostream& operator << (ostream &out, Student &s) { out << "Name: " << s.name << ", Roll No: " << s.rollNo; return out; } int main() { Student s("Ali", 101); cout << s << endl; // Output: Name: Ali, Roll No: 101 }
Rules for Operator Overloading
- Only existing operators can be overloaded โ you can't create new operators
- At least one operand must be a user-defined type
- Operator's original precedence and associativity cannot be changed
- Cannot overload operators for built-in types (can't change how int + int works)
- Some operators must be overloaded as member functions (=, [], (), ->)
Distance with feet and inches. Overload: (1) + operator to add two distances (2) == operator to compare distances (3) << operator to display distance.Virtual Functions and Abstract Classes
Runtime polymorphism and pure virtual functions explained.
What is a Virtual Function?
A virtual function is a member function in the base class that you expect to be redefined in derived classes. It is declared using the virtual keyword. Virtual functions enable runtime polymorphism.
Why Virtual Functions?
Without virtual, a base class pointer calls the base class version of the function, even if it points to a derived class object. With virtual, the correct derived class function is called.
Example โ Without Virtual Function
class Base { public: void display() { cout << "Base class\n"; } }; class Derived : public Base { public: void display() { cout << "Derived class\n"; } }; int main() { Base *ptr; Derived d; ptr = &d; ptr->display(); // Output: Base class (NOT what we want!) }
Example โ With Virtual Function
class Base { public: virtual void display() { // virtual keyword added cout << "Base class\n"; } }; class Derived : public Base { public: void display() { cout << "Derived class\n"; } }; int main() { Base *ptr; Derived d; ptr = &d; ptr->display(); // Output: Derived class โ Correct! }
Pure Virtual Function
A pure virtual function is a virtual function with no implementation in the base class. It is declared by assigning = 0. Any class containing a pure virtual function becomes an abstract class.
class Shape { public: // Pure virtual function virtual void draw() = 0; };
Abstract Class
An abstract class is a class that contains at least one pure virtual function. You cannot create objects of an abstract class โ it is meant to be inherited.
class Animal { // Abstract class public: virtual void sound() = 0; // Pure virtual function void eat() { cout << "Eating...\n"; } }; class Dog : public Animal { public: void sound() { // Must implement pure virtual function cout << "Bark bark!\n"; } }; class Cat : public Animal { public: void sound() { cout << "Meow meow!\n"; } }; int main() { // Animal a; โ ERROR โ Cannot create object of abstract class Animal *ptr; Dog d; Cat c; ptr = &d; ptr->sound(); // Output: Bark bark! ptr = &c; ptr->sound(); // Output: Meow meow! }
Virtual Destructor
If a base class pointer points to a derived class object, and you delete the pointer, only the base class destructor is called unless the destructor is virtual.
class Base { public: virtual ~Base() { // Virtual destructor cout << "Base destructor\n"; } }; class Derived : public Base { public: ~Derived() { cout << "Derived destructor\n"; } }; int main() { Base *ptr = new Derived(); delete ptr; // Both destructors called due to virtual // Output: // Derived destructor // Base destructor }
Virtual vs Pure Virtual Function
| Feature | Virtual Function | Pure Virtual Function |
|---|---|---|
| Declaration | virtual void func() { } | virtual void func() = 0; |
| Implementation | Has implementation in base class | No implementation in base class |
| Class Type | Class remains concrete | Class becomes abstract |
| Object Creation | Can create objects of the class | Cannot create objects |
| Overriding | Optional in derived class | Must be overridden in derived class |
Vehicle with pure virtual function speed(). Create derived classes Car and Bike that implement speed(). Use base class pointer to demonstrate runtime polymorphism.Friend Functions and Friend Classes
Breaking encapsulation when needed โ controlled access to private members.
What is a Friend Function?
A friend function is a function that is not a member of a class but has access to its private and protected members. It is declared using the friend keyword inside the class.
Friend Function Syntax
class MyClass { private: int data; public: // Declare friend function friend void showData(MyClass obj); }; // Define friend function (outside class, no ::) void showData(MyClass obj) { cout << obj.data; // Can access private member }
Simple Friend Function Example
class Box { private: int length; public: Box(int l) : length(l) {} // Declare friend function friend void printLength(Box b); }; // Friend function definition void printLength(Box b) { cout << "Length: " << b.length << endl; // Accessing private member } int main() { Box b(10); printLength(b); // Output: Length: 10 }
Friend Function with Two Classes
A friend function can access private members of multiple classes.
class ClassB; // Forward declaration class ClassA { private: int numA; public: ClassA(int n) : numA(n) {} friend void compare(ClassA, ClassB); }; class ClassB { private: int numB; public: ClassB(int n) : numB(n) {} friend void compare(ClassA, ClassB); }; // Friend function accessing both classes void compare(ClassA a, ClassB b) { if(a.numA > b.numB) cout << "ClassA is greater\n"; else cout << "ClassB is greater\n"; } int main() { ClassA objA(10); ClassB objB(20); compare(objA, objB); // Output: ClassB is greater }
What is a Friend Class?
A friend class is a class whose all member functions are friend functions of another class. It can access all private and protected members of the class that declared it as a friend.
class ClassB { private: int secret; public: ClassB(int s) : secret(s) {} // Declare ClassA as friend friend class ClassA; }; class ClassA { public: void showSecret(ClassB &obj) { // Can access private member of ClassB cout << "Secret: " << obj.secret << endl; } }; int main() { ClassB b(123); ClassA a; a.showSecret(b); // Output: Secret: 123 }
Properties of Friend Functions
- Not a member โ Friend function is not a member of the class
- Can't access directly โ Needs an object to access members (can't use
this) - Not inherited โ Friendship is not inherited by derived classes
- Not transitive โ If A is friend of B, and B is friend of C, A is NOT automatically friend of C
- One-way โ Friendship is granted by the class, not taken
When to Use Friend Functions?
| Use Case | Reason |
|---|---|
| Operator Overloading | Operators like <<, >> need to access private members but can't be members |
| Two Classes Need Each Other's Data | When two classes need to share private data |
| Performance | Direct access to private members can be faster than getters/setters |
| Bridge Between Classes | When you need a function that works with multiple classes |
Friend Function vs Member Function
| Feature | Member Function | Friend Function |
|---|---|---|
| Defined | Inside the class (or using ::) | Outside the class |
| Access | Can access private members | Can access private members |
| Calling | Called using object (obj.func()) | Called like normal function (func(obj)) |
| This Pointer | Has access to this pointer | No access to this |
| Inheritance | Inherited by derived classes | Not inherited |
Student and Teacher. Write a friend function checkEligibility() that accesses private marks from Student and private experience from Teacher to determine if a student can be mentored.Templates โ Generic Programming
Writing code that works with any data type โ function and class templates.
What are Templates?
Templates allow you to write generic code that works with any data type. Instead of writing separate functions for int, float, string, etc., you write one template that works for all types.
Why Use Templates?
- Code Reusability โ Write once, use with any data type
- Type Safety โ Compiler checks types at compile time
- Performance โ No runtime overhead (unlike polymorphism)
- Generic Programming โ Write algorithms independent of data types
- STL Foundation โ Standard Template Library (vector, list, map) uses templates
1. Function Templates
A function template defines a family of functions that can work with different data types.
Function Template Syntax
template <typename T> // or template<class T> T functionName(T parameter) { // code using T }
Simple Function Template Example
#include <iostream> using namespace std; // Function template template <typename T> T getMax(T a, T b) { return (a > b) ? a : b; } int main() { cout << getMax(10, 20) << endl; // Works with int โ 20 cout << getMax(5.5, 2.3) << endl; // Works with double โ 5.5 cout << getMax('a', 'z') << endl; // Works with char โ z return 0; }
Function Template with Multiple Parameters
template <typename T1, typename T2> void display(T1 a, T2 b) { cout << "First: " << a << ", Second: " << b << endl; } int main() { display(10, 3.14); // int and double display("Hello", 100); // string and int display('A', "Grade"); // char and string }
2. Class Templates
A class template defines a generic class that can work with different data types.
Class Template Syntax
template <typename T> class ClassName { private: T data; public: // methods using T };
Simple Class Template Example
template <typename T> class Calculator { private: T num1, num2; public: Calculator(T a, T b) { num1 = a; num2 = b; } T add() { return num1 + num2; } T subtract() { return num1 - num2; } }; int main() { // Integer calculator Calculator<int> intCalc(10, 5); cout << "Int Add: " << intCalc.add() << endl; // Float calculator Calculator<float> floatCalc(5.5, 2.3); cout << "Float Add: " << floatCalc.add() << endl; }
Class Template with Multiple Type Parameters
template <typename T1, typename T2> class Pair { private: T1 first; T2 second; public: Pair(T1 f, T2 s) : first(f), second(s) {} void display() { cout << "First: " << first << ", Second: " << second << endl; } }; int main() { Pair<int, string> p1(1, "Ali"); p1.display(); // First: 1, Second: Ali Pair<char, double> p2('A', 3.14); p2.display(); // First: A, Second: 3.14 }
Class Template with Default Type
template <typename T = int> // Default type is int class Array { private: T arr[5]; public: void insert(int index, T value) { arr[index] = value; } T get(int index) { return arr[index]; } }; int main() { Array<> intArr; // Uses default type (int) Array<float> floatArr; // Explicitly uses float }
Template Specialization
Template specialization allows you to define a different implementation for a specific data type.
// General template template <typename T> class Storage { public: void print(T value) { cout << "General: " << value << endl; } }; // Specialized template for char* template <> class Storage<char*> { public: void print(char* value) { cout << "String: " << value << endl; } }; int main() { Storage<int> intStore; intStore.print(10); // General: 10 Storage<char*> stringStore; stringStore.print("Hello"); // String: Hello }
Templates vs Macros
| Feature | Templates | Macros |
|---|---|---|
| Type Safety | โ Type-checked by compiler | โ No type checking |
| Debugging | Easy to debug | Difficult to debug |
| Code Generation | Generates separate code for each type | Simple text replacement |
| Scope | Follows C++ scope rules | Global replacement |
swap() that swaps two values of any type. Create a class template Stack that can store any data type with push(), pop(), and display() methods.File Handling and Exception Handling in C++
Reading/writing files and handling errors gracefully using OOP approach.
File Handling in C++ (OOP Approach)
C++ provides three classes for file handling: ifstream (input), ofstream (output), and fstream (both). These are part of the <fstream> header.
File Handling Classes
| Class | Purpose | Operations |
|---|---|---|
| ofstream | Write to files (output) | Creating and writing to files |
| ifstream | Read from files (input) | Reading from files |
| fstream | Both read and write | General file operations |
Writing to a File
#include <iostream> #include <fstream> using namespace std; int main() { // Create and open file for writing ofstream outFile("student.txt"); // Check if file opened successfully if(!outFile) { cout << "Error opening file!\n"; return 1; } // Write to file outFile << "Name: Ali\n"; outFile << "Roll No: 101\n"; outFile << "GPA: 3.8\n"; // Close file outFile.close(); cout << "File written successfully!\n"; return 0; }
Reading from a File
#include <iostream> #include <fstream> #include <string> using namespace std; int main() { // Open file for reading ifstream inFile("student.txt"); if(!inFile) { cout << "Error opening file!\n"; return 1; } string line; // Read line by line while(getline(inFile, line)) { cout << line << endl; } inFile.close(); return 0; }
File Handling with Objects
class Student { private: string name; int rollNo; float gpa; public: Student(string n = "", int r = 0, float g = 0.0) : name(n), rollNo(r), gpa(g) {} // Save to file void saveToFile() { ofstream file("students.txt", ios::app); // append mode file << name << ";" << rollNo << ";" << gpa << endl; file.close(); } // Load from file void loadFromFile() { ifstream file("students.txt"); string line; while(getline(file, line)) { cout << line << endl; } file.close(); } }; int main() { Student s1("Ali", 101, 3.8); s1.saveToFile(); Student s2; s2.loadFromFile(); }
Exception Handling in C++
Exception handling is a mechanism to handle runtime errors gracefully without crashing the program. C++ uses try, catch, and throw keywords.
Exception Handling Syntax
try { // Code that might throw an exception throw exception; } catch(exception_type e) { // Handle the exception }
Simple Exception Handling Example
#include <iostream> using namespace std; int main() { int a = 10, b = 0; try { if(b == 0) { throw "Division by zero error!"; } cout << "Result: " << a / b; } catch(const char* msg) { cout << "Exception caught: " << msg << endl; } cout << "Program continues...\n"; return 0; }
Multiple Catch Blocks
try { int age; cout << "Enter age: "; cin >> age; if(age < 0) throw -1; // throw int else if(age > 150) throw 150.5; // throw double else if(age < 18) throw "Minor"; // throw string cout << "Age is valid\n"; } catch(int e) { cout << "Error: Negative age\n"; } catch(double e) { cout << "Error: Age too high\n"; } catch(const char* msg) { cout << "Error: " << msg << endl; }
Custom Exception Class
class DivideByZeroException { private: string message; public: DivideByZeroException(string msg) : message(msg) {} string what() { return message; } }; int divide(int a, int b) { if(b == 0) { throw DivideByZeroException("Cannot divide by zero!"); } return a / b; } int main() { try { cout << divide(10, 0); } catch(DivideByZeroException &e) { cout << "Exception: " << e.what() << endl; } }
Exception Handling Keywords
| Keyword | Purpose |
|---|---|
| try | Block of code that might throw an exception |
| throw | Throws an exception when error occurs |
| catch | Catches and handles the thrown exception |
BankAccount with methods for deposit and withdraw. Use exception handling to throw errors for: (1) Negative amount (2) Insufficient balance. Write a program that saves account details to a file.Final Project โ Student Management System
Console-based OOP application combining all concepts learned.
Project Overview
Build a Student Management System that demonstrates all OOP concepts: classes, objects, inheritance, polymorphism, encapsulation, file handling, and exception handling.
Project Requirements
- Classes: Person (base), Student (derived), Teacher (derived)
- Features: Add, display, search, update, delete records
- File Handling: Save and load data from files
- Exception Handling: Handle invalid inputs gracefully
- Polymorphism: Use virtual functions for displayInfo()
- Encapsulation: Private data members with getters/setters
- Menu-Driven: Interactive console menu
Project Structure
// Base class โ Person class Person { protected: string name; int age; string id; public: Person(string n = "", int a = 0, string i = "") : name(n), age(a), id(i) {} virtual void displayInfo() = 0; // Pure virtual โ abstract class virtual ~Person() {} // Getters and setters string getName() { return name; } void setName(string n) { name = n; } string getId() { return id; } }; // Derived class โ Student class Student : public Person { private: string department; float gpa; public: Student(string n = "", int a = 0, string i = "", string dept = "", float g = 0.0) : Person(n, a, i), department(dept), gpa(g) {} void displayInfo() { cout << "\n--- Student Info ---\n"; cout << "ID: " << id << endl; cout << "Name: " << name << endl; cout << "Age: " << age << endl; cout << "Department: " << department << endl; cout << "GPA: " << gpa << endl; } // Save to file void saveToFile() { ofstream file("students.txt", ios::app); file << id << "," << name << "," << age << "," << department << "," << gpa << endl; file.close(); } }; // Derived class โ Teacher class Teacher : public Person { private: string subject; int experience; public: Teacher(string n = "", int a = 0, string i = "", string sub = "", int exp = 0) : Person(n, a, i), subject(sub), experience(exp) {} void displayInfo() { cout << "\n--- Teacher Info ---\n"; cout << "ID: " << id << endl; cout << "Name: " << name << endl; cout << "Age: " << age << endl; cout << "Subject: " << subject << endl; cout << "Experience: " << experience << " years\n"; } };
Main Program with Menu
int main() { int choice; do { cout << "\n===== Student Management System =====\n"; cout << "1. Add Student\n"; cout << "2. Display All Students\n"; cout << "3. Search Student\n"; cout << "4. Delete Student\n"; cout << "5. Exit\n"; cout << "Enter choice: "; cin >> choice; try { switch(choice) { case 1: { string name, id, dept; int age; float gpa; cout << "Enter ID: "; cin >> id; cout << "Enter Name: "; cin.ignore(); getline(cin, name); cout << "Enter Age: "; cin >> age; cout << "Enter Department: "; cin >> dept; cout << "Enter GPA: "; cin >> gpa; if(gpa < 0 || gpa > 4.0) throw "Invalid GPA!"; Student s(name, age, id, dept, gpa); s.saveToFile(); cout << "Student added successfully!\n"; break; } case 2: { ifstream file("students.txt"); string line; cout << "\n--- All Students ---\n"; while(getline(file, line)) { cout << line << endl; } file.close(); break; } case 5: cout << "Thank you for using the system!\n"; break; default: cout << "Invalid choice!\n"; } } catch(const char* msg) { cout << "Error: " << msg << endl; } } while(choice != 5); return 0; }
Project Enhancement Ideas
- Add teacher management (similar to students)
- Implement update and delete functionality
- Add sorting (by name, GPA, etc.)
- Use vectors or arrays to store multiple records in memory
- Add password protection for admin access
- Generate reports (e.g., students with GPA > 3.5)
- Implement operator overloading for comparison
- Use templates for generic record management
Key OOP Concepts Used
| Concept | Used In |
|---|---|
| Classes & Objects | Person, Student, Teacher classes |
| Inheritance | Student and Teacher inherit from Person |
| Polymorphism | Virtual function displayInfo() |
| Encapsulation | Private data members with public methods |
| Abstract Class | Person class with pure virtual function |
| File Handling | Save and load student data |
| Exception Handling | Validate input and handle errors |
๐ Congratulations!
You've completed the Object-Oriented Programming in C++ course. Now practice by building real-world projects!

