• Welcome to Jose's Read Only Forum 2023.
 

ProgEx28 - Introduction To Operator Overloading

Started by Frederick J. Harris, November 09, 2009, 01:29:26 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Frederick J. Harris


/*
  ProgEx28   C++ Classes : Operator Overloading

  The only major difference between this and the last version of our Box class
  is the addition of what is termed an overloaded operator equal function ...

  Box operator=(Box& AnotherBox)  //Overloaded Operator Equal
  {
   printf
   (
    "Called Box operator= To Assign One Box Object's "
    "Dimensions To Another!\n"
   );
   m_Length=AnotherBox.m_Length;
   m_Width=AnotherBox.m_Width;
   m_Height=AnotherBox.m_Height;
  }

  Note that the function's return value is a Box object.  The context of its
  use in this program is like so...

  Box bx1(2.0,3.0,4.0);
  Box bx2;

  bx2=bx1;

  You can see that the initialized Constructor for a CBox with dimensions 2.0,
  3.0 and 4.0 is called on bx1.  For bx2 the uninitialized Constructor is called
  and bx2 has no valid dimensions.  When program execution hits the statement...

  bx2=bx1;

  the overloaded operator= function is called and the data members of bx1 are
  assigned to bx2, and now they both have the same dimensions and volume.  The
  reason this is termed an overloaded operator= construction is that the equal
  sign itself is being redefined to work with non primitive data types, i.e.,
  data types different from simple ints, longs, doubles, etc.  Its one of the
  tenets of object oriented programming that objects themselves, no matter how
  complex, should be able to be minipulated through operations that are
  intrinsically obvious to us such as assignment, addition, etc.

  I hope you don't think this is trivial because we've just solved our whole C
  String problem in one brilliant fell swoop!  If you recall in dealing with
  character strings we had to obtain storage for enough memory to hold a string
  of some specific size either by declaring a char string array like so...

  char szString[80];

  or so...

  char pStr;
  pStr=(char*)malloc(80);

  ...and then assign the string to the storage like so...

  strcpy(pStr,"C++ Is Brutal To Learn!");

  Well, you've just been shown the key to the solution of this problem.  We'll
  create a String class, and use the operator= functionality of C++ classes to
  allow the assignment of strings to variables of String class type.  We'll look
  at that exciting problem in the next project.  For now, run this program and
  make sure you understand it, because we're in 'building mode', and this is
  important material.
 
  The final point to note about this program is that I used the explicit class
  pointer 'this' in many of my references to class data members.  This
  construction...
 
  this->m_Length
 
  is equivalent to simply...
 
  m_Length
 
  within any of the class methods.  The 'this' pointer points to the base memory
  allocation for the class.  Take note that down in main() the second statement
  outputs the sizeof a Box object.  It comes to 24 bytes because the class
  contains three double data members each of which needs eight bytes.  You might
  wonder why the sizeof the class isn't bigger because of the various other
  member methods of the class such as Volume(), Get_Length(), operator=, etc. 
  Well, as it turns out, if a class does not inherit a memory footprint from an
  ancestor base class, the only memory attributable to it are its data members.
  The class member functions are shared by all instances of a class, and so
  their memory is not counted with any instance.  What actually happens when a
  class member function is called is that the C++ compiler arranges to pass the
  'hidden' this pointer as the first arguement on the stack for the function
  call.  This is how a function (block of code) not associated with any specific
  instance of the class is able to work with the instance data that is to be
  modified.  It has the block of data it needs to work with through the this
  pointer passed to it.
 
  If you look down at the base of the class where I defined the three data
  members you'll see these variables...
 
  private:
  double m_Length;
  double m_Width;
  double m_Height;
 
  If this class was a struct instead of a class it could be partly defined like
  so, at least in terms of its data members...
 
  struct Box
  {
   double m_Length;
   double m_Width;
   double m_Height;     
  };
 
  Whether a class or a struct the compiler would need to allocate a block of
  memory containing 24 bytes to satisfy these memory requirements.  Since the
  this pointer is a variable (a pointer variable) that must point to this base
  allocation, one could surmise that the following equality would hold true for
  an instantiation of of a CBox object named bx2 whose base allocation was at
  2293536...
 
                      &bx2  =  this   =  &this->m_Length
                     
  In fact, down in main() we created such an object and we did it in such a
  manner that the default uninitialized constructor was called.  Within that
  constructor we output both the value held in this and the address at which
  this->m_Length exists and we see they are indeed the same because m_Length is
  the first of the three data members, and so must of necessity align with the
  base memory allocation. After that constructor is called we then output the
  address of bx2 and we see that all three of these entities are indeed the same
  value...                 
 
  &this->m_Length  = 2293536
  this             = 2293536
  &bx2             = 2293536
 
  This object oriented programming in C++ sometimes seems deceptively simple but
  its actually deceptively complex - at least I think so.  Lets move on to the
  next program example and create our String Class!
*/

#include <stdio.h>

