C++ · Computer Science · OOP

Introduction to
Object-Oriented
Programming

A complete guide to the four pillars of OOP — encapsulation, abstraction, inheritance, and polymorphism — with full C++ examples.

💡

What is Object-Oriented Programming?

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

OOP is one of the most popular programming paradigms and forms the foundation of many modern languages including C++, Java, Python, and C#. Understanding OOP is essential for building large, maintainable software systems.

Further reading: MDN — Object-oriented programming · Britannica — OOP


⚖️

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
OrganisationOrganised around procedures/functionsOrganised 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-world entitiesMaps naturally to real-world entities

Why OOP?

  • Modularity: Objects are independent, making them easier to maintain and debug
  • Reusability: Classes can be reused across different programs
  • Flexibility: Easy to add new features without changing existing code
  • Security: Data hiding prevents unauthorised access
  • Maintainability: Changes in one part don't affect others
  • 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 cutter defines the shape, but only the actual cookies have dough, flavour, and sprinkles.

🏛️

The Four Pillars of OOP

OBJECT-ORIENTED PROGRAMMING │ ┌────────┼────────┬────────────┐ │ │ │ │ Encap- Abstr- Inher- Poly- sulat. action itance morphism │ │ │ │ └────────┴────────┴────────────┘
🔒

Encapsulation

Bundle data and methods; restrict direct access to internal components via access specifiers.

🎭

Abstraction

Hide complex implementation details; expose only essential features through abstract interfaces.

🌳

Inheritance

Derive new classes from existing ones, inheriting properties and establishing "is-a" relationships.

🔀

Polymorphism

One interface, many implementations. The same function call behaves differently depending on the object.


🔒

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.

See also: cppreference — Access specifiers · GFG — Encapsulation in C++

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?

  • Data Protection: Prevents accidental or unauthorised modification
  • Control: You can add validation logic before changing data
  • Flexibility: You can change internal implementation without affecting external code
  • Maintainability: Easier to debug because data access is controlled
  • Abstraction: Users don't need to know how data is stored internally

Complete Encapsulation Example — Bank Account System

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

class BankAccount {
private:
    string accountNumber;
    string accountHolderName;
    double balance;
    string pinCode;
    int failedLoginAttempts;

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

public:
    BankAccount(string accNo, string name, string pin, double initialDeposit = 0) {
        accountNumber = accNo;
        accountHolderName = name;
        pinCode = pin;
        balance = (initialDeposit >= 0) ? initialDeposit : 0;
        failedLoginAttempts = 0;
        logTransaction("Account Created", initialDeposit);
    }

    string getAccountNumber() {
        return "XXXX-XXXX-" + accountNumber.substr(accountNumber.length() - 4);
    }

    double getBalance(string enteredPin) {
        if (validatePin(enteredPin)) {
            failedLoginAttempts = 0;
            return balance;
        } else {
            failedLoginAttempts++;
            cout << "Invalid PIN. Attempt " << failedLoginAttempts << " of 3." << endl;
            if (failedLoginAttempts >= 3)
                cout << "ACCOUNT LOCKED! Contact customer support." << endl;
            return -1;
        }
    }

    bool deposit(double amount, string enteredPin) {
        if (!validatePin(enteredPin)) { cout << "Invalid PIN." << endl; return false; }
        if (amount <= 0)          { cout << "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." << endl; return false; }
        if (amount <= 0)             { cout << "Amount must be positive." << endl; return false; }
        if (amount > balance)          { cout << "Insufficient funds." << 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);
            cout << "Transfer successful to: " << destination.getAccountNumber() << endl;
            return true;
        }
        return false;
    }

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

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

    // acc1.balance = 1000000;  // ERROR: 'balance' is private
    // cout << acc1.pinCode;   // ERROR: 'pinCode' is private

    acc1.displaySummary("1234");
    acc1.deposit(500, "1234");
    acc1.withdraw(300, "1234");
    acc1.transferTo(acc2, 200, "1234");
    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.

