Module 4_OOPs
Module 4_OOPs
MODULE 4
POLYMORPHISM
The word “polymorphism” means having many forms. In simple words, we can define
polymorphism as the ability of a message to be displayed in more than one form. A real-life
example of polymorphism is a person who at the same time can have different characteristics.
A man at the same time is a father, a husband, and an employee. So the same person exhibits
different behaviour in different situations. This is called polymorphism. Polymorphism is
considered one of the important features of Object-Oriented Programming.
Polymorphism in C++ allows us to reuse code by creating one function that's usable for
multiple uses. This allows objects of different classes to be treated as objects of a common
base class which means that you can write code that works with a variety of objects, without
having to know their specific types. We can also make operators polymorphic and use them
to add not only numbers but also combine strings. This saves time and allows for a more
streamlined program.
Example of polymorphism:
#include <iostream>
using namespace std;
int main() {
int a = 5, b = 10;
string str1 = "Hello ", str2 = "World!";
cout << "Sum of integers: " << a + b << endl; // Adds two integers
cout << "Concatenation of strings: " << str1 + str2 << endl; // Concatenates two strings
return 0;
}
Types of Polymorphism
Compile-time Polymorphism
Runtime Polymorphism
Compile-time polymorphism: Compile-time polymorphism, also known as static
polymorphism, is a type of polymorphism in C++ where the function call is resolved at
compile time. This means that the compiler determines which function to call based on the
function's name and the types of its arguments.
Important points to remember:
Achieved through: Function overloading and Operator overloading
Binding: Early binding (static binding)
Performance: Faster execution, as function calls are resolved at compile time
Flexibility: Less flexible than runtime polymorphism, as decisions are made before
execution
Function Overloading: When there are multiple functions with the same name but
different parameters, then the functions are said to be overloaded, hence this is known as
Function Overloading. Functions can be overloaded by changing the number of
arguments or/and changing the type of arguments. In simple terms, it is a feature of object-
oriented programming providing many functions that have the same name but distinct
parameters when numerous tasks are listed under one function name.
There are certain Rules of Function Overloading that should be followed while overloading a
function. Some of those rules are:
1. Same Name, Different Parameters:
Overloaded functions must have the same name.
They must differ in either the number of parameters or the types of parameters (or
both).
2. Return Type is Not Enough:
The return type of a function is not considered for overloading.
Two functions with the same name and parameters but different return types will
result in a compiler error.
3. Function Signature Matters:
The function signature is what differentiates overloaded functions.
The signature includes the function name and the parameter list (types and order).
4. Ambiguity is Not Allowed:
The compiler must be able to unambiguously determine which function to call based
on the arguments provided.
If the compiler cannot resolve the function call due to ambiguity, it will result in a
compiler error.
5. Default Arguments and Overloading:
Default arguments can be used with overloaded functions, but be careful to avoid
ambiguity.
If a function with default arguments can match the call, it will be chosen over a
function without default arguments.
Example of function Overloading:
#include <iostream>
void print(int x) {
std::cout << "Printing an integer: " << x << std::endl;
}
void print(double x) {
std::cout << "Printing a double: " << x << std::endl;
}
int main() {
print(10); // Calls print(int)
print(3.14); // Calls print(double)
print("Hello"); // Calls print(const char*)
return 0;
}
Here,
Return_Type is the value type to be returned to another object.
operator op is the function where the operator is a keyword.
op is the operator to be overloaded.
Important points:
Not all operators can be overloaded: You cannot overload the scope resolution
operator (::), the member access operator (.), the pointer-to-member operator (.*), the
ternary operator (?:), and the sizeof operator.
Operator precedence and associativity remain the same: Overloading doesn't
change the order of operations.
Return types should be meaningful: Consider the semantics of the operator and
return a value that makes sense in the context of your class.
Avoid surprises: Make sure your overloaded operators behave in a way that is
consistent with the expectations of users.
Benefits:
Improved readability: Operator overloading can make your code more concise and
intuitive.
Natural syntax: You can use operators with your user-defined types in a way that
feels natural and familiar.
Custom behaviour: You have full control over how operators behave with your data
types.
#include <iostream>
class Complex {
public:
double real;
double imag;
std::cout << result.real << " + " << result.imag << "i" << std::endl;
return 0;
}
Syntax:
Member function: returnType operator<symbol>() { /* implementation */ }
Non-member function: returnType operator<symbol>(className& obj) { /*
implementation */ }
Syntax:
Overloading binary operators is done by defining a special member function or
friend function with the keyword operator.
The general form is:
cpp
return_type operator op (const Type& other);
Where op is the operator to overload, and other is the second operand (the first operand is
the calling object).
#include <iostream>
class Shape {
public:
virtual ~Shape() {} // Virtual destructor
virtual void draw() {
std::cout << "Drawing a generic shape\n";
}
};
int main() {
Shape* shape1 = new Circle();
Shape* shape2 = new Square()
shape1->draw(); // Calls Circle::draw()
shape2->draw(); // Calls Square::draw()
delete shape1;
delete shape2;
return 0;
}
Explanation:
The Shape class is the base class with a virtual function draw().
The Circle and Square classes derive from Shape and override the draw() function.
In main(), we create pointers of type Shape* that point to objects of
type Circle and Square.
When we call draw() on these pointers, the actual object type is determined at
runtime, and the appropriate draw() function is called.
Function overriding
Function Overriding occurs when a derived class has a definition for one of the member
functions of the base class. That base function is said to be overridden.
Polymorphism by Parameter
In C++, polymorphism by parameter, also known as function overloading, is a form of
compile-time polymorphism that allows you to define multiple functions with the same
name but with different parameters. The compiler determines which function to call based
on the number and types of the arguments passed during the function call.
#include <iostream>
int main() {
int result1 = sum(2, 3); // Calls the first sum function
double result2 = sum(2.5, 3.5); // Calls the second sum function
return 0;
}
Explanation:
In the example, there are two functions named sum.
The first sum function takes two integer parameters and returns their sum.
The second sum function takes two double parameters and returns their sum.
When calling sum(2, 3), the compiler determines that the integer version of the
function should be called, as the arguments are both integers.
When calling sum(2.5, 3.5), the compiler determines that the double version of the
function should be called, as the arguments are both doubles.
Pointer to Objects
A pointer to an object is a variable that stores the memory address of an object. This allows
you to indirectly access and manipulate the object's members (data and functions) through
the pointer.
Important Points:
Declaration: Pointers to objects are declared using the * operator, just like pointers
to other data types. For example:
ClassName *objectPointer ;
Initialization: You can initialize a pointer to an object using the address-of operator
(&) or by assigning it the address of an existing object. For example:
ClassName myObject;
ClassName *objectPointer = &myObject ;
Accessing Members: To access the members of an object using a pointer, you use
the arrow operator (->). For example:
objectPointer->memberFunction();
objectPointer->memberVariable = value ;
Deleting Dynamic Objects: When you create an object dynamically using new, it's
crucial to deallocate the memory using delete to prevent memory leaks. For
example:
delete objectPointer;
#include <iostream>
class Rectangle {
public:
int width;
int height;
int getArea() {
return width * height;
}
};
int main() {
Rectangle rect;
Rectangle *rectPointer = ▭
rectPointer->setDimensions(5, 10);
delete dynamicRect;
return 0;
}
Benefits of Using Pointers to Objects:
Dynamic Memory Allocation: Pointers allow you to create objects at runtime,
enabling flexible memory management.
Passing Objects Efficiently: Passing a pointer to an object is more efficient than
passing the entire object by value, especially for large objects.
Polymorphism: Pointers are crucial for working with polymorphism, allowing you
to use base class pointers to point to derived class objects and access their
overridden functions.
Flexibility: You can write code that works with a variety of objects without
knowing their exact types.
Extensibility: You can easily add new derived classes without modifying existing
code.
Code Reusability: You can write generic algorithms that work with a wide range of
objects.
Virtual Functions
A virtual function is a member function that is declared in the base class using the
keyword virtual and is re-defined (Overridden) in the derived class. It tells the compiler to
perform late binding where the compiler matches the object with the right called function
and executes it during the runtime. This technique falls under Runtime Polymorphism.
The idea is that virtual functions are called according to the type of the object instance
pointed to or referenced, not according to the type of the pointer or reference.
In other words, virtual functions are resolved late, at runtime.
#include <fstream>
#include <iostream>
using namespace std;
void calculate()
{
cout << "Enter Width of Rectangle: ";
cin >> width;
virtual ~Rectangle()
{
cout << "Rectangle Destuctor Call\n";
}
};
void calculate()
{
cout << "Enter one side your of Square: ";
cin >> side;
int main()
{
Output:
Enter Width of Rectangle: 10
Enter Height of Rectangle: 20
Area of Rectangle: 200
Enter one side your of Square: 16
Area of Square: 256
Use of Virtual Functions:
Virtual functions allow us to create a list of base class pointers and call methods of any of
the derived classes without even knowing the kind of derived class object.
Syntax:
// An abstract class
class Test {
// Data members of class
public:
// Pure Virtual Function
virtual void show() = 0;
/* Other members */
};
#include <iostream>
using namespace std;
class Base{
int x;
public:
// pure virtual function
virtual void fun() = 0;
public:
// implementation of the pure virtual function
void fun() { cout << "fun() called"; }
};
int main(void)
{
// creating an object of Derived class
Derived d;
return 0;
}
Output:
fun() called
Classes having virtual functions are not Base class containing pure virtual function
abstract. becomes abstract.
Syntax: Syntax:
CPP CPP
Explain virtual<func_type><func_name&
gt;()
virtual<func_type><func_name&
gt;() = 0;
// code
Virtual function Pure virtual function