Lecture Handout
Introduction
to Programming
Lecture No. 31
Deitel & Deitel - C++ How to
Program Chapter 8
8.2,
8.3, 8.4, 8.6, 8.7
Summary
• Lecture
Overview
• What
is Operator Overloading and Why is it Required
• Where
is it Relevant to Apply
• Operators
to Overload
• Restrictions
on Operator Overloading
• Examples
of Operator Overloading
• Non-member
Operator Functions
• Example
Program 1
• Example
Program 2
• Tips
Lecture
Overview
The
topic of this lecture is Operator
Overloading. In previous lectures, we discussed
about
it a bit while discussing about references. So we will see in detail what is
operator
overloading,
how to overload operators, where it is relevant to apply and what are the
restrictions
on it.
What
is Operator Overloading and Why is it Required?
Operator
overloading is to allow the same operator to be bound to more than one
implementation,
depending on the types of the operands.
As
you know that there are standard arithmetic operators in C/C++ for addition ( +
),
subtraction
( - ), multiplication ( * ) and division ( / ). We should only use these
operators
for
their specific purposes. If we want to add two ints,
say i and j, the
addition will take
place
in the following manner i.e. i + j. To add two double
numbers, we use the same
operator
and write d1 + d2. We may add two floats with the
help of the same operator as
f1 + f2. Similarly other operations of -, * and / on
the primitive types (sometimes called
as
native or built-in types) can be employed. In other words, these operators are
already
overloaded
for primitive types in C++. But these C++ operators cannot be used for
classes
and their objects. We have to write our own operator functions that can work
with
objects.
Let’s
take an example of complex numbers. There are two parts of a complex number
i.e.
real and imaginary. As
complex numbers are part of mathematical vocabulary, so the
mathematical
manipulations are done on them like addition, subtraction and
multiplication.
Suppose, we write our own class for complex numbers named Complex,
but
we can’t add two complex numbers c1 and c2 as c1
+ c2 because until now we don’t
know
how to write it. Although, we are able to write a function say cadd() to
serve this
purpose.
Complex cadd ( Complex c1, Complex
c2 ) ;
It
accepts two complex numbers as parameters and returns back the resultant
complex
number.
But the usage of this function to add two complex numbers is generally clumsy.
It
gets more cumbersome and complex if we want to carry out cascading operations
like
i + j + k. It is better to use the standard operators of +, -, * and / as
they are more readable
and
elegant.
Where
is it Relevant to Apply?
Firstly,
the operator overloading gets relevant whenever there is the application of the
mathematical
functions of addition, subtraction, multiplication and division. Complex
number
is one example of it. As discussed earlier, in case of Date
class, the operators can
be
effectively used to get the future or past dates.
Secondly,
the operators are also used sometimes in case of non-mathematical
manipulation.
The example of String class to manipulate strings help
us understand it in
a
better way. The operator + can be used to concatenate two
strings. Previously, we used
strcat() function declared inside string.h header file to concatenate two
strings. As
compared
to strcat(), the
use of + to
concatenate two strings is definitely easier and
more
readable. But there is a little bit cost associated with this process of
operators
overloading.
The
cost is involved whenever we overload an operator. We have to write a function
and
make
use of the operator semantics correctly while implementing the function. This
means
that the function written to overload + operator
should do addition or
concatenation
of strings in case of String objects.
Operators
to Overload
There are two types of operators to overload:
1. Unary
2. Binary
Unary
operators are the ones that require only one operator to work. Unary operators
are
applied
to the left of the operand. For example, ^, &, ~ and !.
Binary
operators require two operands on both sides of the operator. +, -, *, /, %, =, <
and
> are
examples of binary operators.
The
complete list of C++ operators that can be overloaded is as follows:
+ - * / % ^ &
| ~ ! = < > +=
-= *= /= %= ^=
&= |=
<< >>
>>= <<= ==
!= <=
>= && | |
++ - -
->
* ,
[ ] (
) new new[ ]
delete delete[ ]
The
following operators can’t be overloaded.
. : :: .* ? sizeof
Let’s
start with operator overloading mechanism. Consider an object date of the Date
class.
The data member day can be accessed as follows:
date.day = 2;
In
this statement, the day data member of the date
object is accessed and assigned value
2. This expression (date.day) is
driven by the object name at left.
Similarly,
while using operators, the statement like a + b is driven by the object at the
left.
In this case, + operator function for the object a will
be called and b object is passed
explicitly
to the + operator function as an argument.
The rules of function overloading are
applied
to the operator overloading. We cannot write two + operator functions with
exactly
identical parameters. Following the overloading rules, the two operator
functions
have
to be different by the type or number of arguments.
The
syntax of the prototype of the overloaded operator function is:
return-type operator operator-symbol (parameter-list);
operator is the keyword here. An example of this will be as follows:
Complex operator
+ (Complex & );
We
sometimes write only operator to refer to the operator function in our
discussion.
Restrictions
on Operator Overloading
There
are some restrictions on operator overloading.
- The operator overloading functions for
overloading (), [], -> or
the assignment (=)
Operators
must be declared as class members.
- The arity (number of operands) cannot be
changed. If you are overloading an operator
that
requires two operands e.g. *. It cannot be used as a unary
operator that requires
one
operand.
- No new operators can be created. Like in
Fortran language, we have ** as ‘raise to
the
power (exponent) operator’ but this operator does not exist in C++. Therefore,
it
can’t
be overloaded. Hence, only existing operators of C++ are used.
- Overloading can’t be performed for the
built-in (sometimes called primitive or native)
data
types. For example, we cannot change how two ints are
added. That means that
operators
are overloaded to use with defined data types like classes.
- Precedence of an operator cannot be changed.
For example, the * has
higher
precedence
than +. This precedence cannot be
changed.
- Associativity of an operator cannot be
changed. If some operator is right associative,
it
cannot be changed to be left associative.
Examples
of Operator Overloading
Let’s
take the complex number’s class Complex and
define a + operator function.
We
know that when we write the following line:
x =
y + z ;
y and z operands are take part in the
addition operation but there is no change in them due
to
this operation. This is the + operator’s functionality. The
resultant is being assigned to
the
variable x. This is assignment operator’s functionality.
Now
we will discuss a little bit about the assignment operator as well. Let’s say
we write
the
following statement for two complex numbers c1 and c2.
c1 = c2
;
Here
c2 is
being assigned to c1. Will this assignment work when we
have not written any
assignment
operator function for complex number? Apparently, it looks that the
statement
will produce a compilation error (as there is assignment operator defined by
us)
but
this is not true. Whenever, we write our own class and compile it, the compiler
automatically
generates a default assignment operator. The default assignment operator
makes
a member to member assignment. This works fine unless there is a pointer data
member
inside our class and that pointer is pointing to some data inside memory. For
that
case
(when there is a pointer data member) we have to write our own assignment
operator
otherwise
the default assignment operator works fine for us. That will be discussed in
the
subsequent
lectures.
By
definition of addition of complex numbers, we know that whenever two complex
numbers
are added, the real part of one number is added into the real part of other
number.
Similarly, the imaginary part of one number is added to the imaginary part of
the
other
number. We also know that when a complex number is added to another complex
number,
the resultant is also a complex number consisting of real and imaginary parts.
This
addition of real, imaginary parts and return of resultant complex number is the
functionality
of the + operator function we are going to
write.
Another
thing to decide for this + operator is
whether this operator will be a member
operator
or a friend
operator. Normally, operators are member operators but there are
situations
when they cannot be member operators. In case of member operator, following
is
the syntax of its prototype:
Complex operator
+ (parameter-list);
For
member operator, the object on the left side of the + operator is driving this +
operation.
Therefore, the driving object on the left is available by this pointer to +
operator
function. But the object on the right is passed explicitly to the +
operator as an
argument.
We
can define a member operator as under:
1.
Complex Complex
:: operator + (Complex
c)
2.
{
3.
Complex temp ;
4.
temp.real =
real + c.real ;
5.
temp.imag =
imag + c.imag ;
6.
return temp ;
7.
}
Let’s
see this code line by line.
Line
1 indicates that the return type is Complex, it is an operator + function and it is
accepting
a Complex object by value as an argument.
In
line 3, a local Complex object is declared, called temp.
In
line 4, real part
of the calling object (that is the one, driving) on the left of the +
operator
is being added to the real part of the object c,
where c is passed as an argument.
In
line 5, imag part
of the calling object (that is the one, driving) on the left of the +
operator
is being added to the imag part of the object c,
where c is passed as an argument.
In
line 6, the Complex
object temp
containing the resultant of +
operation is being
returned
by value.
In
our code, we can write something as:
Complex c1, c2, c3 ;
.
. .
.
. .
c3 =
c1 + c2 ;
In
the above statement ( c3 = c1
+ c2; ), c1 is the object that
is calling or driving the
+ operator. c2
object is being passed as an argument to the + operator. So c1 and c2
objects
are added by the + operator and resultant Complex
object containing the addition
of
these two numbers is returned back. That returned Complex
object is assigned to the
c3 Complex
object using the default assignment operator (that is created by the C++
compiler
automatically).
What
happens if we want to add a double
number to a complex number (a instance of
Complex)? Like the following:
c3 =
c1 + d ;
This
+
operation is driven by the c1 object of Complex
while double number d of type
double is passed as argument. Therefore, our above written +
operator is not useable for
this
operation of addition. We need to overload +
operator for accepting a parameter of
type
double,
i.e. we need to write another operator function. The definition of this newly
overloaded
+
operator is:
Complex Complex
:: operator + (double
d)
{
Complex temp ;
temp.real =
real + d ; // d
is added into the real part
temp.imag =
imag ;
return temp ;
}
By
now, you should have noticed that operator overloading and function overloading
are
quite
similar.
When
we write the following statement:
c3 =
d + c1;
The
operand on the left of + operator is a double
number d. Therefore, this + operation
should
be driven by (called by) the double
number. Until now, we have not written such
an
operator. Our previously written two +
operators were driven by the Complex
object.
Operator
functions, not driven by the class type objects, are kept as friends to
the class.
friend is the keyword used to declare such functions. A friend
function to a class also
has
access to the private members of that class.
friend Complex
operator + (double d, Complex
c)
{
Complex temp;
temp.real =
d + c.real; // d
is added into the real part of c
temp.imag =
c.imag;
return temp;
}
You
might have noticed that all the three overloaded +
operator functions are accepting
and
returning variables by value. To make these functions better, we can also use
references.
So our first member + operator’s prototype can be
rewritten as:
Complex& operator
+ (Complex& c);
Now
this operator function is accepting a complex number Complex by reference and
returning
a reference to the resultant complex number.
As
discussed above, in case of assignment, the default assignment operator is used
because
we have not implemented (overloaded) our own assignment operator (‘=’).
But
in case, we want to perform the following operation where the two operands are
added
and the resultant is assigned to one of them as:
c1 =
c1 + c2;
There
is one operator (+=) that can be used to do both the
operations of addition and
assignment
instead of doing these operations separately within operator
+ and operator
=. So we can overload this one operator (+=)
here to make the code more efficient and
reduce
our work. Therefore, instead of writing:
c1 =
c1 + c2;
We
will write:
c1 +=
c2;
We
will write our operator += as:
void Complex :: operator += ( Complex& c )
{
real +=
c.real;
imag +=
c.imag;
}
Non-member
Operator Functions
Now
we are much clear that when an operator function is implemented as a member
function,
the leftmost operator must be a class object or reference to a class object of
the
operator’s
class.
When
an operator function is implemented as a non-member function, the left-most
operand
may be an object of the operator’s class, an object of a different class, or a
built-
in
type. Now we discuss it in a detailed manner.
We
can always write our operators as non-member functions. As a non-member
functions,
the binary operators like + gets both the operands as
arguments. One thing to
take
care of while writing non-member functions that they cannot access the private
members
of classes. Actually, this is just to this reason that we make those non-member
functions
as friends to
the classes whose private data
members are required to be
accessed.
But the question arises, can we write a non-member operator function without
making
it a friend of a
class. The answer to this question is yes; If there are public
member
functions to access the private data members of the class then they serve the
purpose.
In this case of Complex class, let’s say we have two
public member functions:
double real( );
double imaginary( );
to
access the private data members real and imag
respectively.
Then we can write non-
member
operator
+
function as:
Complex operator
+ (Complex& c1,
Complex& c2)
{
Complex temp;
temp.real
( c1.real() + c2.real() );
temp.imaginary
( c1.imaginary() + c2.imaginary() );
return
temp;
}
But
this non-member operation functions without declaring a friend of the class is
definitely
slower than the member function or a friend one.
The reason for this is obvious
from
the code that it is making three additional function calls of real() and imaginary()
for
each private data member. Also it is not easy to write as compared to member
functions.
Therefore, it is recommended to write the member functions for operators
instead
of non-members.
Let’s
take an example where the operators are performing a non-arithmetical
operation.
We
are writing a class String for strings manipulation as:
class
String
{
private :
char
string [ 30 ] ;
public :
String ( )
{
strcpy ( string , "" )
;
}
void
getString ( )
{
cout <<
"Enter the String : " ;
cin >>
string ;
}
void displayString ( )
{
cout <<
"The String Is : "
<< string <<
endl ;
}
// Declaration (prototype) of
overloaded sum operator
String
operator + ( String & s ) ;
};
We
want to write + operator to concatenate two strings. Firstly, we will see the
operator’s
behavior
in ordinary context (behavior with primitive variables for example) and try to
implement
the same behavior for this class. We want to concatenate two strings (two
String objects) and then assign the resultant string to a new String
object. Here is how
we
will write + operator function.
String
String :: operator + ( String &s )
{
String temp; //
Declared object temp of String type
strcpy ( temp.string , "" ); //
Initialized the temp with empty string
strcat ( temp.string , string ); //
Concatenated the driving object’s string to
//
temp object
strcat
( temp.string , s.string ); //
Concatenated the argument’s string to the
//
temp object
return
temp; //
Returned the temp object
}
As
you might have guessed already, the String
object on the left will be the one to drive
this
+ operation and the second String
object on the left of + will
be passed as an
argument
to this function. Note that we are not doing the error checking here, the size
of
the
resultant string temp may
increase the array size 30 (
the array size defined in the
class).
Example
Program 1
Rudimentary
implementation of a class named Complex
class to cater complex numbers.
A + operator function has been implemented to add two complex numbers.
/*
This program implements the basic class for complex numbers and demonstrates +
operator
function */
#include
<iostream.h>
class Complex
{
private :
double
real ; // Real Part
double
imag ; // Imaginary Part
public :
/* Parameterless Constructor */
Complex ( )
{
cout <<
"\n Parameterless Constructor called ..." ;
}
/* Parameterized Constructor */
Complex ( double r, double
i )
{
cout <<
"\n Parameterized Constructor called ...";
real = r
;
imag = i
;
}
/* Setter of real data member */
void
real ( double r)
{
real = r
;
}
/* Getter of the real data member */
double
real ( )
{
return real ;
}
/* Setter of the imag data member */
void
imaginary ( double i )
{
imag = i ;
}
/* Getter of the imag data member */
double
imaginary ( )
{
return imag ;
}
/* A Function to display parts of a
Complex object */
void
display ( )
{
cout <<
"\n\n Displaying parts of complex number ...";
cout <<
"\n Real Part : " << real << endl ;
cout <<
" Imaginary Part : " << imag << endl ;
}
/* Declaration (prototype) of
overloaded sum operator */
Complex operator
+ ( Complex & c2 ) ;
};
Complex Complex
:: operator + ( Complex & c1 )
{
cout
<< "\n Operator +
called ...";
Complex
temp ;
temp.real
= real +
c1.real ;
temp.imag
= imag +
c1.imag ;
return
temp ;
}
void
main ( )
{
Complex c1 ( 1 , 2 ) ; // Consturct an object using the
parameterized constructor
Complex c2 ( 2 , 3 ) ; // Consturct another object using the
parameterized
//
constructor
Complex result ; // Construct an object using a parameterless
constructor
result = c1 + c2 ; // Call the Operator + to add two complex
numbers (c1 & c2)
// and then
assign the result to 'result' object
result.display ( ) ; // Display the
result object contents
}
The output of the program is as follows:
Parameterized
Constructor called ...
Parameterized
Constructor called ...
Parameterless
Constructor called ...
Operator
+ called ...
Parameterless
Constructor called ...
Displaying
parts of complex number ...
Real
Part : 3
Imaginary Part : 5
The
+
operator function can be enhanced to return reference of Complex object. We can
also
implement += operator. +=
operator and the enhanced operator + are
implemented
as:
Complex
& Complex ::
operator + ( Complex & c1 )
{
real
= real +
c1.real ;
imag
= imag +
c1.imag ;
return
*this;
}
//
Declaration (prototype) of overloaded sum assignment operator definition
Complex
& Complex :: operator += ( Complex & c2 )
{
real
+= c2.real ;
imag
+= c2.imag ;
return
*this;
}
Example
Program 2
Rudimentary
Implementation of String class to manipulate strings. It uses +
operator to
concatenate
strings.
/*
This program implements the basic class for strings and demonstrates + operator
function
to concatenate two strings*/
#include <iostream.h>
#include <string.h>
class String
{
private :
char
string [ 30 ] ; // Array to
store string
public :
/* Parameterless Constructor */
String ( )
{
strcpy ( string ,
"" ) ;
}
/* Getter function of string */
void
getString ( )
{
cout <<
"Enter the String: " ;
cin >>
string ;
}
/* Function to display string */
void
displayString ( )
{
cout <<
"The String is : "
<< string <<
endl ;
}
// Declaration (prototype) of
overloaded sum operator
String
operator + ( String & s ) ;
};
String String
:: operator + ( String &s )
{
String
temp ;
strcpy ( temp.string , "" ) ;
strcat ( temp.string , string );
strcat ( temp.string , s.string );
return
temp;
}
void
main ( )
{
String
string1 , string2 ; //
Declared two String objects
string1.getString ( ) ; //
Get string for string1 object
string2.getString ( ) ; //
Get string for string2 object
String hold =
string1 + string2 ; // Concatenate string1 and string2 and store the
//
result in hold object
hold.displayString ( ) ; //
Display the string
}
The output of the above program is as follows:
Enter
the String: Operator
Enter
the String: Overloading
The String is : OperatorOverloading
Tips
- Operator Overloading is quite similar to
Function Overloading.
- There are two types of operators to overload:
unary and binary.
- C++ built-in operators work for built-in
(primitve) types but for user defined data
types,
user has to write his/her own operators.
- There are some restriction while performing
Operator Overloading. For example,
only
existing C++ operators are overloaded without creating a new one in the
language.
Also, it should not impact the type, semantics (behavior), arity (number of
operands
required), precedence and associativity of the operator.
- For binary member operators, operands on the
left drives (calls) the operation.
- Operator functions written as non-members but
friends of the class, get both the
operands
as their arguments.
- Operators can be written as non-members and
even without making them friends. But
this
is tedious and less efficient way, therefore, it is not recommended.
Post a Comment
Don't Forget To Join My FB Group VU Vicky
THANK YOU :)