Object-Oriented Programming in C++ – The 4 Pillars Explained


Introduction to Object-Oriented Programming

What is Object-Oriented Programming?

Definition: Object-Oriented Programming (OOP) is a programming paradigm that organizes software design around objects rather than functions and logic. An object is a self-contained entity that contains both data (attributes/properties) and behaviors (methods/functions) that operate on that data.

Procedural vs Object-Oriented Programming

AspectProcedural ProgrammingObject-Oriented Programming
FocusFunctions that perform operationsObjects that contain data and methods
DataData is separate from functionsData and functions are bundled together
OrganizationOrganized around procedures/functionsOrganized around objects/classes
ReusabilityFunctions can be reusedClasses can be reused and extended
SecurityData is globally accessibleData can be hidden (encapsulation)
Real-world mappingDoesn’t map well to real-worldMaps naturally to real-world entities

Why OOP?

  1. Modularity: Objects are independent, making them easier to maintain and debug
  2. Reusability: Classes can be reused across different programs
  3. Flexibility: Easy to add new features without changing existing code
  4. Security: Data hiding prevents unauthorized access
  5. Maintainability: Changes in one part don’t affect others
  6. Scalability: Easier to manage large, complex programs

Classes vs Objects

ClassObject
Blueprint or templateActual instance created from class
Defines properties and methodsHas actual values for properties
Exists at compile timeCreated at runtime
No memory allocated until object createdOccupies memory
Example: class CarExample: Car myCar

Analogy: A class is like a cookie cutter; objects are the cookies themselves.


The 4 Pillars of OOP

                    OBJECT-ORIENTED PROGRAMMING
                              │
        ┌─────────────────────┼─────────────────────┐
        │                     │                     │
   Encapsulation         Abstraction           Inheritance
        │                     │                     │
        └─────────────────────┼─────────────────────┘
                              │
                       Polymorphism

Encapsulation

Definition

Encapsulation is the bundling of data (attributes) and methods (functions) that operate on that data into a single unit (class), while restricting direct access to some of the object’s internal components.

Key Concepts

Access Specifiers in C++

SpecifierAccess LevelDescription
privateClass onlyMembers cannot be accessed from outside the class
protectedClass + Derived classesMembers accessible in class and derived classes
publicAnywhereMembers accessible from anywhere

Why Encapsulation?

  1. Data Protection: Prevents accidental or unauthorized modification
  2. Control: You can add validation logic before changing data
  3. Flexibility: You can change internal implementation without affecting external code
  4. Maintainability: Easier to debug because data access is controlled
  5. Abstraction: Users don’t need to know how data is stored internally

Complete Encapsulation Example

#include <iostream>
#include <string>
using namespace std;

class BankAccount {
private:
    // Private data members - cannot be accessed directly from outside
    string accountNumber;
    string accountHolderName;
    double balance;
    string pinCode;          // Extremely sensitive data
    int failedLoginAttempts;  // Internal tracking

    // Private helper method - internal use only
    bool validatePin(string enteredPin) {
        return pinCode == enteredPin;
    }

    void logTransaction(string transactionType, double amount) {
        cout << "[" << __TIME__ << "] " 
             << transactionType << ": $" << amount 
             << " - New balance: $" << balance << endl;
    }

public:
    // Constructor - initializes the object
    BankAccount(string accNo, string name, string pin, double initialDeposit = 0) {
        accountNumber = accNo;
        accountHolderName = name;
        pinCode = pin;
        if (initialDeposit >= 0) {
            balance = initialDeposit;
        } else {
            balance = 0;
            cout << "Initial deposit cannot be negative. Set to 0." << endl;
        }
        failedLoginAttempts = 0;
        logTransaction("Account Created", initialDeposit);
    }

    // Public getters - controlled access to private data
    string getAccountNumber() {
        return "XXXX-XXXX-" + accountNumber.substr(accountNumber.length() - 4);
    }

    string getAccountHolderName() {
        return accountHolderName;
    }

    double getBalance(string enteredPin) {
        if (validatePin(enteredPin)) {
            failedLoginAttempts = 0;  // Reset on success
            return balance;
        } else {
            failedLoginAttempts++;
            cout << "Invalid PIN. Attempt " << failedLoginAttempts << " of 3." << endl;
            if (failedLoginAttempts >= 3) {
                cout << "ACCOUNT LOCKED! Contact customer support." << endl;
                // In real system, you'd lock the account here
            }
            return -1;  // Indicate authentication failure
        }
    }

    // Public setters with validation
    bool deposit(double amount, string enteredPin) {
        if (!validatePin(enteredPin)) {
            cout << "Invalid PIN. Transaction cancelled." << endl;
            return false;
        }

        if (amount <= 0) {
            cout << "Deposit amount must be positive." << endl;
            return false;
        }

        balance += amount;
        logTransaction("Deposit", amount);
        return true;
    }

    bool withdraw(double amount, string enteredPin) {
        if (!validatePin(enteredPin)) {
            cout << "Invalid PIN. Transaction cancelled." << endl;
            return false;
        }

        if (amount <= 0) {
            cout << "Withdrawal amount must be positive." << endl;
            return false;
        }

        if (amount > balance) {
            cout << "Insufficient funds. Available balance: $" << balance << endl;
            return false;
        }

        balance -= amount;
        logTransaction("Withdrawal", amount);
        return true;
    }

    bool transferTo(BankAccount &destination, double amount, string enteredPin) {
        if (withdraw(amount, enteredPin)) {
            destination.deposit(amount, destination.pinCode);  // Note: This bypasses PIN check
            cout << "Transfer successful to account: " 
                 << destination.getAccountNumber() << endl;
            return true;
        }
        return false;
    }

    // Display account summary (without exposing sensitive data)
    void displaySummary(string enteredPin) {
        if (validatePin(enteredPin)) {
            cout << "\n=== ACCOUNT SUMMARY ===" << endl;
            cout << "Holder: " << accountHolderName << endl;
            cout << "Account: " << getAccountNumber() << endl;
            cout << "Balance: $" << balance << endl;
            cout << "=======================\n" << endl;
        } else {
            cout << "Invalid PIN. Cannot display summary." << endl;
        }
    }
};

int main() {
    // Create accounts
    BankAccount acc1("1234567890", "John Doe", "1234", 1000);
    BankAccount acc2("9876543210", "Jane Smith", "5678", 500);

    // Test encapsulation
    cout << "=== Testing Encapsulation ===" << endl;

    // Cannot access private members directly
    // acc1.balance = 1000000;  // ERROR: 'balance' is private
    // cout << acc1.pinCode;     // ERROR: 'pinCode' is private

    // Must use public interface
    acc1.displaySummary("1234");  // Valid PIN

    double balance = acc1.getBalance("1234");
    if (balance >= 0) {
        cout << "Balance retrieved: $" << balance << endl;
    }

    // Test deposit with validation
    acc1.deposit(500, "1234");     // Valid
    acc1.deposit(-100, "1234");    // Invalid - negative amount
    acc1.deposit(200, "9999");     // Invalid - wrong PIN

    // Test withdrawal with validation
    acc1.withdraw(300, "1234");    // Valid
    acc1.withdraw(2000, "1234");   // Invalid - insufficient funds

    // Test transfer
    cout << "\n=== Testing Transfer ===" << endl;
    acc1.transferTo(acc2, 200, "1234");

    // Check final balances
    cout << "\n=== Final Balances ===" << endl;
    acc1.displaySummary("1234");
    acc2.displaySummary("5678");

    return 0;
}

