/*
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
*/
/*
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.
*/