Saturday, 4 July 2015

OPERATOR OVERLOADING AND TYPE CONVERSIONS

6.1 Operator Overloading:


Operator overloading is another example of C ++ polymorphism. We know that C++ enables you to define several functions having the same name as long as they have different signatures (argument lists). That was function overloading, or functional polymorphism. Its purpose is to let you use the same function name for the same basic operation even though you apply the operation to different data type.

Operator overloading extends the overloading concept to operators, letting you assign multiple meanings to C++ operators. Actually many C++ (and C) operators already are overloaded. For example, the * operator, when applied to an address, yields the value stored at that address. But applying * to two numbers yields the product of the values. C++ uses the number and type of operands to decide which action to taken.

C++ lets you extend operator overloading to user-defined types, permitting you, say, to use the + symbol to add two objects. Again, the compiler will use the number and type of operands to determine which definition of addition to use. Overloaded operators often can make code look more natural.

For instance C++ permits us to add two variables of user-defined types with the same syntax that is applied to the basic types. This means that C++ has the ability to provide the operators with a special meaning for a data type. The mechanism of giving such special meanings to an operator is known as operator overloading.

Operator overloading provides a flexible option for the creation of new definitions for most of the C++ operators. We can almost create a new language of our own by the creative use of the function and operator overloading techniques. We can overload (give additional meaning to) all the C++ operators except the following:

·         Class member access operators ( ., .*).
·         Scope resolution operator (::).
·         Size operator (sizeof)
·         Conditional operator (?:).

The excluded operators are very few when compared to the large number of operators, which qualify for the operator overloading definition.

Although the semantics of an operator can be extended, we cannot change its syntax, the grammatical rules that govern its use such as the number of operands, precedence and associativity. For example, the multiplication operator will enjoy higher precedence than the addition operator. Remember, when an operator is overloaded, its original meaning is not lost.



6.2 Defining Operator Overloading:


To define an additional task to an operator, we must specify what it means in relation to the class to which the operator is applied. This is done with the help of a special function, called operator function, which describes the task. The general form of an operator function is:

returntype classname :: operator op ( arg-Iist )
            {
Function body //task defined
            }

where returntype is the type of value returned by the specified operation and op is the operator being overloaded. The op is preceded by the keyword operator. operator op is the function name.

Functions must be either member functions or friend functions. A basic difference between them is that friend function will have only one argument for unary operators
and two binary operators, while member function has no arguments for unary operators and only one for binary operators. This is because the object used to invoke the member function is passed implicitly and therefore is available for the member function. This is not the case with friend functions. Arguments may be passed either by value or by reference.

Operator functions are declared in the class using prototypes as follows:

vector operator + (vector);                              //vector addition
vector operator -( );                                         //unary minus
friend vector operator + (vector, vector);       //vector addition
friend vector operator -(vector);                     //unary minus
vector operator -(vector & a);                         //subtraction
int operator == (vector);                                  //comparison
friend int operator == (vector, vector)            //comparison

vector is a data type of class and may represent both magnitude and direction (as in physics and engineering) or a series of points called elements (as in mathematics).
The process of overloading involves the following steps:

  1. First, create a class that defines the data type that is to be used in the overloading operation.
  2. Declare the operator function operator op ( ) in the public part of the class. It may be either a member function or a friend function.
  3. Define the operator function to implement the required operations.

Overloaded operator functions can be invoked by expressions such as
op x or x op

for unary operators and
x op y

for binary operators.
op x (OF x op) would be interpreted as operator op (x)

In case of member functions,

operator op (x, y)

Consider the program below which overloads unary operators:

# include<iostream.h>

class Counter {
private:
unsigned int count;
public:
Counter( ) { count = 0; }
int get-count( ) { return count; }
void operator ++ ( ) { count++; }
};
void main( )
{
Counter c1, c2;
cout << "\n c1=" << c1.get-count( );
cout << " \n c2=" << c2 .get-count ( ) ;
c1+t;
c2++;
 ++c2;
cout <<"\n c1="<<c1.get-count( );
cout << "\n c2="<< c2.get-count( );
}

In this program two object of class Counter are created. The counts in the objects are initially 0. Using the overloaded ++ operator, we increment c1 and c2 twice.

