Object Oriented Programming in C++ for Beginners


Illustration of a C++ class and object relationship

What is Object-Oriented Programming?

Object-Oriented Programming is a programming paradigm centered around the concept of objects, which are instances of classes. Unlike procedural programming, object-oriented programming (OOP) offers a more effective way to structure complex programs, promotes code reusability, and models real-world systems in a more intuitive manner.

Class in Object-Oriented Programming

In C++, a class is a user-defined data type or blueprint that encapsulates data and functions into a single unit. It allows for organizing related attributes and functions under one structure. A class can use access specifiers such as public, private, and protected to control the visibility and accessibility of its members. Importantly, a class does not consume memory until an object is created from it. Common examples of classes include Student, Department, Person, and Animal.

Consider the following simple C++ program:


#include <iostream>    // preprocessor directive
using namespace std;       // namespace directive
class Department {   // Class name
public:        //Access modifier
    string name;    //attribute
    void displayInfo() {  // Function name
        cout << "Department: " << name;
    }
};   // Semicolon is mandatory

int main() {
    Department dept;  // Object creation
    dept.name = "Information Systems"; // value
    dept.displayInfo();   //Calling the function
return 0;
}

In the example above, the Department class acts as a blueprint and does not occupy memory by itself. However, when an object such as dept is instantiated, it consumes memory—either on the stack or the heap, depending on how the object is created.

Understanding Objects in OOP

In simple terms, an object is an instance of a class. When you declare a variable of a class type, you're creating an object. Each object has its own memory space, determined by the size of its attributes. For instance, in the class definition above, Department is the class, and dept is an object instantiated from it. This object will occupy memory to hold the values of its data members.

The Four Pillars of Object-Oriented Programming

Encapsulation is the process of combining data and the methods that work with it into a single entity, usually a class. Using access modifiers like private, public, and protected is essential for safeguarding an object's internal state.

In practice, a class is considered encapsulated when it defines private variables alongside public getter and setter methods. Encapsulation fundamentally involves controlling access to an object’s internal data, which is effectively achieved by restricting direct access to private variables and providing controlled access through public accessors

Special functions known as getter and setter methods give users regulated access to secret variables, enabling safe reading or modification of their contents without compromising the object's integrity.

Take a look at this basic example:


#include <iostream>  //preprocessor directive
using namespace std;   //namespace directive
class Student {
private:
    int age;     //private data member	
public:
    void setAge(int a) { //Setter function
            age = a;      //Assining new value to age variable  
    }
    int getAge() {    //Getter function
        return age;
    }
};
int main() {
    Student student1;  //Object creation
    student1.setAge(45); //Calling setAge()
    cout << "Age: " student1.getAge();
    return 0;
}


Note:In the example above, age is a private attribute and cannot be accessed directly by the main function. Therefore, we use indirect access through the setAge function to assign a new value and the getAge function to retrieve it.

Inheritance is the ability of a class to acquire properties and behaviors from another class. It helps hide complex implementation details by exposing only the essential features of an object, thereby reducing complexity and improving code readability.

In C++, the inheritance mode controls the accessibility of base class members within the derived class.

  • Public Inheritance Mode: All public members of the base class remain public in the derived class, and all protected members remain protected in the derived class.
    Syntax: class Derived : public Base
  • Protected Inheritance: Both public and protected members of the base class become protected in the derived class.
    Syntax: class Derived : protected Base
  • Private Inheritance: Both public and protected members of the base class become private in the derived class. Private inheritance is the default mode if no access specifier is provided.
    Syntax: class Derived : private Base

Depending on how a derived class interacts with its base class(es), inheritance can be categorized into the following types: Single, Multilevel, Multiple, Hierarchical, and Hybrid inheritance.

  1. Single Inheritance: A derived class inherits from only one base class.
    Example: A Student class inheriting from a Person class (Student : Person).
  2. Multiple Inheritance: A derived class inherits from more than one base class.
    Example: A Student class inheriting from both Person and Employee classes (Student : Person, Employee).
  3. Multilevel Inheritance: There is a chain of inheritance where a derived class is created from another derived class, which in turn is derived from a base class.
    Example: A Child class inheriting from a Father class, which itself is derived from an Grand Father class (Child :Father and Father : Grand Father).
  4. Hierarchical Inheritance: Multiple subclasses inherit from a single base class.
    Example: Both child and Father classes inherit from a common base class Grand Father (Child :Father and Father : Grand Father).
  5. Hybrid Inheritance: This type combines two or more types of inheritance, such as single, multiple, multilevel, or hierarchical inheritance.
    Example: A Student class inheriting from a Person class (Student : Person).

