抽象类
当把一个类作为一个类型时,都假设程序将创建这种类型的对象,但是,在某些情况下,定义一个程序员永远不打算实例化任何对象的类也是非常有用的,这样的类称为抽象类,因为通常抽象类在类的继承层次结构中作为基类,所以我们有时也称这种类为抽象基类。这种类不能用来实例化对象,抽象类其实是不完整的,其派生类必须在这些类的对象实例化之前定义那些缺少的部分,比如实例化对象。
构造抽象类的目的是为其他类提供适合的基类。可以用来实例化对象的类称为具体类。这些类为它们声明的每一个成员函数定义或继承实现。例如可以定义一个抽象基类TwoDimensionalShape(二维形状),然后由它派生出具体类,如Square(正方形)、Circle(圆)和Triangle(三角形)。或者定义一个抽象基类ThreeDimensionalShape(),并由它派生出具体类,如Cube(立方体)、Sphere(球)和Cylinder(圆柱体)。抽象基类太宽泛以至于无法定义真实的对象,在可以考虑实例化对象之前,需要更加具体的内容。例如,有人说“请绘制这个二维形状”,那么到底该绘制什么形状呢?具体类提供了详细的说明,使得类实例化对象是合理的。
纯虚函数
通过声明类的一个或多个virtual函数为纯virtual函数,可以使一个类成为抽象类。一个纯virtual函数是在声明时“初始化值为0”的函数,如下所示:
virtual void fun() = 0;
“=0”称为纯指示符。纯virtual函数不提供函数的具体实现,每个派生的具体类必须重写所有基类的纯virtual函数的定义,提供这些函数的具体实现。virtual函数和纯virtual函数之间的区别是:virtual函数有函数的实现,并且提供派生类是否重写这些函数的选择权。相反,纯virtual函数并不提供函数的实现,需要派生类重写这些函数以使派生类成为具体类,否则派生类仍然是抽象类。
实例演示(应用多态性的工资发放系统)
问题描述:某家公司按周支付雇员工资。雇员一共有3类:定薪雇员,不管每周工作多长时间都领取固定的周薪;佣金雇员,工资完全是销售业绩提成;带薪佣金雇员,工资是基本工资加销售业绩提成。在这次工资发放阶段,公司决定奖励带薪佣金雇员,把他们的基本工资提高10%。公司想实现一个C++程序多态地执行工资的计算。
我们使用抽象类Employee表示通常概念的雇员类。直接从Employee类中派生的是类SalariedEmployee(定薪雇员类)、CommissionEmployee(佣金雇员类)。而类BasePlueCommissionEmployee(带薪佣金雇员类)又是从CommissionEmployee类直接派生的。抽象基类Employee声明了类层次结构的接口,即程序可以对所有的Employee类对象调用的一组成员函数集合。每个雇员,不论他的工资计算方式如何,都有姓名及社会保险号码,因此在抽象基类Employee中含有 private数据成员First- Name,LastName和SocialSecurityNumber。
抽象基类Employee:Employee类除了包含操作Employee类的数据成员的各种Get和Set函数之外,还提供成员函数earnings()和print()。earnings函数应用于所有的雇员,但是每项收入的计算取决于官员的类型。所以在基类Employee中把earnings()函数设置为纯虚函数,因为这个函数默认地一种实现是没有任何意义的,没有足够的信息决定应该返回的收入是多少。每个派生类都用合适的实现来重写earnings()函数。要计算一个雇员的收入,程序把一个雇员对象的地址赋给一个基类Employee指针,然后调用该对象的earnings函数。我们维护一个Employee指针的vector对象,每个指针都指向一个Employee对象。程序迭代访问此vector对象并调用每个Employee对象的earnings()函数。C++多态得执行这些函数调用,因为Employee类中含有纯虚函数,迫使所有希望成为具体类的那些直接继承Employee类的派生类都重写了earnings()函数。Employee类中的print()函数显示雇员的姓名和社会保险号码。每个Employee类的派生类都重写了print()函数,输出雇员的类型之后紧接着还输出了雇员的其他信息。函数print()可以调用earnings()函数,即使print()函数是类的一个虚函数。
employee.h
#ifndef EMPLOYEE_H
#define EMPLOYEE_H
#include <string>
#include <iostream>
using namespace std;
class Employee
{
public:
Employee(string, string, string); //构造函数
virtual ~Employee(){} //虚析构函数
void SetFirstName(string &); //设置名
string GetFirstName();
void SetLastName(string &); //设置姓
string GetLastName();
void SetSocialSecurityNumber(string &); //设置社会保险号码
string GetSocialSecurityNumber();
virtual double earnings() = 0; //纯虚函数
virtual void print();
private:
string FirstName;
string LastName;
string SocialSecurityNumber;
};
#endif
employee.cpp
#include "employee.h"
#include <iostream>
using namespace std;
//构造函数
Employee::Employee(string first, string last, string ssn)
:FirstName(first), LastName(last), SocialSecurityNumber(ssn)
{
}
//设置名
void Employee::SetFirstName(string &first)
{
FirstName = first;
}
//获取名
string Employee::GetFirstName()
{
return FirstName;
}
//设置姓
void Employee::SetLastName(string &last)
{
LastName = last;
}
//获取姓
string Employee::GetLastName()
{
return LastName;
}
//设置社会保险号码
void Employee::SetSocialSecurityNumber(string &ssn)
{
SocialSecurityNumber = ssn;
}
//获取社会保险号码
string Employee::GetSocialSecurityNumber()
{
return SocialSecurityNumber;
}
//打印
void Employee::print()
{
cout << GetFirstName() << " " << GetLastName() <<
"'s Social Security Number is : " << GetSocialSecurityNumber() << endl;
}
SalaredEmployee(定薪雇员)类是从Employee类派生而来的,其公有成员函数包括:一个以名、姓、社会保险号码和周薪为参数的构造函数,一个virtual析构函数,一个给数据成员WeeklySalary赋值的SetWeeklySalary()函数,一个返回周薪的GetWeeklySalary()函数,还有一个计算定薪雇员收入的virtual函数earnings()函数,打印雇员信息的print()函数,输出内容是雇员类型以及由基类Employee的print()函数和SalariedEmployee类的GetWeeklySalary()函数产生的雇员的特定信息。SalariedEmployee类中的print()函数重写了基类Employee的print()函数,如果SalariedEmployee类不重写print()函数,那么SalariedEmployee类将继承Employee类中的print()函数,这样的话,SalariedEmployee类的print()函数只是简单的返回雇员的姓名和社会保险号码,而这些信息并不能充分的描述一个SalariedEmployee雇员。为了打印SalariedEmployee官员的完整信息,派生类的print()函数首先输出“Salaried Employee :”,然后通过使用作用域分辨运算符调用基类的print()函数,从而输出基类Employee的基本信息。
SalariedEmployee.h
#include <string>
#include "employee.h"
#include <iostream>
using namespace std;
class SalariedEmployee:public Employee
{
public:
SalariedEmployee(string, string, string, double = 0.0); //构造函数
virtual ~SalariedEmployee() //虚析构函数
{}
void SetWeeklySalary(double); //设置周薪
double GetWeeklySalary(); //获取周薪
virtual double earnings() override; //计算收入
virtual void print() override;
private:
double WeeklySalary;
};
SalariedEmployee.cpp
#include <iostream>
#include <stdexcept>
#include "SalariedEmployee.h"
using namespace std;
//构造函数
SalariedEmployee::SalariedEmployee(string first, string last, string ssn, double salary)
:Employee(first, last, ssn)
{
SetWeeklySalary(salary);
}
//设置周薪
void SalariedEmployee::SetWeeklySalary(double salary)
{
if (salary > 0.0)
{
WeeklySalary = salary;
}
else
{
throw invalid_argument("Weekly Salary must be > 0.0");
}
}
//获取周薪
double SalariedEmployee::GetWeeklySalary()
{
return WeeklySalary;
}
//计算收入
double SalariedEmployee::earnings()
{
return GetWeeklySalary();
}
//打印
void SalariedEmployee::print()
{
cout << "Salaried Employee : ";
Employee::print();
cout << "Weekly Salary is : " << GetWeeklySalary() << endl;
}
CommissionEmployee(佣金雇员)类同样也是继承自Employee类。其公有成员函数包括一个以姓名、社会保险号码、销售总额、提成比例为参数的构造函数;给数据成员GrossSales(销售总额)和CommissionRate(提成比例)分别赋值的SetGrossSales()和SetCommissionRate()函数,以及获取销售总额和提成比例的函数,计算一个佣金雇员收入的earnings()函数,输出雇员类型以及详细信息的print()函数。
CommissionEmployee.h
#include <string>
#include "employee.h"
class CommissionEmployee :public Employee
{
public:
CommissionEmployee(string, string, string, double = 0.0, double = 0.0);//构造函数
virtual ~CommissionEmployee() //虚析构函数
{}
void SetCommissionRate(double); //设置提成比例
double GetCommissionRate(); //获取提成比例
void SetGrossSales(double); //设置销售总额
double GetGrossSales(); //获取销售总额
virtual double earnings() override; //计算收入
virtual void print() override; //打印
private:
double CommissionRate;
double GrossSales;
};
CommissionEmployee.cpp
#include <iostream>
#include "CommissionEmployee.h"
#include <stdexcept>
using namespace std;
//构造函数
CommissionEmployee::CommissionEmployee(string first, string last, string ssn, double sales, double rate)
:Employee(first, last, ssn)
{
SetCommissionRate(rate);
SetGrossSales(sales);
}
//设置提成比例
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");
}
}
//设置销售总额
void CommissionEmployee::SetGrossSales(double sales)
{
if (sales >= 0.0)
{
GrossSales = sales;
}
else
{
throw invalid_argument("Gross Sales must be >= 0.0");
}
}
//获取提成比例
double CommissionEmployee::GetCommissionRate()
{
return CommissionRate;
}
//获取销售总额
double CommissionEmployee::GetGrossSales()
{
return GrossSales;
}
//计算收入
double CommissionEmployee::earnings()
{
return GetGrossSales() * GetCommissionRate();
}
//打印
void CommissionEmployee::print()
{
cout << "Commission Employee : ";
Employee::print();
cout << "gross sales:" << GetGrossSales() << ",commission rate:" << GetCommissionRate() << endl;
}
还有一种雇员类型叫带薪佣金雇员。BasePlusCommissionEmployee(带薪佣金雇员)类继承自CommissionEmployee类,是Employee类的间接派生类。其成员函数包括构造函数,设置基础工资的SetBaseSalary()函数和一个返回基础工资的GetBaseSalary()函数,以及计算收入的earnings()函数和打印print()函数。
BasePlusCommissionEmployee.h
#include <string>
#include <iostream>
#include "CommissionEmployee.h"
class BasePlusCommissionEmployee :CommissionEmployee
{
public:
BasePlusCommissionEmployee(string, string, string, double = 0.0, double = 0.0, double = 0.0); //构造函数
virtual ~BasePlusCommissionEmployee() //虚析构函数
{}
void SetBaseSalary(double); //设置基础工资
double GetBaseSalary(); //获取基础工资
virtual double earnings() override; //计算收入
virtual void print(); //打印
private:
double BaseSalary;
};
BasePlusCommissionEmployee.cpp
#include "BasePlusCommissionEmployee.h"
#include <iostream>
#include <stdexcept>
using namespace std;
//构造函数
BasePlusCommissionEmployee::BasePlusCommissionEmployee(string first, string last, string ssn, double sales, double rate, double basesalary)
:CommissionEmployee(first, last, ssn, sales, rate)
{
SetBaseSalary(basesalary);
}
//设置基础工资
void BasePlusCommissionEmployee::SetBaseSalary(double basesalary)
{
if (basesalary >= 0.0)
{
BaseSalary = basesalary;
}
else
{
throw invalid_argument("Base Salary must be >= 0.0");
}
}
//获取基础工资
double BasePlusCommissionEmployee::GetBaseSalary()
{
return BaseSalary;
}
double BasePlusCommissionEmployee::earnings()
{
return GetBaseSalary() + CommissionEmployee::earnings();
}
//打印
void BasePlusCommissionEmployee::print()
{
cout << "Base-Salary :";
CommissionEmployee::print();
cout << ",base salsry:" << GetBaseSalary() << endl;
}
主函数:
#include <vector>
#include <iomanip>
#include <iostream>
#include "BasePlusCommissionEmployee.h"
#include "CommissionEmployee.h"
#include "employee.h"
#include "SalariedEmployee.h"
using namespace std;
void virtualViaPointer(Employee *baseClassPtr)
{
baseClassPtr->print();
cout << "total salary:" << baseClassPtr->earnings() << endl << endl;
}
void virtualViaRefrence(Employee &baseClassRef)
{
baseClassRef.print();
cout << "total salary:" << baseClassRef.earnings() << endl << endl;
}
int main()
{
cout << fixed << setprecision(2);
//创建定薪雇员类对象(定薪800)
SalariedEmployee salariedemployee("John", "Smith", "1-11-111", 800);
//创建佣金雇员类对象(销售总额10000,提成比例6%)
CommissionEmployee commissionemployee("Sue", "Jones", "2-22-222", 10000, 0.06);
//创建带薪佣金雇员类对象(销售总额5000,提成比例4%,基础工资300)
BasePlusCommissionEmployee basepluscommissionemployee("Bob", "Lewis", "3-33-333", 5000, 0.04, 300);
//输出各个员工的信息
salariedemployee.print();
cout << "total salary:" << salariedemployee.earnings() << endl << endl;
commissionemployee.print();
cout << "total salary:" << commissionemployee.earnings() << endl << endl;
basepluscommissionemployee.print();
cout << "total salary:" << basepluscommissionemployee.earnings() << endl << endl;
//创建vector对象,包含3个Employee类指针
vector<Employee *> employees(3);
employees[0] = &salariedemployee;
employees[1] = &commissionemployee;
employees[2] = &basepluscommissionemployee;
//循环遍历vector对象,并为每个元素调用virtualViaPointer()函数
for (Employee *employeePtr : employees)
{
virtualViaPointer(employeePtr);
}
//循环遍历vector对象,并为每个元素调用virtualViaRefrence()函数
{
for (Employee *employeePtr : employees)
{
virtualViaRefrence(*employeePtr);
}
}
return 0;
}
virtualViaPointer()函数用参数baseClassPtr接收保存在employees元素中的地址。每次对virtualViaPointer()函数的调用都利用baseClassPtr调用print()函数和earnings()函数。该函数仅仅知道基类类型是Employee,因此在编译时编译器并不知道通过baseClassPtr在那时指向的对象的函数。输出表明对于每个类程序的确调用了适当的函数,并输出了每个对象的正确信息。
virtualViaRefrence()函数用参数baseClassRefer(类型是引用)接收间接引用保存在employees元素中的指针后形成的引用。每次对virtualViaPointer()函数的调用都会通过引用baseClassRef调用print()函数和earnings()函数,以表明利用基类引用同样产生了多态性的行为。每次虚函数的调用都会调用baseClassRef在执行时所引用对象的函数。
执行结果: