<Previous Lesson

Introduction to Programming

Next Lesson>

Lesson#33

Lesson 33

Summary


22) Operator Overloading
23) Assignment Operator
24) Example
25) this Pointer
26) Self Assignment
27) Returning this Pointer from a Function
28) Conversions
29) Sample Program (conversion by constructor)


Operator Overloading


As earlier discussed, overloading of operators is carried out in classes on some occasions
to enable ourselves to write a code that looks simple and clean. Suppose, there is a class
and its two objects, say a and b, have been defined. Addition of these objects in the class
by writing a + b will mean that we are adding two different objects (objects are instance
of a class which is a user defined data type). We want our code to be simple and elegant.
In object base programming, more effort is made in class definitions, as classes are data
types that know how to manipulate themselves. These know how to add objects of their
own type together, how to display themselves and do many other manipulations. While
discussing date class in the previous lecture, we referred to many examples. In an
example, we tried to increment the ‘date’. The best way is to encapsulate it in the class
itself and not in the main program when we come around to use the date class.

Assignment Operator


At first, we ascertain whether there is need of an assignment operator or not? It is needed
when we are going to assign one object to the other, that means when we want to have
Page 418
expression like a = b. C++ provides a default assignment operator. This operator does a
member-wise assignment. Let’s say, we have in a structure of a class three integers and
two floats as data members. Now we take two objects of this class a and b and write a =
b.
Here the first integer of a will have the value of first integer of b. The second will have
the value of second integer and so on. This means that it is a member-wise copy. The
default assignment operator does this. But what is to do if we want to do something more,
in some special cases?
Now let’s define a String class. We will define it our self, without taking the built in
String
class of C. We know that a string is nothing but an array of characters. So we
define our String class, with a data member buffer, it is a pointer to character and is
written as *buf i.e. a pointer to character (array). There are constructors and destructors of
the class. There is a member function length that returns the length of the string of the
calling object. Now we want an assignment operator for this class. Suppose we have a
constructor that allows placing a string into the buffer. It can be written in the main
program as under:
String s1 ( “This is a test” ) ;
Thus, an object of String has been created and initialized. The string “This is a test” has
been placed in its buffer. Obviously, the buffer will be large enough to hold this string as
defined in our constructor. We allocate the memory for the buffer by using new operator.
What happens if we have another String object, let’s say s2, and want to write s2 = s1 ;
Here we know that the buffer is nothing but a pointer to a memory location. If it is an
array of characters, the name of the array is nothing but a pointer to the start of the
memory location. If default assignment operator is used here, the value of one pointer i.e.
buf
of one object will be assigned to buf of the other object. It means there will be the
same address in the both objects. Suppose we delete the object s1, the destructor of this
object will free the allocated memory while giving it back to the free store. Now the buf
of s2 holds the address of memory, which actually has gone to free store, (by the
destructor of s1). It is no longer allocated, and thus creates a problem. Such problems are
faced often while using default assignment operator. To avoid such problems, we have to
write our own assignment operator.
Before going on into the string assignment operator, let’s have a look on the addition
operator, which we have defined for strings. There is a point in it to discuss. When we
defined addition operator for strings, we talked about that what we have to do if we want
to add (concatenate) a string into the other string. There we had a simple structure i.e.
there is a string defined char buf with a space of 30 characters. If we have two string
objects and the strings are full in the both objects. Then how these will be added? Now
suppose for the moment that we are not doing memory allocation. We have a fixed string
buffer in the memory. It is important that the addition operator should perform an error
check. It should take length of first string , then the length of second string, before adding
them up, and check whether it is greater than the length defined in the String (i.e. 30 in
this case). If it is greater than that, we should provide it some logical behavior. If it is not
Page 419
greater, then it should add the strings. Thus, it is important that we should do proper error
checking.
Now in the assignment operator, the problem here is that the buf, that we have defined,
should not point to the same memory location in two different objects of type String.
Each object should have its own space and value in the memory for its string. So when
we write a statement like s2 = s1, we want to make sure that at assignment time, the
addresses should not be assigned. But there should be proper space and the strings should
be copied there. Let’s see how we can do this.
Now take a look on the code itself. It is quiet straight forward what we want to do is that
we are defining an assignment operator (i.e. =) for the String class. Remember that the
object on left side will call the = operator. If we write a statement like s2 = s1; s2 will be
the calling object and s1 will be passed to the = operator as an argument. So the data
structure of s2 i.e. buf is available without specifying any prefix. We have to access the
buf
of s1 by writing s1.buf.
Suppose s2 has already a value. It means that if s2 has a value, its buffer has allocated
some space in the memory. Moreover, there is a character string in it. So to make an
assignment first empty that memory of s2 as we want to write something in that memory.
We do not know whether the value, we are going to write, is less or greater than the
already existing one. So the first statement is:
delete buf
;
Here we write buf without any prefix as it is buf of the calling object. Now this buffer is
free and needs new space. This space should be large enough so that it can hold the string
of s1. First we find the length of the string of s1 and then we use the new operator and
give it a value that is one more than the length of the buffer of s1. So we write it as:
buf = new char[length + 1]
;
where length is the length of s1. Here buf is without a prefix so it is the buf of object on
the left hand side i.e. s2. Now, when the buf of s2 has a valid memory address, we copy
the buf of s1 into the buf of s2 with the use of string copy function (strcpy). We write it
as:
strcpy ( buf, s1.buf )
;.
Now the buf of string of s1 has been copied in the buf of s2 that are located at different
spaces in the memory. The advantage of it is that now if we delete one object s1 or s2, it
will not affect the other one. The complete code of this example is given below.
Example

/*This program defines the assignment operator. We copy the string of one object
Page 420
into the string of other object using different spaces for both strings in the memory.
*/
#include <iostream.h>
#include <string.h>
#include <stdlib.h>
// class definition
class String
{
private :
char *buf ;
public:
// constructors
String();
String( const char *s )
{
buf = new char [ 30 ];
strcpy (buf,s);
}
// display the string
void display ( )
{
cout << buf << endl ;
}
// getting the length of the string
int length ()const
{
return strlen(buf);
}
// overloading assignment operator
void operator = ( const String &other );
};
// ----------- Assignment operator
void String::operator = ( const String &other )
{
int length ;
length = other.length();
delete buf;
buf = new char [length + 1];
strcpy( buf, other.buf );
}
//the main program that uses the new String class with its assignment operator:
main()
{
Page 421
String myString( "here's my string" );
cout << “My string is = ” ;
myString.display();
cout << '\n';
String yourString( "here's your string" );
cout << “Your string is = ” ;
yourString.display();
cout << '\n';
yourString = myString;
cout << “After assignment, your string is = ” ;
yourString.display();
cout << '\n';
system ("pause");
}
Following is the output of the program.
My string is = here's my string
Your string is = here's your string
After assignment, your string is = here's my string
The above example is regarding the strings. Yet in general, this example pertains to all
classes in which we do memory manipulations. Whenever we use objects that allocate
memory, it is important that an assignment operator (=) should be defined for it.
Otherwise, the default operator will copy the values of addresses and pointers. The actual
values and memory allocation will not be done by it.
Let’s go on and look what happens when we actually do this assignment? In the
assignment of integers, say we have three integers i, j and k with some values. It is quiet
legal to write as i = j ; By this ,we assign the value of j to i. After this we write k = i ; this
assigns the value of i to k. In C, we can write the above two assignment statements in one
line as follows
k = i = j
;
This line means first the value of j is assigned to i and that value of i is assigned to k.
The mechanism that makes this work is that in C or C++ every expression itself has a
value. This value allows these chained assignment statements to work. For example,
when we write k = i = j ; then at first i = j is executed. The value at the left hand side of
this assignment statement is the value of the expression that is returned to the part ‘k =’
.This value is later assigned to k. Now take another example. Suppose we have the
following statement:
Page 422
k = i = ++j
;
In this statement, we use the pre-increment operator, as the increment operator (++) is
written before j. This pre-increment operator will increment the value of j by 1 and this
new value will be assigned to i. It is pertinent to note that this way ++j returns a value
that is used in the statement. After this, the value of i is assigned to k. For integers, it is
ok. Now, how can we make this mechanism work with our String objects. We have three
String
objects s1, s2 and s3. Let’s say there is a value (a string ) in the buffer of s1. How
can we write s3 = s2 = s1;. We have written s2 = s1 in the previous example in
assignment operator. We notice that there is no return statement in the code of the
assignment operator. It means that operator actually returns nothing as it has a void return
type. If this function is not returning any thing then s3 = s2 = s1 ; cannot work. We make
this work with the help of this pointer.