Key Points About Encapsulation

  • Data Hiding: Private members cannot be accessed directly
  • Controlled Access: Public methods act as gatekeepers
  • Validation: Setters can validate data before accepting it
  • Internal Logic: Private methods handle internal operations
  • Information Hiding: Users don’t need to know how data is stored

Abstraction

Definition

Abstraction is the concept of hiding complex implementation details and showing only the essential features of an object. It focuses on what an object does rather than how it does it.

Key Concepts

Abstract Classes vs Interfaces

In C++, pure virtual functions are used to create abstract classes:

Abstract ClassConcrete Class
Has at least one pure virtual functionNo pure virtual functions
Cannot be instantiatedCan be instantiated
Serves as a blueprintComplete implementation

Why Abstraction?

  1. Simplifies Complexity: Users interact with simple interface
  2. Reduces Learning Curve: Don’t need to understand internal workings
  3. Improves Maintainability: Internal changes don’t affect users
  4. Enforces Consistency: All derived classes must implement abstract methods
  5. Promotes Extensibility: Easy to add new types

Complete Abstraction Example

#include <iostream>
#include <string>
#include <vector>
#include <cmath>
using namespace std;

// Abstract base class - cannot be instantiated
class Shape {
protected:
    string color;
    string name;

public:
    Shape(string shapeName, string shapeColor) 
        : name(shapeName), color(shapeColor) {
        cout << "Creating a " << color << " " << name << endl;
    }

    // Pure virtual functions - MUST be overridden by derived classes
    virtual double calculateArea() = 0;
    virtual double calculatePerimeter() = 0;

    // Virtual function with default implementation - CAN be overridden
    virtual void display() {
        cout << "\n=== " << name << " ===" << endl;
        cout << "Color: " << color << endl;
        cout << "Area: " << calculateArea() << endl;
        cout << "Perimeter: " << calculatePerimeter() << endl;
    }

    // Regular function - cannot be overridden (but can be hidden)
    string getColor() { return color; }

    // Virtual destructor - essential for proper cleanup
    virtual ~Shape() {
        cout << "Destroying " << color << " " << name << endl;
    }
};

// Concrete derived class
class Circle : public Shape {
private:
    double radius;
    const double PI = 3.14159;

public:
    Circle(string color, double r) 
        : Shape("Circle", color), radius(r) {
        if (radius <= 0) {
            cout << "Warning: Invalid radius. Setting to 1." << endl;
            radius = 1;
        }
    }

    // Must override pure virtual functions
    double calculateArea() override {
        return PI * radius * radius;
    }

    double calculatePerimeter() override {
        return 2 * PI * radius;
    }

    // Override display to show circle-specific info
    void display() override {
        Shape::display();  // Call base class display first
        cout << "Radius: " << radius << endl;
        cout << "Diameter: " << 2 * radius << endl;
    }

    // Circle-specific method
    void setRadius(double r) {
        if (r > 0) {
            radius = r;
            cout << "Radius updated to " << radius << endl;
        }
    }
};

// Another concrete derived class
class Rectangle : public Shape {
private:
    double length;
    double width;

public:
    Rectangle(string color, double l, double w) 
        : Shape("Rectangle", color), length(l), width(w) {
        if (length <= 0) {
            cout << "Warning: Invalid length. Setting to 1." << endl;
            length = 1;
        }
        if (width <= 0) {
            cout << "Warning: Invalid width. Setting to 1." << endl;
            width = 1;
        }
    }

    double calculateArea() override {
        return length * width;
    }

    double calculatePerimeter() override {
        return 2 * (length + width);
    }

    void display() override {
        Shape::display();
        cout << "Length: " << length << endl;
        cout << "Width: " << width << endl;
        cout << "Is Square: " << (length == width ? "Yes" : "No") << endl;
    }
};

// A more complex shape
class Triangle : public Shape {
private:
    double side1, side2, side3;

public:
    Triangle(string color, double s1, double s2, double s3) 
        : Shape("Triangle", color), side1(s1), side2(s2), side3(s3) {

        // Validate triangle inequality theorem
        if (!isValidTriangle()) {
            cout << "Invalid triangle sides! Setting to equilateral 1,1,1." << endl;
            side1 = side2 = side3 = 1;
        }
    }

    bool isValidTriangle() {
        return (side1 + side2 > side3) &&
               (side1 + side3 > side2) &&
               (side2 + side3 > side1);
    }

    double calculateArea() override {
        // Using Heron's formula
        double s = (side1 + side2 + side3) / 2;
        return sqrt(s * (s - side1) * (s - side2) * (s - side3));
    }

    double calculatePerimeter() override {
        return side1 + side2 + side3;
    }

    void display() override {
        Shape::display();
        cout << "Sides: " << side1 << ", " << side2 << ", " << side3 << endl;
        cout << "Valid triangle: " << (isValidTriangle() ? "Yes" : "No") << endl;
    }
};

// Demonstration class - shows abstraction benefits
class ShapeManager {
private:
    vector<Shape*> shapes;  // Store pointers to abstract base class

public:
    void addShape(Shape* shape) {
        shapes.push_back(shape);
        cout << "Shape added to manager." << endl;
    }

    void displayAllShapes() {
        if (shapes.empty()) {
            cout << "No shapes to display." << endl;
            return;
        }

        cout << "\n=== ALL SHAPES IN MANAGER ===" << endl;
        for (Shape* shape : shapes) {
            shape->display();  // Polymorphic call - correct version called
            cout << "------------------------" << endl;
        }
    }

    double calculateTotalArea() {
        double total = 0;
        for (Shape* shape : shapes) {
            total += shape->calculateArea();
        }
        return total;
    }

    ~ShapeManager() {
        // Clean up all shapes
        for (Shape* shape : shapes) {
            delete shape;
        }
        shapes.clear();
    }
};

int main() {
    cout << "=== ABSTRACTION DEMONSTRATION ===" << endl;
    cout << "Creating shapes through abstract interface..." << endl;
    cout << "===================================\n" << endl;

    // ShapeManager will handle all shapes polymorphically
    ShapeManager manager;

    // Add different shapes - all treated as Shape*
    manager.addShape(new Circle("Red", 5));
    manager.addShape(new Rectangle("Blue", 4, 6));
    manager.addShape(new Triangle("Green", 3, 4, 5));
    manager.addShape(new Circle("Yellow", 3.5));
    manager.addShape(new Rectangle("Purple", 2.5, 2.5));  // Square

    // Display all shapes - abstraction hides complexity
    manager.displayAllShapes();

    // Calculate total area - abstraction makes this simple
    cout << "\n=== STATISTICS ===" << endl;
    cout << "Total area of all shapes: " << manager.calculateTotalArea() << endl;

    // Demonstrate that we can't instantiate abstract class
    // Shape s;  // ERROR: Cannot instantiate abstract class

    cout << "\n=== INDIVIDUAL SHAPE MANIPULATION ===" << endl;

    // Working with specific shapes through abstract interface
    Circle* circle = new Circle("Orange", 2.5);
    circle->display();
    circle->setRadius(3);  // Circle-specific method

    // Still can use abstract interface
    Shape* shapePtr = circle;
    cout << "\nThrough Shape pointer:" << endl;
    cout << "Area: " << shapePtr->calculateArea() << endl;

    delete circle;  // Clean up

    return 0;
}

