0% found this document useful (0 votes)
3 views

Module 4_OOPs

Uploaded by

Aman Singh
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views

Module 4_OOPs

Uploaded by

Aman Singh
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 17

OBJECT ORIENTED PROGRAMMING LANGUAUGE IN C++

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

Disadvantages of Compile-time Polymorphism:


 Limited flexibility: Not suitable for scenarios where the type of object being handled
is unknown at compile time.
 Code bloat: Can potentially lead to code bloat if not used judiciously, as multiple
function definitions are created for different types.

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.

Syntax of function overhandling:

return_type function_name(parameter_type1 param1, parameter_type2 param2, ...) {


// Function body
}

return_type function_name(parameter_type1 param1) {


// Another version of the function with a different parameter list
}

return_type function_name(parameter_type1 param1, parameter_type2 param2,


parameter_type3 param3) {
// Yet another version of the function with a different number/type of parameters
}

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;
}

void print(const char* str) {


std::cout << "Printing a string: " << str << std::endl;
}

int main() {
print(10); // Calls print(int)
print(3.14); // Calls print(double)
print("Hello"); // Calls print(const char*)

return 0;
}

Operation Overloading: Operator overloading in C++ is a powerful feature that


allows you to define the behaviour of operators (like +, -, *, etc.) when used with user-
defined types (like classes and structs). It's a form of compile-time polymorphism, meaning
the decision of which overloaded operator function to call is made during compilation based
on the types of the operands. It is the method by which we can change some specific
operators’ functions to do different tasks.
Syntax:

Return_Type classname :: operator op(Argument list)


{
Function Body
} // This can be done by declaring the function

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.

Example of operator overhandling:

#include <iostream>

class Complex {
public:
double real;
double imag;

Complex(double r = 0, double i = 0) : real(r), imag(i) {}

// Overloading the + operator


Complex operator+(const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}
};
int main;

Complex c1(2, 3);


Complex c2(4, 5);
Complex result = c1 + c2; // Using the overloaded + operator

std::cout << result.real << " + " << result.imag << "i" << std::endl;

return 0;
}

Operator Overloading can be done by using two approaches, i.e.


1. Overloading Unary Operator.
2. Overloading Binary Operator.
Overloading Unary Operator : Unary operator overloading in C++ allows you
to redefine the behaviour of unary operators (operators that work on a single operand) for
user-defined types, like classes. In the unary operator function, no arguments should be
passed. It works only with one class object. It is the overloading of an operator operating on
a single operand . Operators that can be overloaded are+, -, !, ~, +
+, --, * (dereference), & (address-of), -> (member access).
 The Counter class defines a simple counter with ++ and - operator overloads.
 The prefix ++ operator increments the counter and returns a reference to the updated
object.
 The postfix ++ operator creates a copy of the object, increments the original, and
returns the copy.
 The unary - operator returns a new Counter object with the negated value.

Syntax:
 Member function: returnType operator<symbol>() { /* implementation */ }
 Non-member function: returnType operator<symbol>(className& obj) { /*
implementation */ }

Member vs. Non-member:


 Member functions have direct access to the object's private members.
 Non-member functions require access to the object's members through public
interfaces.

Pre-increment vs. Post-increment:

 The pre-increment operator (++obj) is overloaded using the syntax mentioned


above.
 The post-increment operator (obj++) is overloaded with an
additional int argument: returnType operator++(int).

Overloading Binary Operator: In the binary operator overloading function, there


should be one argument to be passed. It is the overloading of an operator operating on two
operands. Binary operators can be overloaded to perform custom operations on user-defined
types like classes.
 Common binary operators that can be overloaded include +, -, *, /, ==, !=, >, <, >=,
<=, &&, ||, etc.
 Some operators, such as = and [], have specific rules or are overloaded implicitly.
 Binary operators usually return a new object by value that represents the result of
the operation.
 It is common practice to pass the second operand by const reference to avoid
copying and to ensure it is not modified.
 Overloaded operators follow the default associativity and precedence rules. You
cannot change these behaviors with overloading.
 For example, * has higher precedence than +, and this does not change when
overloading.
 When overloading comparison operators like ==, !=, >, etc., ensure the return type
is bool.
 These are used to compare two objects logically.
 Compound assignment operators like +=, -=, etc., should also be overloaded for
