4. Object-Oriented Programming (OOP)
Module 4 • Comprehensive Structural Paradigm Guide
4.1. Classes and Objects
In C++, classes and objects are fundamental concepts of Object-Oriented Programming (OOP), which focuses on organizing code into reusable and modular structures. A class is a blueprint for creating objects, which are instances of the class. The class defines the properties (attributes) and behaviors (methods) that the objects will have.
4.1.1. Classes in C++
A class is a user-defined data type that represents a collection of related data and functions. The data is called member variables (attributes), and the functions are called member functions (methods). Classes provide a way to structure the data and the operations that can be performed on that data.
Syntax of a class:
class ClassName {
private:
// Private members (cannot be accessed directly from outside the class)
dataType memberVariable;
public:
// Public members (can be accessed from outside the class)
void memberFunction() {
// function definition
}
};
- Private Members: These are attributes or methods that cannot be accessed directly from outside the class. They are used for encapsulation, which helps in hiding the internal workings of the class.
- Public Members: These can be accessed from outside the class. They are used to define functions that can be called by the user of the class.
2. Objects in C++
An object is an instance of a class. When you create an object of a class, the class defines the properties (variables) and methods (functions) that the object will have. Objects allow you to store data and interact with it using the methods defined in the class.
Syntax for creating objects:
ClassName objectName; // Object creation
3. Example of Classes and Objects in C++
Let’s take a simple example where we define a Car class and then create objects (specific cars) from it.
#include <iostream>
using namespace std;
// Define the Car class
class Car {
// Private members
private:
string brand;
string model;
int year;
// Public members
public:
// Constructor to initialize the object
Car(string b, string m, int y) {
brand = b;
model = m;
year = y;
}
// Member function to display car details
void displayDetails() {
cout << "Car brand: " << brand << endl;
cout << "Car model: " << model << endl;
cout << "Car year: " << year << endl;
}
// Getter functions
string getBrand() {
return brand;
}
string getModel() {
return model;
}
int getYear() {
return year;
}
};
int main() {
// Create objects of the Car class
Car car1("Toyota", "Corolla", 2020); // Initialize car1 with brand, model, and year
Car car2("Honda", "Civic", 2021); // Initialize car2 with brand, model, and year
// Calling member function using objects
car1.displayDetails();
car2.displayDetails();
// Accessing object properties through getter functions
cout << "Brand of car1: " << car1.getBrand() << endl;
cout << "Year of car2: " << car2.getYear() << endl;
return 0;
}
Explanation of the Example:
- Class Definition:
- The Car class contains three private data members: brand, model, and year.
- A constructor is used to initialize the object's attributes when the object is created.
- The displayDetails() function is a member function that prints the details of the car.
- Getter functions (getBrand(), getModel(), getYear()) are used to access the private attributes from outside the class.
- Object Creation:
- Car car1("Toyota", "Corolla", 2020); creates an object car1 of the class Car with the specified values for brand, model, and year.
- Similarly, Car car2("Honda", "Civic", 2021); creates another object car2.
- Calling Member Functions:
- The displayDetails() function is called on both objects car1 and car2 to display their details.
- The getter functions are used to access the private member variables.
4.1.2. Access Modifiers in C++
Access modifiers determine the visibility of class members (attributes and methods). C++ supports three access modifiers:
- Private: Members are accessible only within the class itself and not from outside.
- Public: Members are accessible from anywhere, both inside and outside the class.
- Protected: Members are accessible in the class and by derived classes (used in inheritance).
4.2. Constructor and Destructor
- Constructor: A special member function that is automatically called when an object of the class is created. It is used to initialize the object.
- Destructor: A special member function that is automatically called when an object is destroyed. It is used to clean up resources.
Example of constructor and destructor:
class Car {
private:
string brand;
string model;
int year;
public:
// Constructor
Car(string b, string m, int y) {
brand = b;
model = m;
year = y;
cout << "Car object created!" << endl;
}
// Destructor
~Car() {
cout << "Car object destroyed!" << endl;
}
// Function to display car details
void displayDetails() {
cout << "Car brand: " << brand << endl;
cout << "Car model: " << model << endl;
cout << "Car year: " << year << endl;
}
};
6. Other Key Points:
- Member Initialization: C++ allows for member initialization using the constructor to ensure that objects are created with valid data.
- Encapsulation: Classes in C++ support encapsulation, where data is hidden within the class, and access is provided through public methods (getter and setter functions).
- Inheritance and Polymorphism: C++ classes can inherit properties from other classes, and polymorphism allows objects to behave in different ways based on their class type.
Conclusion: Classes and objects in C++ provide a powerful way to organize and manage code in a structured, reusable way. By using classes, we can define complex types, encapsulate data, and provide methods to interact with the data, which is essential for implementing Object-Oriented Programming principles.
4.2.1. Default, parameterized, and copy constructors
In C++, constructors are special member functions that are automatically called when an object of a class is created. Their primary purpose is to initialize the object. Constructors have the same name as the class and do not return any type, not even void.
There are three types of constructors in C++:
- Default Constructor
- Parameterized Constructor
- Copy Constructor
1. Default Constructor
A default constructor is a constructor that does not take any arguments. If you don't define any constructor in a class, C++ will automatically generate a default constructor for you. However, if you define any constructor, the default constructor is not generated automatically, unless explicitly defined.
A default constructor initializes the object with default values. If no explicit values are provided for the attributes of an object, the default constructor initializes them to some predefined values (like 0 for integers, null for pointers, etc.).
Syntax of a Default Constructor:
class ClassName {
public:
ClassName() {
// Initialization of member variables (optional)
}
};
Example of Default Constructor:
#include <iostream>
using namespace std;
class Car {
private:
string brand;
string model;
int year;
public:
// Default Constructor
Car() {
brand = "Unknown";
model = "Unknown";
year = 0;
}
// Function to display car details
void displayDetails() {
cout << "Car brand: " << brand << endl;
cout << "Car model: " << model << endl;
cout << "Car year: " << year << endl;
}
};
int main() {
Car car1; // Object created with default constructor
car1.displayDetails(); // Displays default values
return 0;
}
Explanation:
- The Car() constructor is a default constructor. It initializes the attributes of Car with default values: "Unknown" for brand, "Unknown" for model, and 0 for year.
- When the object car1 is created, this constructor is called, and the object is initialized with default values.
2. Parameterized Constructor
A parameterized constructor is a constructor that takes one or more arguments. It allows you to initialize an object with specific values at the time of its creation. This provides flexibility, as the object can be initialized with different values based on the parameters passed.
Syntax of a Parameterized Constructor:
class ClassName {
public:
ClassName(type parameter1, type parameter2) {
// Initialize members with parameters
}
};
Example of Parameterized Constructor:
#include <iostream>
using namespace std;
class Car {
private:
string brand;
string model;
int year;
public:
// Parameterized Constructor
Car(string b, string m, int y) {
brand = b;
model = m;
year = y;
}
// Function to display car details
void displayDetails() {
cout << "Car brand: " << brand << endl;
cout << "Car model: " << model << endl;
cout << "Car year: " << year << endl;
}
};
int main() {
// Creating objects with different initial values using parameterized constructor
Car car1("Toyota", "Corolla", 2020);
Car car2("Honda", "Civic", 2021);
car1.displayDetails();
car2.displayDetails();
return 0;
}
Explanation:
- The constructor Car(string b, string m, int y) is a parameterized constructor that initializes the object with the values passed to it when the object is created.
- The object car1 is created with the values "Toyota", "Corolla", and 2020 for brand, model, and year, respectively.
- The object car2 is created similarly with "Honda", "Civic", and 2021.
3. Copy Constructor
A copy constructor is a special constructor that initializes an object using another object of the same class. It creates a new object as a copy of an existing object, and it is invoked when:
- A new object is created as a copy of an existing object.
- An object is passed by value to a function.
- A function returns an object by value.
The copy constructor performs a shallow copy or deep copy depending on the type of members (whether they are simple data types or complex types like pointers).
Syntax of a Copy Constructor:
class ClassName {
public:
ClassName(const ClassName& otherObject) {
// Copy constructor code to copy data from otherObject
}
};
Example of Copy Constructor:
#include <iostream>
using namespace std;
class Car {
private:
string brand;
string model;
int year;
public:
// Parameterized Constructor
Car(string b, string m, int y) {
brand = b;
model = m;
year = y;
}
// Copy Constructor
Car(const Car& other) {
brand = other.brand;
model = other.model;
year = other.year;
cout << "Copy constructor called!" << endl;
}
// Function to display car details
void displayDetails() {
cout << "Car brand: " << brand << endl;
cout << "Car model: " << model << endl;
cout << "Car year: " << year << endl;
}
};
int main() {
Car car1("Toyota", "Corolla", 2020);
Car car2 = car1; // Copy constructor is called here
car1.displayDetails();
car2.displayDetails();
return 0;
}
Explanation:
- The copy constructor Car(const Car& other) is called when car2 is created as a copy of car1. The constructor initializes car2 with the values of car1's attributes (brand, model, and year).
- When Car car2 = car1; is executed, the copy constructor is called to copy the values of car1 into car2.
Key Points about Copy Constructor:
- If you don't define a copy constructor, C++ provides a default one, which does a shallow copy of the object.
- A shallow copy means that the members of the object (including pointers) are copied directly. If there are dynamic resources (like arrays or memory allocated via pointers), it can lead to problems like double-deletion of resources when the object is destroyed.
- A deep copy involves creating copies of dynamically allocated resources to prevent such issues, which can be achieved by manually implementing the copy constructor.
Example of a Deep Copy Constructor (in case of pointers):
class Car {
private:
string* brand;
string* model;
public:
// Parameterized Constructor
Car(string b, string m) {
brand = new string(b);
model = new string(m);
}
// Copy Constructor (Deep Copy)
Car(const Car& other) {
brand = new string(*(other.brand)); // Create new memory for brand
model = new string(*(other.model)); // Create new memory for model
}
// Destructor to release dynamically allocated memory
~Car() {
delete brand;
delete model;
}
// Function to display car details
void displayDetails() {
cout << "Car brand: " << *brand << endl;
cout << "Car model: " << *model << endl;
}
};
Conclusion:
- Default Constructor: Initializes the object with default values.
- Parameterized Constructor: Initializes the object with specified values.
- Copy Constructor: Initializes a new object as a copy of an existing object. The copy constructor can either perform a shallow copy (default) or a deep copy (when dealing with dynamic memory allocation).
Understanding constructors is crucial for managing object initialization and ensuring the correct handling of resources in object-oriented programming.
4.2.2. Destructor in C++ Programming
A destructor in C++ is a special member function of a class that is executed when an object of that class goes out of scope or is explicitly deleted. It is used to release any resources (such as memory, file handles, etc.) that were acquired during the lifetime of an object.
Key Features of Destructors:
- Name: The name of a destructor is the same as the class name, preceded by a tilde (~). For example, if the class is MyClass, the destructor will be ~MyClass().
- No Return Type: A destructor has no return type, not even void.
- No Arguments: Destructors do not accept any parameters, and therefore cannot be overloaded (you can only have one destructor per class).
- Automatic Call: Destructors are automatically invoked when an object goes out of scope or when the delete operator is used on a dynamically allocated object.
- No Overloading: Since destructors have no parameters, they cannot be overloaded like constructors.
Purpose of Destructor
The primary purpose of a destructor is to perform cleanup tasks for the object before it is destroyed. This includes:
- Deallocating memory that was dynamically allocated using new or malloc.
- Closing files that were opened during the lifetime of the object.
- Releasing other resources, such as network connections or locks.
Syntax of Destructor
The syntax of a destructor is as follows:
~ClassName()
{
// code to release resources
}
Example: Destructor in Action
Here's a simple example demonstrating the use of destructors:
Example 1: Basic Destructor Example
#include <iostream>
class MyClass
{
public:
MyClass() {
std::cout << "Constructor called!" << std::endl;
}
~MyClass() {
std::cout << "Destructor called!" << std::endl;
}
};
int main() {
MyClass obj; // Constructor is called here
// Destructor will be called automatically when 'obj' goes out of scope
return 0;
}
Output:
Constructor called!
Destructor called!
In this example:
- The constructor is invoked when the object obj is created.
- The destructor is automatically called when obj goes out of scope at the end of main().
Example 2: Destructor with Dynamic Memory Management
The real usefulness of destructors appears when they handle dynamic memory allocated using new or malloc. If an object allocates memory dynamically, its destructor should deallocate that memory to avoid memory leaks.
#include <iostream>
class MyClass
{
private:
int* data; // Pointer to dynamically allocated memory
public:
MyClass(int size) {
data = new int[size]; // Allocate memory dynamically
std::cout << "Memory allocated!" << std::endl;
}
~MyClass() {
delete[] data; // Deallocate memory in the destructor
std::cout << "Memory deallocated!" << std::endl;
}
};
int main() {
MyClass obj(5); // Constructor is called, memory allocated for 5 integers
// Destructor will be called automatically, and memory will be freed
return 0;
}
Output:
Memory allocated!
Memory deallocated!
Here:
- The constructor dynamically allocates an array of integers.
- The destructor deallocates that memory using delete[].
Example 3: Destructor with File Handling
Destructors are also useful for closing files or releasing other system resources.
#include <iostream>
#include <fstream>
class FileHandler {
private:
std::ofstream file;
public:
FileHandler(const std::string& filename) {
file.open(filename); // Open the file
if (file.is_open()) {
std::cout << "File opened!" << std::endl;
}
}
~FileHandler() {
if (file.is_open()) {
file.close(); // Close the file in the destructor
std::cout << "File closed!" << std::endl;
}
}
};
int main() {
FileHandler fileHandler("example.txt"); // File opened here
// File will be automatically closed when the object goes out of scope
return 0;
}
Output:
File opened!
File closed!
In this example:
- The constructor opens a file for writing.
- The destructor ensures that the file is closed when the FileHandler object is destroyed.
Destructor and Dynamic Memory Management
When using dynamic memory allocation (i.e., memory allocated using new), it's important to ensure that the memory is properly deallocated to avoid memory leaks. The destructor is typically used for this purpose.
Example 4: Using delete and delete[]
#include <iostream>
class MyClass
{
private:
int* data; // Pointer to dynamically allocated memory
public:
MyClass() {
data = new int(10); // Allocates a single integer on the heap
std::cout << "Single integer allocated!" << std::endl;
}
~MyClass() {
delete data; // Deallocate memory
std::cout << "Memory deallocated!" << std::endl;
}
};
int main() {
MyClass obj; // Constructor allocates memory
// Destructor will deallocate memory automatically when the object goes out of scope
return 0;
}
Output:
Single integer allocated!
Memory deallocated!
In this example, new allocates memory for a single integer, and the destructor releases it using delete.
Destructor for Base and Derived Classes (Polymorphism)
When using inheritance, it's important to ensure that the destructor of the base class is virtual if you are dealing with polymorphic objects. This ensures that the correct destructor is called for objects created with base class pointers that point to derived class objects.
Example 5: Virtual Destructor for Inheritance
#include <iostream>
class Base {
public:
Base() { std::cout << "Base class constructor!" << std::endl; }
virtual ~Base() { std::cout << "Base class destructor!" << std::endl; }
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived class constructor!" << std::endl; }
~Derived() { std::cout << "Derived class destructor!" << std::endl; }
};
int main() {
Base* obj = new Derived(); // Base pointer points to Derived object
delete obj; // Correct destructor (Derived's) will be called
return 0;
}
Output:
Base class constructor!
Derived class constructor!
Derived class destructor!
Base class destructor!
In this example:
- The destructor of Base is declared virtual, ensuring that the Derived class destructor is called when the object is deleted using a base class pointer.
- Without a virtual destructor, only the base class destructor would be called, potentially leading to resource leaks.
Conclusion: Destructors are an essential feature in C++ for resource management, particularly for cleaning up dynamically allocated memory or other resources (such as file handles, network connections, etc.). They are automatically called when objects go out of scope, and their proper use prevents memory leaks and ensures the safe release of resources.
4.3. Inheritance
Inheritance is one of the key features of Object-Oriented Programming (OOP) in C++. It allows one class to inherit properties and behaviors (i.e., data members and member functions) from another class. This mechanism promotes code reuse and creates a relationship between classes, where one class (derived class) is a specialized version of another class (base class).
Key Concepts in Inheritance:
- Base Class (Parent Class): This is the class whose properties and methods are inherited by another class.
- Derived Class (Child Class): This class inherits the properties and methods of the base class and can have additional properties or methods of its own.
- Access Modifiers: Inheritance is controlled by access specifiers: public, protected, and private. These define how members of the base class can be accessed from the derived class.
Types of Inheritance:
- Single Inheritance: A derived class inherits from only one base class.
- Multiple Inheritance: A derived class inherits from more than one base class.
- Multilevel Inheritance: A class is derived from another derived class.
- Hierarchical Inheritance: Multiple classes are derived from a single base class.
- Hybrid Inheritance: A combination of two or more types of inheritance.
Syntax of Inheritance:
class BaseClass {
// Members and methods of the base class
};
class DerivedClass : accessSpecifier BaseClass {
// Members and methods of the derived class
};
Access Specifiers in Inheritance:
public inheritance: All public and protected members of the base class are accessible as public and protected members in the derived class.protected inheritance: All public and protected members of the base class become protected in the derived class.private inheritance: All public and protected members of the base class become private in the derived class.
Example of Inheritance in C++
1. Single Inheritance
#include <iostream>
using namespace std;
class Animal {
public:
void speak() {
cout << "Animal speaks" << endl;
}
};
class Dog : public Animal { // Dog is derived from Animal
public:
void bark() {
cout << "Dog barks" << endl;
}
};
int main() {
Dog myDog;
myDog.speak(); // Inherited method
myDog.bark(); // Dog's own method
return 0;
}
Output:
Animal speaks
Dog barks
In this example: Dog is a derived class that inherits the speak method from the Animal class. The Dog class also defines its own method bark.
2. Multiple Inheritance
#include <iostream>
using namespace std;
class Animal {
public:
void speak() {
cout << "Animal speaks" << endl;
}
};
class Bird {
public:
void fly() {
cout << "Bird flies" << endl;
}
};
class Bat : public Animal, public Bird { // Inherits from both Animal and Bird
public:
void hang() {
cout << "Bat hangs upside down" << endl;
}
};
int main() {
Bat myBat;
myBat.speak(); // Inherited from Animal
myBat.fly(); // Inherited from Bird
myBat.hang(); // Bat's own method
return 0;
}
Output:
Animal speaks
Bird flies
Bat hangs upside down
In this example: Bat inherits from both Animal and Bird. The Bat class can access the methods from both base classes (speak from Animal and fly from Bird).
3. Multilevel Inheritance
#include <iostream>
using namespace std;
class Animal {
public:
void speak() {
cout << "Animal speaks" << endl;
}
};
class Mammal : public Animal {
public:
void walk() {
cout << "Mammal walks" << endl;
}
};
class Dog : public Mammal { // Dog is derived from Mammal, which is derived from Animal
public:
void bark() {
cout << "Dog barks" << endl;
}
};
int main() {
Dog myDog;
myDog.speak(); // Inherited from Animal
myDog.walk(); // Inherited from Mammal
myDog.bark(); // Dog's own method
return 0;
}
Output:
Animal speaks
Mammal walks
Dog barks
In this example: Dog inherits from Mammal, which in turn inherits from Animal. Dog has access to all the methods of Animal and Mammal.
4. Hierarchical Inheritance
#include <iostream>
using namespace std;
class Animal {
public:
void speak() {
cout << "Animal speaks" << endl;
}
};
class Dog : public Animal {
public:
void bark() {
cout << "Dog barks" << endl;
}
};
class Cat : public Animal {
public:
void meow() {
cout << "Cat meows" << endl;
}
};
int main() {
Dog myDog;
Cat myCat;
myDog.speak(); // Inherited from Animal
myDog.bark(); // Dog's own method
myCat.speak(); // Inherited from Animal
myCat.meow(); // Cat's own method
return 0;
}
Output:
Animal speaks
Dog barks
Animal speaks
Cat meows
In this example: Both Dog and Cat inherit from the same Animal class. Each derived class (Dog and Cat) has its own method in addition to the inherited speak method.
Constructor and Destructor in Inheritance
- Constructor: The base class constructor is called first, followed by the derived class constructor.
- Destructor: The derived class destructor is called first, followed by the base class destructor.
Example with Constructors and Destructors:
#include <iostream>
using namespace std;
class Animal {
public:
Animal() {
cout << "Animal Constructor" << endl;
}
~Animal() {
cout << "Animal Destructor" << endl;
}
};
class Dog : public Animal {
public:
Dog() {
cout << "Dog Constructor" << endl;
}
~Dog() {
cout << "Dog Destructor" << endl;
}
};
int main() {
Dog myDog; // Object creation will call constructors
return 0;
}
Output:
Animal Constructor
Dog Constructor
Dog Destructor
Animal Destructor
Here, the constructor of the Animal class is called first, then the Dog constructor, and when the object goes out of scope, the destructors are called in reverse order.
Conclusion: Inheritance in C++ allows you to build relationships between classes, promote code reuse, and extend existing code with new functionality. The derived class inherits properties and behaviors from the base class and can override or extend them as needed.
4.4. Polymorphism
Polymorphism is one of the core concepts of Object-Oriented Programming (OOP), and it allows objects of different classes to be treated as objects of a common base class. The term polymorphism comes from the Greek words "poly" (many) and "morph" (form), meaning many forms. In C++, polymorphism enables a function or an operator to behave differently depending on the type of object that it is acting upon.
There are two types of polymorphism in C++:
- Compile-time Polymorphism (Static Polymorphism)
- Runtime Polymorphism (Dynamic Polymorphism)
Let's explore both in detail.
1. Compile-time Polymorphism (Static Polymorphism)
Compile-time polymorphism is resolved during the compilation process. This is primarily achieved through function overloading and operator overloading.
Function Overloading
Function overloading allows multiple functions with the same name but different parameters (either in number or type) to exist in the same scope. The compiler determines which function to call based on the arguments passed at compile time.
Example: Function Overloading
#include <iostream>
using namespace std;
class Printer {
public:
void print(int i) {
cout << "Printing integer: " << i << endl;
}
void print(double d) {
cout << "Printing double: " << d << endl;
}
void print(string s) {
cout << "Printing string: " << s << endl;
}
};
int main() {
Printer printer;
printer.print(10); // Calls print(int)
printer.print(3.14); // Calls print(double)
printer.print("Hello!"); // Calls print(string)
return 0;
}
Output:
Printing integer: 10
Printing double: 3.14
Printing string: Hello!
In the above example, the print function is overloaded to accept different types of arguments. The compiler determines which version of the function to invoke based on the passed arguments.
Operator Overloading
Operator overloading allows you to define custom behaviors for operators when they are used with user-defined objects.
Example: Operator Overloading
#include <iostream>
using namespace std;
class Complex {
private:
int real, imag;
public:
Complex() : real(0), imag(0) {}
Complex(int r, int i) : real(r), imag(i) {}
// Overload the "+" operator to add two complex numbers
Complex operator + (Complex const& obj) {
return Complex(real + obj.real, imag + obj.imag);
}
void display() {
cout << real << " + " << imag << "i" << endl;
}
};
int main() {
Complex c1(3, 4), c2(1, 2);
Complex c3 = c1 + c2; // Using overloaded "+" operator
c3.display(); // Outputs: 4 + 6i
return 0;
}
Output:
4 + 6i
In this example, we overloaded the + operator for the Complex class to add two complex numbers.
2. Runtime Polymorphism (Dynamic Polymorphism)
Runtime polymorphism is achieved through inheritance and virtual functions. It allows a function to behave differently based on the type of object it is called on during execution, rather than at compile time.
Virtual Functions
In C++, a virtual function is a member function of a class that you expect to be overridden in derived classes. By marking a function as virtual, the C++ compiler ensures that the correct function is called for an object, even when the object is accessed through a pointer or reference to a base class.
Example: Runtime Polymorphism with Virtual Functions
#include <iostream>
using namespace std;
class Animal {
public:
virtual void sound() { // Virtual function
cout << "Animal makes a sound." << endl;
}
};
class Dog : public Animal {
public:
void sound() override { // Override the virtual function
cout << "Dog barks." << endl;
}
};
class Cat : public Animal {
public:
void sound() override { // Override the virtual function
cout << "Cat meows." << endl;
}
};
int main() {
Animal* animal;
Dog dog;
Cat cat;
// Pointer of base class type can point to derived class objects
animal = &dog;
animal->sound(); // Outputs: Dog barks.
animal = &cat;
animal->sound(); // Outputs: Cat meows.
return 0;
}
Output:
Dog barks.
Cat meows.
In this example, the sound function is declared as virtual in the Animal base class. When calling sound() through the base class pointer (animal), the actual function invoked depends on the type of object the pointer is pointing to (Dog or Cat). This is resolved at runtime, hence it's called runtime polymorphism.
Pure Virtual Functions and Abstract Classes
A pure virtual function is a function declared in a class that has no definition in that class. The class containing pure virtual functions is known as an abstract class. This forces derived classes to provide an implementation for that function.
Example: Abstract Class and Pure Virtual Function
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() = 0; // Pure virtual function, makes Shape an abstract class
};
class Circle : public Shape {
public:
void draw() override {
cout << "Drawing a Circle." << endl;
}
};
class Square : public Shape {
public:
void draw() override {
cout << "Drawing a Square." << endl;
}
};
int main() {
Shape* shape1 = new Circle();
Shape* shape2 = new Square();
shape1->draw(); // Outputs: Drawing a Circle.
shape2->draw(); // Outputs: Drawing a Square.
delete shape1;
delete shape2;
return 0;
}
Output:
Drawing a Circle.
Drawing a Square.
In this example, Shape is an abstract class with a pure virtual function draw(). Both Circle and Square provide implementations for draw(), and we can create pointers to Shape that can point to objects of either derived class.
Key Points of Polymorphism:
- Function Overloading: Same function name but different parameters (resolved at compile time).
- Operator Overloading: Custom behavior for operators for user-defined types (resolved at compile time).
- Virtual Functions: Used for runtime polymorphism, enabling method overriding in derived classes (resolved at runtime).
- Pure Virtual Functions and Abstract Classes: Forces derived classes to implement specific functions.
Benefits of Polymorphism:
- Code Reusability: You can use the same interface for different data types, reducing code duplication.
- Extensibility: It's easy to add new classes and functionality without modifying existing code.
- Flexibility: The same code can work with different types of objects, which enhances the adaptability of the program.
In summary, polymorphism enhances the flexibility and maintainability of object-oriented systems by allowing functions to operate on different object types without needing to know their exact types at compile time.
Click your choice for each question to view feedback immediately. Complete all questions to evaluate your metric score.