Key Points About Abstraction

  • Pure Virtual Functions: virtual function() = 0 makes a class abstract
  • Cannot Instantiate: Abstract classes cannot create objects
  • Must Override: Derived classes must implement all pure virtual functions
  • Interface vs Implementation: Abstract class defines interface; derived classes provide implementation
  • Polymorphism: Base class pointers can call derived class implementations

Inheritance

Definition

Inheritance is a mechanism where a new class (derived/child class) inherits properties and behaviors from an existing class (base/parent class). It establishes an “is-a” relationship between classes.

Types of Inheritance in C++

1. Single Inheritance:      A
                            │
                            ▼
                            B

2. Multiple Inheritance:    A   B
                            │   │
                            ▼   ▼
                            └─ C ─┘

3. Multilevel Inheritance:  A
                            │
                            ▼
                            B
                            │
                            ▼
                            C

4. Hierarchical Inheritance:    A
                            ┌──┴──┐
                            ▼     ▼
                            B     C

5. Hybrid Inheritance:      A   B
                            │   │
                            ▼   ▼
                            C   D
                            │   │
                            ▼   ▼
                            └─ E ─┘

Access Specifiers in Inheritance

Base Class AccessPublic InheritanceProtected InheritancePrivate Inheritance
publicpublic in derivedprotected in derivedprivate in derived
protectedprotected in derivedprotected in derivedprivate in derived
privateinaccessibleinaccessibleinaccessible

Complete Inheritance Example

#include <iostream>
#include <string>
#include <vector>
using namespace std;

// ===== BASE CLASS =====
class Person {
protected:  // Accessible in derived classes
    string name;
    int age;
    string id;

private:   // Not accessible anywhere except this class
    string nationalInsuranceNumber;

public:    // Accessible everywhere
    // Constructor
    Person(string n, int a, string idNum, string niNum) 
        : name(n), age(a), id(idNum), nationalInsuranceNumber(niNum) {
        cout << "Person constructor called for " << name << endl;
    }

    // Virtual destructor - essential for proper cleanup
    virtual ~Person() {
        cout << "Person destructor called for " << name << endl;
    }

    // Getters and setters
    string getName() { return name; }
    int getAge() { return age; }
    string getId() { return id; }

    void setName(string n) { name = n; }
    void setAge(int a) { 
        if (a > 0 && a < 150) age = a; 
    }

    // Virtual function - can be overridden
    virtual void introduce() {
        cout << "\n--- Person Introduction ---" << endl;
        cout << "Hi, I'm " << name << endl;
        cout << "Age: " << age << endl;
        cout << "ID: " << id << endl;
    }

    // Regular function - cannot be overridden
    void celebrateBirthday() {
        age++;
        cout << "Happy Birthday! Now " << age << " years old!" << endl;
    }
};

// ===== SINGLE INHERITANCE =====
// Student inherits from Person (public inheritance)
class Student : public Person {
private:
    string studentId;
    double gpa;
    vector<string> courses;

public:
    // Constructor calls base class constructor
    Student(string n, int a, string idNum, string niNum, 
            string sId, double g) 
        : Person(n, a, idNum, niNum), studentId(sId), gpa(g) {
        cout << "Student constructor called for " << name << endl;
    }

    ~Student() override {
        cout << "Student destructor called for " << name << endl;
    }

    // Override base class function
    void introduce() override {
        Person::introduce();  // Call base class version
        cout << "I'm a student" << endl;
        cout << "Student ID: " << studentId << endl;
        cout << "GPA: " << gpa << endl;
        cout << "Courses enrolled: " << courses.size() << endl;
    }

    // Student-specific methods
    void addCourse(string course) {
        courses.push_back(course);
        cout << course << " added to schedule" << endl;
    }

    void study() {
        cout << name << " is studying..." << endl;
    }

    double getGpa() { return gpa; }

    void setGpa(double g) {
        if (g >= 0 && g <= 4.0) gpa = g;
    }
};

// ===== ANOTHER DERIVED CLASS =====
// Teacher inherits from Person
class Teacher : public Person {
private:
    string employeeId;
    string department;
    double salary;
    vector<string> coursesTaught;

public:
    Teacher(string n, int a, string idNum, string niNum,
            string empId, string dept, double sal)
        : Person(n, a, idNum, niNum), 
          employeeId(empId), department(dept), salary(sal) {
        cout << "Teacher constructor called for " << name << endl;
    }

    ~Teacher() override {
        cout << "Teacher destructor called for " << name << endl;
    }

    void introduce() override {
        Person::introduce();
        cout << "I'm a teacher" << endl;
        cout << "Employee ID: " << employeeId << endl;
        cout << "Department: " << department << endl;
        cout << "Salary: $" << salary << endl;
    }

    void teach(string course) {
        coursesTaught.push_back(course);
        cout << name << " is teaching " << course << endl;
    }

    void giveGrade(Student &s, double grade) {
        cout << name << " gave grade " << grade << " to " << s.getName() << endl;
        // In real system, would update student's record
    }
};

// ===== MULTILEVEL INHERITANCE =====
// GraduateStudent inherits from Student, which inherits from Person
class GraduateStudent : public Student {
private:
    string thesisTopic;
    string supervisor;

public:
    GraduateStudent(string n, int a, string idNum, string niNum,
                    string sId, double g, string topic, string sup)
        : Student(n, a, idNum, niNum, sId, g),
          thesisTopic(topic), supervisor(sup) {
        cout << "GraduateStudent constructor called for " << name << endl;
    }

    ~GraduateStudent() override {
        cout << "GraduateStudent destructor called for " << name << endl;
    }

    void introduce() override {
        Student::introduce();
        cout << "I'm a graduate student" << endl;
        cout << "Thesis: " << thesisTopic << endl;
        cout << "Supervisor: " << supervisor << endl;
    }

    void research() {
        cout << name << " is researching " << thesisTopic << endl;
    }

    void writeThesis() {
        cout << name << " is writing thesis..." << endl;
    }
};

// ===== MULTIPLE INHERITANCE =====
// TeachingAssistant inherits from both Student and Teacher
class TeachingAssistant : public Student, public Teacher {
private:
    string contractType;
    double hoursPerWeek;

public:
    // Constructor must initialize both base classes
    TeachingAssistant(string n, int a, string idNum, string niNum,
                      string sId, double g,
                      string empId, string dept, double sal,
                      string contract, double hours)
        : Student(n, a, idNum, niNum, sId, g),
          Teacher(n, a, idNum, niNum, empId, dept, sal),
          contractType(contract), hoursPerWeek(hours) {
        cout << "TeachingAssistant constructor called" << endl;
    }

    ~TeachingAssistant() override {
        cout << "TeachingAssistant destructor called" << endl;
    }

    // Must resolve ambiguity - which introduce() to use?
    void introduce() {
        cout << "\n=== Teaching Assistant Introduction ===" << endl;
        // Need to specify which base class
        cout << "Name: " << Student::getName() << endl;
        cout << "Age: " << Student::getAge() << endl;
        cout << "Student ID: " << Student::getStudentId() << endl;
        cout << "Department: " << Teacher::getDepartment() << endl;
        cout << "Contract: " << contractType << ", " << hoursPerWeek << " hours/week" << endl;
    }

    // Access specific base class methods
    void studentLife() {
        Student::study();
    }

    void teacherLife() {
        Teacher::teach("Lab Session");
    }

    // Helper to access names from different base classes
    string getStudentName() { return Student::getName(); }
    string getTeacherName() { return Teacher::getName(); }
};

// ===== DEMONSTRATION =====
int main() {
    cout << "=== INHERITANCE DEMONSTRATION ===\n" << endl;

    // Create objects
    cout << "--- Creating Objects ---" << endl;
    Person person("Generic Person", 30, "P123", "NI123");
    Student student("Alice Smith", 20, "P456", "NI456", "S12345", 3.8);
    Teacher teacher("Bob Johnson", 45, "P789", "NI789", "E67890", "Computer Science", 75000);
    GraduateStudent gradStudent("Carol White", 25, "P101", "NI101", "G54321", 3.9, 
                                "Machine Learning", "Dr. Davis");

    cout << "\n--- Demonstrating Inheritance Hierarchy ---" << endl;

    // Each object can use its own methods and inherited ones
    cout << "\n1. Person object:" << endl;
    person.introduce();

    cout << "\n2. Student object (inherits from Person):" << endl;
    student.introduce();
    student.study();  // Student-specific
    student.addCourse("CS101");
    student.addCourse("CS201");

    cout << "\n3. Teacher object (inherits from Person):" << endl;
    teacher.introduce();
    teacher.teach("OOP Programming");
    teacher.giveGrade(student, 3.9);

    cout << "\n4. GraduateStudent object (multilevel inheritance):" << endl;
    gradStudent.introduce();
    gradStudent.research();
    gradStudent.writeThesis();

    cout << "\n--- Demonstrating Polymorphism with Inheritance ---" << endl;

    // Array of Person pointers - can hold any derived class
    vector<Person*> people;
    people.push_back(&person);
    people.push_back(&student);
    people.push_back(&teacher);
    people.push_back(&gradStudent);

    // Polymorphic behavior - correct introduce() called for each
    for (Person* p : people) {
        p->introduce();
        cout << "------------------------" << endl;
    }

    cout << "\n--- Multiple Inheritance Example ---" << endl;

    TeachingAssistant ta("Eve Brown", 24, "P202", "NI202",
                         "TA12345", 4.0,
                         "E98765", "CS", 25000,
                         "Part-time", 20);

    ta.introduce();
    ta.studentLife();
    ta.teacherLife();

    // Demonstrate ambiguity resolution
    cout << "\nNames from different paths: " << endl;
    cout << "As student: " << ta.getStudentName() << endl;
    cout << "As teacher: " << ta.getTeacherName() << endl;

    cout << "\n--- Destructors Called (in reverse order) ---" << endl;

    return 0;
}

Key Points About Inheritance

  • “is-a” Relationship: Derived class is a type of base class
  • Code Reuse: Inherit common functionality from base class
  • Extensibility: Add new features without modifying existing code
  • Polymorphism: Base class pointers/references can refer to derived objects
  • Constructor/Destructor Order: Base constructed first, destroyed last
  • Access Control: Protected members accessible in derived classes

Polymorphism

Definition

Polymorphism (Greek for “many forms”) allows objects of different types to respond to the same function call in different ways. It provides a single interface to entities of different types.

Types of Polymorphism

                    POLYMORPHISM
                          │
          ┌───────────────┴───────────────┐
          │                               │
    Compile-Time                      Run-Time
    (Static)                          (Dynamic)
          │                               │
    ┌─────┴─────┐                         │
    │           │                         │
Overloading   Operator                  Virtual
             Overloading                 Functions

Compile-Time Polymorphism (Static Binding)

1. Function Overloading

#include <iostream>
#include <string>
using namespace std;

class Calculator {
public:
    // Same function name, different parameters

    // Add two integers
    int add(int a, int b) {
        cout << "Adding two integers: ";
        return a + b;
    }

    // Add three integers
    int add(int a, int b, int c) {
        cout << "Adding three integers: ";
        return a + b + c;
    }

    // Add two doubles
    double add(double a, double b) {
        cout << "Adding two doubles: ";
        return a + b;
    }

    // Concatenate strings (same name, different purpose)
    string add(string a, string b) {
        cout << "Concatenating strings: ";
        return a + b;
    }

    // Add mixed types
    double add(int a, double b) {
        cout << "Adding int and double: ";
        return a + b;
    }
};

class Display {
public:
    // Overload with different parameter types
    void show(int i) {
        cout << "Integer: " << i << endl;
    }

    void show(double d) {
        cout << "Double: " << d << endl;
    }

    void show(string s) {
        cout << "String: " << s << endl;
    }

    void show(int i, string s) {
        cout << "Integer " << i << " and String " << s << endl;
    }

    // Default arguments also contribute to overloading
    void show(int i, int j = 0) {
        cout << "Two ints: " << i << ", " << j << endl;
    }
};

int main() {
    cout << "=== FUNCTION OVERLOADING ===\n" << endl;

    Calculator calc;
    Display disp;

    cout << "Calculator demonstrations:" << endl;
    cout << calc.add(5, 10) << endl;
    cout << calc.add(5, 10, 15) << endl;
    cout << calc.add(3.14, 2.86) << endl;
    cout << calc.add("Hello ", "World") << endl;
    cout << calc.add(5, 3.14) << endl;

    cout << "\nDisplay demonstrations:" << endl;
    disp.show(42);
    disp.show(3.14159);
    disp.show("C++ OOP");
    disp.show(100, "codes");
    disp.show(7, 8);
}

2. Operator Overloading

#include <iostream>
#include <cmath>
using namespace std;

class ComplexNumber {
private:
    double real;
    double imag;

public:
    // Constructor
    ComplexNumber(double r = 0, double i = 0) : real(r), imag(i) {
        cout << "Created: " << real << " + " << imag << "i" << endl;
    }

    // Overload + operator
    ComplexNumber operator+(const ComplexNumber& other) const {
        return ComplexNumber(real + other.real, imag + other.imag);
    }

    // Overload - operator
    ComplexNumber operator-(const ComplexNumber& other) const {
        return ComplexNumber(real - other.real, imag - other.imag);
    }

    // Overload * operator
    ComplexNumber operator*(const ComplexNumber& other) const {
        // (a+bi)(c+di) = (ac - bd) + (ad + bc)i
        return ComplexNumber(
            real * other.real - imag * other.imag,
            real * other.imag + imag * other.real
        );
    }

    // Overload == operator
    bool operator==(const ComplexNumber& other) const {
        return (real == other.real && imag == other.imag);
    }

    // Overload != operator
    bool operator!=(const ComplexNumber& other) const {
        return !(*this == other);
    }

    // Overload prefix ++ operator
    ComplexNumber& operator++() {
        ++real;
        ++imag;
        return *this;
    }

    // Overload postfix ++ operator
    ComplexNumber operator++(int) {
        ComplexNumber temp = *this;
        ++(*this);  // Use prefix version
        return temp;
    }

    // Overload << operator (as friend function)
    friend ostream& operator<<(ostream& os, const ComplexNumber& c) {
        os << c.real;
        if (c.imag >= 0)
            os << " + " << c.imag << "i";
        else
            os << " - " << -c.imag << "i";
        return os;
    }

    // Overload >> operator
    friend istream& operator>>(istream& is, ComplexNumber& c) {
        cout << "Enter real part: ";
        is >> c.real;
        cout << "Enter imaginary part: ";
        is >> c.imag;
        return is;
    }

    // Get magnitude
    double magnitude() const {
        return sqrt(real * real + imag * imag);
    }
};

// Overload global operator (outside class)
ComplexNumber operator*(double scalar, const ComplexNumber& c) {
    return ComplexNumber(scalar * c.getReal(), scalar * c.getImag());
}

int main() {
    cout << "=== OPERATOR OVERLOADING ===\n" << endl;

    // Create complex numbers
    ComplexNumber c1(3, 4);   // 3 + 4i
    ComplexNumber c2(1, 2);   // 1 + 2i

    cout << "\nOriginal numbers:" << endl;
    cout << "c1 = " << c1 << endl;
    cout << "c2 = " << c2 << endl;

    // Use overloaded operators
    cout << "\nOperations:" << endl;
    ComplexNumber c3 = c1 + c2;
    cout << "c1 + c2 = " << c3 << endl;

    ComplexNumber c4 = c1 - c2;
    cout << "c1 - c2 = " << c4 << endl;

    ComplexNumber c5 = c1 * c2;
    cout << "c1 * c2 = " << c5 << endl;

    // Comparison
    cout << "\nComparisons:" << endl;
    cout << "c1 == c2? " << (c1 == c2 ? "Yes" : "No") << endl;
    cout << "c1 != c2? " << (c1 != c2 ? "Yes" : "No") << endl;

    // Increment operators
    cout << "\nIncrement operators:" << endl;
    cout << "c1 before ++: " << c1 << endl;
    cout << "++c1: " << ++c1 << endl;
    cout << "c1 after ++: " << c1 << endl;
    cout << "c2++: " << c2++ << endl;
    cout << "c2 after: " << c2 << endl;

    // Input
    cout << "\nEnter a complex number:" << endl;
    ComplexNumber c6;
    cin >> c6;
    cout << "You entered: " << c6 << endl;
    cout << "Magnitude: " << c6.magnitude() << endl;
}

Run-Time Polymorphism (Dynamic Binding)

Virtual Functions

#include <iostream>
#include <vector>
#include <string>
using namespace std;

// Base class
class Employee {
protected:
    string name;
    int id;
    double baseSalary;

public:
    Employee(string n, int i, double salary) 
        : name(n), id(i), baseSalary(salary) {}

    virtual ~Employee() {}

    // Virtual function - can be overridden
    virtual double calculateSalary() const {
        return baseSalary;
    }

    // Pure virtual function - makes class abstract
    virtual string getRole() const = 0;

    // Virtual function with implementation
    virtual void displayInfo() const {
        cout << "ID: " << id << ", Name: " << name;
        cout << ", Role: " << getRole();
        cout << ", Salary: $" << calculateSalary();
    }

    // Non-virtual function - cannot be overridden
    string getName() const { return name; }
};

// Derived class 1
class Manager : public Employee {
private:
    double bonus;
    int teamSize;

public:
    Manager(string n, int i, double salary, double b, int size)
        : Employee(n, i, salary), bonus(b), teamSize(size) {}

    // Override virtual function
    double calculateSalary() const override {
        return baseSalary + bonus + (teamSize * 1000);  // Bonus per team member
    }

    string getRole() const override {
        return "Manager";
    }

    void displayInfo() const override {
        Employee::displayInfo();
        cout << ", Team Size: " << teamSize;
        cout << ", Bonus: $" << bonus;
    }

    void conductMeeting() {
        cout << name << " is conducting a team meeting." << endl;
    }
};

// Derived class 2
class Engineer : public Employee {
private:
    string specialization;
    int overtimeHours;
    double overtimeRate;

public:
    Engineer(string n, int i, double salary, string spec, int hours, double rate)
        : Employee(n, i, salary), specialization(spec), 
          overtimeHours(hours), overtimeRate(rate) {}

    double calculateSalary() const override {
        return baseSalary + (overtimeHours * overtimeRate);
    }

    string getRole() const override {
        return "Engineer";
    }

    void displayInfo() const override {
        Employee::displayInfo();
        cout << ", Specialization: " << specialization;
        cout << ", Overtime: " << overtimeHours << " hours";
    }

    void writeCode() {
        cout << name << " is writing code in " << specialization << endl;
    }
};

// Derived class 3
class Intern : public Employee {
private:
    string university;
    int durationWeeks;

public:
    Intern(string n, int i, double salary, string uni, int weeks)
        : Employee(n, i, salary), university(uni), durationWeeks(weeks) {}

    // Interns get fixed stipend, no calculation needed
    string getRole() const override {
        return "Intern";
    }

    void displayInfo() const override {
        Employee::displayInfo();
        cout << ", University: " << university;
        cout << ", Duration: " << durationWeeks << " weeks";
    }

    void learn() {
        cout << name << " is learning on the job." << endl;
    }
};

// Company class demonstrating polymorphic behavior
class Company {
private:
    vector<Employee*> employees;

public:
    void addEmployee(Employee* emp) {
        employees.push_back(emp);
        cout << "Added " << emp->getName() << " as " << emp->getRole() << endl;
    }

    void processPayroll() {
        cout << "\n=== PROCESSING PAYROLL ===" << endl;
        double total = 0;

        for (Employee* emp : employees) {
            emp->displayInfo();
            cout << endl;
            total += emp->calculateSalary();
        }

        cout << "\nTotal payroll: $" << total << endl;
    }

    void conductReviews() {
        cout << "\n=== PERFORMANCE REVIEWS ===" << endl;

        for (Employee* emp : employees) {
            cout << "Reviewing " << emp->getName() << " (" << emp->getRole() << ")" << endl;

            // Dynamic cast to check actual type
            if (Manager* mgr = dynamic_cast<Manager*>(emp)) {
                mgr->conductMeeting();  // Manager-specific
            }
            else if (Engineer* eng = dynamic_cast<Engineer*>(emp)) {
                eng->writeCode();       // Engineer-specific
            }
            else if (Intern* intern = dynamic_cast<Intern*>(emp)) {
                intern->learn();        // Intern-specific
            }
        }
    }

    ~Company() {
        // Clean up
        for (Employee* emp : employees) {
            delete emp;
        }
    }
};

