您的位置:首页 > 其它

(一三八)多态公有继承

2016-02-04 01:20 239 查看
假如一个类继承另一个类,但有一个类方法,在不同类中,其行为是不同的。

换句话说,方法的行为,应取决于调用该方法的对象。这种较复杂的行为称为 多态——具有多种形态,即同一种方法的行为,随上下文而异。

有两种重要的机制可用于实现多态公有继承:

①在派生类中重新定义基类的方法;

②使用虚方法。

注:这两种机制共同使用

虚方法的关键字是:virtual

例如:virtual void show();

虚方法的关键字,不在类外部使用,例如.cpp文件中的方法定义

虚方法的作用在于:

在基类和派生类都有同名函数时,决定使用哪个类的方法,不是取决于指针/引用类型,而是取决于他们指向的对象的类型。

解释①:如果不是指针、引用,而是对象,那么根据对象类型决定,虚方法无影响;

解释②:因为基类的指针、引用,可以指向派生类对象;

解释③:假如不使用虚方法,决定使用哪一个方法,取决于指针/引用的类型,而不是取决于它们指向的类型(例如基类指针指向派生类,如果是非虚方法,则使用基类的方法;如果是虚方法,则使用派生类的方法);

代码:

//1.h 基类和派生类声明
#pragma once
#include<iostream>
#include<string>
using std::string;

class Brass
{
string name;
int ID;
double money;
public:
Brass(string na = "None", int id = -1, double mo = 0);	//创建账户
bool Save(double mo);	//存款
virtual bool Load(double mo);	//取款
virtual void Show();	//显示账户信息
double Money() { return money; }	//返回当前存款
};

class Brass_plus :public Brass
{
double overdraft_Max;	//透支上限
double overdraft_Rate;	//透支贷款利率
double overdraft ;	//当前透支总额
public:
Brass_plus(const Brass& br,double ov_M = 500, double ov_R = 0.11125, double ov = 0);
bool ch_ov_M(double ov_M);	//设置透支上限
bool ch_ov_R(double ov_R);	//设置透支利率
virtual bool Load(double mo);	//取款,透支保护
virtual void Show();	//显示账号信息,更多
};
//2.cpp 基类和派生类的定义
#include"1.h"
using std::cout;
using std::endl;
using std::string;
typedef std::ios_base::fmtflags format;
typedef std::streamsize precis;	//这个不明白是什么意思
format setFormat();
void restore(format f, precis p);

Brass::Brass(string na, int id, double mo)
{
name = na;
ID = id;
money = mo;
}
bool Brass::Save(double mo)
{
if (mo < 0)
{
cout << "你不能存入小于0的金钱。" << endl;
return false;
}
else
{
money += mo;
cout << "存款成功。" << endl;
return true;
}
}
bool Brass::Load(double mo)
{
if (mo < 0)
{
cout << "你不能取出小于0的金钱。" << endl;
return false;
}
else if (mo>money)
{
cout << "余额不足。" << endl;
return false;
}
else
{
money -= mo;
cout << "取款成功。" << endl;
return true;
}
}
void Brass::Show()
{
cout << "姓名:" << name << ",存款账号:" << ID << ",账户余额:" << money << "元" << endl;
}
Brass_plus::Brass_plus(const Brass& br, double ov_M, double ov_R, double ov):Brass(br)
{
overdraft_Max = ov_M;
overdraft_Rate = ov_R;
overdraft = ov;
}
bool Brass_plus::ch_ov_M(double ov_M)	//设置透支上限
{
if (ov_M < 0)
{
cout << "设置失败,不能设置为负数。" << endl;
return false;
}
else
{
overdraft_Max = ov_M;
cout << "设置成功,新的透支上限为:" << overdraft_Max << "元" << endl;
return true;
}
}
bool Brass_plus::ch_ov_R(double ov_R)	//设置透支利率
{
if (ov_R < 0)
{
cout << "设置失败,不能设置为负数。" << endl;
return false;
}
else
{
overdraft_Rate = ov_R;
cout << "设置成功,新的利率为:" << overdraft_Rate * 100 << "%" << endl;
return true;
}
}
void Brass_plus::Show()	//显示账号信息,more
{
Brass::Show();
cout << "账户透支上限:" << overdraft_Max << " 元" << endl;
cout << "透支偿还利率:" << overdraft_Rate * 100 << " %" << endl;
cout << "当前透支额度为:" << overdraft << " 元" << endl;
}
bool Brass_plus::Load(double mo)	//取款,带有透支保护
{
format initialState = setFormat();	//这行貌似是存储输入状态(这个输入状态是函数的返回值)
precis prec = cout.precision(2);	//这行感觉是设置为两行输出

double MO = Brass::Money();
if (mo < 0||mo<MO)	//不涉及透支的取款
{
return Brass::Load(mo);
}
else if (mo>overdraft_Max - overdraft + MO)	//透支程度大于限额
{
cout << "超出限额,取款失败。" << endl;
return false;
}
else
{
Brass::Load(MO);	//先取光余额
overdraft += mo - MO;
cout << "取款成功,余额为:" << Brass::Money() << ",透支额为:" << overdraft << " 元,最大透支额为: " << overdraft_Max << "元" << endl;
return true;
}
restore(initialState, prec);	//这行好像是恢复
}

format setFormat()
{
return cout.setf(std::ios_base::fixed, std::ios_base::floatfield);
}
void restore(format f, precis p)
{
cout.setf(f, std::ios_base::floatfield);
cout.precision(p);
}
//1.cpp main函数测试用
#include<iostream>
#include"1.h"

