Contents  1-5  6-11  12-16  17-21  22-27   28-33  34 - 38  39-46  Projects   MFC

   Contact
   C
   C++
   Visual Basic
   Java
   JavaScript
   DHTML
   Style Sheets
   About
   Normalization
   Active X
   TDC Binding
   PHP
   Perl and CGI
   Flash
   XML
   SQL
   Chat
   MCSE
   Linux
   Cabling   
 

   
 
    
    

  "It is appallingly obvious that our technology exceeds our humanity." - Albert Einstein
"No, no, you're not thinking, you're just being logical." - Niels Bohr

C++'s built in types (int, real, char, etc.) and built in operators (multiplication, addition, etc.) can be added to your own classes.

Operator overloading - Example: A "Counter" object will be created and used in loops, incrementing, decrementing, and tracking. The counter object can not be incremented or manipulated unless you use operator overloading to restore its functionality. Example:

//Overloading the increment operator to increment objects
typedef unsigned short USHORT;
#include <iostream.h>

//-------------------------------------------------------------------------------------------------------------------

class
Counter
{
      public:
      Counter();
      ~Counter(){}

      USHORT GetItsVal()const { return itsVal; }

      void SetItsVal(USHORT x) {itsVal = x; }
      void Increment() { ++itsVal; }
      const Counter &operator++();   
//overloads the increment operator

      private:
      USHORT itsVal;
};


//Defines constructor outside of class
Counter::Counter():
itsVal(0)
{};

//Overloaded increment operator - we teach it to increment objects. It returns a reference
//to a const Counter object
. REFERENCE used to avoid creation of extra temporary object.

const Counter &Counter::operator++()
{
      ++itsVal;
      return *this;
}

//-------------------------------------------------------------------------------------------------------------------

int main()
{
    Counter SomeCounterObject;
    cout << "The value of SomeCounterObject is "
         << SomeCounterObject.GetItsVal() << endl;

    SomeCounterObject.Increment();

    cout << "The value of SomeCounterObject is "
         << SomeCounterObject.GetItsVal() << endl;

    ++SomeCounterObject;

    cout << "The value of SomeCounterObject is "
         << SomeCounterObject.GetItsVal() << endl;

    Counter AnotherCounterObject = ++SomeCounterObject;

    cout << "The value of AnotherCounterObject: "
         << AnotherCounterObject.GetItsVal();

    cout << " and SomeCounterObject: "
         << SomeCounterObject.GetItsVal() << endl;

    return 0;
}

Note: The value returned is a Counter reference, avoiding the creation of an extra temporary object.  It is a const reference because the value should not be changed by the function using Counter.  If the Counter allocated memory, it would be important to override the copy constructor, but in this case the default copy constructor works fine.  When overloading these operators, remember the subtleties between postfix vs. prefix:

Prefix - increment and then fetch the value.
Postfix - fetch and then increment the value (returns value that existed BEFORE it was incremented). You must create a temporary object to hold the old value while you increment it to the new value.  You return the temporary, old value because the postfix operator asks for the original value, not the incremented one.  Example:

a = x++;

If x was 5 then a would be 5 and x 6.  If x is an object, and we seek to overload the postfix increment operator, we must teach the compiler how to do this.  We would need to code our function stash away the original value "5" in a temporary object, increment x's value to "6", and then return the temporary object to assign its value to a.  Because the temporary object is being returned, it must be returned by value and not reference. This is because the temporary will go out of scope as soon as the function returns and if there were a reference, it would be left aliasing nothing.  Example:

//Returning the dereferenced this pointer
#include <iostream.h>

typedef unsigned short USHORT;

//-------------------------------------------------------------------------------------------------------------------

class Counter
{
      public:
      Counter();
      ~Counter(){}

      USHORT GetItsVal()const { return itsVal; }
      void SetItsVal(USHORT x) {itsVal = x; }

      const Counter &operator++();     
//prefix
      const Counter operator++(int);   
//postfix

      private:
      USHORT itsVal;
};


Counter::Counter():itsVal(0)
{}

//Overloaded prefix operator
const Counter &Counter::operator++()
{
     ++itsVal;
     return *this;
}

//Overloaded postfix operator
const Counter Counter::operator++(int)
{
       //Create a Counter object that takes a pointer to the object itself calling this function
     Counter temp(*this);   
     ++itsVal;
     return temp;
}

//-------------------------------------------------------------------------------------------------------------------