int main() {
    cout << "=== RUNTIME POLYMORPHISM ===\n" << endl;

    Company techCorp;

    // Add different employee types - all stored as Employee*
    techCorp.addEmployee(new Manager("Alice Johnson", 1001, 80000, 10000, 8));
    techCorp.addEmployee(new Engineer("Bob Smith", 1002, 70000, "C++", 10, 50));
    techCorp.addEmployee(new Intern("Carol White", 1003, 2000, "MIT", 12));
    techCorp.addEmployee(new Manager("David Brown", 1004, 90000, 15000, 12));
    techCorp.addEmployee(new Engineer("Eve Davis", 1005, 72000, "Python", 5, 50));

    // Process payroll - polymorphic salary calculation
    techCorp.processPayroll();

    // Conduct reviews - polymorphic behavior
    techCorp.conductReviews();

    cout << "\n--- Virtual Function Table (VTable) Concept ---" << endl;
    cout << "Each class has its own VTable containing pointers to virtual functions." << endl;
    cout << "When calling a virtual function through base pointer, the VTable" << endl;
    cout << "determines which actual function to call at runtime." << endl;
}

Virtual Functions Deep Dive

#include <iostream>
using namespace std;

class Base {
public:
    // Regular function - early binding (compile-time)
    void nonVirtual() {
        cout << "Base::nonVirtual()" << endl;
    }

    // Virtual function - late binding (runtime)
    virtual void virtualFunc() {
        cout << "Base::virtualFunc()" << endl;
    }

    // Pure virtual function - makes class abstract
    virtual void pureVirtual() = 0;

    // Virtual destructor - essential for proper cleanup
    virtual ~Base() {
        cout << "Base destructor" << endl;
    }
};

class Derived : public Base {
public:
    // Hide base class non-virtual function (not override)
    void nonVirtual() {
        cout << "Derived::nonVirtual()" << endl;
    }

    // Override virtual function
    void virtualFunc() override {
        cout << "Derived::virtualFunc()" << endl;
    }

    // Implement pure virtual function
    void pureVirtual() override {
        cout << "Derived::pureVirtual()" << endl;
    }

    ~Derived() override {
        cout << "Derived destructor" << endl;
    }
};

class AnotherDerived : public Base {
public:
    void pureVirtual() override {
        cout << "AnotherDerived::pureVirtual()" << endl;
    }

    void virtualFunc() override {
        cout << "AnotherDerived::virtualFunc()" << endl;
    }
};

int main() {
    cout << "=== VIRTUAL FUNCTIONS DEEP DIVE ===\n" << endl;

    Base* ptr1 = new Derived();
    Base* ptr2 = new AnotherDerived();

    cout << "Calling through Base pointers:" << endl;

    // Non-virtual - calls Base version (compile-time binding)
    ptr1->nonVirtual();     // Base::nonVirtual()

    // Virtual - calls Derived version (runtime binding)
    ptr1->virtualFunc();    // Derived::virtualFunc()
    ptr1->pureVirtual();    // Derived::pureVirtual()

    cout << "\nAnother derived class:" << endl;
    ptr2->virtualFunc();    // AnotherDerived::virtualFunc()
    ptr2->pureVirtual();    // AnotherDerived::pureVirtual()

    cout << "\n--- Virtual Destructor Importance ---" << endl;
    cout << "Without virtual destructor, only Base destructor would be called." << endl;
    cout << "With virtual destructor, both destructors are called properly." << endl;

    delete ptr1;
    delete ptr2;

    cout << "\n--- VTable Internals (Conceptual) ---" << endl;
    cout << "Class Base VTable: [&nonVirtual? no, &virtualFunc, &pureVirtual, &~Base]" << endl;
    cout << "Class Derived VTable: [&Base::nonVirtual? no, &Derived::virtualFunc, " << endl;
    cout << "                      &Derived::pureVirtual, &Derived::~Derived]" << endl;
}

The Diamond Problem in C++

What is the Diamond Problem?

The Diamond Problem is an ambiguity issue that arises in multiple inheritance when a derived class inherits from two classes that have a common base class.

        Person
        /    \
    Student  Teacher
        \    /
     TeachingAssistant

Complete Diamond Problem Example

#include <iostream>
#include <string>
using namespace std;

// ===== BASE CLASS =====
class Person {
protected:
    string name;
    int age;

public:
    Person(string n = "", int a = 0) : name(n), age(a) {
        cout << "Person constructor: " << name << endl;
    }

    virtual ~Person() {
        cout << "Person destructor: " << name << endl;
    }

    virtual void introduce() {
        cout << "I am " << name << ", " << age << " years old." << endl;
    }

    string getName() { return name; }
};

// ===== PROBLEMATIC MULTIPLE INHERITANCE (NO VIRTUAL) =====

class Student : public Person {
protected:
    string studentId;

public:
    Student(string n, int a, string id) 
        : Person(n, a), studentId(id) {
        cout << "Student constructor: " << name << endl;
    }

    void study() {
        cout << name << " is studying." << endl;
    }
};

class Teacher : public Person {
protected:
    string employeeId;

public:
    Teacher(string n, int a, string id) 
        : Person(n, a), employeeId(id) {
        cout << "Teacher constructor: " << name << endl;
    }

    void teach() {
        cout << name << " is teaching." << endl;
    }
};

// This creates the Diamond Problem
class TeachingAssistant : public Student, public Teacher {
private:
    string contractType;

public:
    TeachingAssistant(string n, int a, string sId, string eId, string contract)
        : Student(n, a, sId), Teacher(n, a, eId), contractType(contract) {
        cout << "TeachingAssistant constructor" << endl;
    }

    void introduce() {
        // Ambiguity! Which Person::name to use?
        cout << "I am a teaching assistant." << endl;

        // Must specify which base class
        cout << "Student name: " << Student::getName() << endl;
        cout << "Teacher name: " << Teacher::getName() << endl;
        cout << "Contract: " << contractType << endl;
    }
};

// ===== SOLUTION: VIRTUAL INHERITANCE =====

class VirtualStudent : virtual public Person {
protected:
    string studentId;

public:
    VirtualStudent(string n, int a, string id) 
        : Person(n, a), studentId(id) {
        cout << "VirtualStudent constructor: " << name << endl;
    }
};

class VirtualTeacher : virtual public Person {
protected:
    string employeeId;

public:
    VirtualTeacher(string n, int a, string id) 
        : Person(n, a), employeeId(id) {
        cout << "VirtualTeacher constructor: " << name << endl;
    }
};

// Virtual inheritance solves the diamond problem
class VirtualTeachingAssistant : public VirtualStudent, public VirtualTeacher {
private:
    string contractType;

public:
    VirtualTeachingAssistant(string n, int a, string sId, string eId, string contract)
        : Person(n, a),           // Must explicitly construct Person
          VirtualStudent(n, a, sId),
          VirtualTeacher(n, a, eId),
          contractType(contract) {
        cout << "VirtualTeachingAssistant constructor" << endl;
    }

    void introduce() {
        // No ambiguity now - only one Person instance
        cout << "I am " << name << ", a teaching assistant." << endl;
        cout << "Student ID: " << studentId << ", Employee ID: " << employeeId << endl;
        cout << "Contract: " << contractType << endl;
    }
};

