Object-oriented programming (OOP) is a widely-used programming paradigm in many modern programming languages, including C++. It is based on the concept of object and class. Classes in C++ define the properties and behavior of objects, including their data members and member functions.
OOP in C++ also supports several key concepts such as encapsulation, inheritance, and polymorphism that help to structure and organize code for large-scale software development, making it more efficient, modular maintainable, and reusable.
In object-oriented programming (OOP), a class is a blueprint or template for creating objects. It defines the properties and behaviors that the objects will have, such as attributes and methods. An object is a specific instance of a class that has been created with specific data. This approach allows us to define complex structures in a meaningful way and use them to solve real-world problems using the software.
We can use classes to create multiple objects, each with its own unique characteristics, which helps us write reusable code. On the other hand, by representing real-world entities as classes, we can better organize our code.
#include <iostream>
#include <string>
using namespace std;
class Employee {
private:
string name;
int age;
double salary;
int employeeId;
public:
Employee(string n, int a, double s, int id) {
name = n;
age = a;
salary = s;
employeeId = id;
}
void setName(string n) {
name = n;
}
string getName() {
return name;
}
void setAge(int a) {
age = a;
}
int getAge() {
return age;
}
void setSalary(double s) {
salary = s;
}
double getSalary() {
return salary;
}
void setEmployeeId(int id) {
employeeId = id;
}
int getEmployeeId() {
return employeeId;
}
double getSalaryWithBonus() {
return salary + (salary * 0.1);
}
void printEmployeeDetails() {
cout << "Name: " << name << endl;
cout << "Age: " << age << endl;
cout << "Salary: " << salary << endl;
cout << "Employee ID: " << employeeId << endl;
}
};
int main() {
Employee emp("Shubham", 30, 150000, 1001);
emp.printEmployeeDetails();
cout << "Salary with Bonus: " << emp.getSalaryWithBonus() << endl;
return 0;
}
In the above code, we have defined a C++ class called "Employee". It represents an employee in an organization, and has private member variables that store the employee's name, age, salary, and ID. The class also includes public member functions for setting and getting the private member variables, calculating the employee's salary with a bonus (10% of the salary), and for printing the employee's details.
In the main function, we have created an object of the Employee class using a constructor with the name, age, salary, and employee ID of an employee as arguments.
Encapsulation in C++ is the technique of hiding the implementation details of a class from the outside world and only exposing a public interface for interacting with the class. This is achieved by using access modifiers such as private, protected, and public.
By declaring class variables as private, and providing public methods to access and modify them, we can control how the data is accessed and modified. This ensures that the data within an object is accessed and modified in a controlled and consistent way, which can improve the reliability and security of the software.
Explore this blog for better understanding: Encapsulation in Object-Oriented Programming
class BankAccount {
private:
double balance;
int accountNumber;
public:
BankAccount(double b, int num) {
balance = b;
accountNumber = num;
}
void deposit(double amount) {
balance = balance + amount;
}
void withdraw(double amount) {
if (amount <= balance) {
balance = balance - amount;
} else {
cout << "Insufficient funds." << endl;
}
}
double getBalance() {
return balance;
}
int getAccountNumber() {
return accountNumber;
}
};
In the above example, the class BankAccount has encapsulated its data by making the variables balance and accountNumber private. This means that they cannot be accessed or modified by external code, providing a layer of protection for sensitive information.
The class also provides a public interface for interacting with the class through its public methods: deposit, withdraw, getBalance, and getAccountNumber. These methods are the only way for external code to access or modify the private variables. For example, the deposit method allows external code to deposit money into the bank account, and the getBalance method allows external code to check the balance of the bank account.
In large-scale software projects that involve millions of code lines, it is often necessary to reuse existing code or extend functionality according to requirements. To accomplish this, we use the concept of Inheritance in C++. It is a process in which a base class or superclass serves as a template for creating other classes, known as derived classes.
In Inheritance, the derived class inherits all properties and behaviors of its parent class. Each derived class can also implement its own methods while still being able to reuse the methods of the parent class. Even if the parent class is modified, all derived classes will inherit the new code.
Explore this blog for better understanding: Inheritance in OOPS
Via this example, we have demonstrated how inheritance allows the derived class to inherit the data members and member methods of the base class, and how the derived class can also have its own data members and member methods.
#include <iostream>
using namespace std;
class Shape {
public:
Shape() { cout << "Shape constructor called." << endl; }
~Shape() { cout << "Shape destructor called." << endl; }
void setWidth(int w) { width = w; }
void setHeight(int h) { height = h; }
int getWidth() { return width; }
int getHeight() { return height; }
protected:
int width;
int height;
};
class Rectangle: public Shape {
public:
Rectangle() { cout << "Rectangle constructor called." << endl; }
~Rectangle() { cout << "Rectangle destructor called." << endl; }
int getArea() { return width * height; }
};
class Square: public Shape {
public:
Square() { cout << "Square constructor called." << endl; }
~Square() { cout << "Square destructor called." << endl; }
void setSide(int s) { width = height = s; }
int getSide() { return width; }
int getArea() { return width * width; }
};
int main() {
Rectangle rect;
rect.setWidth(5);
rect.setHeight(7);
cout << "Width: " << rect.getWidth() << endl;
cout << "Height: " << rect.getHeight() << endl;
cout << "Area: " << rect.getArea() << endl;
Square sq;
sq.setSide(5);
cout << "Side: " << sq.getSide() << endl;
cout << "Area: " << sq.getArea() << endl;
return 0;
}
In the above code, the class "Shape" serves as the parent class, while "Rectangle" and "Square" are child classes that inherit properties and methods from "Shape". Here child classes also have their own unique properties and methods, such as the "getArea" method in the "Rectangle" class and "getArea", "setSide", and "getSide" methods in the "Square" class.
In the main function, we have created objects of the child classes and utilized both the inherited properties from the base class and the unique properties defined in the child classes. The "Rectangle" class inherits the "width" and "height" properties from "Shape" and calculates its area using them. The "Square" class also inherits these properties, but also has its own "setSide" and "getSide" methods to set and retrieve its side length and calculates its area using that side length.
Output
Shape constructor called.
Rectangle constructor called.
Width: 5
Height: 7
Area: 35
Shape constructor called.
Square constructor called.
Side: 5
Area: 25
Square destructor called.
Rectangle destructor called.
Shape destructor called.
Shape destructor called.
OOPS allows us to have many methods, all with the same name, doing a similar job on different data or data types. Suppose we need a few methods with similar traits in each subclass, but the parameters are different. This is where polymorphism comes into play. In simple words, Polymorphism presents the common interface to perform a single action in different ways.
Method overloading, an example of polymorphism in C++, enables multiple methods to have the same name, yet different parameters. The appropriate method is selected based on the type and number of arguments passed.
#include <iostream>
using namespace std;
class Shape {
public:
// function overloading
void draw(string color) {
cout << "Drawing a shape with color " << color << endl;
}
void draw(string color, int width) {
cout << "Drawing a shape with color " << color << " and width " << width << endl;
}
};
int main() {
Shape s;
s.draw("red");
s.draw("blue", 10);
return 0;
}
In this example, we have a class "Shape" that has two methods with the same name "draw", but with different parameters. The first method takes a single string parameter "color", and the second method takes two parameters "color" and "width". When the first method is called with one argument, it prints "Drawing a shape with color red", and when the second method is called with two arguments, it prints "Drawing a shape with color blue and width 10.
Operator overloading means that the operation performed by the operator depends on the type of operands provided to the operator method. It is similar to function overloading, where we have many versions of the same function differentiated by their parameter lists.
OOPS lets us extend operator overloading to user-defined types (classes) i.e., a programmer can provide their own operator to a class by overloading the built-in operator and perform some specific computation when the operator is used on objects of that class.
Explore this blog for better understanding: Operator overloading in C++
Polymorphism allows objects of different classes to be used interchangeably. One way this is achieved is through inheritance, where a base class is defined and other classes inherit from it. These derived classes can have their own unique methods and properties, but they can still be treated as objects of the base class.
For example, consider a game where different types of characters can be controlled by the player. Each character class has its own unique abilities but they all share some common properties.
Instead of writing separate code to handle each character class, we can create a base class called "Character" and have the other classes inherit from it. This way, all the characters can be treated as objects of the "Character" class and we can write code that works with any character without having to know the specific class it belongs to.
This allows for more flexibility and maintainability in the code. For example, if we want to add a new character class or change the behaviour of an existing class, we do not have to change the code that works with characters, because it is not dependent on the specific class.
class Character {
public:
int health;
int attack;
int defense;
virtual void attackEnemy() = 0;
virtual void takeDamage(int damage) = 0;
};
class Warrior: public Character {
public:
void attackEnemy() {
// warrior's attack code
}
void takeDamage(int damage) {
// warrior's take damage code
}
};
class Mage: public Character {
public:
void attackEnemy() {
// mage's attack code
}
void takeDamage(int damage) {
// mage's take damage code
}
};
class Thief: public Character {
public:
void attackEnemy() {
// thief's attack code
}
void takeDamage(int damage) {
// thief's take damage code
}
};
Character *playerCharacter = new Warrior();
playerCharacter->attackEnemy();
Character *enemyCharacter = new Thief();
enemyCharacter->takeDamage(10);
Abstraction is a fundamental concept in object-oriented programming. It allows the user to interact with an object through its interface without needing to know the underlying mechanics of how it is implemented.
For example, a basic example of abstraction in C++ is the use of classes and objects. A class defines the properties and behavior of an object, but the user does not need to know the specific implementation details of the object. Instead, the user can simply use the object and its methods as needed.
One of the main benefits of abstraction is that it makes modifications to the code more manageable. For example, when driving a car, one only needs to know the process of driving and not the mechanics of the car engine. Similarly, if the implementation details of an object or function change, the user's access to it remains the same.
class Shape {
public:
virtual double area() = 0; // pure virtual function
};
class Rectangle: public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) {
width = w;
height = h;
}
double area() {
return width * height;
}
};
class Circle: public Shape {
private:
double radius;
public:
Circle(double r) {
radius = r;
}
double area() {
return 3.14159 * radius * radius;
}
};
int main() {
Shape *shape1 = new Rectangle(10, 5);
Shape *shape2 = new Circle(2);
cout << "Area of rectangle: " << shape1->area() << endl;
cout << "Area of circle: " << shape2->area() << endl;
return 0;
}
In this example, the Shape class is an abstract class and Rectangle and Circle classes are concrete classes that inherit from Shape class. The area() function is a pure virtual function in Shape class, which means it needs to be implemented in derived classes. Using the object of the shape class, user can use the area() function without knowing how it's implemented.
In OOP, data and methods are combined and organized into objects. These objects can interact with each other by exposing certain data and methods to other objects, allowing them to communicate and work together. This allows for a more modular and organized approach to programming.
On the other hand, procedural programming is built around procedures or methods that operate on data. These procedures or methods are the building blocks of procedural programming, and they are typically used to perform specific tasks or operations on the data. In procedural programming, the focus is on breaking down the problem into smaller procedures or functions that can be executed in a specific order to achieve the desired outcome.
Enjoy learning, Enjoy OOPS!
Subscribe to get weekly content on data structure and algorithms, machine learning, system design and oops.