Further reading: cppreference — Abstract classes · GFG — Abstraction in C++

Abstract Class vs Concrete Class

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?

  • Simplifies Complexity: Users interact with a simple interface
  • Reduces Learning Curve: Don't need to understand internal workings
  • Improves Maintainability: Internal changes don't affect users
  • Enforces Consistency: All derived classes must implement abstract methods
  • Promotes Extensibility: Easy to add new types
Pure Virtual Syntax
Declare a pure virtual function with = 0: virtual double calculateArea() = 0; — any class containing one becomes abstract and cannot be instantiated.

Complete Abstraction Example — Shape Hierarchy

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

class Shape {
protected:
    string color, name;
public:
    Shape(string shapeName, string shapeColor) : name(shapeName), color(shapeColor) {}
    virtual double calculateArea()      = 0;
    virtual double calculatePerimeter() = 0;
    virtual void display() {
        cout << "\n=== " << name << " ===\nColor: " << color
             << "\nArea: "      << calculateArea()
             << "\nPerimeter: " << calculatePerimeter() << endl;
    }
    virtual ~Shape() {}
};

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) radius=1; }
    double calculateArea()      override { return PI*radius*radius; }
    double calculatePerimeter() override { return 2*PI*radius; }
    void display() override { Shape::display(); cout << "Radius: " << radius << endl; }
};

class Rectangle : public Shape {
private: double length, width;
public:
    Rectangle(string c, double l, double w) : Shape("Rectangle",c), length(l), width(w) {}
    double calculateArea()      override { return length*width; }
    double calculatePerimeter() override { return 2*(length+width); }
};

class Triangle : public Shape {
private: double side1, side2, side3;
public:
    Triangle(string c, double s1, double s2, double s3)
        : Shape("Triangle",c), side1(s1), side2(s2), side3(s3) {}
    double calculateArea() override {
        double s=(side1+side2+side3)/2;
        return sqrt(s*(s-side1)*(s-side2)*(s-side3));
    }
    double calculatePerimeter() override { return side1+side2+side3; }
};

class ShapeManager {
private: vector<Shape*> shapes;
public:
    void   addShape(Shape* s)   { shapes.push_back(s); }
    void   displayAll()         { for(Shape* s:shapes) s->display(); }
    double totalArea()          { double t=0; for(Shape* s:shapes) t+=s->calculateArea(); return t; }
    ~ShapeManager()             { for(Shape* s:shapes) delete s; }
};

int main() {
    ShapeManager m;
    m.addShape(new Circle("Red",5));
    m.addShape(new Rectangle("Blue",4,6));
    m.addShape(new Triangle("Green",3,4,5));
    m.displayAll();
    cout << "Total area: " << m.totalArea() << endl;
}

Key Points About Abstraction

  • Pure Virtual Functions: virtual f() = 0 makes a class abstract
  • Cannot Instantiate: Abstract classes cannot create objects directly
  • 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 behaviours from an existing class (base/parent class). It establishes an "is-a" relationship between classes.

See also: cppreference — Derived classes · GFG — Inheritance in C++

Types of Inheritance in C++

1. Single A 2. Multiple A B │ │ │ ▼ └─ C ─┘ B 3. Multilevel A 4. Hierarchical A │ ┌──┴──┐ ▼ ▼ ▼ B B C │ ▼ C

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 — Person Hierarchy

class Person {
protected:
    string name; int age; string id;
public:
    Person(string n, int a, string idNum) : name(n), age(a), id(idNum) {}
    virtual ~Person() {}
    virtual void introduce() {
        cout << "Hi, I'm " << name << ", Age: " << age << endl;
    }
    string getName() { return name; }
};

class Student : public Person {
private:
    string studentId; double gpa; vector<string> courses;
public:
    Student(string n, int a, string idNum, string sId, double g)
        : Person(n,a,idNum), studentId(sId), gpa(g) {}
    void introduce() override {
        Person::introduce();
        cout << "Student ID: " << studentId << ", GPA: " << gpa << endl;
    }
    void addCourse(string c) { courses.push_back(c); }
    void study() { cout << name << " is studying." << endl; }
};

class Teacher : public Person {
private:
    string employeeId, department; double salary;
public:
    Teacher(string n, int a, string idNum, string eId, string dept, double sal)
        : Person(n,a,idNum), employeeId(eId), department(dept), salary(sal) {}
    void introduce() override {
        Person::introduce();
        cout << "Dept: " << department << ", Salary: $" << salary << endl;
    }
    void teach(string c) { cout << name << " is teaching " << c << endl; }
};
class GraduateStudent : public Student {
private:
    string thesisTopic, supervisor;
public:
    GraduateStudent(string n, int a, string idNum,
                    string sId, double g, string topic, string sup)
        : Student(n,a,idNum,sId,g), thesisTopic(topic), supervisor(sup) {}
    void introduce() override {
        Student::introduce();
        cout << "Thesis: " << thesisTopic << ", Supervisor: " << supervisor << endl;
    }
    void research()    { cout << name << " is researching " << thesisTopic << endl; }
    void writeThesis() { cout << name << " is writing thesis..." << endl; }
};
class TeachingAssistant : public Student, public Teacher {
private:
    string contractType; double hoursPerWeek;
public:
    TeachingAssistant(string n, int a, string idNum,
                      string sId, double g,
                      string eId, string dept, double sal,
                      string contract, double hours)
        : Student(n,a,idNum,sId,g),
          Teacher(n,a,idNum,eId,dept,sal),
          contractType(contract), hoursPerWeek(hours) {}
    void introduce() {
        cout << "Name: "     << Student::getName() << endl;
        cout << "Contract: " << contractType << ", " << hoursPerWeek << " hrs/wk" << endl;
    }
};

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.

See also: cppreference — Virtual functions · GFG — Polymorphism in C++

Types of Polymorphism

POLYMORPHISM │ ┌────────────┴────────────┐ │ │ Compile-Time Run-Time (Static Binding) (Dynamic Binding) │ │ ┌────┴────┐ │ │ │ │ Func. Operator Virtual Overload Overload Functions

1. Function Overloading (Compile-Time)

class Calculator {
public:
    int    add(int a, int b)           { return a+b; }
    int    add(int a, int b, int c)    { return a+b+c; }
    double add(double a, double b)   { return a+b; }
    string add(string a, string b)     { return a+b; }
    double add(int a, double b)       { return a+b; }
};

class Display {
public:
    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 << i << " and " << s << endl; }
};

int main() {
    Calculator calc;
    cout << calc.add(5, 10)           << endl;
    cout << calc.add(3.14, 2.86)      << endl;
    cout << calc.add("Hello ", "World") << endl;
}

2. Operator Overloading (Compile-Time)

class ComplexNumber {
private: double real, imag;
public:
    ComplexNumber(double r=0, double i=0) : real(r), imag(i) {}
    ComplexNumber operator+(const ComplexNumber& o) const { return {real+o.real, imag+o.imag}; }
    ComplexNumber operator-(const ComplexNumber& o) const { return {real-o.real, imag-o.imag}; }
    ComplexNumber operator*(const ComplexNumber& o) const {
        return {real*o.real - imag*o.imag, real*o.imag + imag*o.real};
    }
    bool operator==(const ComplexNumber& o) const { return real==o.real && imag==o.imag; }
    ComplexNumber& operator++() { ++real; ++imag; return *this; }
    friend ostream& operator<<(ostream& os, const ComplexNumber& c) {
        os << c.real;
        c.imag >= 0 ? os << " + " << c.imag << "i" : os << " - " << -c.imag << "i";
        return os;
    }
    double magnitude() const { return sqrt(real*real + imag*imag); }
};

3. Virtual Functions — Run-Time Polymorphism

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 calculateSalary() const = 0;
    virtual string getRole() const = 0;
    virtual void displayInfo() const {
        cout << "ID: " << id << " Name: " << name
             << " Role: " << getRole()
             << " Salary: $" << calculateSalary() << endl;
    }
};

class Manager : public Employee {
    double bonus; int teamSize;
public:
    Manager(string n, int i, double sal, double b, int sz)
        : Employee(n,i,sal), bonus(b), teamSize(sz) {}
    double calculateSalary() const override { return baseSalary+bonus+teamSize*1000; }
    string getRole() const override { return "Manager"; }
};

class Engineer : public Employee {
    string spec; int overtime; double rate;
public:
    Engineer(string n, int i, double sal, string s, int h, double r)
        : Employee(n,i,sal), spec(s), overtime(h), rate(r) {}
    double calculateSalary() const override { return baseSalary+overtime*rate; }
    string getRole() const override { return "Engineer"; }
};

class Intern : public Employee {
    string school; int weeks;
public:
    Intern(string n, int i, double sal, string s, int w)
        : Employee(n,i,sal), school(s), weeks(w) {}
    double calculateSalary() const override { return baseSalary; }
    string getRole() const override { return "Intern"; }
};

int main() {
    vector<Employee*> company;
    company.push_back(new Manager("Alice",1001,80000,10000,8));
    company.push_back(new Engineer("Bob",1002,70000,"C++",10,50));
    company.push_back(new Intern("Carol",1003,2000,"MIT",12));
    double total=0;
    for(Employee* e:company){ e->displayInfo(); total+=e->calculateSalary(); }
    cout << "Total payroll: $" << total << endl;
    for(Employee* e:company) delete e;
}

4. Virtual Functions Deep Dive — VTable

class Base {
public:
    void nonVirtual()           { cout << "Base::nonVirtual()"  << endl; }
    virtual void virtualFunc()  { cout << "Base::virtualFunc()" << endl; }
    virtual void pureVirtual() = 0;
    virtual ~Base() {}
};

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

int main() {
    Base* ptr = new Derived();
    ptr->nonVirtual();  // Compile-time: Base::nonVirtual()
    ptr->virtualFunc(); // Run-time:     Derived::virtualFunc()
    ptr->pureVirtual(); // Run-time:     Derived::pureVirtual()
    delete ptr;
}
Always use virtual destructors
Without a virtual destructor in the base class, deleting a derived object through a base pointer causes undefined behaviour. The derived class destructor will never be called, leading to resource leaks.

💎

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 both share a common base class. This creates two separate copies of the base class, causing ambiguity when accessing inherited members.

See also: cppreference — Virtual base classes · GFG — Multiple Inheritance

Person / \ Student Teacher \ / TeachingAssistant Without virtual: TA contains TWO Person sub-objects → ambiguity With virtual: TA contains ONE shared Person sub-object → solved
class Person {
protected: string name; int age;
public:
    Person(string n="", int a=0) : name(n), age(a) {}
    virtual ~Person() {}
    string getName() { return name; }
};

class Student : public Person {        // Person copy #1
protected: string studentId;
public:
    Student(string n, int a, string id) : Person(n,a), studentId(id) {}
};

class Teacher : public Person {        // Person copy #2
protected: string employeeId;
public:
    Teacher(string n, int a, string id) : Person(n,a), employeeId(id) {}
};

class TeachingAssistant : public Student, public Teacher {
public:
    TeachingAssistant(string n, int a, string sId, string eId)
        : Student(n,a,sId), Teacher(n,a,eId) {}
    void demonstrate() {
        // getName();               // ERROR: ambiguous!
        cout << Student::getName(); // must qualify
        cout << Teacher::getName(); // must qualify
    }
};
class VStudent : virtual public Person {
protected: string studentId;
public:
    VStudent(string n, int a, string id) : Person(n,a), studentId(id) {}
};

class VTeacher : virtual public Person {
protected: string employeeId;
public:
    VTeacher(string n, int a, string id) : Person(n,a), employeeId(id) {}
};