int main() {
    cout << "=== THE DIAMOND PROBLEM ===\n" << endl;

    cout << "--- Without Virtual Inheritance ---" << endl;
    TeachingAssistant ta1("John Doe", 25, "S123", "E456", "Part-time");

    // This creates two separate Person objects!
    cout << "\nMemory layout: TA contains two Person subobjects" << endl;
    ta1.introduce();

    // Ambiguity examples:
    // ta1.getName();  // ERROR: ambiguous
    // ta1.introduce(); // Must specify which one

    cout << "\nAccessing through specific paths:" << endl;
    cout << "As Student: " << ta1.Student::getName() << endl;
    cout << "As Teacher: " << ta1.Teacher::getName() << endl;

    cout << "\n--- With Virtual Inheritance (Solution) ---" << endl;
    VirtualTeachingAssistant ta2("Jane Smith", 24, "S789", "E012", "Full-time");

    cout << "\nMemory layout: Single Person subobject shared" << endl;
    ta2.introduce();  // No ambiguity

    // Can access directly now
    cout << "\nDirect access: " << ta2.getName() << endl;

    cout << "\n--- Constructor Call Order (Virtual Inheritance) ---" << endl;
    cout << "Note: Virtual base class constructed FIRST, " << endl;
    cout << "even before non-virtual bases." << endl;

    cout << "\n--- Memory Layout Comparison ---" << endl;
    cout << "Without virtual: [Person][Student] [Person][Teacher] [TA]" << endl;
    cout << "With virtual:    [Person] [Student][Teacher] [TA]" << endl;
    cout << "                (shared)" << endl;
}

Virtual Functions vs Virtual Inheritance

ConceptVirtual FunctionsVirtual Inheritance
PurposeRuntime polymorphismSolve diamond problem
When usedFunction overridingMultiple inheritance
EffectDynamic dispatchSingle shared base instance
Keywordvirtual before functionvirtual before base class
Memory impactVTable per classExtra indirection for base

Comprehensive Programming Tasks

Task 1: Encapsulation – Bank Account System

#include <iostream>
#include <string>
#include <vector>
#include <ctime>
using namespace std;

class BankAccount {
private:
    string accountNumber;
    string accountHolderName;
    double balance;
    string pin;
    vector<string> transactionHistory;
    bool isActive;
    int failedAttempts;
    const int MAX_FAILED_ATTEMPTS = 3;

    // Private helper methods
    string getCurrentTime() {
        time_t now = time(0);
        return ctime(&now);
    }

    void addTransaction(string type, double amount) {
        string transaction = getCurrentTime() + ": " + type + 
                            " $" + to_string(amount) + 
                            " - Balance: $" + to_string(balance);
        transactionHistory.push_back(transaction);
    }

    bool validatePin(string enteredPin) {
        if (enteredPin == pin) {
            failedAttempts = 0;
            return true;
        } else {
            failedAttempts++;
            return false;
        }
    }

    bool isAccountLocked() {
        return failedAttempts >= MAX_FAILED_ATTEMPTS;
    }

public:
    // Constructor
    BankAccount(string accNo, string name, string pinCode, double initialDeposit = 0) {
        accountNumber = accNo;
        accountHolderName = name;
        pin = pinCode;
        balance = (initialDeposit >= 0) ? initialDeposit : 0;
        isActive = true;
        failedAttempts = 0;
        addTransaction("Account Created", initialDeposit);
    }

    // Getters with validation
    string getAccountNumber(string enteredPin) {
        if (validatePin(enteredPin) && !isAccountLocked()) {
            return "XXXX-XXXX-" + accountNumber.substr(accountNumber.length() - 4);
        }
        return "ACCESS DENIED";
    }

    double getBalance(string enteredPin) {
        if (validatePin(enteredPin) && !isAccountLocked()) {
            return balance;
        }
        return -1;
    }

    // Deposit with validation
    bool deposit(double amount, string enteredPin) {
        if (!validatePin(enteredPin) || isAccountLocked()) {
            cout << "Authentication failed!" << endl;
            return false;
        }

        if (amount <= 0) {
            cout << "Deposit amount must be positive!" << endl;
            return false;
        }

        balance += amount;
        addTransaction("Deposit", amount);
        cout << "Deposit successful. New balance: $" << balance << endl;
        return true;
    }

    // Withdraw with validation
    bool withdraw(double amount, string enteredPin) {
        if (!validatePin(enteredPin) || isAccountLocked()) {
            cout << "Authentication failed!" << endl;
            return false;
        }

        if (amount <= 0) {
            cout << "Withdrawal amount must be positive!" << endl;
            return false;
        }

        if (amount > balance) {
            cout << "Insufficient funds! Available: $" << balance << endl;
            return false;
        }

        balance -= amount;
        addTransaction("Withdrawal", amount);
        cout << "Withdrawal successful. New balance: $" << balance << endl;
        return true;
    }

    // Transfer between accounts
    bool transferTo(BankAccount &destination, double amount, string enteredPin) {
        if (withdraw(amount, enteredPin)) {
            destination.deposit(amount, destination.pin);
            addTransaction("Transfer Out to " + destination.accountHolderName, amount);
            return true;
        }
        return false;
    }

    // Display transaction history
    void showTransactionHistory(string enteredPin) {
        if (!validatePin(enteredPin) || isAccountLocked()) {
            cout << "Authentication failed!" << endl;
            return;
        }

        cout << "\n=== TRANSACTION HISTORY ===" << endl;
        for (const string &trans : transactionHistory) {
            cout << trans << endl;
        }
        cout << "===========================" << endl;
    }

    // Change PIN
    bool changePin(string oldPin, string newPin) {
        if (!validatePin(oldPin) || isAccountLocked()) {
            cout << "Authentication failed!" << endl;
            return false;
        }

        if (newPin.length() != 4) {
            cout << "PIN must be 4 digits!" << endl;
            return false;
        }

        pin = newPin;
        cout << "PIN changed successfully!" << endl;
        return true;
    }
};

int main() {
    // Test encapsulation
    BankAccount acc1("1234567890", "John Doe", "1234", 1000);

    // Valid operations
    acc1.deposit(500, "1234");
    acc1.withdraw(200, "1234");

    // Invalid operations (should be rejected)
    acc1.withdraw(2000, "1234");  // Insufficient funds
    acc1.deposit(-100, "1234");    // Negative amount

    // Wrong PIN (should fail)
    acc1.withdraw(100, "0000");

    // Show history
    acc1.showTransactionHistory("1234");
}

Task 2: Abstract Shape Class

#include <iostream>
#include <vector>
#include <cmath>
using namespace std;

// Abstract base class
class Shape {
protected:
    string color;
    string name;

public:
    Shape(string n, string c) : name(n), color(c) {}

    // Pure virtual functions
    virtual double area() const = 0;
    virtual double perimeter() const = 0;

    // Virtual function with default implementation
    virtual void display() const {
        cout << "\n=== " << name << " ===" << endl;
        cout << "Color: " << color << endl;
        cout << "Area: " << area() << endl;
        cout << "Perimeter: " << perimeter() << endl;
    }

    // Non-virtual function
    string getColor() const { return color; }
    void setColor(string c) { color = c; }

