Inheritance Inheritance

1  Inheritance

1.1  IS A relationships

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:

1.2  You do it

  1. Name two other IS A relationships.

1.3  Derived classes

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:

  1. The square is derived from the rectangle class. The computer knows this because of the line class square:public rectangle
  2. The square constructor calls the rectangle constructor with

    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.

  3. If we don't arrange for the square constructor to call the rectangle constructor, then the computer call it anyway. If the rectangle constructor requires arguments (and it does!) then we will get an error.
  4. The Draw function in the rectangle class is available for use by the square class. All public functions or data of the rectangle class are inherited by the square class because of the line class square:public rectangle.
  5. The length and width data in the protected section of the rectangle class are available to the square class. (But data in the private section is not!)

    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;
	}

2  Parent class pointers that point to child objects

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:

  1. In the line theSq.Draw (); the computer sees the square object as a square, so it uses the square Draw and draws the square double sized.
  2. In the line ptr -> Draw (); the computer sees the square object as a rectangle (because ptr is of type rectangle*). It uses the rectangle Draw and draws the square single sized.

3  virtual functions

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;
	};

4  Polymorphism

Polymorphism means:

  1. Different kinds of objects are handled with the same code.
  2. The special handling for the differences are handled internally by the objects themselves.

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.

4.1  A shapes example

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:

  1. There will be a generic rectShape class that will store length and width and direct the computer to the right Draw function.
  2. Derived from the generic rectShape class will be a rectangle class, an isosceles right triangle class, and an equal sided parallelogram class. Each one will have its own Draw function.
  3. The square class will still be derived from the rectangle class.

4.2  The header file, rect.h

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:

  1. The computer will be directed to the Draw in the derived classes because of the virtual keyword, as discussed before.
  2. The = 0 part means that the rectShape class doesn't have a Draw function at all. Such a function is called pure virtual.
  3. Since

    			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

4.3  The functions file, rect.cpp

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 ());
   }

4.4  A main program

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;
	}


File translated from TEX by TTH, version 2.25.
On 15 Nov 2002, 14:36.