this Pointer


Whenever an object calls a member function, the function implicitly gets a pointer from
the calling object. That pointer is known as this pointer. ‘this’ is a key word. We cannot
use it as a variable name. ‘this’ pointer is present in the function, referring to the calling
object. For example, if we have to refer a member, let’s say buf, of our String class, we
can write it simply as:
buf ;
That means the buf of calling object is being considered. We can also write it as
this->buf
;
i.e. the data member of the object pointed by this pointer is being called. These ( buf and
this->buf
) are exactly the same. We can also write it in a third way as:
(*this).buf
;.
So these three statements are exactly equivalent. Normally we do not use the statements
written with this key word. We write simply buf to refer to the calling object.
In the statement (*this).buf ; The parentheses are necessary as we know that in object.buf
the binding of dot operator is stronger than the *. Without parentheses the object.buf is
resolved first and is dereferenced. So to dereference this pointer to get the object, we
enforced it by putting it in parentheses.

Self Assignment


Suppose, we have an integer ‘i. In the program, somewhere, we write i = i ; It’s a do
nothing line which does nothing. It is not an error too. Now think about the String object,
we have a string s that has initialized to a string, say, ‘This is a test’. And then we write s
= s
; The behavior of equal operator that we have defined for the String object is that it, at
first deletes the buffer of the calling object. While writing s = s ; the assignment operator
Page 423
frees the buffer of s. Later, it tries to take the buffer of the object on right hand side,
which already has been deleted and trying to allocate space and assign it to s. This is
known as self-assignment. Normally, self -assignment is not directly used in the
programs. But sometimes, it is needed. Suppose we have the address of an object in a
pointer. We write:
String s, *sptr
;
Now sptr is a pointer to a String while s is a String object. In the code of a program we
can write
sptr = &s
;
This statement assigns the address of s to the pointer sptr. Now some where in the
program we write s = *sptr ; that means we assign to s the object being pointed by sptr.
As sptr has the address of s , earlier assigned to it. This has the same effect as s = s ;. The
buffer of s will be deleted and the assignment will not be done. Thus, the program will
become unpredictable. So self-assignment is very dangerous especially at a time when we
have memory manipulation in a class. The String class is a classic example of it in which
we do memory allocation. To avoid this, in the equal operator (operator=), we should first
check whether the calling object (L.H.S.) and the object being gotten (R.H.S.) are the
same or not. So we can write the equal operator (operator=) as follows
void String::operator=( const String &other )
{
if( this == &other )
return;
delete buf;
length = other.length;
buf = new char[length + 1];
strcpy( buf, other.buf );
}

Here above, the statement if ( this == &other) checks that if the calling object (which is
referred by this) is the same as the object being called then do nothing and return as in
this case it is a self assignment. By doing this little change in our assignment operator, it
has become safe to use. So it is the first usage of this pointer that is a check against self
assignment.
Page 424
Returning
this Pointer From a Function

Now lets look at the second use of this pointer. We want to do the assignment as
s3 = s2 = s1
;
In this statement the value of s2 = s1 is assigned to s3. So to do this it is necessary that
the assignment operator should return a value. Thus, our assignment operator will expand
so that it could return a value. The assignment operator, till by now copies the string on
right hand side to the left hand side. This means s2 = s1 ; can be done by this. Now we
want that this s2 should be assigned to s3, which can be done only if s2 = s1 returns s2
(an object). So we need to return a String object. Here becomes the use of this pointer, we
will write as
return *this
;
Here, in the assignment operator code this is referring to the calling object (i.e. s2 in this
case). So when we write return *this ; it means return the calling object ( object on
L.H.S.) as a value. Thus s3 gets the value of s2 by executing s3 = s2 where s2 is the
value returned by the assignment operator by s2 = s1; Thus the complete assignment
operator (i.e. operator= function) that returns a reference to an object will be written as
under
String &String::operator=( const String &other )
{
if( &other == this ) //if calling and passed objects are
return *this; // same then do nothing and
return
delete buf;

Page 425
length = other.length;
buf = new char[length + 1];
strcpy( buf, other.buf );
return *this;
}

