Object-Oriented Programming in C++ โ€“ University Level โ€“ Pak Notes Hub
๐Ÿ“š University Level โ€” BS CS / BS IT

Object-Oriented Programming
in C++ Complete Notes

Classes ยท Objects ยท Inheritance ยท Polymorphism ยท Templates โ€” All in Easy English

๐ŸŽฏ 15 Units ๐ŸŽ“ University Level ๐Ÿ’ป Code Examples ๐Ÿ“ Practice Tasks ๐Ÿš€ Final Project
Unit 1

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.

๐Ÿ’ก Think of an object like a real-world thing: a car has properties (color, model, speed) and behaviors (start, stop, accelerate). OOP lets us represent this in code!

Procedural vs Object-Oriented Programming

FeatureProcedural (C)Object-Oriented (C++)
FocusFunctions and proceduresObjects and classes
Data & FunctionsSeparateBundled together in objects
Data SecurityData can be accessed anywhereData can be hidden (encapsulation)
Code ReusabilityLimitedHigh (through inheritance)
Real-World ModelingDifficultNatural and easy
ExamplesC, Pascal, FORTRANC++, Java, Python

Four Pillars of OOP

1. Encapsulation
Bundling data and methods together, hiding internal details
2. Inheritance
Creating new classes from existing ones, reusing code
3. Polymorphism
Same function name, different behaviors in different contexts
4. Abstraction
Showing only essential features, hiding complex implementation

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

TermMeaning
ClassBlueprint or template for creating objects
ObjectInstance of a class (actual entity)
Attribute/Data MemberVariables inside a class (properties)
Method/Member FunctionFunctions inside a class (behaviors)
InstanceA specific object created from a class
Message PassingObjects 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.

โœ… Supports Multiple Paradigms
โšก High Performance
๐Ÿ”ง Low-Level Memory Control
๐Ÿ“š Rich Standard Library (STL)
๐ŸŽฏ Used in System Software
๐ŸŽฎ Popular in Game Development
โœ๏ธ Practice: Write the difference between procedural and object-oriented programming with at least 3 points. Name 3 real-world objects and list their attributes and behaviors.
Unit 2

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

ClassObject
Blueprint or templateInstance of a class
Logical entityPhysical entity
Declared onceCan be created multiple times
No memory allocatedMemory is allocated
Example: Student classExample: 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();
}
โœ๏ธ Practice: Create a class Car with attributes: brand, model, year, and a method displayInfo(). Create 2 objects and display their information.
Unit 3

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

SpecifierAccessible FromUse Case
publicAnywhere (inside class, outside class, derived classes)Interface of the class โ€” methods users can call
privateOnly inside the class (not in derived classes)Internal implementation โ€” data that should be hidden
protectedInside class + derived classesData 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 Frompublicprivateprotected
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.

๐Ÿ’ก Best Practice: Always explicitly write access specifiers to make your code clear and avoid confusion. Keep data members private and provide public methods to access them (getters and setters).
โœ๏ธ Practice: Create a class Employee with private attributes (id, salary), protected attribute (department), and public methods to set and get these values. Test it in main().
Unit 4

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

TypeDescription
Default ConstructorNo parameters โ€” initializes with default values
Parameterized ConstructorTakes parameters to initialize with specific values
Copy ConstructorCreates 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

FeatureConstructorDestructor
NameSame as class nameSame as class name with ~ prefix
Called WhenObject is createdObject is destroyed
ParametersCan have parametersNo parameters
OverloadingYes โ€” multiple constructorsNo โ€” only one destructor
PurposeInitialize objectClean up resources (free memory)
โœ๏ธ Practice: Create a class Book with attributes title, author, price. Write: (1) Default constructor (2) Parameterized constructor (3) Copy constructor (4) Destructor that displays "Book destroyed".
Unit 5

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.

๐Ÿ’ก Think of encapsulation like a capsule medicine โ€” the medicine (data) is protected inside the capsule, and you can't directly touch it. You take the whole capsule (use public methods) to get the benefit.

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

BenefitExplanation
SecurityPrevents unauthorized or accidental modification of data
ValidationAllows you to add checks before setting values
FlexibilityInternal implementation can be changed without breaking code
Read-Only/Write-OnlyCan provide only getters (read-only) or only setters (write-only)
Better DebuggingEasier to track where data is being modified
โœ๏ธ Practice: Create a class Temperature with a private attribute celsius. Provide setter with validation (must be above -273.15) and getters for both Celsius and Fahrenheit values.
Unit 6

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.

๐Ÿ’ก Think of inheritance like family genes โ€” a child inherits features from parents. In programming, a derived class inherits attributes and methods from a base class!

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 MemberPublic InheritanceProtected InheritancePrivate Inheritance
publicpublicprotectedprivate
protectedprotectedprotectedprivate
privateNot inheritedNot inheritedNot 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();
}
โœ๏ธ Practice: Create a base class Vehicle with attributes (brand, model) and a derived class Car with additional attribute (numberOfDoors). Demonstrate inheritance with proper constructors.
Unit 7

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

TypeStructureExample
SingleA โ†’ BAnimal โ†’ Dog
MultipleA, B โ†’ CFather, Mother โ†’ Child
MultilevelA โ†’ B โ†’ CGrandfather โ†’ Father โ†’ Son
HierarchicalA โ†’ B, C, DAnimal โ†’ Dog, Cat, Bird
HybridCombinationMix of above types
โœ๏ธ Practice: Draw diagrams for all 5 types of inheritance. Implement multilevel inheritance: Person โ†’ Employee โ†’ Manager with appropriate attributes and methods.
Unit 8

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.

