Inheritance

 

Let's build  a class for employees.

 

const int MAX_NAME = 20;

 

class Employee

{

public:

      Employee( /* in */ const char initName[] );

      void Print() const;

private:

Employee

name

Employee()

Print()

 

 
      char name[MAX_NAME+1];

};

 

Employee::Employee( /* in */ const char initName[] )

{

      strcpy( name, initName );

}

 

void Employee::Print() const

{

      cout << name << endl;

}

 

void main()

{

      Employee e( "Jim" );

      e.Print(); // Jim

}

 

Managers are a special kind of employee.  We can build a Manager class by starting from an Employee class.  Managers will have the same data and function members as Employees, in addition to data and function members that are specific to Managers.

 

class Manager : public Employee

{

public:

      Manager( /* in */ const char initName[], /* in */ int initLevel );

Manager

name

level

Employee()

Print()

 

 

 
      void Print() const;

private:

      int level;

};

 

 

 

 

 

 

 

Manager::Manager( /* in */ const char initName[], /* in */ int initLevel  )

      : Employee( initName )

{

      level = initLevel;

}

 

void Manager::Print() const

{

      Employee::Print();

      cout << "level = " << level << endl;

}

 

The Manager class cannot access the private data member of the Employee class.  We will make name[] a protected data member.

 

class Employee

{

public:

      Employee( /* in */ const char initName[] );

      void Print() const;

protected:

      char name[MAX_NAME+1];

};

 

The protected data members of Employee are accessible in all classes derived from Employee.

 

void main ()

{

      ...

      Manager m( "Mary", 1 );

m.Print();  // Mary

                  // level = 1

      ...

      Employee e2 = m;          // Only the employee part is copied

                                // The object is "sliced"

      e2.Print();  // Mary

 

     


Employee* pe3 = new Employee( "Burt" );

pe3->Print();  // Burt

 

 

      Employee* pe4 = new Manager( "Susan", 1 );

      pe4->Print();  // Susan

 

                        pe4 points to a Manager object.

                                    But since pe4 is of type (Employee*), C++ calls Employee::Print()

 

      ((Manager*)pe4)->Print(); // Susan

                                   level = 1

 

            pe4 actually points to a  Manager object.

            We can cast pe4 to a Manager pointer

            ( (Manager*) pe4 )->Print() calls Manager::Print()

}

 


Let's create a linked list of employees and managers.

 

struct Node

{

    Employee* employeePtr;

    Node*     next;

};

 

     Node* front = new Node;

     front->next = new Node;

     front->next->next = NULL;

     front->employeePtr = new Employee( "Bill" );

     front->next->employeePtr = new Manager( "Judy", 1 );

 

We now loop through the linked list and call each object's Print() method.

 

     for ( Node* ptr = front; ptr != NULL; ptr = ptr->next )

          ptr->employeePtr->Print();

 

                        // Bill

          // Judy

 

Employee::Print() is called for each object, even for the Manager.

 


Virtual Methods

 

We would like each object to call its own Print() method independent of whether the pointer is an Employee* or a Manager*.

 

Change the declaration of Print() in Employee.

 

class Employee

{

public:

     Employee( /* in */ const char initName[] );

     virtual void Print() const;

protected:

     char name[MAX_NAME+1];

};

 

Now the loop prints out:

 

                        // Bill

          // Mary

          // level = 1  

 

 

Interfaces

 

Let's define a Shape class and derive from it a Square class, a Circle class, and a Rectangle class.

 

class Shape

{

public:

      virtual void Draw() const;

};

 

class Circle : public Shape

{

public:

       void Draw() const;

private:

       ...

};

class Square : public Shape

{

public:

       void Draw() const;

private:

       ...

};

class Rectangle : public Shape

{

public:

       void Draw() const;

private:

       ...

};

 

Let's add lots of circles, squares, and rectangles to a linked list of shapes.

 

 


Then loop through the linked list calling each object's Draw() method.

 

for ( Node* ptr = root; ptr != NULL; ptr = ptr->next )

{

      ptr->shapePtr->Draw();

}

 

Since Draw is a virtual method, a Square shape will call Square::Draw(), a circle shape will call Circle::Draw(), etc.

 

Each object uses its own Draw() method.  A square object draws itself as a square.  A circle draws itself as a circle, etc.

 

 

 

Interfaces

 

We don't need (or want) to implement the Shape class' methods.

 

In C++ we would declare its methods to be pure virtual methods.

           

class Shape

{

public:

      virtual void Draw() const = 0;

};

 

Of course, the methods must be implemented in each of the derived classes.

 

We call Shape an interface (= abstract) class.

 

We say that Circle, Square, and Rectangle implement the Shape interface.