int main()
{
using namespace std;
string name;
cout << "输入姓名:";
cin >> name;	//不能读取空格
cout << "输入ID编号(数字形式):";
int ID;
cin >> ID;
cout << "输入存款金额:";
double money;
cin >> money;
Brass one(name, ID, money);
cout << "银行账户创建完毕。" << endl;
Brass_plus two(one);
cout << "已建立信用账号:" << endl;
double a;
cout << "s.存\tl.取.\tc.查询\tq.退出\n选择->";
char ch;
while (cin>>ch&&ch!='q')
{
cin.sync();
switch (ch)
{
case's':cout << "输入存款金额:";
cin >> a;
two.Save(a);
break;
case'l':cout << "输入取款金额:";
cin >> a;
two.Load(a);
break;
case'c':two.Show();
break;
default:cout << "输入错误。" << endl;
cin.clear();
cin.sync();
break;
}
cout << "s.存\tl.取.\tc.查询\tq.退出\n选择->";
}
cout << "设置利率(%):";
double LiLv;
cin >> LiLv;
LiLv /= 100;
two.ch_ov_R(LiLv);
cout << "设置最大透支额度:";
double Max;
cin >> Max;
two.ch_ov_M(Max);
cout << "再次查看账户信息:";
two.Show();
cout << "Done." << endl;
system("pause");
return 0;
}


显示:

输入姓名:王冬
输入ID编号(数字形式):12321
输入存款金额:1000
银行账户创建完毕。
已建立信用账号:
s.存    l.取.   c.查询  q.退出
选择->c
姓名:王冬,存款账号:12321,账户余额:1000元
账户透支上限:500 元
透支偿还利率:11.125 %
当前透支额度为:0 元
s.存    l.取.   c.查询  q.退出
选择->s
输入存款金额:400
存款成功。
s.存    l.取.   c.查询  q.退出
选择->c
姓名:王冬,存款账号:12321,账户余额:1400元
账户透支上限:500 元
透支偿还利率:11.125 %
当前透支额度为:0 元
s.存    l.取.   c.查询  q.退出
选择->l
输入取款金额:1500
取款成功。
取款成功,余额为:0.00,透支额为:100.00 元,最大透支额为: 500.00元
s.存    l.取.   c.查询  q.退出
选择->c
姓名:王冬,存款账号:12321,账户余额:0.00元
账户透支上限:500.00 元
透支偿还利率:11.13 %
当前透支额度为:100.00 元
s.存    l.取.   c.查询  q.退出
选择->l
输入取款金额:500
超出限额,取款失败。
s.存    l.取.   c.查询  q.退出
选择->q
设置利率(%):15
设置成功,新的利率为:15.00%
设置最大透支额度:5000
设置成功,新的透支上限为:5000.00元
再次查看账户信息:姓名:王冬,存款账号:12321,账户余额:0.00元
账户透支上限:5000.00 元
透支偿还利率:15.00 %
当前透支额度为:100.00 元
Done.
请按任意键继续. . .


总结:

①派生类调用基类的公有方法,采用:基类名::基类方法 的形式。例如:

Brass::Show();

就是Brass_plus类的方法内,调用Brass类方法show()。

由于派生类和基类都有show()函数,假如不加类名。那么在派生类函数show()中使用show(),并不会调用基类的show()函数,反而会进入到无限递归之中。

②我在程序中,没有在透支时直接加上利息。原因在于,假如透支500元,加上利息后,实际需要偿还金额可能已经超过默认上限500元了,也就是超出透支额度。

③可以用指针数组。具体用法可以如下:

声明一个基类的指针数组:Brass people[4];

然后指针指向基类或者派生类,可以使用new来分配内存:

people[0]=new Brass(xxxxxxx);

people[1]=new Brass_plus(xxxx); //这个需要输入的内容更多,包括Brass类的数据成员

然后调用people[i],使用虚方法,就可以展现出不同类型的结果(原因在于虚方法是根据指针指向的对象的类型决定调用哪一个,而不是根据指针的类型)。

关于虚函数的更多说明:

①当基类使用虚函数的时候,那么使用基类的指针/引用,将根据其指向的对象的类型决定使用哪个类的方法。

例如,基类的函数是自动继承到派生类的。如果派生类需要自定义使用某个基类的函数的实现,那么是可以直接在派生类中添加代码。

调用时,根据对象的类型决定调用哪个;

如果是指针,则根据指针的类型。——但若使用虚函数,这里则是根据指针指向的类型,即Brass类虚指针也可能使用Brass_plus类的方法。

②对于析构函数而言,派生类的对象调用析构函数时,则先调用派生类的析构函数,然后随之调用基类的虚构函数。

若使用基类的指针,那么基类的指针是可以指向派生类的对象的(前面说过)。

假如因为某种需要,基类的指针是new分配内存的派生类的对象(这是可以的),那么在delete的时候,也应该调用调用派生类的析构函数,再调用基类的析构函数。

然而,对于非虚函数而言,由于是基类的指针,因此直接调用了基类的析构函数,而没有调用派生类的析构函数。

但若基类的析构函数是虚函数。那么在调用时,则会根据指针或引用指向的对象,决定调用是基类还是派生类的析构函数。

基类指针被delete释放内存——》查看析构函数——》发现关键字virtual——》决定根据指针指向内容而决定使用哪个类方法。

也就是说,假如某个类是基类,那么最好给他的析构函数加上关键字virtual,让它成为一个虚析构函数。

③由此反推,假如某个类不会成为基类,那么它的函数就不需要加上关键字成为虚函数(即使他是某个基类的派生类),因为派生类的指针只能指向它自己,而不是指向基类(但基类指针可以指向派生类)。

但由于为了一目了然,因此,一般情况下,假如基类是虚函数,那么派生类也应该加上关键字 virtual 表示 有虚函数,以防混淆。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: