Bootstrap

《C++大学教程》 第12章 面对对象编程:多态性 笔记

文章目录

0. 前言

《C++大学教程》 第12章 笔记更一下。
附部分课后题代码。

12.1 简介

多态性利用了基类的指针句柄和基类的引用句柄

12.2 多态性介绍:多态视频游戏

每个派生类都以适合自己的方式来实现基类的成员函数。
相同消息发送给不同类型的对象,产生不同形式的结果——故而称之为多态性。

利用多态性,程序员可以处理普遍性问题并让执行时的环境自己关心特殊性。可以指挥各种对象执行与它们相符的行为,甚至不需要知道它们的类型(只要这些对象属于同一个继承层次,并且它们都是通过一个共同的基类指针或者一个共同的基类引用访问的)。

多态性提供了软件的可拓展性,调用多态行为的软件可以用与接收消息的对象类型无关的方式编写。因此,不用修改基本系统,就可以把能够响应现有消息的新类型的对象添加到系统中。只有实例化新对象的客户代码必须修改,以适应新类型。

12.3 类继承层次中对象之间的关系

12.3.1 从派生类对象调用基类函数

演示了将基类指针和派生类指针指向基类对象和派生类对象的三种方式。

  • 将基类指针指向基类对象并调用基类的功能
  • 将派生类指针指向派生类对象并调用派生类的功能
  • 把基类指针指向派生类对象,展示基类的功能性在派生类对象中是可用的
// Fig. 12.1: fig12_01.cpp
// Aiming base-class and derived-class pointers at base-class
// and derived-class objects, respectively.
#include <iostream>
#include <iomanip>
#include "CommissionEmployee.h"
#include "BasePlusCommissionEmployee.h" 
using namespace std;

int main()
{
   // create base-class object
   CommissionEmployee commissionEmployee( 
      "Sue", "Jones", "222-22-2222", 10000, .06 );

   // create base-class pointer
   CommissionEmployee *commissionEmployeePtr = nullptr;

   // create derived-class object
   BasePlusCommissionEmployee basePlusCommissionEmployee(
      "Bob", "Lewis", "333-33-3333", 5000, .04, 300 );

   // create derived-class pointer
   BasePlusCommissionEmployee *basePlusCommissionEmployeePtr = nullptr;

   // set floating-point output formatting
   cout << fixed << setprecision( 2 );

   // output objects commissionEmployee and basePlusCommissionEmployee
   cout << "Print base-class and derived-class objects:\n\n";
   commissionEmployee.print(); // invokes base-class print
   cout << "\n\n";
   basePlusCommissionEmployee.print(); // invokes derived-class print
   
   // aim base-class pointer at base-class object and print
   commissionEmployeePtr = &commissionEmployee; // perfectly natural
   cout << "\n\n\nCalling print with base-class pointer to "
      << "\nbase-class object invokes base-class print function:\n\n";
   commissionEmployeePtr->print(); // invokes base-class print

   // aim derived-class pointer at derived-class object and print
   basePlusCommissionEmployeePtr = &basePlusCommissionEmployee; // natural
   cout << "\n\n\nCalling print with derived-class pointer to "
      << "\nderived-class object invokes derived-class " 
      << "print function:\n\n";
   basePlusCommissionEmployeePtr->print(); // invokes derived-class print

   // aim base-class pointer at derived-class object and print
   commissionEmployeePtr = &basePlusCommissionEmployee;
   cout << "\n\n\nCalling print with base-class pointer to "
      << "derived-class object\ninvokes base-class print "
      << "function on that derived-class object:\n\n";
   commissionEmployeePtr->print(); // invokes base-class print
   cout << endl;
} // end main

每个派生类对象都是一个基类对象。虽然事实上基类CommissionEmployee的指针指向了派生类BasePlusCommissionEmployee的对象,但调用的仍是基类CommissionEmployee的成员函数print
本程序中对各个print成员函数调用的输出结果表明,被调用的功能取决于用来调用函数的句柄(如指针或者引用)类型,而不是句柄所指向的对象类型。

12.3.2 将派生类指针指向基类对象

// Fig. 12.2: fig12_02.cpp
// Aiming a derived-class pointer at a base-class object.
#include "CommissionEmployee.h"
#include "BasePlusCommissionEmployee.h"

int main()
{
   CommissionEmployee commissionEmployee( 
      "Sue", "Jones", "222-22-2222", 10000, .06 );
   BasePlusCommissionEmployee *basePlusCommissionEmployeePtr = nullptr;

   // aim derived-class pointer at base-class object
   // Error: a CommissionEmployee is not a BasePlusCommissionEmployee
   basePlusCommissionEmployeePtr = &commissionEmployee;
} // end main

编译器不允许将基类对象commissionEmployee的地址赋给派生类指针basePlusCommissionEmployeePtr,会产生错误信息。因为CommissionEmployee类对象不是BasePlusCommissionEmployee类对象。
基类未有的成员函数可能覆盖内存中的其他重要的数据。

12.3.3 通过基类指针调用派生类的成员函数

// Fig. 12.3: fig12_03.cpp
// Attempting to invoke derived-class-only member functions
// through a base-class pointer.
#include <string>
#include "CommissionEmployee.h"
#include "BasePlusCommissionEmployee.h"
using namespace std;