6.3 operator Keyword:

The keyword operator is used to overload the ++ operator in this declaration:

void operator ++ ( )

The return type (void in this case) comes first, followed by the keyword operator, followed by the operator itself ( ++ ), and finally the argument list enclosed in parentheses (which are empty here). This declaration syntax tells the compiler to call this member function whenever the ++ operator is encountered, provided the operand (the variable operated on by the ++) is of type Counter. The only way the compiler can distinguish between overloaded functions is by looking at the data type of their arguments. In the same way, it can distinguish between overloaded operators by looking at the data type of their operands. If the operand is a basic type like an int, as 

intvar ++;
then the compiler will use its built-in routine to increment an int. But if the operand is a Counter variable, then the compile will know to use our user-written operator++().

6.4 Operator Return Values:

The operator++ ( ) function has a subtle defect. You will discover it if you use a statement like this in main ( ) :

c1 = c2++;

The compiler will give an error. Because we have defined the ++ operator to have a return type of void in the operator++ ( ) function, while in the assignment statement it is being asked to return a variable of type Counter. That is, the compiler is being asked to return whatever value c2 has after being operated on by the ++ operator, and assign this value to c1. So we can't use ++ to increment Counter objects in expression; it must always stand alone with its operand. To make it possible to use our user defined operator+ + ( ) in expressions, we must provide a way for it to return a value. The program below does this technique.

# include <iostream.h>
class Counter {
private:
unsigned int count;
public:
Counter( ) { count = 0; }
int get-count( ) { return count; }

Counter operator ++ ( )
            {
count++;
Counter temp;
temp.count = count;
return temp;
}
};
void main( ) {
Counter c1, c2;
cout << "\n c1=" << c1.get_count( );
cout << "\n c2=" << c2.get_count( );
c1++;
c2 = c1++;
cout << "\n c1=" << c1.get_count( );
cout << "\n c2=" << c2++.get-count( );
}

In this program the operator++ ( ) function creates a new object of type Counter, called temp to use as a return value. It increments the count data in its own object as before, then creates the new, temp object and assigns count in the new object the same value as in its own object. Finally it returns the temp object. This has the desired effect. Expressions like
c1++

now returns a value, so they can be used in other expressions, such as

c2 = c1++; and
c2++.get_count( )

In the first of these statements the value returned from c1 ++ will be assigned to c2, and in the second it will be used to display itself using the get_count( ) member function.

6.5 Multiple Overloading:

We can have same operator function called for different operand. Consider that you use three different programs of the + operator to add distances, to add polar coordinates, and to concatenate strings. You could put all these classes together in the same program, and C++ would still know how to interpret the + operator. It selects the correct function to carry out the "addition" based on the type of operand.

In our first example we'll overload the less than operator < in the Distance class, so that we can compare two distances.

# include <iostream.h>
enum boolean { false, true };
class Distance                         
{
private:
int feet;
float inches;
public:
            Distance( )                               { feet = 0; inches = 0.0; }
Distance(int ft, float in)          { feet = ft; inches = in; }
void getdist( )
{
cout << "\nEnter feet: ";          cin >> feet;
cout << "Enter inches: ";         cin >> inches;
}
void showdist( )
{
cout << feet << "\,-" << inches << '\"';
}
boolean operator < (Distance);
      };

boolean Distance::operator < (Distance d2)
       {
           float bf1 = feet + inches / 12;
           float bf2 = d2.feet + d2.inches /12;
           return (bf1 < bf2) ? true: false;
        }
void main( ) 
       {
            Distance dist1;
dist1.getdist( );

Distance dist2(6, 2.5);                     
cout << "\ndist1 = ";  dist1.showdist( );
cout << "\ndist2 = ";   dist2.showdist( );

if( dist1 < dist2 )                                            
          cout << "\ndist1 is less than dist2";
else
                       cout << "\ndist1 is greater than dist2";
         }

This program compares a distance entered by the user with a distance, 6'-2.5", initialized by the program. Depending on the result, it then prints one of two possible sentences. Here the operator<( ) function has a return type of  boolean (defined in the e n u m statement at the beginning of the program). The return value is false or true, depending on the comparison of the two distances. The comparison is made by converting both distances to floating-point feet, and comparing them using the normal < operator. Similarly you can overload other operators.

