ITEEDU

9.5 使用成员函数

当从基类派生出一个派生类时,派生类的成员函数可能需要访问基类的某些成员函数。

软件工程视点9. 2

派生类不能直接访问其基类的private成员。

这是C++中关键的软件工程视点。如果派生类能访问其基类的private成员,那么就会破坏基类的封装性。隐藏private成员有助于测试、调试和正确地修改系统。如果派生类能访问其基类的private成员,那么从派生类派生出的类也应该能访问这些成员,这样就会传递对private数据的访问权,从而使封装所带来的益处在整个类层次上损失殆尽。

9.6 在派生类中重定义基类成员

派生类可以通过提供同样签名的新版本(如果签名不同,则是函数重载而不是函数重定义)重新定义基类成员函数。派生类引用该函数时会自动选择派生类中的版本。作用域运算符可用来从派生类中访问基类的该成员函数的版本。

常见编程错误9.3

派生类中重新定义基类的成员函数时,为完成某些附加工作.派生类版本通常要调用基类中的该函数版本。不使用作用域运算符会由于派生类成员函数实际上调用了自身而引起无穷递归。这样会使系统用光内存,是致命的运行时错误。

考察一个简单的类Employee,它存储雇员的姓(成员firstName)和名(成员lastName)。这种信息对于所有雇员(包括Employee的派生类的雇员)是很普遍的。现在假设从雇员类Employee派生出了小时工类HourlyWorker、计件工类PieceWorker、老板类Boss和销售员类CommissionWorker。小时工每周工作40小时,超过40小时部分的报酬是平时的1.5倍;计件工是按生产的工作计算报酬的,每件的报酬是固定的,假设他只生成一种类型的工件,因而类PieceWorker的private数据成员是生产的工件数量和每件的报酬;老板每周有固定的薪水;销售员每周有小部分固定的基本工资加上其每周销售额的固定百分比。为简单起见,此处只研究类Empbyee和派生类HourlyWorker。

本章的第二个例子见图9.5。第1行到47行分别是类Employee的定义和其成员函数的定义,第48行到94行分别是类HoudyWorker的定义和其成员函数的定义,第95行到结束是类继承层次Employee/HourlyWorker的驱动程序,该程序很简单,仅仅建立并初始化了类HourlyWorker的对象,然后调用类HourlyWorker的成员函数print输出对象的数据。

   // Fig. 9.5: employ.h
 // Definition of class Employee
 #ifndef EMPLOY_H
 #define EMPLOY_H
 class Employee {
 public:
   Employee( const char *, const char * );  // constructor
   void print() const;  // output first an last name
  ~Employee();         // destructor
 private:
   char *firstName;    // dynamically allocated string
   char * lastName;    // dynamically allocated string
 } ;
 #endif
 // Fig. 9.5: employ.cpp
 // Member function definitions for class Employee
 #include < string.h>
 #include < assert.h>
 #include "employ.h"
 // Constructor dynamically allocates space for the
 // first and last name and uses strcpy to copy
 // the first and last names into the object.
 Employee::Employee( const char *first, const char *last )
 {
   firstName = new char( strlen( first ) + 1);
   assert( firstName != 0 ); // terminate if not allocated
   strcpy( firstName, first );
   lastName = new char( strlen( last ) + 1 );
   assert( lastName != 0 );  // terminate if not allocated
   strcpy( lastName, last );
 }
 // Output employee name
 void Employee::print() const
   {cout << firstName << ' ' << lastName; }
 // Destructor deallocates dynamically allocated memory
 Employee::~Employee()
 {
   delete [] firstName;  // reclaim dynamic memory
   delete [] lastName;   // reclaim dynamic memory
 }
 // Fig. 9.5: hourly.h
 // Definition of class HourlyWorker
 #ifndef HOURLY_H
 #define HOURLY_H
 #include "employ.h"
 class HourlyWorker : public Employee {
 public:
   HourlyWorker( const char*, const char*, double, double );
   double getPayO const;  // calculate and return salary
   void print() const;    // overridden base-class print
 private:
   double wage;          // wage per hour
   double hours;         // hours worked for week
 };
 #endif
 // Fig. 9.5: hourly.cpp
 // Member function definitions for class HourlyWorker
 #include < iostream.h>
 #include < iomanip.h>
 #include "hourly.h"
 // Constructor for class HourlyWorker
 HourlyWorker::HourlyWorker(constchar*first,
                       const char *last,
                       double initHours, double initwage )
   : Employee( first, last )  // call base-class constructor
 {
   hours = initHours;  // should validate
   wage = initWage;   // should validate
 }
 // Get the HourlyWorker's pay
 double HourlyWorker::getPay() const { return wage * hours; }
 // Print the HourlyWorker's name and pay
 void HourlyWorker::print() const
 {
   cout << "HourlyWorker::print() is executing\n\n";
   Employee::print();  // call base-class print function
   cout <<" is an hourly worker with pay of $"
        << setiosflags( ios::fixed | ios::showpoint )
       << setprecision( 2 ) << getPay() << endl;
 }
 // Fig. 9.5: fig.09_05.cpp
 // Overriding a base-class member function in a
 // derived class.
 #include < iostream.h>
 #include "hourly.h"
 int main()
 {
   HourlyWorker h( "Bob", "Smith", 40.0, 10.00 );
   h.print();
   return 0;
 }

输出结果:

HourlyWorker::print() is executing

Bob Smith is an hourly worker with pay of $400.00

图 9.5 在派生类中重新定义基类的成员函数

类Employee的定义由两个private char*类型的数据成员(fisttName和lastName)和三个成员函数(构造函数、析构函数和print函数)组成。构造函数接收两个字符串,并动态分配存储字符串的字符数组。宏assert(见第18章)用来确定是否为firstName和lastName分配了内存。如果没有,程序终止并返回一条出错信息,该信息指出了被测试的条件以及条件所在的行号和文件。由于Employee的数据是private类型,所以只能用成员函数print访问数据,函数print非常简单,仅仅输出雇员的姓和名。析构函数将动态分配的内存交还给系统(防止内存泄漏)。

类HoudyWorker对类Employee的继承是public继承。类定义的第一行指定了这种继承方式:

class HourlyWorker:public EmPloyee

HourlyWorker的public接口包括Employee的函数print和HourlyWorker的成员函数getPay和print。注意,类HourlyWorker定义了其自身的print函数(使用同样的函数原型Employee:print()),所以类HourlyWorker有权访问两个print函数。类HourlyWorker还包含用来计算雇员的每周薪水的private数据成员wage和hours。

HourlyWorker的构造函数用成员初始化值语法将字符串first和last传递给Employee的构造函数,从而初始化了基类的成员,然后再初始化成员wage和hours。成员函数getPay用来计算HourlyWorker的工资。

类HourlyWorker的成员函数print重新定义Employee的print成员函数。为提供更多的功能而在派生类中重新定义基类的成员函数是常有的事。被重新定义的函数有时候为执行一些新任务而要调用基类中的函数版本。在本例中,派生类函数print调用基类Employee的print函数输出了雇员的名字(基类print函数是惟一能访问该类private数据的函数),派生类的print函数输出了雇员的工资。

调用基类print函数的方法如下:

Employee::print();

因为基类函数和派生类函数的名字相同,所以必须在基类函数前使用基类名和作用域运算符,否则将调用派生类的函数版本(即类HourlyWorker的print函数调用其自身),从而导致无穷递归。