int main()
{
    Counter ACounterObject;

    cout << "The value of ACounterObject is "
         << ACounterObject.GetItsVal() << endl;

    ACounterObject++;    
//calls overloaded postfix increment operator

    cout << "The value of ACounterObject is "
         << ACounterObject.GetItsVal() << endl;

    ++ACounterObject;    
//calls overloaded prefix increment operator

    cout << "The value of ACounterObject is "
         << ACounterObject.GetItsVal() << endl;

    Counter AnotherCounterObject = ++ACounterObject;

    cout << "The value of AnotherCounterObject: "
         << AnotherCounterObject.GetItsVal();

    cout << " and ACounterObject: "
         << ACounterObject.GetItsVal() << endl;

    AnotherCounterObject = ACounterObject++;

    cout << "The value of AnotherCounterObject: "
         << AnotherCounterObject.GetItsVal();

    cout << " and ACounterObject: "
         << ACounterObject.GetItsVal() << endl;

    return 0;
}

Output:
The value of
ACounterObject is 0
The value of
ACounterObject is -1
The value of
ACounterObject is 2
The value of
AnotherCounterObject: 3 and ACounterObject: 3
The value of
AnotherCounterObject: 4 and ACounterObject
: 4

The call to the overloaded prefix increment operator does not include the flag integer (x), but it is used with normal syntax.  The overloaded postfix operator uses a flag value (x) represented by its sole parameter "int" to signal that it is the overloaded postfix function, rather than the prefix function.  The flag value (x) is never actually used to pass values, however, it is merely a detector.

unary operator - Operates on one object only. The increment and decrement operators are urnary. 
binary operator - Operates on two objects. The addition (+) operator is binary.  When overloading binary operators, we would declare two Counter objects as though they were Counter variables and then add them:

Counter varOne, varTwo, varThree;
VarThree = VarOne + VarTwo;

Start by writing the function Add().  It takes a Counter as its argument, adds the values, then returns a Counter object with the result.  Example:

//The Add() function
#include <iostream.h>
typedef unsigned short USHORT;

//-------------------------------------------------------------------------------------------------------------------

class Counter
{
      public:
      Counter();
      Counter(USHORT initialValue);
      ~Counter(){}

      USHORT GetItsVal()const { return itsVal; }
      void SetItsVal(USHORT x) {itsVal = x; }
     
     
//Takes a reference to a constant Counter object
      Counter Add(const Counter &);

      private:
      USHORT itsVal;
};

//Counter constructor can take an argument
Counter::Counter(USHORT initialValue):itsVal(initialValue)
{ }

Counter::Counter():itsVal(0)
{ }

//Define the Add fucntion
Counter Counter::Add(const Counter & rhs)
{
        return Counter(itsVal+ rhs.GetItsVal());
}

//-------------------------------------------------------------------------------------------------------------------

int main()
{
    Counter varOne(2), varTwo(4), varThree;

    varThree = varOne.Add(varTwo);

    cout << "varOne: " << varOne.GetItsVal()<< endl;
    cout << "varTwo: " << varTwo.GetItsVal() << endl;
    cout << "varThree: " << varThree.GetItsVal() << endl;

    return 0;
}

Output:
varOne: 2
varTwo: 4
varThree: 6


We might provide the default value 0 to the constructor declared here.  The Add() function is declared.  It takes a const Counter reference which is the number to add to the current object.  Since varOne and varTwo need to be initialized to a non-zero value, another constructor is created.  The default constructor initializes itsVal to 0.  The Add() function returns a Counter object which is the result to be assigned to the left side of the assignment statement.  At present, varOne is the object, varTwo is the parameter to the Add() function, and the result is assigned to varThree. To create varThree without having to initialize a value for it, the default constructor is required.

Operator + - The Add() function shown previously works, but its use seems unnatural.  Overloading the operator + would make for a more natural use of the Counter class.  Example: 

// Overloading operator plus (+) to add complete objects
#include <iostream.h>

typedef unsigned long ULONG;

//-------------------------------------------------------------------------------------------------------------------

class Counter
{
      public:
      Counter();
      Counter(ULONG initialValue);
      ~Counter(){}

      ULONG GetItsVal()const { return itsVal; }
      void SetItsVal(ULONG x) {itsVal = x; }

     
      Counter operator+ (const Counter &);

      private:
      ULONG itsVal;
};

Counter::Counter(ULONG initialValue):
itsVal(initialValue)
{}

Counter::Counter():
itsVal(0)
{}

//Define overloaded addition operator
//Adds the "itsVal" data member of the object passed in to the other "itsVal" data member
Counter Counter::operator+ (const Counter & rhs)
{
        return Counter(itsVal + rhs.GetItsVal());
}

//-------------------------------------------------------------------------------------------------------------------

int main()
{
    Counter varOne(2), varTwo(4), varThree;

   
//Add 2 objects together and assign it to the third object
    varThree = varOne + varTwo;    
//looks like plain math but really operator overloading!

    cout << "varOne: " << varOne.GetItsVal()<< endl;
    cout << "varTwo: " << varTwo.GetItsVal() << endl;
    cout << "varThree: " << varThree.GetItsVal() << endl;

    return
0;
}