int main()
{
   CommissionEmployee *commissionEmployeePtr = nullptr; // base class ptr
   BasePlusCommissionEmployee basePlusCommissionEmployee(
      "Bob", "Lewis", "333-33-3333", 5000, .04, 300 ); // derived class
   
   // aim base-class pointer at derived-class object (allowed)
   commissionEmployeePtr = &basePlusCommissionEmployee;

   // invoke base-class member functions on derived-class
   // object through base-class pointer (allowed)
   string firstName = commissionEmployeePtr->getFirstName();
   string lastName = commissionEmployeePtr->getLastName();
   string ssn = commissionEmployeePtr->getSocialSecurityNumber();
   double grossSales = commissionEmployeePtr->getGrossSales();
   double commissionRate = commissionEmployeePtr->getCommissionRate();
   
   // attempt to invoke derived-class-only member functions
   // on derived-class object through base-class pointer (disallowed)
   double baseSalary = commissionEmployeePtr->getBaseSalary();
   commissionEmployeePtr->setBaseSalary( 500 );
} // end main

试图调用BasePlusCommissionEmployee类的成员函数getBaseSalary和setBaseSalary。
C++编译器产生错误信息,因为这两个函数都不是基类CommissionEmployee的成员函数。该句柄只能调用与它关联的类类型的成员函数。

向下转换

向下强制类型转换技术C++编译器允许通过指向派生类对象的基类指针访问只在派生类中拥有的成员,只要显式地把基类指针转换为派生类指针。
一个基类指针只能用来调用基类中声明的函数。

向下强制类型转换技术允许程序通过指向派生类对象的基类指针,执行只有派生类才拥有的操作。
经过强制类型转换之后,程序就可以调用基类中没有的派生类函数。

向下强制类型转换具有潜在风险。

如果一个派生类对象的地址已经赋给了一个它的直接或者间接基类指针,把这个基类指针进行强制类型转换而转换成派生类指针是允许的。

12.3.4 virtual函数和virtual析构函数

使用了virtual函数,调用成员函数的对象的类型而不是句柄的类型,决定调用哪个版本的virtual函数。

为什么virtual函数是有用的

多态行为利用基类的指针调用成员函数,程序根据任意给定时刻基类指针所指向的对象的类型,动态地(即在执行时)决定应该调用哪个派生类的成员函数。

声明virtual函数

要允许多态行为,必须在基类中把成员函数声明为virtual函数,在每个派生类中重写成员函数,使之能实现正确的功能
在派生类中重写的函数和它重写的基类函数具有相同的签名和返回值类型。
如果没有把基类函数声明为virtual,那么可以重新定义这个函数;相反,如果将基类函数声明为virtual,那么可以重写此函数并导致多态性行为。

一旦一个函数声明为virtual,那么从整个继承层次地那一点起,向下的所有的类中,它将保持是virtual的,即使当派生类重写此函数时并没有显式地将它声明为virtual
为了使程序更加清晰可读,最好还是在类层次结构地每一级中都把它们显式地声明为virtual函数。

C++ 11中,在派生类的每一个覆盖函数上使用override关键词,会迫使编译器检查基类是否有一个同名及同参数列表的成员函数。如果没有,则编译器报错。

当派生类选择不重写从其基类继承而来的virtual函数时,派生类就简单地继承它的基类的virtual函数的实现。

通过基类指针或者引用调用虚virtual函数

如果程序通过指向派生类对象的基类指针(shapePtr->draw())或者指向派生类对象的基类引用(shapeRef.draw())调用virtual函数,那么程序会根据所指对象的类型而不是指针类型,动态(即执行时)选择正确的派生类draw函数
在执行时选择合适的调用函数称为动态绑定或迟绑定。

通过对象名称调用虚virtual函数

virtual函数通过按名引用特定对象和使用圆点成员选择运算符的方式(squareObject.draw())被调用时,调用哪个函数在编译时就已经决定了(称为静态绑定),所以调用的virtual函数正是为该特定对象所属的类定义的(或继承而来的)函数,这并不是多态性行为。
因此,使用virtual函数进行动态绑定只能通过指针(以及即将看到的引用)句柄完成

CommissionEmployee层次中的virtual函数

// Fig. 12.4: CommissionEmployee.h
// CommissionEmployee class header declares earnings and print as virtual.
#ifndef COMMISSION_H
#define COMMISSION_H

#include <string> // C++ standard string class

class CommissionEmployee
{
public:
   CommissionEmployee( const std::string &, const std::string &, 
      const std::string &, double = 0.0, double = 0.0 );
   
   void setFirstName( const std::string & ); // set first name
   std::string getFirstName() const; // return first name

   void setLastName( const std::string & ); // set last name
   std::string getLastName() const; // return last name

   void setSocialSecurityNumber( const std::string & ); // set SSN
   std::string getSocialSecurityNumber() const; // return SSN

   void setGrossSales( double ); // set gross sales amount
   double getGrossSales() const; // return gross sales amount

   void setCommissionRate( double ); // set commission rate
   double getCommissionRate() const; // return commission rate

   virtual double earnings() const; // calculate earnings
   virtual void print() const; // print object
private:
   std::string firstName;
   std::string lastName;
   std::string socialSecurityNumber;
   double grossSales; // gross weekly sales
   double commissionRate; // commission percentage
}; // end class CommissionEmployee

#endif
// Fig. 12.5: BasePlusCommissionEmployee.h
// BasePlusCommissionEmployee class derived from class 
// CommissionEmployee.
#ifndef BASEPLUS_H
#define BASEPLUS_H