    virtual ~Shape() {}
};

class Rectangle : public Shape {
private:
    double length;
    double width;

public:
    Rectangle(string c, double l, double w) 
        : Shape("Rectangle", c), length(l), width(w) {
        if (length <= 0) length = 1;
        if (width <= 0) width = 1;
    }

    double area() const override {
        return length * width;
    }

    double perimeter() const override {
        return 2 * (length + width);
    }

    void display() const override {
        Shape::display();
        cout << "Length: " << length << ", Width: " << width << endl;
        cout << "Diagonal: " << sqrt(length*length + width*width) << endl;
    }

    void setDimensions(double l, double w) {
        if (l > 0) length = l;
        if (w > 0) width = w;
    }
};

class Circle : public Shape {
private:
    double radius;
    const double PI = 3.14159;

public:
    Circle(string c, double r) 
        : Shape("Circle", c), radius(r) {
        if (radius <= 0) radius = 1;
    }

    double area() const override {
        return PI * radius * radius;
    }

    double perimeter() const override {
        return 2 * PI * radius;
    }

    void display() const override {
        Shape::display();
        cout << "Radius: " << radius << endl;
        cout << "Diameter: " << 2 * radius << endl;
        cout << "Circumference: " << perimeter() << endl;
    }
};

class Triangle : public Shape {
private:
    double side1, side2, side3;

    bool isValidTriangle() const {
        return (side1 + side2 > side3) &&
               (side1 + side3 > side2) &&
               (side2 + side3 > side1);
    }

public:
    Triangle(string c, double s1, double s2, double s3) 
        : Shape("Triangle", c), side1(s1), side2(s2), side3(s3) {
        if (!isValidTriangle()) {
            side1 = side2 = side3 = 1;
        }
    }

    double area() const override {
        double s = (side1 + side2 + side3) / 2;
        return sqrt(s * (s - side1) * (s - side2) * (s - side3));
    }

    double perimeter() const override {
        return side1 + side2 + side3;
    }

    void display() const override {
        Shape::display();
        cout << "Sides: " << side1 << ", " << side2 << ", " << side3 << endl;
        cout << "Valid: " << (isValidTriangle() ? "Yes" : "No") << endl;
        cout << "Type: ";
        if (side1 == side2 && side2 == side3)
            cout << "Equilateral";
        else if (side1 == side2 || side2 == side3 || side1 == side3)
            cout << "Isosceles";
        else
            cout << "Scalene";
        cout << endl;
    }
};

int main() {
    vector<Shape*> shapes;

    shapes.push_back(new Rectangle("Red", 5, 3));
    shapes.push_back(new Circle("Blue", 4));
    shapes.push_back(new Triangle("Green", 3, 4, 5));
    shapes.push_back(new Rectangle("Yellow", 2.5, 2.5));

    double totalArea = 0;
    for (Shape* s : shapes) {
        s->display();
        totalArea += s->area();
    }

    cout << "\nTotal Area of all shapes: " << totalArea << endl;

    // Clean up
    for (Shape* s : shapes) {
        delete s;
    }
}

Task 3: Employee Hierarchy with Virtual Functions

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

class Employee {
protected:
    string name;
    int id;
    double baseSalary;

public:
    Employee(string n, int i, double sal) 
        : name(n), id(i), baseSalary(sal) {}

    virtual ~Employee() {}

    virtual double calculatePay() const = 0;
    virtual string getRole() const = 0;

    virtual void display() const {
        cout << "ID: " << id << ", Name: " << name;
        cout << ", Role: " << getRole();
        cout << ", Pay: $" << calculatePay();
    }

    int getId() const { return id; }
    string getName() const { return name; }
};

class Manager : public Employee {
private:
    double bonus;
    vector<int> teamMembers;

public:
    Manager(string n, int i, double sal, double b) 
        : Employee(n, i, sal), bonus(b) {}

    double calculatePay() const override {
        return baseSalary + bonus + (teamMembers.size() * 500);
    }

    string getRole() const override { return "Manager"; }

    void display() const override {
        Employee::display();
        cout << ", Bonus: $" << bonus;
        cout << ", Team size: " << teamMembers.size();
    }

    void addTeamMember(int empId) {
        teamMembers.push_back(empId);
    }
};

class Engineer : public Employee {
private:
    string specialization;
    int overtimeHours;
    double overtimeRate;

public:
    Engineer(string n, int i, double sal, string spec, double rate) 
        : Employee(n, i, sal), specialization(spec), 
          overtimeHours(0), overtimeRate(rate) {}

    double calculatePay() const override {
        return baseSalary + (overtimeHours * overtimeRate);
    }

    string getRole() const override { return "Engineer"; }

    void display() const override {
        Employee::display();
        cout << ", Specialization: " << specialization;
        cout << ", Overtime: " << overtimeHours << " hours";
    }

    void addOvertime(int hours) {
        if (hours > 0) {
            overtimeHours += hours;
        }
    }
};

class Intern : public Employee {
private:
    string school;
    int durationWeeks;

public:
    Intern(string n, int i, double sal, string sch, int weeks) 
        : Employee(n, i, sal), school(sch), durationWeeks(weeks) {}

    double calculatePay() const override {
        return baseSalary;  // Fixed stipend
    }

    string getRole() const override { return "Intern"; }

    void display() const override {
        Employee::display();
        cout << ", School: " << school;
        cout << ", Duration: " << durationWeeks << " weeks";
    }
};

class PayrollSystem {
private:
    vector<Employee*> employees;

public:
    void addEmployee(Employee* emp) {
        employees.push_back(emp);
    }

    void processPayroll() {
        cout << "\n=== PROCESSING PAYROLL ===" << endl;
        double totalPay = 0;

        for (Employee* emp : employees) {
            emp->display();
            cout << endl;
            totalPay += emp->calculatePay();
        }

        cout << "\nTOTAL PAYROLL: $" << totalPay << endl;
    }

    Employee* findEmployee(int id) {
        for (Employee* emp : employees) {
            if (emp->getId() == id) return emp;
        }
        return nullptr;
    }

    ~PayrollSystem() {
        for (Employee* emp : employees) {
            delete emp;
        }
    }
};

int main() {
    PayrollSystem payroll;

    payroll.addEmployee(new Manager("Alice", 1001, 80000, 10000));
    payroll.addEmployee(new Engineer("Bob", 1002, 70000, "C++", 50));
    payroll.addEmployee(new Intern("Carol", 1003, 2000, "MIT", 12));

    // Add some overtime
    Engineer* bob = dynamic_cast<Engineer*>(payroll.findEmployee(1002));
    if (bob) {
        bob->addOvertime(10);
    }

    payroll.processPayroll();
}

Summary Table

PillarPurposeHow AchievedKey Benefit
EncapsulationBundle data and methods, hide internal detailsPrivate members, public getters/settersData protection, controlled access
AbstractionShow essential features, hide implementationPure virtual functions, abstract classesSimplify complex systems
InheritanceReuse code, establish relationshipsDerive classes from base classesCode reusability, hierarchical organization
PolymorphismOne interface, many implementationsFunction overloading, virtual functionsFlexibility, extensibility

Scroll to Top