Consider the following example of Public Inheritance:


#include <iostream>  // preprocessor directive
using namespace std;   // namespace directive
class Person {  // Base class
public:
    int id;    // public variable
    void displayId() {  // public finction
        cout  < < "Person ID: "  < < id;
    }
};
class Student : public Person { //  public inheritance mode
public:
    void showId() { // public function
        cout  < <"Student ID: "  < < id;
    }
};
int main() {
    Student student1; // Object creation
    student1.id = 7; // value
	student1.showId();  // calling child function
    student1.displayId(); // calling a parent (or base) class function using a derived class object
    return 0;
}

Abstraction is the process of hiding complex implementation details while exposing only the essential features of an object. This concept helps reduce complexity and allows programmers to focus on higher-level interactions. For example, when you call a function, you only need to know what it does,not how it works internally.

In C++, abstraction can be implemented in two main ways:

  • By using abstract classes with pure virtual functions
  • By using access specifiers: private, protected, and public

Example: Abstraction using an Abstract Class

#include <iostream>
using namespace std;
class Person { // Abstract class
public:
    virtual void talk() = 0; // Pure virtual function
};
class Student : public Person { // Concrete class
public:
    void talk() override {
        cout  < < "I am Talking";
    }
};
int main() {
    Person* p = new Student(); // Object creation using a pointer to base class
    p->talk();
    delete p;
}

Polymorphism is the ability of objects to take on multiple forms depending on their context or usage. It enables different objects to respond uniquely to the same function call or message, allowing them to exhibit different behaviors while sharing a common interface.

This dynamic behavior enhances flexibility, scalability, and code reusability in object-oriented programming. As a fundamental pillar of OOP, polymorphism plays a key role in designing modular, maintainable, and extensible systems.

Polymorphism in C++ can be categorized as:

  • Compile-Time Polymorphism
  • Runtime Polymorphism

Compile-time Polymorphism is a mechanism where the compiler decides which function to execute during compilation, based on the function’s signature and the context of its call. This is commonly implemented through function overloading and operator overloading.

When multiple functions have the same name but different argument types or numbers, this is known as function overloading. This improves the clarity and flexibility of code by enabling a single function name to carry out many operations based on the arguments given. A fundamental aspect of object-oriented programming is the ability to define several functions with different parameter lists but the same name.

Below is an example demonstrating function overloading in C++:


#include <iostream>
using namespace std;
class Employee {
public:
    void display(int num) {
        cout<< "Result: "<<num;
    }
    void display(double num) {
        cout<< "Result: "<<num;
    }
};
int main() {
    Employee emp;
    emp.display(5);
    emp.display(10.5); 
    return 0;
}

Runtime Polymorphism is achieved through function overriding, where the decision about which function to invoke is made at runtime. This allows for greater flexibility, as the specific function implementation is determined based on the actual type of the object during program execution.

When a derived class defines a function with the same name and signature as one in its base class, this is known as function overriding. This supports runtime polymorphism by allowing derived class objects to define their own behavior while preserving a consistent interface.

Below is an example demonstrating function overriding in C++:


#include <iostream>
#include <string>
using namespace std;
class Department {
public:
    virtual void manage() {
        cout<<"The department can manage.";
    }
};
class InformationSystem : public Department {
public:
    void manage() override {
        cout<<"Information System can manage.";
    }
};
class ComputerScience : public Department {
public:
    void manage() override {
        cout<<"Computer Science can manage.";
    }
};
int main() {
    Department* is = new InformationSystem();
    Department* cs = new ComputerScience();
    is->manage();
    cs->manage();
    delete is;
    delete cs;
 
    return 0;
}

In the above example, the derived classes InformationSystems and ComputerScience override the manage() function from the Department class and execute their own specific tasks.

Yilma Goshime

I’m committed to providing tailored solutions and always ready to assist if any issue arises.

LetUsLearn