Search

Sponsored Links

Meta

Categories

Archives

Recent Posts

RSS Feeds

06
Dec

C++ Basics Tutorial - Lesson 7

Related Blog Items

Please refer lesson 6..

7. Operator Overloading
7.1 Fundamentals of Operator Overloading
7.2 Overloading binary operators
7.3 Operator << and >> can not be Member Functions
7.4 Overloading Unary Operators
7.5 Operator Cascading
7.6 Subscription Operator [ ]
7.7 “operator =” and Default Memberwise Copy
7.8 Orthodox Canonical Form (OCF)
7.9 Check for Self-assignment
7.10 An Example about Pass-by-reference
7.11 lvalue and rvalue
7.12 Overloading ++ and - -
7.13 Example: Date Class

7. Operator Overloading

7.1 Fundamentals of Operator Overloading

When operators such as +, -, *, /, = are used for different built-in types such as int and float , they actually have been overloaded by C++. They can also be overloaded for any user-defined type, to perform the same or similar operation.

For built-in type e.g. int, you can say

c = a + b   or  cout << a << b << c .

Now with operator overloading, you can declare a new type e.g. Test a, b, c , calculate them with

c = a + b

output them with

cout << a << b << c .

Therefore, the user-defined type Test is fully equal to built-in type such as int . That’s why we say that C++ is an extensible language.

When overloading (), [], ->, or =, the operator overloading method must be a class member.

7.2 Overloading binary operators

A binary overloading member method taks one argument:

Test & operator+(Test &);

When the compiler sees a binary formula such as “a + b”, it calls the “operator+” method of the left-hand operand “a”, and passes the right-hand operand “b” as its argument. So “c = a + b” is implicitly converted to

c = a.operator+(b);

A global binary overloading function takes two arguments:

Test & operator<<(Test &, Test &);

When the compiler sees ” a + b”, it calls the independent “operator+” method, and passes the two operands “a” and “b” as its arguments:

operator<<(a, b);

7.3 Operator << and >> can not be Member Functions

The global binary operator overloading function is specifically useful when the left-hand operand does not belong to the class in question, such as

CMyOwnClass a, b;
cout << a;
cin >> b;

Because class cout and cin have not overloaded operator << and >> for type CMyOwnClass, you have to overload it yourself. However, you can not put this function in class CMyOwnClass, because a and b are not left-hand operand. So you have to use a global operator overloading function, such as stream-insertion and extraction operators:

ostream & operator<<(ostream &, Test &);
istream & operator>>(istream &, Test &);

As an independent function, for performance reason, it is better to be a friend of the class, so that it can directly access its private data members. Otherwise it has to access them through get and set method calls. If that s the case, you can make these non-methods inlined to reduce the overhead.

7.4 Overloading Unary Operators

Because the operand of a unary operator is always on the right, the unary operator method can either be a class member or an independent function. But it is preferable to make it a method. The use of friend here violates the encapsulation of a class.

=>Method

Suppose a is an object of class Test , and operator ! is overloaded by a method, then this method has no argument:

a80ad649efb7dbd9cf1804767fc44f88009

and !a is equal to a.operator!() .

=>Independent function

Suppose ! is overloaded by a friend method, then the method has one argument:
bool operator!(const Array &);
and “!a” is equal to “operator!(a)”.

7.5 Operator Cascading

The return type of all operator overloading methods, both methods and independent functions, can be void. The disadvantage of having void return type is, you can t use cascading operators, such as “cout << c << endl" or "c = a + b", because "cout << c" as a method call returns nothing, and apparently "void << endl" has no meaning. So is "c = void;".

To enable such cascading, normally operator overloading methods has its return type defined as class type, and returns the result object. Return-by reference is widely used here, because it can save the copying process, which is a big overhead for objects of large size.

7.6 Subscription Operator [ ]

The subscript operator [ ] is not restricted for use only with arrays; it can be used to select elements from any kinds of container classes such as linked lists, strings, dictionaries, and so on. Also, subscripts no longer have to be integers; characters or strings could be used, for example.

7.7 “operator =” and Default Memberwise Copy

If you do not explicitly overload assignment operator, the compiler will use default memberwise copy to perform object assignment. Normally it will do the job, but for objects with pointers, it will create a new pointer pointing to the same memory location instead of new memory location.

7.8 Orthodox Canonical Form (OCF)

A constructor, a destructor, an overloaded assignment operator, and a copy constructor are recommended for all classes, even if it does not have dynamic members at this stage. Programs containing these four components are said to be in Orthodox Canonical Form.

7.9 Check for Self-assignment