consistency if the basic arithmetic operators are overloaded.

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).

Run-time overloading: This type of polymorphism is achieved by Function


Overriding. Late binding and dynamic polymorphism are other names for runtime
polymorphism. The function call is resolved at runtime in runtime polymorphism. In
contrast, with compile time polymorphism, the compiler determines which function call to
bind to the object after deducing it at runtime. Runtime polymorphism (also known as
dynamic polymorphism) is achieved through virtual functions and inheritance. This allows
you to determine which function to call at runtime based on the actual object type, not just
the declared type.
 Inheritance: Runtime polymorphism requires a base class and one or more derived
classes.
 Virtual Functions: The base class must declare a virtual function that derived
classes can override.
 Pointers or References: You must use pointers or references to the base class to
access the virtual function.
 Dynamic Dispatch: The decision of which function to call is made at runtime based
on the actual type of the object being pointed to or referenced.

Key Differences from Compile-time Overloading (Function Overloading):


 Binding Time: Runtime polymorphism uses late binding (dynamic dispatch), while
function overloading uses early binding (compile-time).
 Function Signature: Function overloading relies on different function signatures
(parameter types and/or number), while runtime polymorphism uses the same
function signature in the base and derived classes.
 Inheritance: Runtime polymorphism requires inheritance, while function
overloading does not.

Example of run-time polymorphism:

#include <iostream>

class Shape {
public:
virtual ~Shape() {} // Virtual destructor
virtual void draw() {
std::cout << "Drawing a generic shape\n";
}
};

class Circle : public Shape {


public:
void draw() override {
std::cout << "Drawing a circle\n";
}
};