Now, here the first line shows that this operator= function returns a reference to an
object of type String and this function takes a reference as an argument. The above
version of operator= function takes a reference as an argument. First of all it checks it
with the calling object to avoid self assignment. Then it deletes the buffer of calling
object and creates a new buffer large enough to hold the argument object. And then at the
last it returns the reference of the calling object by using this pointer.
With this version of the assignment operator ( operator= function) we can chain together
assignments of String objects like s3 = s2 = s1 ;
Actually, we have been using this pointer in chained statements of cout. For example, we
write
cout << a << b << c ;

Where a, b, and c are any data type. It works in the way that the stream of cout i.e. << is
left associative. It means first cout << a is executed and to further execute it, the second
<< should have cout on its left hand side. So here, in a way, in this operator overloading,
a reference to the calling object that is cout is being returned to the stream insertion <<.
Thus a reference of cout is returned, and (as a reference to cout is returned) the << sees a
cout
on left hand side and thus the next << b is executed and it returns a reference to cout
and with this reference the next << c works. This all work is carried out by this pointer.
We have seen that value can be returned from a function with this pointer. We used it
with assignment operator. Lets consider our previous example of Date class. In that class
we defined increment operator, plus operator, plus equal operator, minus operator and
minus equal operator. Suppose we have Date objects d1, d2 and d3. When we write like
d2 = d1++
; or d2 = d1 + 1 ; here we realize that the + operator and the ++ operator
should return a value. Similarly, other operators should also return a value. Now let’s
consider the code of Date class. Now we have rewritten these operators. The difference in
code of these is not more than that now it returns a reference to an object of type Date.
There are two changes in the previous code. First is in the declaration line where we now
use & sign for the reference and we write it like
Date& Date::operator+=(int days)

We write this for the all operators (i.e. +, ++, - and -=). Then in the function definition we
return the reference of the left hand side Date object ( i.e. calling object) by writing
return *this
;
Now we can rewrite the operator+= of the Date class as follows.
The declaration line in the class definition will be as
Page 426
Date& operator+=(int days);

And the function definition will be rewritten as the following.
Date& Date::operator+=(int days) // return type reference to
object
{
for (int i=0; i < days; i++)
*this++;
return *this; // return reference to object
}

This concludes that whenever we are writing arithmetic operator and want that it can be
used in chained statements (compound statements) then we have to return a value. The
easiest and most convenient way of returning that value is by returning a reference to the
calling object. So, by now we can easily write the statements of date object, just like we
write for integers or floats. We can write
date2 = date1 + 1
;
Or date2 = date1++ ;
and so on.

Conversions


