在寫程式時,若需定義多個類別(比如類別A、B、C),而類別B、C擁有類別A的某些資料成員、或某些成員方法,則我們可以使用繼承讓B/C直接獲得A的public與protect部分,在C++中一般稱呼A為基本類別,B與C為衍生類別,基本類別就是Java裡的父類別而衍生類別就是子類別,C++真是一個麻煩的語言,在C++中類別分成public, protect, private三部分,繼承也分為public, protect, private inheritance,我們主要探討螞蟻書的public inheritance中關於私有資料成員如何處理的範例:
如上圖所示即使是public inheritance也無法存取父類別的private member(date and method),下面是父類別Employee的標頭檔可以看到有三個private date member,分別是firstName, lastName, socialSecurityNumber, 子類別是無法讀取的
本日篇幅較多包含10個檔案所以所有程式碼都擺在day25_example
// Fig. 13.13: Employee.h
// Employee abstract base class.
#ifndef EMPLOYEE_H
#define EMPLOYEE_H
#include <string> // C++ standard string class
using std::string;
class Employee
{
public:
Employee( const string &, const string &, const string & );
void setFirstName( const string & ); // set first name
string getFirstName() const; // return first name
void setLastName( const string & ); // set last name
string getLastName() const; // return last name
void setSocialSecurityNumber( const string & ); // set SSN
string getSocialSecurityNumber() const; // return SSN
// pure virtual function makes Employee abstract base class
virtual double earnings() const = 0; // pure virtual
virtual void print() const; // virtual
private:
string firstName;
string lastName;
string socialSecurityNumber;
}; // end class Employee
#endif // EMPLOYEE_H
類別CommissionEmployee的原始碼.cpp檔中可以發現子類別的print()方法中有一段呼叫父類別方法的代碼Employee::print();,再去點開父類別的原始碼.cpp檔會發現父類別的print()方法會再call三個成員方法getFirstName(),getLastName(), getSocialSecurityNumber()。
// Fig. 13.14: Employee.cpp
// Abstract-base-class Employee member-function definitions.
// Note: No definitions are given for pure virtual functions.
#include <iostream>
using std::cout;
#include "Employee.h" // Employee class definition
// constructor
Employee::Employee( const string &first, const string &last,
const string &ssn )
: firstName( first ), lastName( last ), socialSecurityNumber( ssn )
{
// empty body
} // end Employee constructor
// set first name
void Employee::setFirstName( const string &first )
{
firstName = first;
} // end function setFirstName
// return first name
string Employee::getFirstName() const
{
return firstName;
} // end function getFirstName
// set last name
void Employee::setLastName( const string &last )
{
lastName = last;
} // end function setLastName
// return last name
string Employee::getLastName() const
{
return lastName;
} // end function getLastName
// set social security number
void Employee::setSocialSecurityNumber( const string &ssn )
{
socialSecurityNumber = ssn; // should validate
} // end function setSocialSecurityNumber
// return social security number
string Employee::getSocialSecurityNumber() const
{
return socialSecurityNumber;
} // end function getSocialSecurityNumber
// print Employee's information
void Employee::print() const
{
cout << getFirstName() << ' ' << getLastName()
<< "\nsocial security number: " << getSocialSecurityNumber();
} // end function print
在CommissionEmployee的標頭檔可以發現確實沒有定義firstName, lastName, socialSecurityNumber三個資料成員與getFirstName(),getLastName(), getSocialSecurityNumber()三個成員方法,現在好玩的來了!如果再多一個子類別:佣金+底薪員工去繼承佣金員工CommissionEmployee會怎樣呢? 老樣子直接點開BasePlusCommissionEmployee的標頭檔與與原始碼.cpp,可以驚人的發現BasePlusCommissionEmployee的成員方法print()直接呼叫CommissionEmployee::print();也沒有去定義所需的成員方法與資料。這就是繼承的核心大量的程式碼復用reuse。
最後點開主程式fig13_25.cpp,可以看出父類別Employee有三個子類別SalariedEmployee, HourlyEmployee, CommissionEmployee與一個孫類別BasePlusCommissionEmployee,首先創造一個Employee類別指標向量vector < Employee * > employees( 4 ); 由於四個類別都繼承自Employee所以可以直接擺到Employee類別指標向量中,接著透過for迴圈逐一呼叫print()方法成員,這裡有個Tricky,因為我們前面使用的是指標所以是必須使用->如employees[i]->print();而非.(dot) 呼叫成員方法,而回到typeid時就不再是指標所以用.(dot) 如typeid( *employees[j] ).name() << endl;