class VTeachingAssistant : public VStudent, public VTeacher {
private: string contractType;
public:
    VTeachingAssistant(string n, int a, string sId, string eId, string c)
        : Person(n,a),         // constructs the shared Person ONCE
          VStudent(n,a,sId),
          VTeacher(n,a,eId),
          contractType(c) {}
    void introduce() {
        cout << name << ", TA"   << endl; // no ambiguity
        cout << getName() << endl;        // direct access works
    }
};

// Memory layout:
// Without virtual: [Person][Student]  [Person][Teacher]  [TA] — 2 Persons
// With virtual:    [Person (shared)]  [Student]  [Teacher]  [TA] — 1 Person

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

Practice tip
Try these tasks in Compiler Explorer or Replit to run them online without installing a compiler.

Task 1 — Bank Account System (Encapsulation)

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

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

    string getTime() { time_t now=time(0); return ctime(&now); }
    void log(string type, double amount) {
        transactionHistory.push_back(getTime()+": "+type+" $"+to_string(amount)+" Bal:$"+to_string(balance));
    }
    bool auth(string p) { if(p==pin){failedAttempts=0;return true;} failedAttempts++; return false; }
    bool locked() { return failedAttempts>=MAX_FAILED; }

public:
    BankAccount(string acc, string name, string pinCode, double init=0)
        : accountNumber(acc), accountHolderName(name), pin(pinCode),
          balance(init>=0?init:0), isActive(true), failedAttempts(0) { log("Created",init); }

    bool deposit(double a, string p) {
        if(!auth(p)||locked()) { cout<<"Auth failed\n"; return false; }
        if(a<=0)               { cout<<"Positive amounts only\n"; return false; }
        balance+=a; log("Deposit",a); cout<<"Deposited $"<<a<<". Bal: $"<<balance<<"\n"; return true;
    }
    bool withdraw(double a, string p) {
        if(!auth(p)||locked()) { cout<<"Auth failed\n"; return false; }
        if(a<=0)               { cout<<"Positive amounts only\n"; return false; }
        if(a>balance)           { cout<<"Insufficient funds\n"; return false; }
        balance-=a; log("Withdrawal",a); cout<<"Withdrew $"<<a<<". Bal: $"<<balance<<"\n"; return true;
    }
    void showHistory(string p) {
        if(!auth(p)||locked()) { cout<<"Auth failed\n"; return; }
        cout<<"\n=== HISTORY ===\n";
        for(const string& t:transactionHistory) cout<<t;
    }
};

int main() {
    BankAccount acc("1234567890","John Doe","1234",1000);
    acc.deposit(500,"1234");
    acc.withdraw(200,"1234");
    acc.withdraw(2000,"1234");
    acc.withdraw(100,"0000");
    acc.showHistory("1234");
}

Task 2 — Abstract Shape Class (Abstraction)

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

class Shape {
protected: string color, name;
public:
    Shape(string n, string c) : name(n), color(c) {}
    virtual double area()      const=0;
    virtual double perimeter() const=0;
    virtual void display() const {
        cout<<"\n=== "<<name<<" ===\nColor: "<<color
            <<"\nArea: "<<area()<<"\nPerimeter: "<<perimeter()<<endl;
    }
    virtual ~Shape() {}
};

class Rectangle : public Shape {
    double l,w;
public:
    Rectangle(string c,double l,double w):Shape("Rectangle",c),l(l>0?l:1),w(w>0?w:1){}
    double area()      const override{return l*w;}
    double perimeter() const override{return 2*(l+w);}
    void display() const override{Shape::display();cout<<"Is square: "<<(l==w?"Yes":"No")<<endl;}
};

class Circle : public Shape {
    double r; const double PI=3.14159;
public:
    Circle(string c,double r):Shape("Circle",c),r(r>0?r:1){}
    double area()      const override{return PI*r*r;}
    double perimeter() const override{return 2*PI*r;}
};

