|
|||||
CS201
Introduction to Programming
Lecture
Handout
Introduction
to Programming
Lecture
No. 33
Reading
Material
Deitel
& Deitel - C++ How to
Program
Chapter.
7, 8
7.5,
8.1, 8.2, 8.7,
8.9
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
417
CS201
Introduction to Programming
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
418
CS201
Introduction to Programming
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
419
CS201
Introduction to Programming
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
420
CS201
Introduction to Programming
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
421
CS201
Introduction to Programming
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
422
CS201
Introduction to Programming
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
423
CS201
Introduction to Programming
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
424
CS201
Introduction to Programming
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
425
CS201
Introduction to Programming
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
426
CS201
Introduction to Programming
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
427
CS201
Introduction to Programming
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
library, 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
428
CS201
Introduction to Programming
*/
#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
429
CS201
Introduction to Programming
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
430
CS201
Introduction to Programming
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.
Page
431
Table of Contents:
|
|||||