C++基础(十一)
<十三章 类的继承----多态公有继承>
如果希望同一个方法在派生类和基类中的行为不同,方法的行为应取决于调用该方法的对象。这种复杂的行为称为多态------具有多种形态,即同一个方法的行为随着对象不同而改变。有两种重要的机制可用于实现多态公有继承:
在派生类重新定义基类方法
使用虚方法
思考:这里的多态的意思就是对于一个函数来说,我们希望赋予他更多的功能,使得对象不同实现的功能也不同。
使用虚方法的办法时使用关键字virtual来修饰函数,使其成为一个虚函数。
1、构建基类和它的派生类
在这里为了方便我把基类和派生类都放在一个类项目内,其实还是分开比较好:
头文件:
#pragma once
#include"list.h"
class Brass
{
private:
string name; //名字
long accountId; //用户id
double balance; //余额
public:
Brass(const string &na = "none", long an = 0, double ba = 0);
void Deposit(double amt); //存钱函数
virtual void Withdraw(double amt); //取钱函数
virtual void show_info()const;
double show_balance()const { return balance; }
};
class BrassPlus :public Brass
{
private:
double maxLoan; //最大可借款
double rate; //利率
double owesbank; //银行欠款
public:
BrassPlus(const string& s = "none", long an = 0, double bl = 0, double ml = 0, double ra = 0);
BrassPlus(const Brass& b, double ml = 0, double ra = 0);//另外一种写法,使用复制构造函数
virtual void Withdraw(double amt);
virtual void show_info()const; //显示信息
void ResetMax(double m) { maxLoan = m; } //重置最大额度
void ResetRate(double r) { rate = r; } //重置利率
void ResetOwes() { owesbank = 0; } //清零欠款额度
};
上述代码可以发现,使用virtual关键字声明了两个虚函数,该虚函数名在基类和派生类当中都是存在的。
下面是在.cpp文件当中对函数进行定义:
name = na;
accountId = an;
balance = ba;
}
void Brass::Deposit(double amt)
{
if (amt < 0)
cout << "not allowed" << endl;
else
balance += amt;
}
void Brass::Withdraw(double amt)
{
if (amt < 0)
cout << "not allowed!" << endl;
else if (amt <= balance)
balance -= amt;
else
cout << "unable!" << endl;
}
void Brass::show_info()const
{
cout << "name is: " << name
<< " " << "Account id:" << accountId
<< " " << "Balance is:" << balance << endl;
}
BrassPlus::BrassPlus(const string& s, long an, double bl, double ml, double ra) : Brass(s, an, bl)
{
maxLoan = ml;
rate = ra;
owesbank = 0;
}
BrassPlus::BrassPlus(const Brass& b, double ml, double ra):Brass(b)
{
maxLoan = ml;
rate = ra;
owesbank = 0;
}
void BrassPlus::show_info()const
{
Brass::show_info();
cout << "max load:" << maxLoan
<< " "
<< "rate is: " << rate
<< " "
<< "owesbank is:" << owesbank << endl;
}
void BrassPlus::Withdraw(double amt)
{
double bal = show_balance();
if (amt <= bal)
Brass::Withdraw(amt);
else if (amt <= (bal + maxLoan - owesbank))
{
double advanced = amt - bal;
owesbank = advanced * (1 + rate); //更新后的欠款
cout << "BANK ADVANCED: " << advanced << endl;
cout << "rate charge:" << advanced * rate
<< endl;/*当前欠款的手续费*/
Deposit(advanced);
Brass::Withdraw(amt);
}
else
cout << "unable!" << endl;
}
2、类功能解释以及使用
再继续之前,首先来解释一下该基类与其派生类的功能,按照书上的介绍来讲,这是模拟了一个银行系统来完成对客户的信息存储以及金融服务。
基类的数据就是保存用户的个人信息以及余额,其成员函数的作用时存钱、取钱、显示余额、显示信息;
派生类的额作用时为用户提供金融服务,在基类的基础上,增加了用户的借钱功能(相当于信用卡,有透支额度以及利率)
但是这些都不重要,重要的是虚函数的使用以及多态的概念理解
在主函数中构建两个对象分别是基类对象和派生类对象:
int main()
{
Brass client1("jack", 0001, 2000);
BrassPlus client2("rose", 0002, 2000);
client1.show_info();
client2.show_info();
然后执行显示信息操作(是一个虚函数),显示结果表明,其中的基类对象调用的是基类方法,而派生类对象调用的是派生类的方法。
为了更好的说明函数的多态,在基类和派生类虚函数的定义中,我让其输出一下属于基类还是派生类:
基类函数:
void Brass::show_info()const
{
cout << "name is: " << name
<< " " << "Account id:" << accountId
<< " " << "Balance is:" << balance;
cout << "打印为基类方法!" << endl;
}
派生类:
void BrassPlus::show_info()const
{
Brass::show_info();
cout << "max load:" << maxLoan
<< " "
<< "rate is: " << rate
<< " "
<< "owesbank is:" << owesbank;
cout << "打印为派生类方法!" << endl;
}
最后执行结果为:
因为派生类函数中调用了基类的打印函数,所以这里也显示了“打印为基类方法”。
若想要使用虚方法,那么就必须配合指针或者引用来使用,因为对象时明确的条件下其调用的方法归宿也应当是确定的。
创建一个指针数组来存储不同的对象:
int main()
{
Brass clientarr0("jeck", 2001, 2000);
BrassPlus clientarr1("keil", 2002, 2000, 2000, 0.001);
Brass* parr[2] = { &clientarr0,&clientarr1 };
clientarr0.show_info();
clientarr1.show_info();
parr[0]->show_info();
parr[1]->show_info();
思考:指针数组的类型应该是基类还是派生类?
应该是基类的,前面讲过基类的指针既可以指向基类对象,也可以指向派生类对象。
允许程序,发现同样为Brass类型的指针,但是其调用的虚方法则是根据对象来确定的。
3、虚析构函数
若程序中涉及了new和delete,那么就应该在基类中声明虚析构函数,因为若使用普通的析构函数,那么编译器就会调用默认指针类型的析构函数,由于我们使用了基类类型的指针指向了派生类对象,那么假设有两个对象,则基类对象会被释放,但是派生类对象得不到释放。
virtual ~Brass{}
总结:虚方法地存在使得同名方法可以根据对象类型来进行转换,对于基类与派生类对象,如果方法不是虚方法,那么则都会调用基类方法。(仅在引用或者指针操作的条件下)
验证结论,将类声明中地virtual关键字全部去掉,包括基类与派生类,然后执行程序:
int main()
{
Brass clientarr0("jeck", 2001, 2000);
BrassPlus clientarr1("keil", 2002, 2000, 2000, 0.001);
Brass* parr[2] = { &clientarr0,&clientarr1 };
parr[0]->show_info();
parr[1]->show_info();
......
发现无论指针指向的对象是基类还是派生类对象,都只能调用基类方法。