๐Ÿ’ก Real-world example: A person can be a student at school, a customer at a shop, and a player in a game โ€” same person, different roles (forms) in different contexts!

Types of Polymorphism in C++

Compile-Time Polymorphism
Decision made at compile time. Includes function overloading and operator overloading.
Runtime Polymorphism
Decision made at runtime. Achieved through virtual functions and inheritance.

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

TypeDescriptionExample
Function OverloadingMultiple functions with same name but different parametersadd(int, int) and add(float, float)
Operator OverloadingGiving special meaning to operators for user-defined typesUsing + 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 virtual keyword
  • 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

FeatureCompile-TimeRuntime
Binding TimeCompile time (early binding)Runtime (late binding)
Achieved ByFunction/operator overloadingVirtual functions, inheritance
PerformanceFaster โ€” resolved at compile timeSlightly slower โ€” resolved at runtime
FlexibilityLess flexibleMore flexible
Exampleadd(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
}
โœ๏ธ Practice: Explain the difference between compile-time and runtime polymorphism with examples. Create a base class Vehicle with virtual function speed() and derived classes Car and Bike that override it.
Unit 9

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.

๐Ÿ’ก Think of function overloading like a restaurant menu โ€” "Order Pizza" can mean cheese pizza, pepperoni pizza, or veggie pizza depending on what you specify!

Ways to Overload Functions

MethodDescriptionExample
Number of ParametersDifferent number of argumentsadd(int, int) and add(int, int, int)
Type of ParametersDifferent data typesadd(int, int) and add(float, float)
Order of ParametersDifferent sequencedisplay(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

BenefitExplanation
Code ClaritySame name for similar operations makes code easier to read
FlexibilityHandle different data types with the same function name
ConsistencyLogical naming โ€” all "add" functions actually add things
Less MemoryNo need to remember multiple function names
โœ๏ธ Practice: Create a class 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().
Unit 10

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.

๐Ÿ’ก Example: You can't directly add two "Complex" number objects using +, but with operator overloading, you can write: c3 = c1 + c2, just like adding regular numbers!

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

CategoryOperators
Arithmetic+, -, *, /, %, ++, --
Relational==, !=, <, >, <=, >=
Logical&&, ||, !
Bitwise&, |, ^, ~, <<, >>
Assignment=, +=, -=, *=, /=
Other[], (), ->, <<, >>, new, delete

Operators That CANNOT Be Overloaded

  • :: โ€” Scope resolution operator
  • . โ€” Member access operator
  • .* โ€” Pointer to member operator
  • ?: โ€” Ternary operator
  • sizeof โ€” 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 (=, [], (), ->)
โœ๏ธ Practice: Create a class Distance with feet and inches. Overload: (1) + operator to add two distances (2) == operator to compare distances (3) << operator to display distance.
Unit 11

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

FeatureVirtual FunctionPure Virtual Function
Declarationvirtual void func() { }virtual void func() = 0;
ImplementationHas implementation in base classNo implementation in base class
Class TypeClass remains concreteClass becomes abstract
Object CreationCan create objects of the classCannot create objects
OverridingOptional in derived classMust be overridden in derived class
โœ๏ธ Practice: Create an abstract class Vehicle with pure virtual function speed(). Create derived classes Car and Bike that implement speed(). Use base class pointer to demonstrate runtime polymorphism.
Unit 12

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 functions break the encapsulation rule, but sometimes they're necessary โ€” like when you need two classes to share data or when overloading certain operators like << and >>.

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 CaseReason
Operator OverloadingOperators like <<, >> need to access private members but can't be members
Two Classes Need Each Other's DataWhen two classes need to share private data
PerformanceDirect access to private members can be faster than getters/setters
Bridge Between ClassesWhen you need a function that works with multiple classes

Friend Function vs Member Function

FeatureMember FunctionFriend Function
DefinedInside the class (or using ::)Outside the class
AccessCan access private membersCan access private members
CallingCalled using object (obj.func())Called like normal function (func(obj))
This PointerHas access to this pointerNo access to this
InheritanceInherited by derived classesNot inherited
โœ๏ธ Practice: Create two classes 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.
Unit 13

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.

๐Ÿ’ก Think of templates like a cookie cutter โ€” you have one cutter (template), but you can use it with different doughs (data types) to make different cookies!

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

FeatureTemplatesMacros
Type Safetyโœ“ Type-checked by compilerโœ— No type checking
DebuggingEasy to debugDifficult to debug
Code GenerationGenerates separate code for each typeSimple text replacement
ScopeFollows C++ scope rulesGlobal replacement
โœ๏ธ Practice: Create a function template 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.
Unit 14

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

ClassPurposeOperations
ofstreamWrite to files (output)Creating and writing to files
ifstreamRead from files (input)Reading from files
fstreamBoth read and writeGeneral 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

KeywordPurpose
tryBlock of code that might throw an exception
throwThrows an exception when error occurs
catchCatches and handles the thrown exception
โœ๏ธ Practice: Create a class 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.
Unit 15

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

ConceptUsed In
Classes & ObjectsPerson, Student, Teacher classes
InheritanceStudent and Teacher inherit from Person
PolymorphismVirtual function displayInfo()
EncapsulationPrivate data members with public methods
Abstract ClassPerson class with pure virtual function
File HandlingSave and load student data
Exception HandlingValidate input and handle errors
โœ๏ธ Project Task: Complete the Student Management System with all features: Add, Display, Search, Update, Delete. Add Teacher management as well. Ensure proper use of OOP concepts, file handling, and exception handling. Test thoroughly!

๐ŸŽ‰ Congratulations!

You've completed the Object-Oriented Programming in C++ course. Now practice by building real-world projects!