6.6 Data conversion:

Normally, when the value of one object is assigned to another, of the same type, the value, or the member data items are simply copied into the new object. The compiler, doesn’t need any special instructions to use = for the assignment of user-defined objects such as Distance objects. Thus assignment between type, whether, they are basic types or user defined types, are handled by the compile, with no effort on our part provided that the same data type is used on both sides of the equals sign. But what happens when the variables on different sides of the = are of different types? The answer to this is Data conversion.

Conversions Between Basic Types:

 

When we write a statement like


intvar = floatvar;

where intvar is of type int and floatvar is of type float, we are assuming that the compiler will call a special routine to convert the value of floatvar, which is expressed in floating-point format, to an integer format so that it can be assigned to  intvar. There are of course many such conversions: from float to double, char to float, and so on. Each such conversion has its own routine, built into the compiler and called up when the data types on different sides of the = sign so dictate. We say such conversions are implicit, because they aren't apparent in the listing. Sometimes we want to force the compiler to convert one type to another. To do this we use the cast operator.

For instance; to convert float to int we could say

intvar = int(floatvar);

Casting provides explicit conversion: It's obvious in the listing that the int( ) conversion function will convert from float to int. However, such explicit conversions use the same built-in routines as implicit conversion.

Basic to Class Type: The conversion from basic type to class type is easy to accomplish. It may be recalled that the use of constructors was illustrated in a number of examples to initialize objects. For example, a constructor was used to build a vector object from an int type array. Similarly, we used another constructor to build a string type object from a char* type variable. Consider the following constructor:


string :: string(char *a)
            {
length = strlen(a);
p = new char[length+ 1];
strcpy (P, a);
            }

This constructor builds a string type object from a char * type variable a. The variables length and p are data members of the class string. Once this constructor has been defined in the string class, it can be used for conversion from char * type to string type. Example:

string S1, S2;
char * name1 = "IBM PC";
char * name2 = "Apple Computers";
S1 =5tring(name1);
S2 = name2;

The statement


S1 = Strlng(name1);

first converts name1 from char * type to string type and then assigns the string type values to the object s1. The statement

S2 = name2;

also does the same job by invoking the constructor implicitly.
Let us consider another example of converting an int type to a class type

class time {
int hrs;
int mins;
public :
            ……
            …….
time(int t)        // constructor
{
hours = t / 60; // t in minutes
mins = t% 60;
}
};

The following conversion statements can be used in a function:

time = T1 ;                   I lobject T1 created
int duration = 85;
T1 = duration;             Il int to class type

After this conversion, the hrs member of TI will contain a value of 1 and mins member a value of 25, denoting 1 hour and 25 minutes.

Note that the constructors used for the type conversion take a single argument whose type is to be converted. In both the examples, the left-hand operand of = operator is always a class object. Therefore, we can also accomplish this conversion using an overloaded = operator.

Class to Basic Type: The constructors did a fine job in type conversion from a basic to Class type. What about the conversion from a class to basic type? The constructor functions do not support this operation. Luckily, C++ allows us to define an overloaded casting operator that could be used to convert a class type data to a basic type. The general form of an overloaded casting operator function, usually referred to as a conversion function is:

 operator typename( )
{
……………... (Function statements)
……………...
……………...
            }

This function converts a class type data to typename. For example, the operator double( ) converts a class object to type double, the oðerator int( ) converts a class type object to type int, and so on. Consider the following conversion function:

vector :: operator double( ) {
double sum = 0;
for(int i = 0; i < size; i++)
sum = sum + v [i] * v [i];
return sqrt(sum);
            }
           
This function converts a vector to the corresponding scalar magnitude. Recall that the magnitude of a vector is given by the square root of the sum of the squares of its ãomponents. The operator double( ) can be used as follows:

double length = double(V1);
 or
double length = V1 ;

where V1 is an object of type vector. Both the statements have exactly the same effect. When the compiler encounters a statement that requires the conversion of a class type to a basic type, it quietly calls the casting operator function to do the job.

The casting operator function should satisfy the following conditions:
  • It must be a class member.
  • It must not specify a return type. .
  • It must not have any arguments.

Since it is a member function, the object invokes it and therefore, the values used for conversion inside the function belong to the object that invoked the function. This means that the function does not need an argument.

In the string example described in the previous section, we can do the conversion from string to char * as follows:

string :: operator char*( )
{
return(p);
            }

One Class to Another Class Type: We have just seen data conversion techniques from a basic to class type and a class to basic type. But there are situations where we would like to convert one class type data to another class type. Example:


objX = objY;               // objects of different types

objX is an  object of class X and objY is an object of class Y. The class Y type data is converted to the class X type data and the converted value is assigned to the objX. Since the conversion takes place from class Y to class X, Y is known as the source class and X is known as the destination class.

Either a constructor or a conversion function can carry out such conversions between objects of different classes. The compiler treats them the same way. Then, how do we decide which - form to use? It depends upon where we want the type-conversion function to be located, in the source class or in the destination class. We know that the casting operator function

operator typename( )

converts the class object of which it is a member to typename. The typename may be a built-in type or a user-defined one (another class type). In the case of conversions between objects, typename refers to the destination class. Therefore, when a class needs to be converted, a casting operator function can be used (i.e. source class). The conversion takes place on the source class and the result is given to the destination class object.

Now consider a single-argument constructor function, which serves as an instruction for converting the argument's type to the class type of which it is a member. This implies that the argument belongs to the source class and is class for conversion. This makes it necessary that the conversion constructor be placed in the destination class.

Table provides a summary of all the three conversions. It shows that the conversion from a class to any other type (or any other class) should make use of a casting operator in the source class. On the other hand, to perform the conversion from any other type/class to a class type, a constructor should be used in the destination class.

Table: Type conversions

Conversion
Required

Conversion takes place in

source class
Destination class
Basic -> class
Not applicable
Constructor
Class -> basic
Casting operator
Not applicable
Class -> class
Casting operator
Constructor


When a conversion using a constructor is performed in the destination class, we must be able to access the data members of the object sent (by the source class) as an argument. Since data members of the source class are private, we must use special access functions in the source class to facilitate its data flow to the destination class. For example:

 # include <iostream.h>

class inventl                                         Il source class
{
int code;                                              // item code
int items;                                             // no of items
float price;                                           Il cost of each item
public:
inventl(int a, int b, float c)
            {
code- a;
items = b;
price = c;
            }
 void putdata( )
{
cout << "Code: " << coda << "\n";
cout << “Items: " << items << "\n”;
cout << "Value:”  << price << "\n”;
I           }
int getcode( ) { return code; }
int getitems( ) { return items;}
int getprice( )  { return price; }
operator float( ) {return(items * price) ;}
};                                                         11 End of source class

class invent2                                        // destination class
{
int code;
f1oat value;
public: invent2( )                                 // constructor 1
{          code = 0;  value = 0;  }
invent2(int x, float y)              // constructor 2
{ code = X; value = y;  }
void putdata( ) 
            {
cout << “ Code: “ << code << "\n";
cout << "Value: “ << value << “\n\n" ;
            }

invent2{invent1 p)                  //conversion constructor
            {
                        code = p.getcode ( ) ;
value = p.getitems( ) * p.getprice( ) ;
            }
};                                                         II End of destination class
main( )
{
inventl s1(100, 5, 140.0) ;
invent2 dl;
float total value;

/* inventl To float */
totalvalue = sl;

/* inventl To invent2 */
dl = s1;

cout << "Product details inventl type" << "\n”;
sl.putdata( ) ;

cout << "\nStock value" << "\n";
cout << "Value = " << total-value << "\n\n";
           
cout << "Product details-invent2 type" << "\n";
d1.putdata( ) ;
}







Exercise:

  1. Define a class String. Use overloaded = = operator to compare two string.

  1. Write a program that substitutes an overloaded + = operator. The operator should allow statements like: s1 + = s2 where s2 is added (concatenated) to s1 and the result in s1.

  1. Write a program to overload the - - operator.

  1. Define two classes Polar and Rectangle to represent points in the polar and rectangle systems. Use conversion routines to convert from one system to the other.