#include <string> // C++ standard string class
#include "CommissionEmployee.h" // CommissionEmployee class declaration

class BasePlusCommissionEmployee : public CommissionEmployee
{
public:
   BasePlusCommissionEmployee( const std::string &, const std::string &, 
      const std::string &, double = 0.0, double = 0.0, double = 0.0 );
   
   void setBaseSalary( double ); // set base salary
   double getBaseSalary() const; // return base salary

   virtual double earnings() const override; // calculate earnings
   virtual void print() const override; // print object
private:
   double baseSalary; // base salary
}; // end class BasePlusCommissionEmployee

#endif
/ Fig. 12.6: fig12_06.cpp
// Introducing polymorphism, virtual functions and dynamic binding.
#include <iostream>
#include <iomanip>
#include "CommissionEmployee.h"
#include "BasePlusCommissionEmployee.h" 
using namespace std;

int main()
{
   // create base-class object
   CommissionEmployee commissionEmployee( 
      "Sue", "Jones", "222-22-2222", 10000, .06 );

   // create base-class pointer
   CommissionEmployee *commissionEmployeePtr = nullptr;

   // create derived-class object
   BasePlusCommissionEmployee basePlusCommissionEmployee(
      "Bob", "Lewis", "333-33-3333", 5000, .04, 300 );

   // create derived-class pointer
   BasePlusCommissionEmployee *basePlusCommissionEmployeePtr = nullptr;

   // set floating-point output formatting
   cout << fixed << setprecision( 2 );

   // output objects using static binding
   cout << "Invoking print function on base-class and derived-class "
      << "\nobjects with static binding\n\n";
   commissionEmployee.print(); // static binding
   cout << "\n\n";
   basePlusCommissionEmployee.print(); // static binding
   
   // output objects using dynamic binding
   cout << "\n\n\nInvoking print function on base-class and "
      << "derived-class \nobjects with dynamic binding";

   // aim base-class pointer at base-class object and print
   commissionEmployeePtr = &commissionEmployee;
   cout << "\n\nCalling virtual function print with base-class pointer"
      << "\nto base-class object invokes base-class "
      << "print function:\n\n";
   commissionEmployeePtr->print(); // invokes base-class print

   // aim derived-class pointer at derived-class object and print
   basePlusCommissionEmployeePtr = &basePlusCommissionEmployee;
   cout << "\n\nCalling virtual function print with derived-class "
      << "pointer\nto derived-class object invokes derived-class "
      << "print function:\n\n";
   basePlusCommissionEmployeePtr->print(); // invokes derived-class print

   // aim base-class pointer at derived-class object and print
   commissionEmployeePtr = &basePlusCommissionEmployee;
   cout << "\n\nCalling virtual function print with base-class pointer"
      << "\nto derived-class object invokes derived-class "
      << "print function:\n\n";

   // polymorphism; invokes BasePlusCommissionEmployee's print;
   // base-class pointer to derived-class object
   commissionEmployeePtr->print();
   cout << endl;
} // end main

相同的消息发送给(通过基类指针)不同的派生类对象时,将会呈现出“多种形式”,这就是多态性的行为

virtual析构函数

如果要删除一个具有非虚析构函数的派生类对象,却显式地通过指向该对象的一个基类指针对它应用delete运算符,那么C++标准会指出这一行为未定义。
解决方法:在基类中创建virtual析构函数。如果基类析构函数声明为virtual,那么任何派生类的析构函数都是virtual并重写基类的析构函数。
如果对一个基类指针用delete运算符来显式地删除它所指的类层次中的某个对象,那么系统会根据该指针所指对象调用相应类的析构函数。

当一个派生类对象被销毁时,派生类对象中属于基类的部分也会被销毁,因此执行派生类和基类的析构函数是很重要的。基类的析构函数在派生类的析构函数执行之后自动执行。最顶层基类的析构函数最后运行。
我们在所有包括虚函数的类中都包含了virtual析构函数。

如果一个类含有virtual函数,该类就要提供一个virtual析构函数,即使该析构函数并不一定是该类需要的。这可以保证当一个派生类的对象通过基类指针删除时,这个自定义的派生类析构函数(如果存在的话)会被调用。

构造函数不能是virtual函数。

C++ 11:Final成员函数和类

C++ 11之前,派生类可以覆盖基类的任何virtual函数。在C++ 11中,基类的virtual函数在原型中声明为final,如:

	virtual someFunction(parameters) final;

那么,该函数在任何派生类中都不能被覆盖。这保证了基类final成员函数定义被所有基类对象和所有基类直接、非直接派生类的对象使用。

同样,C++ 11之前,任何现有类在层次上可被用为基类,而在C++ 11中,可以将类声明为final以防被用作基类。

	class MyClass final
	{
	
	};

试图重写一个final函数或者继承一个final基类会导致编译错误。

12.4 类型与和switch语句

判断加入到大型程序中的对象类型的一种方式是使用switch语句检查对象的域值。
然而,使用switch逻辑容易导致程序产生各种潜在的问题。例如,程序员忘记必要的类型检查或忘记在switch语句中检查所有可能的情况。当对于一个基于switch语句的系统通过添加新类型的方式进行修改时,程序员可能会忘记在所有相关的switch语句中插入这些新的类型。

多态性程序设计可以消除不必要的switch逻辑。

12.5 抽象类和纯virtual函数

抽象类通常作为基类,所以称它们为抽象基类。抽象基类不能实例化任何对象。
具体类:用来实例化对象的类。

纯虚函数

通过声明类的一个或者多个virtual函数为纯virtual函数,可以使一个类成为抽象类。一个纯virtual函数是在声明时“初始化值为0”的函数,如下所示:

	virtual void draw() conse = 0;

“=0”称为纯指示符。
virtual函数不提供函数的具体实现。
每个派生类的具体类必须重写所有基类的纯virtual函数的定义,提供这些函数的具体实现。

virtual函数和纯virtual函数之间的区别是:virtual函数有函数的实现,并且提供派生类是否重写这些函数的选择权。相反,纯virtual函数并不提供函数的实现,需要派生类重写这些函数以使派生类称为具体类,否则派生类仍然是抽象类。

抽象类为类层次结构中的各种类定义公共的通用接口。抽象类包含一个或多个纯virtual函数,这些函数必须在具体的派生类中重写。

未能在派生类中重写纯virtual函数会使得派生类也变成抽象的。试图实例化抽象类的对象将导致编译错误。

抽象类也可以有数据成员和具体的函数(包括构造函数和析构函数),它们被派生类继承时都符合继承的一般规则。

虽然我们不能实例化抽象基类的对象,但是可以使用抽象基类来声明指向抽象类派生出的具体类对象的指针和引用。这样的指针和引用可以用来对实例化的具体派生类的对象进行多态性的操作。

设备驱动与多态性

多态性对于实现分层的软件系统特别有效。
一个面向对象的操作系统可能会使用抽象基类为所有设备驱动程序提供一个合适的接口。然后,通过对抽象基类的继承生成所有操作都类似的派生类。

12.6 实例研究:应用多态性的工资发放系统

12.6.1 创建抽象基类:Employee类

Employee类的头文件

// Fig. 12.9: Employee.h
// Employee abstract base class.
#ifndef EMPLOYEE_H
#define EMPLOYEE_H

#include <string> // C++ standard string class

class Employee 
{
public:
   Employee( const std::string &, const std::string &, 
      const std::string & );
   virtual ~Employee() { } // virtual destructor

   void setFirstName( const std::string & ); // set first name
   std::string getFirstName() const; // return first name

   void setLastName( const std::string & ); // set last name
   std::string getLastName() const; // return last name

   void setSocialSecurityNumber( const std::string & ); // set SSN
   std::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:
   std::string firstName;
   std::string lastName;
   std::string socialSecurityNumber;
}; // end class Employee

#endif // EMPLOYEE_H

earnings函数声明为纯virtual函数,表明每个具体的派生类必须提供一个适当的earnings函数的实现,并且程序可以利用基类Employee指针根据雇员的不同类型来多态地调用earnings函数。

Employee类成员函数定义

没有为纯virtual函数earnings提供任何实现代码。

// Fig. 12.10: Employee.cpp
// Abstract-base-class Employee member-function definitions.
// Note: No definitions are given for pure virtual functions.
#include <iostream>
#include "Employee.h" // Employee class definition
using namespace std;

// 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 (virtual, but not pure virtual)
void Employee::print() const
{ 
   cout << getFirstName() << ' ' << getLastName() 
      << "\nsocial security number: " << getSocialSecurityNumber(); 
} // end function print

virtual函数print提供的实现会在每个派生类中被重写。可是,这些print函数都将使用这个抽象类中print函数的版本,输出Employee类层次结构中所有类共有的信息。

12.6.2 创建具体的派生类:SalariedEmployee类

SalariedEmployee类的头文件

// Fig. 12.11: SalariedEmployee.h
// SalariedEmployee class derived from Employee.
#ifndef SALARIED_H
#define SALARIED_H

#include <string> // C++ standard string class
#include "Employee.h" // Employee class definition

class SalariedEmployee : public Employee 
{
public:
   SalariedEmployee( const std::string &, const std::string &, 
      const std::string &, double = 0.0 );
   virtual ~SalariedEmployee() { } // virtual destructor

   void setWeeklySalary( double ); // set weekly salary
   double getWeeklySalary() const; // return weekly salary

   // keyword virtual signals intent to override
   virtual double earnings() const override; // calculate earnings
   virtual void print() const override; // print object
private:
   double weeklySalary; // salary per week
}; // end class SalariedEmployee

#endif // SALARIED_H

SalariedEmployee类成员函数的定义

// Fig. 12.12: SalariedEmployee.cpp
// SalariedEmployee class member-function definitions.
#include <iostream>
#include <stdexcept>
#include "SalariedEmployee.h" // SalariedEmployee class definition
using namespace std;

// constructor 
SalariedEmployee::SalariedEmployee( const string &first, 
   const string &last, const string &ssn, double salary )
   : Employee( first, last, ssn )
{ 
   setWeeklySalary( salary ); 
} // end SalariedEmployee constructor

// set salary
void SalariedEmployee::setWeeklySalary( double salary )
{ 
   if ( salary >= 0.0 )
      weeklySalary = salary;
   else
      throw invalid_argument( "Weekly salary must be >= 0.0" );
} // end function setWeeklySalary

// return salary
double SalariedEmployee::getWeeklySalary() const
{
   return weeklySalary;
} // end function getWeeklySalary

