Here is a simple and easy tutorial on Object-Oriented Programming (OOP) in C++ programming language.
1. Introduction to OOP
2. Classes and Objects
3. Constructors and Destructors
4. Access Specifiers
5. Member Functions
6. Inheritance
7. Polymorphism
8. Encapsulation
9. Abstraction
10. Operator Overloading
1. Introduction to OOP
Object-Oriented Programming (OOP) is a programming paradigm that uses objects and classes to organize code. Learn about the key concepts and principles of OOP.
2. Classes and Objects
Learn how to define classes and create objects. Classes act as blueprints for objects, and objects are instances of those classes.
In C++, classes and objects are fundamental concepts in object-oriented programming (OOP). They allow you to create structured, reusable code by defining blueprints for objects and then creating instances of those objects. Let's explore classes and objects in C++:
Classes:
A class in C++ is a user-defined data type that serves as a blueprint or template for creating objects. It defines a set of attributes (data members) and methods (member functions) that represent the properties and behaviors of objects belonging to that class.
Here's a basic example of defining a class in C++:
#include <iostream>
using namespace std;
class Rectangle {
public:
// Data members (attributes)
double length;
double width;
// Member functions (methods)
double calculateArea() {
return length * width;
}
};
In this example, we've defined a class named Rectangle with data members length and width to represent the dimensions of a rectangle and a member function calculateArea() to calculate its area.
Objects
Objects are instances of classes. They are created based on the blueprint provided by the class. Each object has its own set of data members, and you can call the member functions on these objects to perform actions or operations associated with the class.
Here's how you can create objects of the Rectangle class and use
int main() {
// Create two Rectangle objects
Rectangle rect1;
Rectangle rect2;
// Initialize the attributes of rect1
rect1.length = 5.0;
rect1.width = 3.0;
// Initialize the attributes of rect2
rect2.length = 7.0;
rect2.width = 4.0;
// Calculate and display the areas of the rectangles
cout << "Area of rect1: " << rect1.calculateArea() << endl;
cout << "Area of rect2: " << rect2.calculateArea() << endl;
return 0;
}
In this example, we create two Rectangle objects, rect1 and rect2, and set their attributes (length and width). We then call the calculateArea() method on each object to calculate and display their areas.
In summary, classes provide a blueprint for creating objects in C++. Objects are instances of classes, and they encapsulate data and behavior into a single unit, making it easier to model real-world entities and create reusable code.
A C++ Program example to create and use a car class and its objects.
#include <iostream>
using namespace std;
class Car {
public:
string brand;
string model;
int year;
};
int main() {
Car myCar;
myCar.brand = "Toyota";
myCar.model = "Camry";
myCar.year = 2022;
cout << "Car: " << myCar.brand << " " << myCar.model << " " << myCar.year << endl;
return 0;
}
3. Constructors and Destructors
In C++, constructors and destructors are special member functions in a class that
allow you to initialize and clean up objects of that class, respectively.
Let's explain constructors and destructors in the context of the Rectangle
class, and provide an example program:
Constructors
A constructor is a special member function that is automatically called when an
object of a class is created. It is responsible for initializing the object's
data members. Constructors have the same name as the class and do not have
a return type (not even void). You can have multiple constructors with
different parameter lists, allowing you to create objects in different ways.
Here's how you can define and use constructors in the Rectangle class:
#include <iostream>
using namespace std;
class Rectangle {
public:
// Parameterized constructor
Rectangle(double len, double wid) {
length = len;
width = wid;
}
// Member function to calculate area
double calculateArea() {
return length * width;
}
private:
double length;
double width;
};
int main() {
// Create Rectangle objects using the parameterized constructor
Rectangle rect1(5.0, 3.0);
Rectangle rect2(7.0, 4.0);
// Calculate and display the areas of the rectangles
cout << "Area of rect1: " << rect1.calculateArea() << endl;
cout << "Area of rect2: " << rect2.calculateArea() << endl;
return 0;
}
In this example, we've added a parameterized constructor to the Rectangle class, which takes two arguments (len and wid) and initializes the length and width data members accordingly. When we create rect1 and rect2 objects, the constructor is automatically called with the specified arguments, initializing the objects with the given dimensions.
Destructors
A destructor is another special member function in a class, and it is automatically called when an object goes out of scope or is explicitly destroyed. The destructor is responsible for cleaning up any resources acquired by the object.
Here's how you can add a destructor to the Rectangle class:
#include <iostream>
using namespace std;
class Rectangle {
public:
// Parameterized constructor
Rectangle(double len, double wid) {
length = len;
width = wid;
}
// Destructor
~Rectangle() {
cout << "Rectangle object destroyed." << endl;
}
// Member function to calculate area
double calculateArea() {
return length * width;
}
private:
double length;
double width;
};
int main() {
// Create Rectangle objects using the parameterized constructor
Rectangle rect1(5.0, 3.0);
Rectangle rect2(7.0, 4.0);
// Calculate and display the areas of the rectangles
cout << "Area of rect1: " << rect1.calculateArea() << endl;
cout << "Area of rect2: " << rect2.calculateArea() << endl;
// Objects go out of scope, and destructors are called automatically
return 0;
}
In this updated example, we've added a destructor to the Rectangle class, which simply prints a message when the object is destroyed. When rect1 and rect2 go out of scope at the end of the main function, their destructors are automatically called to clean up the objects.
4. Access Specifiers
Learn about public, private, and protected access specifiers and how they control the visibility of class members.
Access specifiers in C++ are keywords that control the visibility and accessibility of class members (data members and member functions) from outside the class. There are three main access specifiers in C++:
Public: Members declared as public are accessible from anywhere in the program. They can be accessed by objects of the class and external functions or classes.
Private: Members declared as private are only accessible within the class. They cannot be accessed directly from outside the class.
Protected: Members declared as protected are similar to private members, but they are accessible by derived classes (in the context of inheritance).
Let's use the Rectangle class to illustrate access specifiers:
#include <iostream>
using namespace std;
class Rectangle {
public: // Public access specifier
// Public data members
double length;
double width;
// Public member function to set dimensions
void setDimensions(double len, double wid) {
length = len;
width = wid;
}
// Public member function to calculate area
double calculateArea() {
return length * width;
}
private: // Private access specifier
// Private data member
string color;
public: // Public access specifier again (not recommended in practice)
// Public member function to set color (just for illustration)
void setColor(string c) {
color = c;
}
// Public member function to get color (just for illustration)
string getColor() {
return color;
}
};
int main() {
Rectangle rect;
// Accessing public members
rect.length = 5.0; // Okay
rect.width = 3.0; // Okay
rect.setDimensions(7.0, 4.0); // Okay
cout << "Area of the rectangle: " << rect.calculateArea() << endl; // Okay
// Accessing private members (not allowed)
// rect.color = "Red"; // Error: 'string Rectangle::color' is private
// Accessing public members for color (just for illustration)
rect.setColor("Red");
cout << "Color of the rectangle: " << rect.getColor() << endl;
return 0;
}
In the Rectangle class above:
- length and width are public data members, so they can be accessed directly from outside the class.
- setDimensions and calculateArea are public member functions, so they can be called from outside the class.
- color is a private data member, so it cannot be accessed directly from outside the class. Attempting to do so will result in a compilation error.
- setColor and getColor are public member functions that allow limited access to the private color member for illustration purposes. In practice, you would typically use setter and getter functions like these to control access to private members.
- Access specifiers are crucial for encapsulation and data hiding in C++. They help you control which parts of your class are exposed to the external world, ensuring better control and maintainability of your code.
5. Member functions in C++
Member functions in C++ are functions defined within a class, and they operate on the data members (attributes) of that class. These functions define the behavior or operations that can be performed on objects of the class. Member functions can be categorized as public, private, or protected, depending on their access specifiers.
Let's continue using the Rectangle class as an example to illustrate member functions:
#include <iostream>
using namespace std;
class Rectangle {
public:
// Public data members
double length;
double width;
// Public member function to set dimensions
void setDimensions(double len, double wid) {
length = len;
width = wid;
}
// Public member function to calculate area
double calculateArea() {
return length * width;
}
private:
// Private data member
string color;
public:
// Public member function to set color (just for illustration)
void setColor(string c) {
color = c;
}
// Public member function to get color (just for illustration)
string getColor() {
return color;
}
};
int main() {
Rectangle rect;
// Accessing public data members and member functions
rect.setDimensions(7.0, 4.0); // Calling a public member function
cout << "Area of the rectangle: " << rect.calculateArea() << endl; // Calling a public member function
// Accessing private data members via public member functions (just for illustration)
rect.setColor("Red"); // Calling a public member function
cout << "Color of the rectangle: " << rect.getColor() << endl; // Calling a public member function
return 0;
}
In the Rectangle class and the main function above:
setDimensions and calculateArea are public member functions. They are defined within the class and can be accessed and called from outside the class using an object of the class, as shown in the main function.
setColor and getColor are public member functions used to set and get the color data member. These functions provide controlled access to the private color member, ensuring that it can only be modified or accessed through these functions.
Member functions are essential for encapsulation in object-oriented programming. They allow you to define how objects of a class interact with and manipulate their data. By controlling access to data members through member functions, you can enforce data integrity and maintain the internal state of objects while providing a well-defined interface for users of the class.
6. Inheritance in C++
Inheritance is a fundamental concept in object-oriented programming (OOP) that allows you to create a new class (called a derived or child class) by inheriting properties and behaviors from an existing class (called a base or parent class). In C++, you can implement inheritance using the class keyword along with the colon : to specify the base class.
Let's illustrate inheritance using the Rectangle class as the base class and creating a ColoredRectangle class as the derived class.
Here's an example program:
#include <iostream>
using namespace std;
// Base class: Rectangle
class Rectangle {
public:
double length;
double width;
Rectangle(double len, double wid) : length(len), width(wid) {}
double calculateArea() {
return length * width;
}
};
// Derived class: ColoredRectangle
class ColoredRectangle : public Rectangle {
public:
string color;
// Constructor for ColoredRectangle
ColoredRectangle(double len, double wid, string c) : Rectangle(len, wid), color(c) {}
};
int main() {
// Create a ColoredRectangle object
ColoredRectangle coloredRect(7.0, 4.0, "Red");
// Accessing base class members
cout << "Area of the colored rectangle: " << coloredRect.calculateArea() << endl;
// Accessing derived class member
cout << "Color of the colored rectangle: " << coloredRect.color << endl;
return 0;
}
In this example:
- The Rectangle class is defined as the base class. It has data members length and width, along with a constructor to initialize them and a calculateArea function to calculate the area of a rectangle.
- The ColoredRectangle class is defined as the derived class. It inherits the properties of the Rectangle class (i.e., length and width) and adds an additional data member color. It has its constructor that initializes the base class's members along with its own color member.
- In the main function, we create a ColoredRectangle object, coloredRect, using the derived class constructor. This object inherits the length and width attributes from the base class Rectangle and has its color attribute.
- We can access both the base class's member function (calculateArea()) and the derived class's member (color) using the coloredRect object.
Inheritance allows for code reuse and the creation of specialized classes based on existing ones. It promotes a hierarchical structure, making it easier to model real-world relationships between objects. In this example, ColoredRectangle is a specialized version of a Rectangle with an additional property, demonstrating how inheritance can be used to build upon existing classes.
7. Polymorphism in C++
Polymorphism is a fundamental concept in object-oriented programming (OOP) that allows objects of different classes to be treated as objects of a common base class. It enables you to write more flexible and reusable code by allowing different classes to provide their own implementations of common functions.
In C++, polymorphism is primarily achieved through function overriding and virtual functions. Let's demonstrate polymorphism using the Rectangle class example:
#include <iostream>
using namespace std;
// Base class: Shape
class Shape {
public:
virtual double calculateArea() {
return 0.0; // Default implementation for the base class
}
};
// Derived class: Rectangle
class Rectangle : public Shape {
public:
double length;
double width;
Rectangle(double len, double wid) : length(len), width(wid) {}
// Overriding the calculateArea() function
double calculateArea() override {
return length * width;
}
};
int main() {
// Create a Rectangle object
Rectangle rect(7.0, 4.0);
// Create a Shape pointer pointing to a Rectangle object
Shape* shapePtr = ▭
// Calculate the area using polymorphism
cout << "Area of the shape: " << shapePtr->calculateArea() << endl;
return 0;
}
Types of Polymophism in C++
Polymorphism in C++ can be classified into two main types: compile-time (static) polymorphism and runtime (dynamic) polymorphism. These two types of polymorphism are achieved through different mechanisms and are used in different scenarios:
Compile-Time (Static) Polymorphism:
Compile-time polymorphism is also known as static polymorphism because the selection of which function to call is done at compile time, not at runtime. This type of polymorphism is achieved through function overloading and operator overloading.
Function Overloading: As explained earlier, function overloading allows you to define multiple functions with the same name but with different parameter lists. The compiler selects the appropriate function to call based on the arguments provided during the function call.
Operator Overloading: Operator overloading allows you to define custom behaviors for operators, such as +, -, *, /, ==, and more, when applied to objects of user-defined classes. This is determined at compile time based on the operator used and the types of operands.
Example using function overloading:
int add(int a, int b);
double add(double a, double b);
Example using operator overloading:
class Complex {
public:
Complex operator+(const Complex& other);
};
Runtime (Dynamic) Polymorphism:
Runtime polymorphism is achieved through function overriding and virtual functions. In this type of polymorphism, the selection of which function to call is determined at runtime, based on the actual type of the object being referred to (late binding). Runtime polymorphism is primarily used in scenarios involving inheritance and base and derived classes.
Function Overriding: Function overriding allows a derived class to provide its own implementation of a function that is already defined in the base class. The selection of which function to call is determined based on the type of the object being referred to at runtime. To enable this behavior, you mark the base class function with the virtual keyword and override it in the derived class.
Virtual Functions: A virtual function is a member function in the base class that is declared with the virtual keyword. Virtual functions enable dynamic binding, meaning that the actual function to be called is determined at runtime based on the type of the object being referred to. The derived class can override the virtual function.
Example using function overriding and virtual functions:
class Shape {
public:
virtual void draw() const {
cout << "Drawing a shape." << endl;
}
};
class Circle : public Shape {
public:
void draw() const override {
cout << "Drawing a circle." << endl;
}
};
In summary, C++ supports both compile-time polymorphism (achieved through function and operator overloading) and runtime polymorphism (achieved through function overriding and virtual functions). These types of polymorphism provide flexibility and extensibility to your programs, allowing you to write more dynamic and expressive code.
Here are separate complete programs that illustrate both compile-time polymorphism and runtime polymorphism using C++:
Compile-Time (Static) Polymorphism Example:
#include <iostream>
using namespace std;
// Function Overloading for addition
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int main() {
int num1 = 5, num2 = 7;
double num3 = 3.5, num4 = 2.1;
// Compile-time polymorphism using function overloading
int result1 = add(num1, num2);
double result2 = add(num3, num4);
cout << "Result 1: " << result1 << endl;
cout << "Result 2: " << result2 << endl;
return 0;
}
In this program, we have two add functions that are overloaded based on their parameter types (integers and doubles). The appropriate function to call is determined at compile time based on the argument types provided during the function calls.
Runtime (Dynamic) Polymorphism Example:
#include <iostream>
using namespace std;
// Base class: Shape
class Shape {
public:
virtual void draw() const {
cout << "Drawing a shape." << endl;
}
};
// Derived class: Circle
class Circle : public Shape {
public:
void draw() const override {
cout << "Drawing a circle." << endl;
}
};
int main() {
// Creating objects of base and derived classes
Shape shape;
Circle circle;
// Using runtime polymorphism (dynamic binding)
Shape* shapePtr = &circle; // Pointer to a Circle object
shapePtr->draw(); // Calls the draw() function of the Circle class
return 0;
}
In this program, we have a base class Shape with a virtual function draw, and a derived class Circle that overrides the draw function. We create objects of both classes and demonstrate runtime polymorphism by using a pointer to the base class (Shape) that points to an object of the derived class (Circle). The appropriate draw function is determined at runtime based on the type of the object being referred to.
8. Encapsulation in C++ Programming
Encapsulation is one of the four fundamental principles of object-oriented programming (OOP), along with inheritance, polymorphism, and abstraction. It refers to the practice of bundling data (attributes or properties) and the methods (functions or behaviors) that operate on that data into a single unit known as a class. Encapsulation restricts direct access to some of the object's components and prevents unintended interference, providing better control over the data and the functions that manipulate it.
Let's use the Rectangle class as an example to demonstrate encapsulation:
#include <iostream>
using namespace std;
class Rectangle {
private: // Private access specifier for data members
double length;
double width;
public:
// Public member functions to set and get the dimensions
void setDimensions(double len, double wid) {
if (len > 0.0 && wid > 0.0) {
length = len;
width = wid;
} else {
cout << "Invalid dimensions. Please provide positive values." << endl;
}
}
double getLength() const { // Using const member function for read-only access
return length;
}
double getWidth() const { // Using const member function for read-only access
return width;
}
double calculateArea() const { // Using const member function for read-only access
return length * width;
}
};
int main() {
Rectangle rect;
// Setting dimensions using the public member function
rect.setDimensions(7.0, 4.0);
// Accessing dimensions and calculating area using public member functions
cout << "Length: " << rect.getLength() << " units" << endl;
cout << "Width: " << rect.getWidth() << " units" << endl;
cout << "Area: " << rect.calculateArea() << " square units" << endl;
// Attempting to access private data members directly (not allowed)
// rect.length = 10.0; // Error: 'double Rectangle::length' is private
return 0;
}
In this example:
- The Rectangle class encapsulates the length and width data members as private members, preventing direct access from outside the class.
- Public member functions (setDimensions, getLength, getWidth, and calculateArea) are provided to allow controlled access to the private data members. The setDimensions function ensures that only valid dimensions (positive values) are set.
- In the main function, we use the public member functions to set and retrieve the dimensions and calculate the area of the rectangle. Attempting to access the private data members directly would result in a compilation error.
9. Encapsulation
Encapsulation helps in maintaining the integrity of the data by preventing unauthorized access and manipulation. It also allows for better code organization and flexibility in changing the internal implementation of the class without affecting the code that uses the class's public interface.
Abstraction is one of the core principles of object-oriented programming (OOP). It involves simplifying complex reality by modeling classes based on the essential features and properties while hiding the unnecessary details. In C++, abstraction is achieved by defining a class interface that exposes only relevant information to the outside world while concealing the implementation details.
Let's use the Rectangle class to demonstrate abstraction:
#include <iostream>
using namespace std;
class Rectangle {
private: // Private data members
double length;
double width;
public:
// Constructor to initialize the dimensions
Rectangle(double len, double wid) : length(len), width(wid) {}
// Public member function to calculate area
double calculateArea() const { // Using const member function for read-only access
return length * width;
}
// Public member function to display information (abstraction)
void display() const {
cout << "Rectangle Information:" << endl;
cout << "Length: " << length << " units" << endl;
cout << "Width: " << width << " units" << endl;
cout << "Area: " << calculateArea() << " square units" << endl;
}
};
int main() {
// Creating a Rectangle object
Rectangle rect(7.0, 4.0);
// Using the abstraction provided by the display function
rect.display();
return 0;
}
In this example:
The Rectangle class encapsulates the length and width data members as private members, as shown in the previous example.
We have added an additional member function named display(). This function is used to abstract the details of displaying the rectangle's information. It hides the specific implementation of formatting and displaying the data.
In the main function, we create a Rectangle object named rect and then use the display() member function to display the information about the rectangle. The main function does not need to know how the data is displayed or how the area is calculated; it simply uses the abstraction provided by the display() function.
Abstraction simplifies the usage of classes by providing a clear and simplified interface while hiding the complexity of implementation details. It allows you to focus on the essential aspects of an object and its behavior without being concerned about how it achieves its functionality internally. This principle promotes modularity, maintainability, and code reusability.
Operator overloading in C++
Operator overloading in C++ allows you to define how operators should behave when applied to objects of user-defined classes. You can provide custom implementations for operators such as +, -, *, /, ==, !=, and more. Operator overloading is a powerful feature that makes your classes more intuitive and expressive.
Let's demonstrate operator overloading using the Rectangle class to overload the + operator for combining rectangles' areas:
#include <iostream>
using namespace std;
class Rectangle {
private:
double length;
double width;
public:
Rectangle(double len, double wid) : length(len), width(wid) {}
double calculateArea() const {
return length * width;
}
// Overloading the + operator to combine areas of two rectangles
Rectangle operator+(const Rectangle& other) {
double newLength = length + other.length;
double newWidth = width + other.width;
return Rectangle(newLength, newWidth);
}
};
int main() {
// Creating two Rectangle objects
Rectangle rect1(7.0, 4.0);
Rectangle rect2(5.0, 3.0);
// Calculating the individual areas
double area1 = rect1.calculateArea();
double area2 = rect2.calculateArea();
cout << "Area of rect1: " << area1 << " square units" << endl;
cout << "Area of rect2: " << area2 << " square units" << endl;
// Adding the areas of two rectangles using operator overloading
Rectangle combinedRect = rect1 + rect2;
// Calculating the combined area
double combinedArea = combinedRect.calculateArea();
cout << "Combined Area: " << combinedArea << " square units" << endl;
return 0;
}
In this example:
The Rectangle class is defined as before, with private data members length and width.
We overload the + operator by defining a member function operator+ inside the class. This function takes another Rectangle object as a parameter and returns a new Rectangle object representing the combined area.
In the main function, we create two Rectangle objects (rect1 and rect2) and calculate their individual areas using the calculateArea() member function.
We then use operator overloading by adding the rect1 and rect2 objects together using the + operator. This operation combines their areas and returns a new Rectangle object (combinedRect).
Finally, we calculate and display the combined area.
Operator overloading allows you to use familiar operators with user-defined classes, making your code more intuitive and readable. It's important to note that you can overload other operators in a similar way to provide custom behavior for your classes.