Being in the C language, suppose we have an integer i and a float x. Now in the program
we write x = i ; As we know that the operations of int and float are different in the
memory. Therefore, we need to do some kind of conversion. The language automatically
converts i (int) to a float (or to whatever type is on the L.H.S.) and then does the
assignment.
Both C and C++ have a set of rules for converting one type to another. These rules are
used in the following situations
- When assigning a value. For example, if you assign an integer to a variable of
type long, the compiler converts the integer to a long.
- When performing an arithmetic operation. For example, if you add an integer and
a floating-point value, the compiler converts the integer to a float before it performs
the addition.
- When passing an argument to a function; for example, if you pass an integer to a
function that expects a long.
- When returning a value from a function; for example, if you return a float from a
function that has double as its return type.
In all of these situations, the compiler performs the conversion implicitly. We can make
the conversion explicit by using a cast expression.
Page 427
Now the question arises that can we do conversion with objects of our own classes. The
answer is yes. If we go to the basic definition of a class it is nothing but a user defined
data type. As it is a user defined data type, we can also define conversion on it. When we
define a class in C++, we can specify the conversions that the compiler can apply when
we use instances of that class. We can define conversions between classes, or between a
class and a built-in type
There is an example of it. Suppose we have stored date in a serial number form. So now it
is not in the form of day, month and year but it is now a long integer. For example if we
start from January 1, 1900 then 111900 will be the day one, second January will be the
day 2, third January will be the day 3 and so on. Going on this way we reached in year
2000. There will be a serial number in year 2000. This will be a single long integer that
represents the number of days since a particular date. Now if we have a Date object
called d and an integer i. We can write d = i ; which means we want that i should go in
the serial part of d. This means convert the integer i into date object and then the value of
i
should go into the serial number and then that object should be assigned to d. We can
make this conversion of Date object. We do this in the constructor of the class. We pass
the integer to the constructor, here convert it into a date, and the constructor then returns
a Date object. On the other hand, if there is no constructor then we can write conversion
function. We have used the conversion operator with cast. The way of casting is that we
write the name of the cast (type to which we want to convert) before the name of
variable. Thus if we have to write x = i ; where x is a float and i is an integer then we
write it with conversion function as x = (float) i ; The (float) will be the conversion
operator. Normally this conversion is done by default but sometimes we have to force it.
Now we want to write a conversion operator, which converts an integer to a Date object.
(here Date is not our old class, it’s a new one). We can write a conversion function. This
function will be a member of the Date class. This function will return nothing, as it is a
conversion function. The syntax of this function will be Date () that means now this is a
conversion function.
In the body of this function we can write code of our own. Thus we can define the
operator, and it will work like that we write within the parentheses the name of the
conversion operator. The conversion functions are quiet interesting. They allow us to
manipulate objects of different classes. For example, we have two classes one is a truck
and other is a car. We want to convert the car to a truck. Here we can write a conversion
function, which says take a car convert it into a truck and then assign it to an object of
class truck.

Sample Program (conversion by constructor)


