Many shapes have something that can be described as length and width. Two of these are rectangles and squares. The square can be thought of as a special case of a rectangle. So the square has an IS A relationship with the rectangle. This is just the relationship we need to do inheritance.
Other IS A relationships are:
Here is the rectangle class and functions:
class rectangle
{
public:
rectangle (int l, int w); //Sets up a rectangle from length and width
void Draw (); //Draws the rectangle
protected:
int length;
int width;
void SolidRow (int n); //Prints a row of n *'s
void HollowRow (int n); //Prints a row like this: *----------*
void Spaces (int n); //Prints n spaces
};
rectangle::rectangle (int l, int w)
{
length = l;
width = w;
}
void rectangle::Draw ()
{
//Draws a hollow rectangle.
//First draw the solid top of *'s.
SolidRow (width);
//Then draw all the rows but the last as hollow.
for (int row = 2; row < length; row++)
HollowRow (width);
//Then draw a solid row for the bottom
SolidRow (width);
}
void rectangle::SolidRow (int n)
{
//Prints a row of n *'s
for (int col = 1; col <= n; col++)
cout << "*";
cout << endl;
}
void rectangle::HollowRow (int n)
{
//HollowRow (5) looks like: * * -- 3 spaces between the *'s.
cout << "*";
Spaces (n - 2);
cout << "*" << endl;
}
void rectangle::Spaces (int n)
{
//Prints n spaces
for (int col = 1; col <= n; col++)
cout << " ";
}
From the rectangle class we use inheritance to derive a square class:
class square:public rectangle
{
public:
square (int);
private:
};
square::square (int side):rectangle (side, side)
{}
Important things to note here:
square::square (int side):rectangle (side, side)
When a square is declared, as in square theSquare (5); then the 5 goes to the square constructor. The square constructor then calls the rectangle constructor with 5 and 5, so that length and width will both be set to 5.
The square class also has access to all the protected functions in the rectangle class.
A main program for testing out the rectangle and square classes might be:
int main ()
{
rectangle theRect (4,7);
square theSq (5);
theRect.Draw ();
cout << endl;
theSq.Draw ();
return 0;
}
Here is a variation on the square class of the last section. We add a special Draw function with the intention that, squares will alway be drawn double sized.
class square:public rectangle
{
public:
square (int);
void Draw ();
private:
};
square::square (int side):rectangle (side, side)
{}
void square::Draw ()
{
//Draws a double sized square. length is the same as width.
SolidRow (2 * length);
for (int row = 2; row < 2 * length; row++)
HollowRow (2 * length);
SolidRow (2 * length);
}
Here is a main program to test it:
int main ()
{
rectangle *ptr;
square theSq (5);
ptr = &theSq;
theSq.Draw ();
ptr -> Draw ();
return 0;
}
Question: In the two Draw lines, which Draw function gets used?
Answer:
In the last section, a pointer of type rectangle* pointed to a square object. However, this made the computer think the square object was a rectangle. As a result the square object got drawn using the Draw function of the rectangle class.
Question: How can we get the computer to find the right Draw function?
Answer: In the parent (rectangle) class, declare the Draw function to be virtual.
With the following revised rectangle class, the computer will find the correct version of Draw. The square object will be drawn double sized.
class rectangle
{
public:
rectangle (int, int);
virtual void Draw ();
private:
int length;
int width;
};
Polymorphism means:
We have already used the polymorphism idea through operator overloading and templates.
Here the polymorphism idea means that different child classes will be handled with the same code. The child classes will themselves internally handle their own special needs.
The polymorphism idea turns out to make our code simpler and easier to modify. Following is an example based on an extension of the rectangles and squares.
There are numerous shapes that can be thought to have length and width (or something that means length and width). Some of these are: rectangles, squares, isosceles right triangles, and equal sided parallelograms. We will structure our class hierarchy as follows:
Below are the classes, in rect.h.
A very important thing to note is the line in rectShape that reads
virtual void Draw () = 0;
This means:
rectShape shape (3, 5); shape.Draw ();
is impossible, there can not be any objects of the rectShape class - only pointers to rectShape. What will be stored in such pointers? The addresses of objects of the derived classes.
A class that contains one or more pure virtual functions is called an abstract base class.
We can't have create objects of the abstract base class type, but we create objects of its child classes.
Now for the class definitions:
#ifndef RECT_H
#define RECT_H
class rectShape //Parent class for all the shapes
{
public:
rectShape (int l, int w){length = l; width = w;}
virtual void Draw () = 0; //This is pure virtual, so there can not
// be any objects of this class.
protected: //Data and functions available to child classes
void SolidRow (int);
void HollowRow (int);
void Spaces (int);
int getLength ();
int getWidth ();
private: //Data and functions in here are NOT available
// to child classes
int length;
int width;
};
class rectangle:public rectShape //First child class
{
public:
rectangle (int l, int w);
virtual void Draw ();
private:
};
class square:public rectangle //This is a child class of the
// rectangle class, which makes it
// a grandchild of the rectShape class.
{
public:
square (int side);
void Draw ();
private:
};
class isoscelesTri:public rectShape
{
public:
isoscelesTri (int base);
void Draw ();
private:
};
class parallelogram:public rectShape
{
public:
parallelogram (int width);
void Draw ();
};
#endif
Next the file of functions, rect.cpp.
#include <iostream>
#include "rect.cpp"
using namespace std;
int rectShape::getLength ()
{
return length;
}
int rectShape::getWidth ()
{
return width;
}
void rectShape::SolidRow (int n)
{
//Prints a row of n *'s
for (int col = 1; col <= n; col++)
cout << "*";
cout << endl;
}
void rectShape::HollowRow (int n)
{
//HollowRow (5) looks like: * * -- 3 spaces between the *'s.
cout << "*";
Spaces (n - 2);
cout << "*" << endl;
}
void rectShape::Spaces (int n)
{
//Prints n spaces
for (int col = 1; col <= n; col++)
cout << " ";
}
rectangle::rectangle (int l, int w):rectShape (l, w)
{}
void rectangle::Draw ()
{
//Draws a rectangle
SolidRow (getWidth ());
for (int row = 2; row < getLength (); row++)
HollowRow (getWidth ());
SolidRow (getWidth ());
}
square::square (int side):rectangle (side, side)
{}
void square::Draw ()
{
//Draws a square at double size
SolidRow (2 * getLength ());
for (int row = 2; row < 2 * getLength (); row++)
HollowRow (2 * getLength ());
SolidRow (2 * getLength ());
}
isoscelesTri::isoscelesTri (int base):rectShape (base, base)
{}
void isoscelesTri::Draw ()
{
//Draws an isosceles right triangle
cout << "*" << endl;
for (int row = 2; row < getLength (); row++)
{
HollowRow (row);
}
SolidRow (getLength ());
}
parallelogram::parallelogram (int width):rectShape (width, width)
{}
void parallelogram::Draw ()
{
//Draws a parallelogram with equal sides.
SolidRow (getLength ());
for (int row = 2; row < getLength (); row++)
{
Spaces (row - 1);
HollowRow (getLength ());
}
Spaces (getLength ());
SolidRow (getLength ());
}
Here is a main program to show how the whole scheme works:
int main ()
{
const int MAX = 4;
rectShape *shapes [MAX];
shapes [0] = new rectangle (3, 6);
shapes [1] = new square (5);
shapes [2] = new isoscelesTri (5);
shapes [3] = new parallelogram (6);
if (shapes [0] != NULL && //Make sure the new worked in all cases
shapes [1] != NULL &&
shapes [2] != NULL &&
shapes [3] != NULL)
{
for (int n = 0; n < MAX; n++) //Draw all the shapes
{
shapes [n] -> Draw (); //This line works for ALL the shapes
// The shapes themselves handle their
// differences internally.
cout << endl;
}
}
for (int n = 0; n < MAX; n++) //Done, so release the memory
delete shapes [n];
return 0;
}