Output:
varOne: 2
varTwo: 4
varThree: 6


In this example, the overloaded operator + is declared.  The syntax is different in this case - it is more natural to say varThree = varOne + varTwo; than it is to say varThree = varOne.Add(varTwo);.  By overloading the "+" operator, we can add objects together as well as integers and floats.

Operators on built-in types, like int, cannot be overloaded.   Only operators on user-defined types.  The precedence and the "arity" of the operator cannot be changed.  The "arity" refers to whether it is "unary" or "binary".  Unfortunately, you cannot make new operators, so you cannot declare ** to be the "power of" operator.  Operator overloading is very abused and misused in C++ programming.  It can be dangerous to use "+" to concatenate a series of letters, or to use / to split a string.  Operator overloading is a powerful tool, but it should be used cautiously.

It is also possible to overload operator = .  The compiler provides a default constructor, destructor, copy constructor and an assignment operator "=", if one is not declared.  The default assignment operator is called whenever you assign to an object.  Example: CAT catTwo(3,4) - catTwo is created and assigned values 3 and 4.  A quick review:

Shallow copy - Just copies members, both objects point to same area on free store.  Two pointers end up pointing to same memory location.
Deep copy - Allocates necessary memory to NEW locations.  Pointers pointers point to different locations.

In the expression ,CAT catTwo(3,4), the object, catTwo, already exists and already has memory allocated.  That memory must be deleted to avoid a memory leak.  The first thing to do is delete memory assigned to its pointers.  But what if you assign catTwo to itself, like catTwo = catTwo; ?  It is possible for this to happen by accident when referenced and dereferenced pointers hide the fact that the object's assignment is to itself.  The object catTwo could delete its memory allocation, and when it tried to copy memory from the right-hand assignment, the memory would be gone.  To protect against this, the assignment operator should check to see if the right-hand side of the assignment operator is the object itself by examining the "this" pointer to reveal whether or not they have the same address.  Example:

//Copy constructors
#include <iostream.h>

//----------------------------------------------------------------------------------------------------------------

class Platypus
{
      public:
      Platypus();      
  //default constructor
    
      //Accessor methods
      int GetAge() const { return *itsAge; }
      int GetWeight() const { return *itsWeight; }
      void SetAge(int age) { *itsAge = age; }

     
//Overloaded assignment operator
      Platypus operator=(const Platypus &);

      private:
      int *itsAge;
      int *itsWeight;
};


//Platypus constructor assigns default values
Platypus::Platypus()
{
     itsAge = new int;
     itsWeight = new int;
     *itsAge = 5;
     *itsWeight = 9;
}

//By overloading the assignment operator, we can use it to copy one object's data member to another
//Overloaded assignment operator checks to make sure address of object being passed in is not the same as object itself

Platypus Platypus::operator=(const Platypus & rhs)
{
     if (this == &rhs)
     return *this;
    
     delete itsAge;
     delete itsWeight;

     itsAge = new int;
     itsWeight = new int;
     *itsAge = rhs.GetAge();
     *itsWeight = rhs.GetWeight();

     return *this;
}

//----------------------------------------------------------------------------------------------------------------

int main()
{
    Platypus Joe;

    cout << "Joe's age: " << Joe.GetAge() << endl;
    cout << "Setting Joe to 6...\n";

    Joe.SetAge(6);

    Platypus Bill;

    cout << "Bill's age: " << Bill.GetAge() << endl;
    cout << "copying Joe to Bill...\n";

    Bill = Joe;  
//calls the overloaded assignment operator

    cout << "Bill' age: " << Bill.GetAge() << endl;

    return 0;
}

Output: 
Joe's age: 5
Setting Joe to 6
Bill's age: 5
copying Joe to Bill...
Bill's age: 6


In this example, the current Platypus object being assigned to is tested to see whether it is the same as the Platypus being assigned.  This is done  by checking whether the address of "rhs" is the same as the address stored in the "this" pointer.  Remember it is common to refer to the parameter of a copy constructor as "rhs", which stands for "right hand side", signifying that which is on the right side of the assignment operator. 

Another way is to check this is to dereference the "this" pointer and see if the 2 objects are the same, possessing the same address.  Example:
if (*this == rhs). The equality operator (==) can be overloaded, allowing you to determine for yourself what it means for your objects to be equal.  Now that is power and freedom!  Let's pose a question: What happens if you try to assign a variable of a built in type (such as int or unsigned short) to an object of a user defined class?

// This code won't compile!

#include <iostream.h>

typedef unsigned short USHORT;

//----------------------------------------------------------------------------------------------------------------

class Counter
{
      public:
      Counter();
      ~Counter(){}

     
//Accessor methods
      USHORT GetItsVal()const { return itsVal; }
      void SetItsVal(USHORT x) {itsVal = x; }

