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
| Aspect | Procedural Programming | Object-Oriented Programming |
|---|---|---|
| Focus | Functions that perform operations | Objects that contain data and methods |
| Data | Data is separate from functions | Data and functions are bundled together |
| Organization | Organized around procedures/functions | Organized around objects/classes |
| Reusability | Functions can be reused | Classes can be reused and extended |
| Security | Data is globally accessible | Data can be hidden (encapsulation) |
| Real-world mapping | Doesn’t map well to real-world | Maps 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 unauthorized access
- Maintainability: Changes in one part don’t affect others
- Scalability: Easier to manage large, complex programs
Classes vs Objects
| Class | Object |
|---|---|
| Blueprint or template | Actual instance created from class |
| Defines properties and methods | Has actual values for properties |
| Exists at compile time | Created at runtime |
| No memory allocated until object created | Occupies memory |
Example: class Car | Example: 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++
| Specifier | Access Level | Description |
|---|---|---|
| private | Class only | Members cannot be accessed from outside the class |
| protected | Class + Derived classes | Members accessible in class and derived classes |
| public | Anywhere | Members accessible from anywhere |
Why Encapsulation?
- Data Protection: Prevents accidental or unauthorized 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
#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 Class | Concrete Class |
|---|---|
| Has at least one pure virtual function | No pure virtual functions |
| Cannot be instantiated | Can be instantiated |
| Serves as a blueprint | Complete implementation |
Why Abstraction?
- Simplifies Complexity: Users interact with 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
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() = 0makes 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 Access | Public Inheritance | Protected Inheritance | Private Inheritance |
|---|---|---|---|
| public | public in derived | protected in derived | private in derived |
| protected | protected in derived | protected in derived | private in derived |
| private | inaccessible | inaccessible | inaccessible |
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
| Concept | Virtual Functions | Virtual Inheritance |
|---|---|---|
| Purpose | Runtime polymorphism | Solve diamond problem |
| When used | Function overriding | Multiple inheritance |
| Effect | Dynamic dispatch | Single shared base instance |
| Keyword | virtual before function | virtual before base class |
| Memory impact | VTable per class | Extra 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
| Pillar | Purpose | How Achieved | Key Benefit |
|---|---|---|---|
| Encapsulation | Bundle data and methods, hide internal details | Private members, public getters/setters | Data protection, controlled access |
| Abstraction | Show essential features, hide implementation | Pure virtual functions, abstract classes | Simplify complex systems |
| Inheritance | Reuse code, establish relationships | Derive classes from base classes | Code reusability, hierarchical organization |
| Polymorphism | One interface, many implementations | Function overloading, virtual functions | Flexibility, extensibility |
