(一三八)多态公有继承

假如一个类继承另一个类,但有一个类方法,在不同类中,其行为是不同的。

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

 

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

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

②使用虚方法。

注:这两种机制共同使用

 

 

 

虚方法的关键字是: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 表示 有虚函数,以防混淆。

 

 

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值