      private:
      USHORT itsVal;
};

//Constructor implemented outside of class, assigns a default value
Counter::Counter():
itsVal(0)
{}

//----------------------------------------------------------------------------------------------------------------

int main()
{
    USHORT ShortInteger = 5;

    Counter ACounter = ShortInteger;

    cout << "ACounter: " << ACounter.GetItsVal() << endl;

    return 0;
}

In the example above,  the Counter class declared has only a default constructor.  It declares no particular method for turning an int into a Counter object, so trying to compile this will cause cause a compile time error.  The compiler can not figure out, unless we tell it, that given an int, it should assign that value to the Counter class data member variable, "itsVal".   We can correct this by creating a conversion operator.  In this case, we will implement this in our constructor.  

Conversion operator - a constructor that takes an int and produces a Counter object.  Conversion operators are implemented using constructor or operator overloading.  They allow us to take an object, and using a basic operator, teach the compiler how to manipulate the object's data members.

// Constructor as conversion operator
#include <iostream.h>

typedef unsigned short USHORT;

//----------------------------------------------------------------------------------------------------------------

class Counter
{
      public:
     
//2 overloaded constructors.  The second is our conversion operator.
      Counter();
      Counter(USHORT val);
      ~Counter(){}

     
//Accessor methods
      USHORT GetItsVal()const { return itsVal; }
      void SetItsVal(USHORT x) {itsVal = x; }

      private:
      USHORT itsVal;
};

Counter::Counter():itsVal(0)
{ }

Counter::Counter(USHORT val):itsVal(val)
{ }

//----------------------------------------------------------------------------------------------------------------

int main()
{
    USHORT AShortInteger = 5;

    Counter ACounterObject = AShortInteger;

    cout << "ACounterObject: " << ACounterObject.GetItsVal() << endl;

    return
0;
}

Output:
ACounterObject: 5

This implements an important change, the constructor is overloaded to take an int.  This effectively creates a Counter object out of an int.  The compiler is able to call the constructor that takes an int as its argument.  If you try to reverse it, however, you will get a compile time error.  The compiler knows how to create a Counter out of an int, but it does not know how to reverse the process and create an int out of a Counter. 

For this, C++ provides conversion operators that can be added to your class.  They allow your class to specify how to do implicit conversions to built-in types.  Conversion operators do not specify a return value, even though they do return a "converted" value.  Example:

// Overload operator plus (+)
#include <iostream.h>

typedef
unsigned long ULONG;

//----------------------------------------------------------------------------------------------------------------

class Counter
{
      public:
     
//2 overloaded constructors.  The second is our conversion operator.
      Counter();
      Counter(ULONG initialValue);
      ~Counter(){}

     
//Accessor methods
      ULONG GetItsVal()const { return itsVal; }
      void SetItsVal(ULONG x) {itsVal = x; }

        //Overload operator + - takes a reference to a const Counter object
      Counter operator+ (const Counter &);

      private:
      ULONG itsVal;
};


Counter::Counter():itsVal(0)
{ }

Counter::Counter(ULONG initialValue):itsVal(initialValue)
{ }

//Overload operator + to add "itsVal" data member when adding objects
Counter Counter::operator+ (const Counter & rhs)
{
        return Counter(itsVal + rhs.GetItsVal());
}

//----------------------------------------------------------------------------------------------------------------

int main()
{
    Counter CounterOne(2), CounterTwo(4), CounterThree;

    //The statement below uses both our overloaded operator + and our conversion operator.
    //The overloaded operator + allows us to add the right and left objects together by adding their data members.
    //The conversion operator allows us to assign that integer result to the third object's int data member.

   
CounterThree = CounterOne + CounterTwo;

    cout << "CounterOne: " << CounterOne.GetItsVal()<< endl;
    cout << "CounterTwo: " << CounterTwo.GetItsVal() << endl;
    cout << "CounterThree: " << CounterThree.GetItsVal() << endl;

    return 0;
}

Output:
CounterOne: 2
CounterTwo: 4
CounterThree: 6


In this example, a conversion operator is declared by overloading the second constructor - It has no return value.  The compiler has no idea how to add the user-defined data type "Counter" to other Counter objects.  So, operator "+" is overloaded, so that when it is used to add Counter objects, it will in fact add the values of their "itsVal" data members together.  We may now return the value of itsVal, a data member of a Counter object,  converted to an int.  Now, relatively speaking, the compiler can turn int's into Counter objects and Counter objects into int's.  They can be assigned to each other freely.  This is because the overloaded operator + allows us to add the right and left objects together by adding their integer data members together.  The conversion operator then allows us to assign the integer result of the addition to the third object's int data member.


©2004 C. Germany