文章目录
- 0. 前言
- 12.1 简介
- 12.2 多态性介绍:多态视频游戏
- 12.3 类继承层次中对象之间的关系
- 12.4 类型与和switch语句
- 12.5 抽象类和纯virtual函数
- 12.6 实例研究:应用多态性的工资发放系统
- 12.7 (选读)多态性、virtual函数和动态绑定的底层实现机制
- 12.8 实例研究:应用向下强制类型转换、dynamic_cast、typeid和type_info并使用多态性和运行时类型信息的工资发放系统
- 练习题
- 结语
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 演示多态性的执行过程
静态绑定:使用名称句柄(不是可以在执行时设置的指针或引用),编译器可以识别每个对象的类型,从而决定应该调用哪个print
和earnings
函数。
// 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
对象。因此可以把对象SalariedEmployee
、 CommissionEmployee
和BasePlusCommissionEmployee
的地址赋给基类Employee
的指针(尽管Employee
是一个抽象类)。
for
语句循环遍历vector
对象employees
。
virtualViaPointer
函数用参数baseClassPtr
接收保存在employees
元素中的地址。每次对virtualViaPointer
函数的调用都利用baseClassPtr
调用virtual
函数print
和earnings
。
virtualViaReference
函数用参数baseClassRef
(类型是const Employee &
)接收间接引用存储在每个employees
元素中的指针后形成的引用。而对virtualViaReference
函数的每次调用,都会通过引用baseClassRef
调用virtual
函数print
和earnings
。
采用动态绑定方式时,在运行时决定调用哪个类的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++,还是转硬件。
个人水平有限,有问题欢迎各位大神批评指正!