Fundamentals of Object Oriented Programming in C++

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.

What is Class and Object in C++?

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.

  • When a class is defined, no memory is allocated.
  • Memory is only allocated when an object is created.
  • Objects can interact by sending messages to one another without knowing each other's details.

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.

Example code to understand class and object in C++

#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: Hiding implementation details

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.

  • Encapsulation also allows for greater flexibility in code development, as the implementation details of a class can be changed without affecting the code of any other class that uses it.
  • Encapsulation also improves code readability by combining data and methods, making the code cleaner and easier to understand.
  • Encapsulation provides an abstraction between an object and the users of the object, so that users do not need to know the implementation details to use the object. This makes the code more user-friendly and easier to use, which can improve the maintainability of the software.

Explore this blog for better understanding: Encapsulation in Object-Oriented Programming

Example code to understand encapsulation in C++

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.

Inheritance: Reusing code and extending functionalities

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

Example code to understand Inheritance in C++

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.

Polymorphism: Performing a single action in different ways

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.

Polymorphism via method overloading in C++

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.

Polymorphism via Operator Overloading in C++

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.

Operator overloading in c++ code example

Operator overloading code example in c++

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++

How Polymorphism support code reusability with Inheritance?

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: Exposing essential details

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.

There are several ways to achieve abstraction in C++

  • Via Abstract Classes: An abstract class is a class that cannot be instantiated but can be inherited from. It can contain both concrete and pure virtual functions. Pure virtual functions are functions that have no implementation in the abstract class and must be implemented in derived classes. This allows the user to work with the object without knowing the specific implementation details of the pure virtual functions.
  • Via Interfaces: An interface is similar to an abstract class, but it only contains pure virtual functions. This means that it does not have any implementation details, and the user must use a concrete class that implements the interface to access the functionality.
  • Via Encapsulation: Encapsulation is the process of hiding the implementation details of an object or function and only exposing its interface or functionality to the user. This is achieved by making the data members of a class private and providing public member functions to access and modify the data.
  • Via Function Templates: Function templates allow you to write a single function that can work with different data types. The implementation details of the function are hidden from the user, who only needs to know the function's interface.

Example code to understand abstraction in C++

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.

Advantages of OOPS

  • Makes code easy to maintain and modify: In OOPS, each object has its own data and behavior, and the interactions between objects are defined through methods. This modular approach makes it easier to identify and make changes to specific parts of the code without affecting the entire system.
  • Helps us write reusable code: OOP promotes code reuse through the use of classes and objects. Classes can be used as templates for creating new objects, and objects can be used in multiple parts of the code. This reduces the amount of duplicate code and makes it easier to add new features to the system.
  • Provide data security and control data accessibility: OOP allows for the encapsulation of data through the use of private and protected access modifiers. This means that the data can only be accessed and modified through the class's methods, providing a level of security and control over the data.
  • Provide a mechanism to support high code cohesion: Code cohesion refers to the degree to which the elements of a module belong together. In OOP, classes and objects provide a natural way to group related data and behavior together, resulting in high code cohesion.
  • Provide a mechanism to minimize code coupling or dependency: Code coupling refers to the degree to which one module depends on another. OOP promotes the use of encapsulation, inheritance, and polymorphism to minimize code coupling and dependency between modules.
  • Improve code readability and ease in documentation: OOP makes the code more readable and easier to understand by breaking it down into smaller, more manageable pieces. Additionally, the use of class and object diagrams, and commenting can make the code more self-documenting.
  • Simplify large-scale software development: OOP allows for the development of large-scale software systems by breaking them down into smaller, more manageable pieces. This makes it easier to identify and correct bugs, and to add new features to the system.

Procedural programming vs Object-oriented programming

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.

Object-oriented programming vs Procedural programming

Additional blogs to explore

Enjoy learning, Enjoy OOPS!

Share feedback with us

More blogs to explore

Our weekly newsletter

Subscribe to get weekly content on data structure and algorithms, machine learning, system design and oops.

© 2022 Code Algorithms Pvt. Ltd.

All rights reserved.