C++面向对象编程中的多态性深入解析
1. 多态处理的演示
在测试员工层次结构时,会创建
SalariedEmployee
、
CommissionEmployee
和
BasePlusCommissionEmployee
这三个具体类的对象。程序首先使用静态绑定来操作这些对象,然后使用
Employee
指针的向量进行多态处理。
以下是相关代码示例:
// Fig. 20.17: fig20_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’s 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";
cout << "Employees processed polymorphically via dynamic binding:\n\n";
// create vector of three base-class pointers
vector< Employee * > employees( 3 );
// initialize vector with pointers to Employees
employees[ 0 ] = &salariedEmployee;
employees[ 1 ] = &commissionEmployee;
employees[ 2 ] = &basePlusCommissionEmployee;
cout << "Virtual function calls made off base-class pointers:\n\n";
for ( const Employee *employeePtr : employees )
virtualViaPointer( employeePtr );
cout << "Virtual function calls made off base-class references:\n\n";
for ( const Employee *employeePtr : employees )
virtualViaReference( *employeePtr ); // note dereferencing
return 0;
}
// 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";
}
// 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";
}
在上述代码中,静态绑定的函数调用在编译时就确定了要调用的函数,因为使用的是对象名而不是指针或引用。而多态处理则通过
Employee
指针的向量来实现,在运行时根据指针实际指向的对象类型来调用相应的函数。
2. 多态、虚函数和动态绑定的底层实现
C++在编译包含一个或多个虚函数的类时,会为该类构建一个虚函数表(vtable)。vtable 包含指向类的虚函数的指针,程序在调用虚函数时会使用 vtable 来选择合适的函数实现。
以下是不同类的 vtable 情况:
| 类名 | 虚函数 1(earnings) | 虚函数 2(print) |
| ---- | ---- | ---- |
| Employee | 0(纯虚函数) | 指向显示员工全名和社保号的函数 |
| SalariedEmployee | 指向返回员工周薪的函数 | 指向显示员工类型、姓名、社保号和周薪的函数 |
| CommissionEmployee | 指向返回员工销售提成的函数 | 指向显示员工类型、姓名、社保号、提成率和销售总额的函数 |
| BasePlusCommissionEmployee | 指向返回员工基本工资加销售提成的函数 | 指向显示员工类型、姓名、社保号、提成率、销售总额和基本工资的函数 |
多态的实现通过三个层次的指针:
1.
vtable 中的函数指针
:指向虚函数实际执行的代码。
2.
对象的 vtable 指针
:当创建包含虚函数的类的对象时,编译器会为对象附加一个指向该类 vtable 的指针。
3.
接收虚函数调用的对象句柄
:如
Employee
指针或引用。
以
baseClassPtr->print()
调用为例,其执行流程如下:
graph TD;
A[选择 employees 中的元素并传递给 virtualViaPointer] --> B[解引用指针得到对象];
B --> C[解引用对象的 vtable 指针得到 vtable];
C --> D[跳过偏移量选择 print 函数指针];
D --> E[解引用 print 函数指针并执行函数];
3. 运行时类型信息和动态类型转换
在某些情况下,需要在运行时确定对象的具体类型并进行相应操作。例如,为
BasePlusCommissionEmployee
员工增加 10% 的基本工资。
以下是相关代码示例:
// Fig. 20.19: fig20_19.cpp
// Demonstrating downcasting and runtime type information.
#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;
// determine whether element points to a BasePlusCommissionEmployee
BasePlusCommissionEmployee *derivedPtr =
dynamic_cast < BasePlusCommissionEmployee * >( employeePtr );
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;
}
cout << "earned $" << employeePtr->earnings() << "\n\n";
}
// release objects pointed to by vector’s elements
for ( const Employee *employeePtr : employees )
{
cout << "deleting object of "
<< typeid( *employeePtr ).name() << endl;
delete employeePtr;
}
return 0;
}
在上述代码中,使用
dynamic_cast
来判断当前
Employee
指针是否指向
BasePlusCommissionEmployee
对象,如果是则进行相应的工资调整。同时,使用
typeid
来获取对象的类型信息。
通过使用
dynamic_cast
,可以避免一些编译错误。如果直接将基类指针赋值给派生类指针,会导致编译错误,因为 C++ 不允许这种操作,而
dynamic_cast
会进行类型检查。
4. 性能考量与优化建议
在使用多态性、虚函数和动态绑定的过程中,性能是一个需要考虑的重要因素。虽然这些特性为编程带来了很大的灵活性,但也会引入一定的开销。
4.1 性能开销分析
- 时间开销 :每次调用虚函数时,程序需要通过虚函数表(vtable)来查找实际要执行的函数,这涉及到指针的解引用和内存访问操作,会增加额外的执行时间。特别是在频繁调用虚函数的场景下,这种开销会更加明显。
- 空间开销 :每个包含虚函数的类都有一个虚函数表,并且每个该类的对象都需要一个指向虚函数表的指针,这会增加内存的使用量。
4.2 优化建议
- 合理使用虚函数 :在设计类时,只将确实需要多态行为的函数声明为虚函数,避免不必要的虚函数声明。
-
避免过度使用多态
:在对性能要求极高的场景下,如实时系统,应谨慎使用多态性。可以考虑使用更简单的编程方式,如
switch语句来替代多态处理。 -
选择合适的数据结构
:一些 C++ 标准库类,如
array和vector,在实现时没有使用多态和虚函数,以避免执行时的开销,从而获得最佳性能。在合适的场景下,可以优先选择这些数据结构。
5. 总结与实践建议
多态性是面向对象编程中的一个强大特性,它通过虚函数和动态绑定实现了代码的灵活性和可扩展性。在实际编程中,我们需要根据具体的需求和场景来合理使用多态性。
以下是一些实践建议:
1.
设计类层次结构时
:明确哪些函数需要实现多态行为,将这些函数声明为虚函数。同时,确保抽象基类中包含纯虚函数,以强制派生类实现这些函数。
2.
使用多态处理时
:通过基类指针或引用操作派生类对象,利用虚函数的动态绑定特性,让程序在运行时根据对象的实际类型调用相应的函数。
3.
处理特定类型对象时
:当需要对特定类型的对象进行特殊处理时,使用
dynamic_cast
进行类型检查和转换,避免出现未定义行为。
4.
性能优化时
:根据项目的性能要求,合理权衡多态性带来的灵活性和性能开销,采取相应的优化措施。
为了更好地理解和掌握多态性,建议进行以下实践操作:
1.
编写示例代码
:根据本文中的示例代码,自己动手编写一些简单的程序,加深对多态性、虚函数和动态绑定的理解。
2.
调试和测试
:在编写代码的过程中,进行调试和测试,观察程序的运行结果,验证多态性的实现效果。
3.
分析性能
:使用性能分析工具,分析程序中多态函数调用的性能开销,找出性能瓶颈并进行优化。
通过不断的实践和学习,我们可以更加熟练地运用多态性,编写出高质量、高性能的 C++ 程序。
| 操作 | 说明 |
|---|---|
| 设计类层次结构 | 明确虚函数,包含纯虚函数 |
| 使用多态处理 | 通过基类指针或引用操作对象 |
| 处理特定类型对象 |
使用
dynamic_cast
进行类型检查和转换
|
| 性能优化 | 合理权衡灵活性和性能开销 |
graph LR;
A[设计类层次结构] --> B[使用多态处理];
B --> C[处理特定类型对象];
C --> D[性能优化];
希望这些内容能够帮助你更好地理解和应用 C++ 中的多态性。在实际编程中,不断探索和实践,你会发现多态性的强大之处。
超级会员免费看
275

被折叠的 条评论
为什么被折叠?