// calculate earnings; 
// override pure virtual function earnings in Employee
double SalariedEmployee::earnings() const 
{ 
   return getWeeklySalary(); 
} // end function earnings

// print SalariedEmployee's information 
void SalariedEmployee::print() const
{
   cout << "salaried employee: ";
   Employee::print(); // reuse abstract base-class print function
   cout << "\nweekly salary: " << getWeeklySalary();
} // end function print

12.6.3 创建具体的派生类:CommissionEmployee类

CommissionEmployee类的头文件

// Fig. 12.13: CommissionEmployee.h
// CommissionEmployee class derived from Employee.
#ifndef COMMISSION_H
#define COMMISSION_H

#include <string> // C++ standard string class
#include "Employee.h" // Employee class definition

class CommissionEmployee : public Employee 
{
public:
   CommissionEmployee( const std::string &, const std::string &,
      const std::string &, double = 0.0, double = 0.0 );
   virtual ~CommissionEmployee() { } // virtual destructor

   void setCommissionRate( double ); // set commission rate
   double getCommissionRate() const; // return commission rate

   void setGrossSales( double ); // set gross sales amount
   double getGrossSales() const; // return gross sales amount

   // keyword virtual signals intent to override
   virtual double earnings() const override; // calculate earnings
   virtual void print() const override; // print object
private:
   double grossSales; // gross weekly sales
   double commissionRate; // commission percentage
}; // end class CommissionEmployee

#endif // COMMISSION_H

CommissionEmployee类成员函数定义

// Fig. 12.14: CommissionEmployee.cpp
// CommissionEmployee class member-function definitions.
#include <iostream>
#include <stdexcept>
#include "CommissionEmployee.h" // CommissionEmployee class definition
using namespace std;

// constructor 
CommissionEmployee::CommissionEmployee( const string &first, 
   const string &last, const string &ssn, double sales, double rate )
   : Employee( first, last, ssn )  
{
   setGrossSales( sales );
   setCommissionRate( rate );
} // end CommissionEmployee constructor

// set gross sales amount
void CommissionEmployee::setGrossSales( double sales ) 
{ 
   if ( sales >= 0.0 )
      grossSales = sales;
   else
      throw invalid_argument( "Gross sales must be >= 0.0" );
} // end function setGrossSales

// return gross sales amount
double CommissionEmployee::getGrossSales() const
{
    return grossSales;
} // end function getGrossSales

// set commission rate
void CommissionEmployee::setCommissionRate( double rate )
{ 
   if ( rate > 0.0 && rate < 1.0 )
      commissionRate = rate;
   else
      throw invalid_argument( "Commission rate must be > 0.0 and < 1.0" );
} // end function setCommissionRate

// return commission rate
double CommissionEmployee::getCommissionRate() const
{
    return commissionRate;
} // end function getCommissionRate

// calculate earnings; override pure virtual function earnings in Employee
double CommissionEmployee::earnings() const
{ 
   return getCommissionRate() * getGrossSales(); 
} // end function earnings

// print CommissionEmployee's information 
void CommissionEmployee::print() const
{
   cout << "commission employee: ";
   Employee::print(); // code reuse
   cout << "\ngross sales: " << getGrossSales() 
      << "; commission rate: " << getCommissionRate();
} // end function print

12.6.4 创建间接派生的具体类:BasePlusCommissionEmployee类

BasePlusCommissionEmployee类的头文件

// Fig. 12.15: BasePlusCommissionEmployee.h
// BasePlusCommissionEmployee class derived from CommissionEmployee.
#ifndef BASEPLUS_H
#define BASEPLUS_H

#include <string> // C++ standard string class
#include "CommissionEmployee.h" // CommissionEmployee class definition

class BasePlusCommissionEmployee : public CommissionEmployee 
{
public:
   BasePlusCommissionEmployee( const std::string &, const std::string &,
      const std::string &, double = 0.0, double = 0.0, double = 0.0 );
   virtual ~BasePlusCommissionEmployee() { } // virtual destructor

   void setBaseSalary( double ); // set base salary
   double getBaseSalary() const; // return base salary

   // keyword virtual signals intent to override
   virtual double earnings() const override; // calculate earnings
   virtual void print() const override; // print object
private:
   double baseSalary; // base salary per week
}; // end class BasePlusCommissionEmployee

#endif // BASEPLUS_H

BasePlusCommissionEmployee类成员函数定义

// Fig. 12.16: BasePlusCommissionEmployee.cpp
// BasePlusCommissionEmployee member-function definitions.
#include <iostream>
#include <stdexcept>
#include "BasePlusCommissionEmployee.h"
using namespace std;

// constructor 
BasePlusCommissionEmployee::BasePlusCommissionEmployee( 
   const string &first, const string &last, const string &ssn, 
   double sales, double rate, double salary )
   : CommissionEmployee( first, last, ssn, sales, rate )  
{
   setBaseSalary( salary ); // validate and store base salary
} // end BasePlusCommissionEmployee constructor

// set base salary
void BasePlusCommissionEmployee::setBaseSalary( double salary )
{ 
   if ( salary >= 0.0 )                                        
      baseSalary = salary;                                     
   else                                                        
      throw invalid_argument( "Salary must be >= 0.0" );       
} // end function setBaseSalary

// return base salary
double BasePlusCommissionEmployee::getBaseSalary() const
{ 
    return baseSalary; 
} // end function getBaseSalary