Lets take a look at an example in which the conversion functions are being used. There is
a class Fraction. Lets talk why we called it Fraction class. Suppose we have an
assignment
double x = 1/3
;
Page 428
When we store 1/3 in the computer memory, it will be stored as a double precision
number. There is a double precision division of 1 by 3 and the answer 0.33333… is
stored. Here the number of 3s depends upon the space in the memory for a double
precision number. That value is assigned to a double variable. What happens if we
multiply that double variable by 3, that means we write 3 * x; where x was equal to 1/3
(0.33333…). The answer will be 0.99999…, whatever the number of digits was. The
problem here is that the answer of 3 * 1 / 3 is 1 and not 0.99999. This problem occurs,
when we want to represent the numbers exactly. The fraction class is the class in which
we provide numerator and denominator to the object and it stores them separately. It will
always keep them as an integer numerator and an integer denominator and we can do all
kinds of arithmetic with it. Now if 1 and 3 are stored separately as numerator and
denominator, how we can add them to some other fraction. We have enough tools at our
disposal. One of which is that we can overload the addition operator. We can add 1/3 and
2/5, the result of which is another fraction i.e. numerator and denominator. In this way we
have no worries of round off errors, the truncation and conversions of int and floats.
Considering the Fraction class we can think of a constructor which takes an integer and
converts it into a fraction. We do not want that as a fraction there should be some value
divided by zero. So we define the default constructor that takes two integers, one for
numerator and one for denominator. We provide a default value for the denominator that
is 1. It means that now we can construct a fraction by passing it a single integer, in which
case it will be represented as a fraction with the passed integer as a numerator and the
default vale i.e. 1 as the denominator. If we have a fraction object f and we write f = 3;
Then automatically this constructor (i.e. we defined) will be called which takes a single
integer as an argument. An object of type fraction will be created and the assignment will
be carried out. In a way, this is nothing more than a conversion operation. That is a
conversion of an integer into a fraction. Thus a constructor that takes only one parameter
is considered a conversion function; it specifies a conversion from the type of the
parameter to the type of the class.
So we can write a conversion function or we can use a constructor of single argument for
conversion operation. We cannot use both, we have to write one or the other. Be careful
about this. We don’t use conversion functions often but sometimes it is useful to write
them. The conversion operators are useful for defining an implicit conversion from the
class to a class whose source code we don't have access to. For example, if we want a
conversion from our class to a class that resides within a , we cannot define a
single-argument constructor for that class. Instead, we must use a conversion operator.
It makes our code easier and cleaner to maintain. It is important that pay more attention
while defining a class. A well-defined class will make their use easy in the programming.
Following is the code of the example stated above.
/* This program defines a class Fraction which stores numerator and
denominator of a fractional number separately. It also overloads the
addition operator for adding the fractional numbers so that exact results
can be obtained.
Page 429
*/
#include <stdlib.h>
#include <math.h>
#include <iostream.h>
// class definition
class Fraction
{
public:
Fraction();
Fraction( long num, long den );
void display() const;
Fraction operator+( const Fraction &second ) const;
private:
static long gcf( long first, long second );
long numerator, denominator;
};
// ----------- Default constructor
Fraction::Fraction()
{
numerator = 0;
denominator = 1;
}
// ----------- Constructor
Fraction::Fraction( long num, long den )
{
int factor;
if( den == 0 )
den = 1;
numerator = num;
denominator = den;
if( den < 0 )
{
numerator = -numerator;
denominator = -denominator;
}
factor = gcf( num, den );
if( factor > 1 )
{
numerator /= factor;
denominator /= factor;
}
}
// ----------- Function to print a Fraction
Page 430
void Fraction::display() const
{
cout << numerator << '/' << denominator;
}
// ----------- Overloaded + operator
Fraction Fraction::operator+( const Fraction &second ) const
{
long factor, mult1, mult2;
factor = gcf( denominator, second.denominator );
mult1 = denominator / factor;
mult2 = second.denominator / factor;
return Fraction( numerator * mult2 + second.numerator * mult1,
denominator * mult2 );
}
// ----------- Greatest common factor
// computed using iterative version of Euclid's algorithm
long Fraction::gcf( long first, long second )
{
int temp;
first = labs( first );
second = labs( second );
while( second > 0 )
{
temp = first % second;
first = second;
second = temp;
}
return first;
}
//main program
void main()
{
Fraction a, b( 23, 11 ), c( 2, 3 );
a = b + c;
a.display();
cout << '\n';
system("pause");
}
The output of the program is as follows
91/33
Page 431
Here is an example from the real world. What happens if we are dealing with currency?
The banks deal with currency by using computer programs. These programs maintain the
accounts by keeping the track of transactions and manipulating deposits and with drawls
of money. Suppose the bank declares that this year the profit ratio is 3.76 %. Now if the
program calculates the profit as 3.67 % of the balance, will it be exactly in rupees and
paisas or in dollars and cents? Normally, all the currencies have two decimal digits after
decimal point. Whenever we apply some kind of rates in percentage the result may
become in three or four decimal places. The banks cannot afford that the result of 90
paisas added to 9 rupees and 10 paisas become 10 rupees and 1 paisa. They have to
accurate arithmetic. So they do not rely on programs that use something like double
precisions to represent currencies. They would have rather written in the program to treat
it as a string. Thus 9 is a string, 10 is a string and the program should define string
addition such that the result of addition of the strings .10 and .90 should be 1.00. Thus,
there are many things that happen in real world that force us as the programmer to
program the things differently. So we do not use native data types.
The COBOL, COmmon Business Oriented Language has a facility that we can represent
the decimal numbers exactly. Internally it (the language) keeps them as strings in the
memory. There is no artificial computer representation of numbers. Now a day, the
languages provide us the facility by which we can define a data type according to a
specific requirement, as we defined fraction to store numerator and denominator
separately. By using this fraction data type, we never loose precision in arithmetic. The
same thing applies to the object like currency, where we can store the whole number part
and the fractional part both as separate integers and never loose accuracy. But whenever
we get into these classes, it is our responsibility to start writing all of the operators that
are required to make a complete class.

<Previous Lesson

Introduction to Programming

Next Lesson>

Home

Lesson Plan

Topics

Go to Top

Copyright © 2008-2013 zainbooks All Rights Reserved
Next Lesson
Previous Lesson
Lesson Plan
Topics
Home
Go to Top