Reading: Deitel & Deitel, Chap. 9
So far, we have always created new classes from scratch. One of the fundamental features of C++, and object oriented programming in general, is the concept of inheritance in which classes are derived from other classes. The derived class inherits all of the members of the base class. Inheritance saves time in software development, because if a program requires that a number of similar classes be developed, as is often the case, it saves the time required in rewriting each new class from scratch.
To show that a class is derived from another class, follow the class definition with a colon, followed by the keyword public 1 followed by the name of the base class.
The syntax of inheritance is as follows:
class baseclass {
protected:
...
public:
...
private:
...
};
class derivedclass : public baseclass {
private:
...
public:
...
};
Notice the new keyword protected in the base class. Members which are protected are accessible to members of the class itself and derived classes, but not other functions. Members of the base class which are private are not accessible even to classes derived from the base class. Members which are public in the base class are public in the derived class as well.
Here is a trivial example:
class baseclass {
protected:
int x;
private:
int y;
public:
baseclass() {x = y = 0;}
void setx(int n) {x = n;}
int getx() {return x;}
void sety(int n) {y = n;}
int gety() {return y;}
};
class derivedclass : public baseclass {
private:
int z;
public:
derivedclass() {x = z = 0; sety(0);}
void setz(int n) {z = n;}
int getz() {return z;}
int addxz() {return z+x;}
};
Because the class derivedclass inherits all of the public and
protected members of baseclass, it can add the values of x
and z. However, it cannot access the value y directly
because y is private. Thus a function like this:
int addyz() {return y+z;}
would be illegal as a member of derivedclass. (It is unusual to have private variables in base
classes.)
If d is an instance of derivedclass, the following calls are all legal even though they are defined in baseclass.
d.setx(n); n = d.getx(); d.sety(n);
An example will clarify this. Suppose that a banking software system has an object called an account. There are several kinds of accounts, such as checking accounts and savings accounts. These differ from each other in some ways, but they have certain things in common. It would be possible to create a base class account which contained all of the members common to all accounts, and then create derived classes for checking account and savings account. Here is an example in which savings accounts can accumulate interest so they have an interest rate, but checking accounts have overdraft privileges so they have a maximum overdraft.
Here is a program that does this. (\dept\cs\cs2\download\bank.cpp)
#include <iostream>
using namespace std;
class Account {
protected:
char owner[32];
int accountnum;
double balance;
public:
Account() { }
Account(int num, char name[]){
accountnum=num;
strcpy(owner,name);
balance=0;
}
void deposit(double amt) {
balance += amt;
}
bool withdraw(double amt) {
if (balance >= amt) {
balance -= amt;
return true;
}
else return false; // insufficient funds
}
double getbalance(){return balance;}
void SetName(char name[]) {strcpy(owner,name);}
void SetAcctNum(int num) {accountnum=num;}
};
class CheckingAcct : public Account {
private:
double maxoverdraft;
public:
CheckingAcct() { }
CheckingAcct(int num, char name[]) {
accountnum = num;
strcpy(owner,name);
balance = 0;
}
void SetOverdraft(double amt) {
maxoverdraft = amt;
}
bool withdraw(double amt){
if (amt <= balance + maxoverdraft) {
balance -= amt;
return true;
}
else return false;
}
};
class SavingsAcct : public Account {
private:
double interestrate;
public:
SavingsAcct(){}
SavingsAcct(int num, char name[]) {
accountnum = num;
strcpy(owner,name);
balance = 0;
}
void setrate(double rate) {
interestrate = rate;
}
void computeinterest() {
balance = balance + balance*interestrate;
}
};
int main()
{
CheckingAcct c1(123456789,"Mickey Mouse");
SavingsAcct s1(234567890,"Donald Duck");
c1.deposit(456.78);
s1.deposit(678.90);
s1.setrate(0.05);
c1.SetOverdraft(500.00);
if (c1.withdraw(700.00) == false)
cerr <<"Insufficient funds in account c1" << endl;
cout << "The balance of c1 is " << c1.getbalance() << endl;
return 0;
}
Exercise 1: Write a program that defines a base class Employee which has a name and a social security number. It should have a constructor which takes two values, the name and ss-number, a destructor, and a function called printdata which displays the two values.
Derive two classes from this. One class is hourly_employee that contains two new double data members, rate and hours, and three public member functions void setrate(double), void sethours(double), and double getwage() which returns the product of rate times hours.
The second derived class is salaried_employee, which takes one additional private data member, double salary. This should have two public member functions, void setsalary(double), and double getsalary();
Both of these should have a constructor which takes a name and a social security number as arguments.
Write a main that creates an instance of each class and tests all of the member functions of each.
Redefining Base Class Members in a Derived Class
A derived class can redefine a member function of the base class. For example, suppose the following public member function is added to the class Account.
void Account::printdata(){
cout << "Owner " << owner << " Acct Num " << accountnum <<
" Balance " << balance ;
}
The derived classes CheckingAcct and SavingsAcct could also have their own member functions called printdata defined as follows:
void SavingsAcct::printdata() {
cout << "Owner " << owner << " Acct Num " << accountnum <<
" Balance " << balance << " Rate " << interestrate ;
}
void CheckingAcct::printdata() {
cout << "Owner " << owner << " Acct Num " << accountnum <<
" Balance " << balance << " Overdraft level " <<
maxoverdraft ;
}
Notice that there are now three separate functions called printdata, each doing something slighly different. The compiler will know which of the three to call depending on the type of the class.
Calling a base class function from a derived class function
It is also possible to have a function in a derived class call the print function of the base class. A better way to write the example above is to have the SavingsAcct printdata function first call the printdata function of the base class Account, and then print out the additional information belonging only to the derived class. To call the base class version of a function, you must preface the function name with the base class and two colons (e.g. Account::printdata()). We could rewrite the printdata function for SavingsAcct above in this way:
void SavingsAcct::printdata() {
cout << "Savings: ";
Account::printdata(); // call base function first
cout << " Rate " << interestrate ; // print out extra information
}
This code is simpler to read and less likely to cause an error later, since the code for printing out the members of the base Account class appears only once in the program. (If you changed one of the functions later on, you might forget to change the others in the same way.)
Base Class Initialization
When a constructor in a derived class is called, it is possible to call a constructor for the base class and pass arguments to it. This is done by following the declaration of the constructor of the derived class with a call to a constructor for the base class delimited by a colon. Here is an example:
class baseclass {
protected:
int x;
public:
baseclass(int n){x=n;} // constructor for base class
// ...
};
class derivedclass : public baseclass {
private:
int y;
public:
derivedclass(int n1, int n2): baseclass(n1) {y = n2;}
// ...
};
int main()
{
derivedclass d(45,78);
...
Notice that the constructor for the derived class calls the constructor for the base class and passes one of its arguments to it. The value of n1 is assigned to x and the value of n2 is assigned to y.
According to the C++ standard, even fundamental data types like int and double have constructors. The following statement declares a variable x of type int and calls its constructor, which sets its initial value.
int x(34);
This is equivalent (and probably preferable) to this statement
int x = 34;
One of the functions of a class constructor is often to initialize variables, and many of our constructors have looked like this.
class trivial {
private:
int x; double y;
public:
trivial(int n1, double n2)
{
x = n1;
y = n2;
}
...
Here is a better way to write the constructor for trivial.
trivial(int n1, double n2):x(n1), y(n2){ }
This is calling the constructor for an int and for a double and passing
arguments to them.
Here is a better way to write the constructor for derivedclass in the above example.
derivedclass(int n1, int n2): baseclass(n1) , y(n2){ }
Derived Class Destructors Here is an example of a base class which has a pointer data member.
class baseclass {
protected:
char* ptr;
public:
baseclass(char* s) // constructor for base class
{
ptr = new char[strlen(s)+1];
strcpy(ptr, s);
}
// ...
~baseclass(){delete [] ptr;}
};
class derivedclass : public baseclass {
public:
// ...
~derivedclass() {}
};
When a derived class's destructor is called, the base class's destructor is implicitly called before any instructions in the derived class's destructor are executed. Therefore, a derived class destructor should only deal with data members which are explicitly defined in its class, instead of any inherited members.
The following code would produce a delete error.
class derivedclass : public baseclass {
public:
// This code is incorrect
~derivedclass() { if (ptr != NULL) delete [] ptr;}
};