// calculate earnings;
// override virtual function earnings in CommissionEmployee
double BasePlusCommissionEmployee::earnings() const
{ 
    return getBaseSalary() + CommissionEmployee::earnings(); 
} // end function earnings

// print BasePlusCommissionEmployee's information 
void BasePlusCommissionEmployee::print() const
{
   cout << "base-salaried ";
   CommissionEmployee::print(); // code reuse
   cout << "; base salary: " << getBaseSalary();
} // end function print

12.6.5 演示多态性的执行过程

静态绑定:使用名称句柄(不是可以在执行时设置的指针或引用),编译器可以识别每个对象的类型,从而决定应该调用哪个printearnings函数。

// Fig. 12.17: fig12_17.cpp
// Processing Employee derived-class objects individually 
// and polymorphically using dynamic binding.
#include <iostream>
#include <iomanip>
#include <vector>
#include "Employee.h"
#include "SalariedEmployee.h" 
#include "CommissionEmployee.h"  
#include "BasePlusCommissionEmployee.h" 
using namespace std;

void virtualViaPointer( const Employee * const ); // prototype
void virtualViaReference( const Employee & ); // prototype

int main()
{
   // set floating-point output formatting
   cout << fixed << setprecision( 2 );

   // create derived-class objects
   SalariedEmployee salariedEmployee( 
      "John", "Smith", "111-11-1111", 800 );
   CommissionEmployee commissionEmployee( 
      "Sue", "Jones", "333-33-3333", 10000, .06 );
   BasePlusCommissionEmployee basePlusCommissionEmployee( 
      "Bob", "Lewis", "444-44-4444", 5000, .04, 300 );
   
   cout << "Employees processed individually using static binding:\n\n";

   // output each Employee抯 information and earnings using static binding
   salariedEmployee.print();
   cout << "\nearned $" << salariedEmployee.earnings() << "\n\n";
   commissionEmployee.print();
   cout << "\nearned $" << commissionEmployee.earnings() << "\n\n";
   basePlusCommissionEmployee.print();
   cout << "\nearned $" << basePlusCommissionEmployee.earnings() 
      << "\n\n";

   // create vector of three base-class pointers
   vector < Employee * > employees( 3 );

   // initialize vector with Employees
   employees[ 0 ] = &salariedEmployee;
   employees[ 1 ] = &commissionEmployee;
   employees[ 2 ] = &basePlusCommissionEmployee;

   cout << "Employees processed polymorphically via dynamic binding:\n\n";

   // call virtualViaPointer to print each Employee's information
   // and earnings using dynamic binding
   cout << "Virtual function calls made off base-class pointers:\n\n";

   for ( const Employee *employeePtr : employees )
      virtualViaPointer( employeePtr );

   // call virtualViaReference to print each Employee's information 
   // and earnings using dynamic binding
   cout << "Virtual function calls made off base-class references:\n\n";

   for ( const Employee *employeePtr : employees )
      virtualViaReference( *employeePtr ); // note dereferencing
} // end main

// call Employee virtual functions print and earnings off a 
// base-class pointer using dynamic binding
void virtualViaPointer( const Employee * const baseClassPtr )
{
   baseClassPtr->print();
   cout << "\nearned $" << baseClassPtr->earnings() << "\n\n";
} // end function virtualViaPointer

// call Employee virtual functions print and earnings off a 
// base-class reference using dynamic binding
void virtualViaReference( const Employee &baseClassRef )
{
   baseClassRef.print();
   cout << "\nearned $" << baseClassRef.earnings() << "\n\n";
} // end function virtualViaReference

创建vector对象employees,它包含了三个Employee类指针。编译器允许这样赋值,因为每个SalariedEmployee对象、 CommissionEmployee对象和BasePlusCommissionEmployee对象都是Employee对象。因此可以把对象SalariedEmployeeCommissionEmployeeBasePlusCommissionEmployee的地址赋给基类Employee的指针(尽管Employee是一个抽象类)。

for语句循环遍历vector对象employees
virtualViaPointer函数用参数baseClassPtr接收保存在employees元素中的地址。每次对virtualViaPointer函数的调用都利用baseClassPtr调用virtual函数printearnings
virtualViaReference函数用参数baseClassRef(类型是const Employee &)接收间接引用存储在每个employees元素中的指针后形成的引用。而对virtualViaReference函数的每次调用,都会通过引用baseClassRef调用virtual函数printearnings

采用动态绑定方式时,在运行时决定调用哪个类的print函数和earnings函数。
使用基类引用和使用基类指针产生了同样的输出结果。

12.7 (选读)多态性、virtual函数和动态绑定的底层实现机制

继承具体的虚函数

如果BasePlusCommissionEmployee类没有重写earnings函数,那么BasePlusCommissionEmployee类的virtual函数表中的earnings函数指针和CommissionEmployee类的virtual函数表中的earnings函数指针都指向同一个earnings函数。

对于大多数应用来说,多态性的开销是可以接受的。但在某些情况下(比如性能要求很高的实时应用程序),多态性的开销可能就太高了。

12.8 实例研究:应用向下强制类型转换、dynamic_cast、typeid和type_info并使用多态性和运行时类型信息的工资发放系统

这一节演示了运行时类型信息(RTTI)和动态强制类型转换的强大功能,它们使程序在运行时能够判定对象的类型,从而对对象进行操作。