class Box
{
public:
Box()   //Box Uninitialized Constructor
{
  printf("Box Uninitialized Constructor Called!\n");
  printf("&this->m_Length  = %u\n",&this->m_Length);
  printf("this             = %u\n",this);
}

Box(double dblLength, double dblWidth, double dblHeight)
{
  printf("Called Box Fully Initialized Constructor!\n");
  this->m_Length=dblLength;
  this->m_Width=dblWidth;
  this->m_Height=dblHeight;
}
     
~Box()  //Box Destructor
{
  printf("CBox Destructor Called!\n");
}

Box operator=(Box& AnotherBox)  //Overloaded Operator Equal
{
  printf
  (
   "Called Box operator= To Assign One Box Object's "
   "Dimensions To Another!\n"
  );
  this->m_Length=AnotherBox.m_Length;
  this->m_Width=AnotherBox.m_Width;
  this->m_Height=AnotherBox.m_Height;
}

double Get_Length()
{
  return this->m_Length;
}

void Set_Length(double dblLength)
{
  if(dblLength>=0)
     this->m_Length=dblLength;
}

double Get_Width()
{
  return this->m_Width;
}

void Set_Width(double dblWidth)
{
  if(dblWidth>=0)
     this->m_Width=dblWidth;
}

double Get_Height()
{
  return this->m_Height;
}

void Set_Height(double dblHeight)
{
  if(dblHeight>=0)
     this->m_Height=dblHeight;
}

double Volume()
{
  return m_Length*m_Width*m_Height;
}

private:
double m_Length;
double m_Width;
double m_Height; 
};   

int main(void)
{
Box bx1(2.0,3.0,4.0);
Box bx2;

printf("&bx2             = %u\n",&bx2);             //output address of bx2
bx2=bx1;                                            //will cause operator= call
printf("sizeof(Box)      = %u\n",sizeof(Box));      //will output sizeof(CBox)
printf("bx1.Volume()     = %f\n",bx1.Volume());     //output bx1 volume
printf("bx2.Volume()     = %f\n",bx2.Volume());     //output bx2 volume
printf("bx2.Get_Length() = %f\n",bx2.Get_Length()); //output length of bx2
printf("bx2.Get_Width()  = %f\n",bx2.Get_Width());  //output width of bx2
printf("bx2.Get_Height() = %f\n",bx2.Get_Height()); //output height of bx2
getchar();   
   
return 0;   
}

/*        --Screen Output--
Called CBox Fully Initialized Constructor!
CBox Uninitialized Constructor Called!
&this->m_Length  = 2293536
this             = 2293536
&bx2             = 2293536
Called CBox operator= To Assign One CBox Object's Dimensions To Another!
CBox Destructor Called!
sizeof(CBox)     = 24
bx1.Volume()     = 24.000000
bx2.Volume()     = 24.000000
bx2.Get_Length() = 2.000000
bx2.Get_Width()  = 3.000000
bx2.Get_Height() = 4.000000
*/

Frederick J. Harris


/*
  ProgEx28a  Reference Return Values Of Class Member Functions  C++ Compilation

  Just above I made the statement that C++ classes can seem deceptively simple,
  but are in fact deceptively complex.  I believe the program below is a good
  example of that.  I wanted to cover this material in a bit more detain with
  the simple Box class we've been using because I wanted to go into a little
  more detail on something that can be very confusing - at least to me, and its
  very important.  And that is the topic of reference return values of functions
  or class methods.

  If you recall our last example of the Box class we had accessor methods to Get
  and Set the private data members of the class.  Therefore, we could initialize
  a Box object through its constructor in a Box declaration like so...

  Box bx1(2.0, 3.0, 4.0);

  or during a program run like so...

  bx1.Set_Length(3.0);
  bx1.Set_Width(4.0);
  bx1.Set_Height(5.0);

  Well, there is another more elegant way to do it and that is to create the
  accessor/mutator method with a reference return value like so...

  double& Box::length();

  and the method would look like this...

  double& length()
  {
   return m_length;
  }

  What happens here is almost magic!  A reference is an alias for something else
  and does not provide its own storage.  In the case above the real storage is
  in an instantiated Box object through its m_length member.  What's really
  interesting about this is that m_length and its alias, i.e.,

  double& length();

  are something termed lvalues, and an lvalue can be the target of an assignment
  statement and hence can appear on the left of an equal sign.  Therefore, the
  following enigmatic statement where you have what looks like a function call
  on the left being assigned something on the right becomes possible...

  bx1.length() = 2.0;

  And this also works to use the reference return value in the more typical
  manner of a member function returning a value...

  printf("bx1.length()  = %f\n", bx.length() );
 
  Output:  bx1.length() = 2.0

  Below is what I would think of as a more elegant way of creating the Box class
  we've been playing with using reference return values in place of Get()/Set()
  methods.
*/

#include <cstdio>

class Box
{
public:
double   Volume()  const {return m_Length*m_Width*m_Height;}
double&  length()        {return m_Length;}
double&  width()         {return m_Width;}
double&  height()        {return m_Height;}

private:
double m_Length;
double m_Width;
double m_Height;
};

int main(void)
{
Box bx1;

bx1.length()=2.0, bx1.width()=3.0, bx1.height()=4.0;
printf("bx1.length() = %1.0f\n",bx1.length());
printf("bx1.width()  = %1.0f\n",bx1.width());
printf("bx1.height() = %1.0f\n",bx1.height());
printf("bx1.Volume() = %2.0f\n",bx1.Volume());
getchar();

return 0;
}

/*  --Output--
  bx1.length() = 2
  bx1.width()  = 3
  bx1.height() = 4
  bx1.Volume() = 24


  Continuing with the discussion above on references, references are actually an
  allowable variable type in C++.  We could have this...

  int  iNumber  = 25;
  int& rNumber  = iNumber;

  In the case above, rNumber is a free standing reference.  One can think of
  them as pointers that have already been dereferenced by the compiler to refer
  to the variable for which the reference was made.  Unlike with pointers
  references are always initialized as in the code just above.  You can't
  declare a reference without initializing it at the same time.  This won't
  work...

  int  iNumber  = 25;
  int& rNumber;
  rNumber= iNumber;

  Dev-C++ will report this error...

  82 C:\Code\Dev-Cpp\Projects\CPrimer\ProgEx28a\Main.cpp `rNumber' declared as
  reference but not initialized.
*/