多态公有继承
基类的公有函数被派生类继承下来,但是派生类又重新定义了某个基类共有函数,也就是说同一个对象在派生类和基类的表现是不一样的,方法的的调用取决于调用方法的对象.
实现这种行为的方法:
-
在派生类中重新定义基类的方法
-
使用虚方法
下面设计一个类来演示这种方法
设计一个Brass类和BrassPlus类
需求如下:
支票账户如下:
- 客户姓名
- 账号
- 当前余额
执行操作:
- 创建账户
- 存款
- 取款
- 显示账户信息
银行可以透支,添加如下信息:
- 透支上限
- 透支利率
- 当前透支总额
改变以往操作:
- 取款考虑到透支上限
- 显示操作要显示新增信息
对于以上操作实现如下:
class Brass
{
private:
string fullName; // 客户姓名
long acctNum; // 账号
double balance; // 余额
public:
Brass(const string & s = "Nullbody", long an = -1,
double bal = 0.0); // Brass默认构造函数
void Deposit(double amt); // 存款
virtual void Withdraw(double amt); // 虚方法取款
double Balance() const; // 返回余额
virtual void ViewAcct() const; // 虚方法打印类中的内容
virtual ~Brass() {} // 后面说明虚析构函数的作用
};
//Brass Plus是Brass的派生类
class BrassPlus : public Brass
{
private:
double maxLoan; // 透支上限
double rate; // 透支利率
double owesBank; // 欠帐
public:
BrassPlus(const string & s = "Nullbody", long an = -1,
double bal = 0.0, double ml = 500,double r = 0.11125); // BrassPlus默认构造函数
BrassPlus(const Brass & ba, double ml = 500,
double r = 0.11125); // BrassPlus构造函数
virtual void ViewAcct()const; // 虚方法打印类中的内容
virtual void Withdraw(double amt); // // 虚方法取款
void ResetMax(double m) { maxLoan = m; }
void ResetRate(double r) { rate = r; };
void ResetOwes() { owesBank = 0; }
};
在派生类中重新定义了方法ViewAcct(),Withdraw(double amt),所以可以直接通过调用的对象来确定使用的那个类中的方法.
但在这个例子中,重新定义的方法前面加入了virtual关键字,被称为虚方法,这个虚方法的作用如下:
-
如果方法是通过指针或者引用调用的,正常来讲,或者没有使用虚方法,就是通过指针类型或者引用类型来确定用什么方法.
-
如果使用了虚方法,那么就会根据指针和引用指向的对象来确定使用该对象的方法.
例:
// 假设ViewAcct()是虚方法
Brass dom("Dominic Banker" , 12118 , 4183.45);
BrassPlus dot("Dorothy Banker" , 12118 , 2592.00);
Brass & b1_ref = dom;
Brass & b2_ref = dot;
b1_ref.ViewAcct(); // 使用的是Brass的方法
b2_ref.ViewAcct(); // 使用的是BrassPlus的方法
若使用指针:
Brass * p1_brass = & dom;
Brass * p2_brass = & dot;
p1_brass->ViewAcct(); // 使用的是Brass的方法
p2_brass->ViewAcct(); // 使用的是BrassPlus的方法
Brass和BrassPlusd的实现:
Brass::Brass(const string & s, long an, double bal) // 基类默认构造函数
{
fullName = s;
acctNum = an;
balance = bal;
}
void Brass::Deposit(double amt) // 积累存钱
{
if (amt < 0)
cout << "Negative deposit not allowed; "
<< "deposit is cancelled.\n";
else
balance += amt;
}
void Brass::Withdraw(double amt) // 基类取钱
{
if (amt < 0)
cout << "Withdrawal amount must be positive; "
<< "withdrawal canceled.\n";
else if (amt <= balance)
balance -= amt;
else
cout << "Withdrawal amount of $" << amt
<< " exceeds your balance.\n"
<< "Withdrawal canceled.\n";
}
double Brass::Balance() const // 返回余额
{
return balance;
}
void Brass::ViewAcct() const // 基类打印出类中数据
{
cout << "Client: " << fullName << endl;
cout << "Account Number: " << acctNum << endl;
cout << "Balance: $" << balance << endl;
}
// BrassPlus方法
BrassPlus::BrassPlus(const string & s, long an, double bal,
double ml, double r) : Brass(s, an, bal) // 派生类默认构造函数
{
maxLoan = ml;
owesBank = 0.0;
rate = r;
}
BrassPlus::BrassPlus(const Brass & ba, double ml, double r)
: Brass(ba) // 利用基类的隐式复制构造函数进行初始化
{
maxLoan = ml;
owesBank = 0.0;
rate = r;
}
void BrassPlus::ViewAcct() const // 重新定义ViewAcct()方法,增加打印派生类数据操作
{
Brass::ViewAcct(); // 利用域运算符可以调用同名基类方法
cout << "Maximum loan: $" << maxLoan << endl;
cout << "Owed to bank: $" << owesBank << endl;
cout.precision(3);
cout << "Loan Rate: " << 100 * rate << "%\n";
}
void BrassPlus::Withdraw(double amt) // 重新定义Withdraw()方法,增加关于透支的相关操作
{
double bal = Balance(); // 因为没有重新定义Balance()函数,所以直接调用等于就等于调用基类方法
if (amt <= bal)
Brass::Withdraw(amt);
else if ( amt <= bal + maxLoan - owesBank)
{
double advance = amt - bal;
owesBank += advance * (1.0 + rate);
cout << "Bank advance: $" << advance << endl;
cout << "Finance charge: $" << advance * rate << endl;
Deposit(advance);
Brass::Withdraw(amt);
}
else
cout << "Credit limit exceeded. Transaction cancelled.\n";
}
测试函数如下:
int main()
{
using std::cout;
using std::endl;
Brass Piggy("Porcelot Pigg", 381299, 4000.00); // 初始化基类对象
BrassPlus Hoggy("Horatio Hogg", 382288, 3000.00); // 初始化派生类对象
Piggy.ViewAcct(); // 调用基类的ViewAcct()方法
cout << endl;
Hoggy.ViewAcct(); // 调用派生类的ViewAcct()方法
cout << endl;
cout << "Depositing $1000 into the Hogg Account:\n";
Hoggy.Deposit(1000.00);
cout << "New balance: $" << Hoggy.Balance() << endl;
cout << "Withdrawing $4200 from the Pigg Account:\n";
Piggy.Withdraw(4200.00);
cout << "Pigg account balance: $" << Piggy.Balance() << endl;
cout << "Withdrawing $4200 from the Hogg Account:\n";
Hoggy.Withdraw(4200.00);
Hoggy.ViewAcct();
return 0;
}
以上函数都是通过对象直接调用相应的接口函数,测试结果如下:
以上测试函数并没有体现出虚函数的作用,重新定义一个测试函数如下:
const int CLIENTS = 4;
int main()
{
using std::cin;
using std::cout;
using std::endl;
Brass * p_clients[CLIENTS]; // 基类指针数组
string temp;
long tempnum;
double tempbal;
char kind;
for (int i = 0; i < CLIENTS; i++)
{
cout << "Enter client's name: ";
getline(cin,temp);
cout << "Enter client's account number: ";
cin >> tempnum;
cout << "Enter opening balance: $";
cin >> tempbal;
cout << "Enter 1 for Brass Account or "
<< "2 for BrassPlus Account: ";
while (cin >> kind && (kind != '1' && kind != '2')) // 选择基类还是派生类
cout <<"Enter either 1 or 2: ";
if (kind == '1')
p_clients[i] = new Brass(temp, tempnum, tempbal); // '1'是new出Brass对象
else // '2'是new出BrassPlus对象
{
double tmax, trate;
cout << "Enter the overdraft limit: $";
cin >> tmax;
cout << "Enter the interest rate "
<< "as a decimal fraction: ";
cin >> trate;
p_clients[i] = new BrassPlus(temp, tempnum, tempbal,
tmax, trate);
}
while (cin.get() != '\n') // 去掉多余字符
continue;
}
cout << endl;
for (int i = 0; i < CLIENTS; i++)
{
p_clients[i]->ViewAcct(); // 指向Brass对象的调用Brass::ViewAcct(),指向BrassPlus对象的调用BrassPlus::ViewAcct()
cout << endl;
}
for (int i = 0; i < CLIENTS; i++)
{
delete p_clients[i]; // 释放指针所指控间
}
cout << "Done.\n";
return 0;
}
效果如下:
为什么使用虚析构函数
delete释放某个对象的空间会调用相应指针的析构函数,就算Brass指针是指向BrassPlus对象,也会调用Brass的析构函数,如果是虚析构函数,则会调用指向对象的析构函数,然后自动调用基类的析构函数,这样才能保证正常的虚构函数被调用.