// Fig. 12.19: fig12_19.cpp
// Demonstrating downcasting and runtime type information.
// NOTE: You may need to enable RTTI on your compiler
// before you can compile this application.
#include <iostream>
#include <iomanip>
#include <vector>
#include <typeinfo>
#include "Employee.h"
#include "SalariedEmployee.h" 
#include "CommissionEmployee.h"  
#include "BasePlusCommissionEmployee.h" 
using namespace std;

int main()
{
   // set floating-point output formatting
   cout << fixed << setprecision( 2 );   
   
   // create vector of three base-class pointers
   vector < Employee * > employees( 3 );

   // initialize vector with various kinds of Employees
   employees[ 0 ] = new SalariedEmployee( 
      "John", "Smith", "111-11-1111", 800 );
   employees[ 1 ] = new CommissionEmployee( 
      "Sue", "Jones", "333-33-3333", 10000, .06 );
   employees[ 2 ] = new BasePlusCommissionEmployee( 
      "Bob", "Lewis", "444-44-4444", 5000, .04, 300 );

   // polymorphically process each element in vector employees
   for ( Employee *employeePtr : employees ) 
   {
      employeePtr->print(); // output employee information
      cout << endl;

      // downcast pointer
      BasePlusCommissionEmployee *derivedPtr =
         dynamic_cast < BasePlusCommissionEmployee * >( employeePtr );

      // determine whether element points to a BasePlusCommissionEmployee 
      if ( derivedPtr != nullptr ) // true for "is a" relationship
      {
         double oldBaseSalary = derivedPtr->getBaseSalary();
         cout << "old base salary: $" << oldBaseSalary << endl;
         derivedPtr->setBaseSalary( 1.10 * oldBaseSalary );
         cout << "new base salary with 10% increase is: $" 
            << derivedPtr->getBaseSalary() << endl;
      } // end if
      
      cout << "earned $" << employeePtr->earnings() << "\n\n";
   } // end for   
 
   // release objects pointed to by vector抯 elements
   for ( const Employee *employeePtr : employees ) 
   {
      // output class name
      cout << "deleting object of " 
         << typeid( *employeePtr ).name() << endl;

      delete employeePtr;
   } // end for
} // end main

使用dynamic_cast决定对象类型

向下强制类型转换运算:使用运算符dynamic_cast决定每个所属的类型是不是BasePlusCommissionEmployee

// downcast pointer
      BasePlusCommissionEmployee *derivedPtr =
         dynamic_cast < BasePlusCommissionEmployee * >( employeePtr );

动态地把employeePtr从类型Employee *向下强制转换为类型BasePlusCommissionEmployee *
如果该employeePtr所指向的对象是一个BasePlusCommissionEmployee对象,那么这个对象的地址就赋值给派生类指针derivedPtr;否则,employeePtr赋值为nullPtr

使用dynamic_cast来进行基对象的类型检查,而static_cast仅仅将Employee *转换为BasePlusCommissionEmployee *,无论潜在对象是什么类型。

展示雇员类型

使用for循环显示了每个雇员对象的类型,并使用delete运算符释放每个vector元素所指向的动态分配的内存。

运算符typeid返回一个type_info类对象的引用,包含了包括操作数类型名称在内的关于该运算符操作数类型的信息。调用时,type_info类的成员函数name返回一个基于指针的字符串,它包含传递给typeid实参的类型名称(例如“class BasePlusCommissionEmployee”)。
要使用typeid,程序必须包含头文件<type_info>

练习题

12.15 工资发放系统修正

基类:雇员Employee

同本章Fig. 12.9: Employee.h和Fig. 12.10: Employee.cpp

派生类:计件雇员PieceWorker

#ifndef PIECE_H
#define PIECE_H

#include <string>
#include "Employee.h"

class PieceWorker : public Employee
{
public:
	PieceWorker(const std::string &, const std::string &, const std::string &,
		double = 0.0, double = 0.0);
	virtual ~PieceWorker(){}

	void setWage(double);
	double getWage() const;
	void setPieces(double);
	double getPieces() const;

	virtual double earnings() const override;
	virtual void print() const override;

private:
	double wage;
	double pieces;
};
#endif // !PIECE_H

#include <iostream>
//#include <stdexcept>

#include "PieceWorker.h"
using namespace std;

PieceWorker::PieceWorker(const std::string &first, const std::string &last, const std::string &ssn,
	double wageNum, double piecesNum)
	:Employee(first, last, ssn)
{
	setWage(wageNum);
	setPieces(piecesNum);
}

void PieceWorker::setWage(double wageNum)
{
	wage = wageNum;
}
double PieceWorker::getWage() const
{
	return wage;
}
void PieceWorker::setPieces(double piecesNum)
{
	pieces = piecesNum;
}
double PieceWorker::getPieces() const
{
	return pieces;
}

double PieceWorker::earnings() const
{
	return getWage() * getPieces();
}
void PieceWorker::print() const
{
	cout << "PieceWorker:";
	Employee::print();
	cout << "\nWage: " << getWage() << ";Pieces: " << getPieces();
}

派生类:计时雇员HourlyWorker

#ifndef HOUR_H
#define HOUR_H

#include <string>
#include "Employee.h"

class HourlyWorker : public Employee
{
public:
	HourlyWorker(const std::string &, const std::string &, const std::string &,
		double = 0.0, double = 0.0);
	virtual ~HourlyWorker() {}

	void setWage(double);
	double getWage() const;
	void setHours(double);
	double getHours() const;

	virtual double earnings() const override;
	virtual void print() const override;

private:
	double wage;
	double hours;
};
#endif // !HOUR_H

#include <iostream>
//#include <stdexcept>

#include "HourlyWorker.h"
using namespace std;

HourlyWorker::HourlyWorker(const std::string &first, const std::string &last, const std::string &ssn,
	double wageNum, double hoursNum)
	:Employee(first, last, ssn)
{
	setWage(wageNum);
	setHours(hoursNum);
}

void HourlyWorker::setWage(double wageNum)
{
	wage = wageNum;
}
double HourlyWorker::getWage() const
{
	return wage;
}
void HourlyWorker::setHours(double hoursNum)
{
	hours = hoursNum;
}
double HourlyWorker::getHours() const
{
	return hours;
}

double HourlyWorker::earnings() const
{
	if( hours < 40)
		return getWage() * getHours();
	else 
		return getWage() * getHours() + 0.5 * getWage() * (getHours()-40);
	
}
void HourlyWorker::print() const
{
	cout << "PieceWorker:";
	Employee::print();
	cout << "\nWage: " << getWage() << ";Pieces: " << getHours();
}

测试代码

#include <iostream>
#include <iomanip>
#include <vector>
#include "Employee.h"
#include "PieceWorker.h"
#include "HourlyWorker.h"

using namespace std;

void virtualViaPointer(const Employee * const); // prototype
void virtualViaReference(const Employee &); // prototype

int main()
{
	// set floating-point output formatting
	cout << fixed << setprecision(2);

	// create derived-class objects	
	PieceWorker pieceWorker(
		"Sue", "Jones", "333-33-3333", .06, 10000);
	HourlyWorker hourlyEmployee(
		"John", "Smith", "111-11-1111", 15, 30);
	HourlyWorker hourlyEmployeePlus(
		"Bob", "Lewis", "444-44-4444", 15, 50);

	cout << "Employees processed individually using static binding:\n\n";

	// output each Employee's information and earnings using static binding
	pieceWorker.print();
	cout << "\nearned $" << pieceWorker.earnings() << "\n\n";
	hourlyEmployee.print();
	cout << "\nearned $" << hourlyEmployee.earnings() << "\n\n";
	hourlyEmployeePlus.print();
	cout << "\nearned $" << hourlyEmployeePlus.earnings()<< "\n\n";

	// create vector of three base-class pointers
	vector < Employee * > employees(3);

	// initialize vector with Employees
	employees[0] = &pieceWorker;
	employees[1] = &hourlyEmployee;
	employees[2] = &hourlyEmployeePlus;

	cout << "Employees processed polymorphically via dynamic binding:\n\n";

	// call virtualViaPointer to print each Employee's information
	// and earnings using dynamic binding
	cout << "Virtual function calls made off base-class pointers:\n\n";

	for (const Employee *employeePtr : employees)
		virtualViaPointer(employeePtr);

	// call virtualViaReference to print each Employee's information 
    // and earnings using dynamic binding
	cout << "Virtual function calls made off base-class references:\n\n";

	for (const Employee *employeePtr : employees)
		virtualViaReference(*employeePtr); // note dereferencing
} // end main

// call Employee virtual functions print and earnings off a 
// base-class pointer using dynamic binding
void virtualViaPointer(const Employee * const baseClassPtr)
{
	baseClassPtr->print();
	cout << "\nearned $" << baseClassPtr->earnings() << "\n\n";
} // end function virtualViaPointer

// call Employee virtual functions print and earnings off a 
// base-class reference using dynamic binding
void virtualViaReference(const Employee &baseClassRef)
{
	baseClassRef.print();
	cout << "\nearned $" << baseClassRef.earnings() << "\n\n";
} // end function virtualViaReference

代码效果

Employees processed individually using static binding:

PieceWorker:Sue Jones
social security number: 333-33-3333
Wage: 0.06;Pieces: 10000.00
earned $600.00

PieceWorker:John Smith
social security number: 111-11-1111
Wage: 15.00;Pieces: 30.00
earned $450.00

PieceWorker:Bob Lewis
social security number: 444-44-4444
Wage: 15.00;Pieces: 50.00
earned $825.00

Employees processed polymorphically via dynamic binding:

Virtual function calls made off base-class pointers:

PieceWorker:Sue Jones
social security number: 333-33-3333
Wage: 0.06;Pieces: 10000.00
earned $600.00

PieceWorker:John Smith
social security number: 111-11-1111
Wage: 15.00;Pieces: 30.00
earned $450.00

PieceWorker:Bob Lewis
social security number: 444-44-4444
Wage: 15.00;Pieces: 50.00
earned $825.00

Virtual function calls made off base-class references:

PieceWorker:Sue Jones
social security number: 333-33-3333
Wage: 0.06;Pieces: 10000.00
earned $600.00

PieceWorker:John Smith
social security number: 111-11-1111
Wage: 15.00;Pieces: 30.00
earned $450.00

PieceWorker:Bob Lewis
social security number: 444-44-4444
Wage: 15.00;Pieces: 50.00
earned $825.00

结语

第12章已全部更完。

不知道要不要继续学完C++,还是转硬件。

个人水平有限,有问题欢迎各位大神批评指正!

;