The check for self-assignment is only necessary for a assignment operator when dynamic memory allocation is involved, in which some deletion job is done. In such a case if we don t check for self-assignment, the object s memory space will be deleted first and all the info stored in the dynamic memory will be lost.

7.10 An Example about Pass-by-reference

 
class Test 
{
public:
  int &bridge( int &);
};
 
int &Test::bridge (int & input)
{ return input; }
 
int main()
{
  Test a;
  int b=333;
  int c = a.bridge(b);
  cout << "c = " << c << endl; // should be 333
  a.bridge(b) = 444;
  cout << "b= " << b << endl; // used to be 333, now should be 444
}

Because a reference is the address of the original object, this address can be passed back and forth to anywhere, and all parties who have a copy of this address can directly manipulate the original object.

Method bridge receives one reference from calling method, and return this reference back to the calling method. Any modification on the returned value will affect the passed argument.

7.11 lvalue and rvalue

lvalue means a variable that is modifiable, such as the left variable in an assignment. rvalue means a variable which doesn t need to be modified, such as the variable on the right of an assignment.

7.12 Overloading ++ and - -

=>Preincrementing

The prefix versions of ++ and - - (such as ++a and - -a) are overloaded exactly as any other prefix unary operator:

Test & operator++();

Suppose there is an object “a” of class “Test”, when the compiler sees the preincrementing expression “++a”, it generates the method call

a.operator++();

When the preincrementing is implemented as an independent function:

Test & operator++(Test &);

When the compiler sees the expression “++a”, it generates the method call

operator++(a);
=>Postincrementing

To make the overloaded method of postincrementing operator distinguishable from preincrementing one, the operator method should be:
Test & operator++(int);
When the compiler sees the expression “a++”, it generates a method call

a.operator++(0);

“0″ is a “dummy value” to make the parameter list of the overloaded operator method different from preincrementing one.
If the postincrementing is implemented as an independent function:
Test operator++(Test &, int);
when the compiler sees the expression “a++”, it generates method call

operator(a, 0);

The return type for postincrementing can not be a reference, because the returned object should be the object BEFORE increment, not after increment. So we have to create a temporary local object to hold the value of the original object, then increment the original object, and return the temporary one.

7.13 Example: Date Class

 
#include <iostream>
#include <conio.h>
 
class Date 
{
   friend ostream & operator<<(ostream &, const Date &);
public:
   Date(int=1, int=1, int=1900);
   void set(int, int, int);
   const Date & operator++();
   const Date operator++(int);
   const Date & operator +=(int);
private:
   int day;
   int month;
   int year;
   static const int days[13];
   bool leap();
   const int checkDay();
   void increment();
};
 
const int Date::days[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
 
Date::Date(int d, int m, int y)
{  set(d, m, y); }
 
void Date::set(int d, int m, int y)
{
  day = d;
  month = m;
  year = y;
 
  if(checkDay1()<0 || month<=0 || month>12)
  {
    cout << "Wrong Date format!  Date is set to be 1/1/1900.\n";
    day = 1;
    month = 1;
    year = 1900;
  }
}
 
const Date &amp; Date::operator ++()
{
   increment();
   return *this;
}
 
const Date Date::operator ++(int)
{
   Date temp = *this;             // default memberwise copy
   increment();
   return temp;
}
 
const Date & Date::operator +=(int dd1)
{
   for(int i=1; i<=dd; i++)
      increment();
   return *this;
}
 
bool Date::leap()
{
   if (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0))
      return true;
   else
      return false;
}
 
const int Date::checkDay()
{
   if(month == 2 && leap())
      return 29 - day;
   else
      return days[month] - day;
}
 
void Date::increment()
{
   if( checkDay1()>0 )
      day ++;
   else
   {
      day = 1;
      if (month < 12)
         month ++;
      else
      {
          month = 1;
          year ++;
      }
   }
}
 
ostream & operator<<(ostream &amp; output, const Date & obj)
{
   output << "Date: " << obj.day << "/" << obj.month << "/" << obj.year << endl;
   return output;
}
 
int main()
{
   Date d1(31,12,1999), d2(31,1,1999), d3(23,2,2000),
   d4(23,2,4444), d5(23,2,1999);
   d4.set(23,2,1212);
   ++d1;
   cout << d1;
   d2++;
   cout << d2;
   d3 += 6;
   cout << d3;
   d4 += 7;
   cout << d4;
   d5 += 6;
   cout << d5 << "Press any key when ready...";
   getch();
}

Popularity: 17%

You need to log on to convert this article into PDF


Related Blog Items

1 Comment

Leave a comment

*
To prove you're a person (not a spam script), type the security word shown in the picture.
Anti-spam image