This base class / derived class relationship is often referred to as an "is a" relationship, because the derived object really is an instance of the base object -- it's usually just a bit more specific (like a subcategory). Examples:
A class called Geometric_Object. From this base class we could
derive classes like Circle, Square, and Line.
A class called Sport. From this class we could derive classes
like Football, Baseball, and Soccer, because each one is a Sport.
A class called BankAccount. From this we could derive the classes
Savings, Checking, Debit (each is a type of account).
A class called Vehicle. From this we could derive classes Car,
Train, Bus (each one is a vehicle). Furthermore, we could use the
Car class as a base class from which to derive more new classes, such as
Ford, Toyota, and Honda.
Note that this is different from the "has-a" relationship, in which
objects are embedded inside of others (i.e. one object is a component
of another).
class derivedClassName : public baseClassName
The word "public" in this format causes derivedClassName to be publicly derived from baseClassName. The base class would be declared normally somewhere above (or in an included file). This essentially means that the protection levels in the derived class are the same as in the base class. (Note: It is possible to derive with different protection levels -- we will not worry about those here).
So, the class declarations for some of the above examples might look like this:
class Sport { .... }; class Football : public Sport { .... }; class Checking : public Account class Baseball : public Sport class Car : public Vehicle class Honda : public Car
Notice that in the last two declarations, Car inherits everything in the Vehicle class, and Honda inherits everything in the Car class. Because of this, Honda has also inherited everything from the Vehicle class as well.
Example: classes in a drawing program
public - Any member that is public can be accessed by name from
anywhere.
private - Any member that is private can be accessed directly
only by the class in which it is declared.
This poses a problem, since we would like derived classes to have access to the members that it inherited from the base class, but we still want protection from outside access. For this reason, there is a third level of protection for member data and functions, called protected.
protected - Any member that is protected can be accessed directly by the class in which it is declared, and by any classes that are derived from that class (but not from anywhere else).
Protection levels in the drawing program example
In this case, the derived object "is-an" instance of the base class as well. So, when a derived object is created, the constructors from the base and derived classes will run. The order in which they run is important, too -- the base class constructor runs first, then the derived.
Using the examples given above, consider these object declarations:
Vehicle obj1; Car obj2; Honda obj3;
In the first declaration, only the Vehicle() constructor runs.
In the second declaration, the Vehicle() constructor runs
for obj2, followed by the Car() constructor.
In the third declaration, the constructors that run, in order, are:
Vehicle(),
Car(), Honda()
Note: When an object goes out of scope, the destructors will run in reverse order: ~Honda(), ~Car(), ~Vehicle()
You can verify for yourself in code what order the constructors and destructors run in. Click here for an example.
Constructors with parameters:
Since no parameters were specified in the above examples, the default
constructors (i.e. no parameters) are used.
What if we have parameters? Remember in the Fraction class,
we could declare the following:
Fraction f(3,4); // calls constructor with parameters
So, with a derived class, we might want to declare:
Honda h(2,3,4,"green","Accord"); // wants to send in 5 parameters
However, the 2 and 3 might be codes intended for a variables like "vehicleCategory" and "idNumber" in the Vehicle class; the 4 and "green" might be intended for variables like "numDoors" and "carColor" in the Car class; and the "Accord" might be intended for a variable "model" in the Honda class. This means that the first two parameters are needed by the Vehicle constructor, the second two are needed by the Car constructor, and the last is needed in the Honda constructor. How do we distribute the parameters to the appropriate places?
To do this with derived classes, use an Initialization List. And initialization list is something that can go along with any function definition. The format is:
function prototype : initialization list { function body }
We will use the initialization list to call the next higher constructor explicitly, in order to send the needed parameters up to the parent constructor. An initialization list is run before the function body actually runs. Here is how the constructors might look in our car example:
Vehicle::Vehicle(int c, int id) // this parent constructor uses the first two parameters { vehicleCategory = c; idNumber = id; } Car::Car(int c, int id, int nd, char* cc) : Vehicle(c, id) // this function passes the first two parameters up to Vehicle, and uses nd and cc { numDoors = nd; strcpy(carColor,cc); } Honda::Honda(int c, int id, int nd, char* cc, char* mod) : Car(c, id, nd, cc) // This function passes the first four parameters up to car, and uses mod { strcpy(model, mod); }
Drawing program example, with constructors
Suppose we have the following base class:
class Student { public: void GradeReport(); ... (other member functions and data) };
Let's assume that the "GradeReport" function will print out a grade report for a Student.
Now, take the following as classes derived from Student:
class Grad : public Student class Undergrad : public Student
Since these classes are derived from Student, they inherit everything from the Student class. So, we could build the following objects and make the following function calls:
Student s; Grad g; Undergrad u; s.GradeReport(); g.GradeReport(); u.GradeReport();
The Grad and Undergrad classes both inherited the GradeReport function, so they can call it. However, what if grade reports look different for undergrads and grads? Then, these classes need to have their own functions! With inheritance, a derived class can create its own version of a function in the base class, with the exact same prototype, which will override the base class version:
class Grad : public Student { public: void GradeReport(); ... (other stuff) }; class Undergrad : public Student { public: void GradeReport(); ... (other stuff) };
So, in the calls listed above, each object calls it's own version of the GradeReport function:
s.GradeReport(); // runs Student's version g.GradeReport(); // runs Grad's version u.GradeReport(); // runs Undergrad's version
Now, does this mean that for the grad object g, the parent version is no longer accessible? No! Remember, a derived class inherits everything from its parent class. If, inside the Grad's version of GradeReport, you would like to call upon the parent's version as well (usually done if you want to split the work and let the parent function do as much as possible, based on data stored in the base class), you can. Here's an example -- these are outlines of the function definitions:
void Student::GradeReport() { ... processing done by parent function ... } void Grad::GradeReport() { Student::GradeReport(); // explicit call to parent function // other processing specific to Grad's version }
Notice that you can explicitly call the parent's version of the function by specifying the parent class name and the scope resolution operator:
className::memberName
Examples of function overriding for the drawing program example.