class Triangle : public Shape {
    double s1,s2,s3;
public:
    Triangle(string c,double a,double b,double d):Shape("Triangle",c),s1(a),s2(b),s3(d){}
    double area() const override{double s=(s1+s2+s3)/2;return sqrt(s*(s-s1)*(s-s2)*(s-s3));}
    double perimeter() const override{return s1+s2+s3;}
};

int main() {
    vector<Shape*> shapes={new Rectangle("Red",5,3),new Circle("Blue",4),new Triangle("Green",3,4,5)};
    double total=0;
    for(Shape* s:shapes){s->display();total+=s->area();}
    cout<<"\nTotal Area: "<<total<<endl;
    for(Shape* s:shapes) delete s;
}

Task 3 — Employee Hierarchy with Virtual Functions (Polymorphism)

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

class Employee {
protected: string name; int id; double baseSalary;
public:
    Employee(string n,int i,double s):name(n),id(i),baseSalary(s){}
    virtual~Employee(){}
    virtual double calculatePay() const=0;
    virtual string getRole() const=0;
    virtual void display() const{
        cout<<"ID: "<<id<<" | "<<name<<" | "<<getRole()<<" | $"<<calculatePay()<<endl;
    }
    int getId() const{return id;}
};

class Manager:public Employee{
    double bonus; vector<int> team;
public:
    Manager(string n,int i,double s,double b):Employee(n,i,s),bonus(b){}
    double calculatePay() const override{return baseSalary+bonus+team.size()*500;}
    string getRole() const override{return "Manager";}
    void addMember(int id){team.push_back(id);}
};

class Engineer:public Employee{
    string spec; int overtime; double rate;
public:
    Engineer(string n,int i,double s,string sp,double r):Employee(n,i,s),spec(sp),overtime(0),rate(r){}
    double calculatePay() const override{return baseSalary+overtime*rate;}
    string getRole() const override{return "Engineer ("+spec+")";}
    void addOvertime(int h){if(h>0)overtime+=h;}
};

class Intern:public Employee{
    string school; int weeks;
public:
    Intern(string n,int i,double s,string sc,int w):Employee(n,i,s),school(sc),weeks(w){}
    double calculatePay() const override{return baseSalary;}
    string getRole() const override{return "Intern @ "+school;}
};

class PayrollSystem{
    vector<Employee*> employees;
public:
    void add(Employee* e){employees.push_back(e);}
    Employee* find(int id){
        for(auto* e:employees)if(e->getId()==id)return e;
        return nullptr;
    }
    void run(){
        double total=0; cout<<"\n=== PAYROLL ===\n";
        for(auto* e:employees){e->display();total+=e->calculatePay();}
        cout<<"TOTAL: $"<<total<<endl;
    }
    ~PayrollSystem(){for(auto* e:employees)delete e;}
};

int main(){
    PayrollSystem p;
    p.add(new Manager("Alice",1001,80000,10000));
    p.add(new Engineer("Bob",1002,70000,"C++",50));
    p.add(new Intern("Carol",1003,2000,"MIT",12));
    if(Engineer* b=dynamic_cast<Engineer*>(p.find(1002))) b->addOvertime(10);
    p.run();
}

📊

Summary

🔒 Encapsulation
Purpose: Bundle data and methods; hide internal details
How: Private members, public getters/setters
Benefit: Data protection, controlled access
🎭 Abstraction
Purpose: Show essential features, hide implementation
How: Pure virtual functions, abstract classes
Benefit: Simplify complex systems
🌳 Inheritance
Purpose: Reuse code, establish relationships
How: Derive classes from base classes
Benefit: Code reusability, hierarchy
🔀 Polymorphism
Purpose: One interface, many implementations
How: Function overloading, virtual functions
Benefit: Flexibility, extensibility
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 organisation
PolymorphismOne interface, many implementationsFunction overloading, virtual functionsFlexibility, extensibility

Further Resources

Scroll to Top