class Square : public Shape {


public:
void draw() override {
std::cout << "Drawing a square\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.

Benefits of Polymorphism by Parameter:


 Readability and Maintainability: It allows you to use a single function name for
similar operations, making your code cleaner and more intuitive.
 Flexibility: It enables you to write functions that can handle different types of data
without resorting to complex conditional statements.
 Code Reusability: You can write generic functions that can operate on a variety of
data types, reducing code duplication.

Example of Polymorphism by parameter:

#include <iostream>

using namespace std;

int sum(int a, int b) {


return a + b;
}

double sum(double a, double b) {


return a + b;
}

int main() {
int result1 = sum(2, 3); // Calls the first sum function
double result2 = sum(2.5, 3.5); // Calls the second sum function

cout << "Result 1: " << result1 << endl;


cout << "Result 2: " << result2 << endl;

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 ;

 Dynamic Memory Allocation: Pointers are essential for dynamic memory


allocation, allowing you to create objects on the heap using the new operator. For
example:
ClassName *objectPointer = new ClassName();

 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;

Example of Pointer to Object:

#include <iostream>

class Rectangle {
public:
int width;
int height;

void setDimensions(int w, int h) {


width = w;
height = h;
}

int getArea() {
return width * height;
}
};

int main() {
Rectangle rect;
Rectangle *rectPointer = &rect;
rectPointer->setDimensions(5, 10);

std::cout << "Area: " << rectPointer->getArea() << std::endl;

Rectangle *dynamicRect = new Rectangle();


dynamicRect->setDimensions(3, 6);

std::cout << "Dynamic Area: " << dynamicRect->getArea() << std::endl;

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.

How Pointers Work with Polymorphism


 Polymorphism enables you to treat objects of different derived classes through a
common base class pointer.
 This is achieved using virtual functions in the base class.
 When you call a virtual function through a base class pointer, the program
determines the correct function to call at runtime based on the actual type of the
object being pointed to. This is known as dynamic dispatch or late binding.

Important Points to remember:


 Base Class Pointer: You can use a pointer of the base class type to point to objects
of derived classes.
 Virtual Functions: To achieve polymorphism, the functions in the base class that
need to be overridden in derived classes must be declared as virtual.
 Dynamic Binding: The decision of which function to call is made at runtime, not at
compile time.

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.

Point to remember about Virtual functions:


 In C++ what calling a virtual functions means is that; if we call a member function
then it could cause a different function to be executed instead depending on what
type of object invoke it.
Because overriding from derived classes hasn’t happened yet, the virtual call
mechanism is disallowed in constructors. Also to mention that objects are built from
the ground up or follows a bottom to top approach.

Example of Virtual Function:

#include <fstream>
#include <iostream>
using namespace std;

// Declaration of Base class


class Shape {
public:
// Usage of virtual constructor
virtual void calculate()
{
cout << "Area of your Shape ";
}
// usage of virtual Destuctor to avoid memory leak
virtual ~Shape()
{
cout << "Shape Destuctor Call\n";
}
};

// Declaration of Derived class


class Rectangle : public Shape {
public:
int width, height, area;

void calculate()
{
cout << "Enter Width of Rectangle: ";
cin >> width;

cout << "Enter Height of Rectangle: ";


cin >> height;

area = height * width;


cout << "Area of Rectangle: " << area << "\n";
}

virtual ~Rectangle()
{
cout << "Rectangle Destuctor Call\n";
}
};

class Square : public Shape {


public:
int side, area;

void calculate()
{
cout << "Enter one side your of Square: ";
cin >> side;

area = side * side;


cout << "Area of Square: " << area << "\n";
}

// Virtual Destuctor for every Derived class


virtual ~Square()
{
cout << "Square Destuctor Call\n";
}
};

int main()
{

// base class pointer


Shape* S;
Rectangle r;

// initialization of reference variable


S = &r;

// calling of Rectangle function


S->calculate();
Square sq;

// initialization of reference variable


S = &sq;

// calling of Square function


S->calculate();

// return 0 to tell the program executed


// successfully
return 0;
}

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.

Pure Virtual Functions


Sometimes implementation of all functions cannot be provided in a base class because we
don’t know the implementation. Such a class is called an abstract class. For example an
Animal class doesn’t have the implementation of move() (assuming that all animals move),
but all animals must know how to move. We cannot create objects of abstract classes.
A pure virtual function (or abstract function) in C++ is a virtual function for which we
can have an implementation, But we must override that function in the derived class,
otherwise, the derived class will also become an abstract class. A pure virtual function is
declared by assigning 0 in the declaration and is implemented by classes that are derived from
an Abstract class.

Syntax:
// An abstract class
class Test {
// Data members of class
public:
// Pure Virtual Function
virtual void show() = 0;

/* Other members */
};

Example of Pure Virtual Functions:

#include <iostream>
using namespace std;

class Base{
int x;

public:
// pure virtual function
virtual void fun() = 0;

int getX() { return x; }


};

class Derived : public Base {


// private member variable
int y;

public:
// implementation of the pure virtual function
void fun() { cout << "fun() called"; }
};

int main(void)
{
// creating an object of Derived class
Derived d;

// calling the fun() function of Derived class


d.fun();

return 0;
}
Output:
fun() called

Important Points to Remember:


 A class is abstract if it has at least one pure virtual function.
 We can have pointers and references of abstract class type.
 If we do not override the pure virtual function in the derived class, then the derived
class also becomes an abstract class.
 An abstract class can have constructors.
 An abstract class in C++ can also be defined using struct keyword.

Similarities between Pure virtual and Virtual functions:

1. These are the concepts of Run-time polymorphism.


2. Prototype i.e. Declaration of both the functions remains the same throughout the
program.
3. These functions can’t be global or static.

Difference between virtual function and pure virtual function:

Virtual function Pure virtual function

A pure virtual function is a member


function of base class whose only
A virtual function is a member function of
declaration is provided in base class and
base class which can be redefined by
should be defined in derived class
derived class.
otherwise derived class also becomes
abstract.

Classes having virtual functions are not Base class containing pure virtual function
abstract. becomes abstract.

Syntax: Syntax:

CPP CPP

Explain virtual&lt;func_type&gt;&lt;func_name&
gt;()
virtual&lt;func_type&gt;&lt;func_name&
gt;() = 0;

// code
Virtual function Pure virtual function

Definition is given in base class. No definition is given in base class.

Base class having pure virtual function


Base class having virtual function can be
becomes abstract i.e. it cannot be
instantiated i.e. its object can be made.
instantiated.

If derived class does not redefine virtual


If derived class does not redefine virtual function of the base class, then no
function of the base class, then it does not compilation error is encountered, but like
affect compilation. the base class, derived class also becomes
abstract.

You might also like