(一四一)抽象基类

  1. 云栖社区>
  2. 博客>
  3. 正文

(一四一)抽象基类

零零水 2016-02-04 02:00:00 浏览900
展开阅读全文

抽象基类(abstract base class,简称ABC)。

 

抽象基类的前提是,类方法里有 纯虚函数pure virtual function)。

纯虚函数需要在函数声明的结尾处添加“=0”。

 

当一个类有了纯虚函数之后,它就成为了一个抽象基类。

抽象基类的特点是,不能创造该类的对象。

 

例如B类和C类的有一定的共同点,把这些共同点(数据成员和方法)抽象出来,创建一个A类,而B类和C类都从A类派生出来。而A类有一个纯虚函数,因此A类就成为了一个抽象基类。

 

对于纯虚函数而言,可以在实现中不定义该函数,也可以定义该函数。不过对于在不需要在基类中定义的函数(例如两个派生类定义都不同的)可以让其称为纯虚函数。

但总之,用=0来指出这是一个纯虚函数,于是类就成为了一个抽象基类。

 

使用抽象基类后,不能创造该基类的对象,但可以声明该基类的指针,然后用指针去指向派生类对象,用于管理派生类的对象。

 

另外,抽象基类的派生类,有时候被称为具体类。这表示可以创建这些类型的对象。

 

 

总之,ABC描述的是至少使用一个纯虚函数的接口,从ABC派生出的类将根据派生类的具体特征,使用常规虚函数来实现这种接口。

 

如代码:

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

class BaseBank
{
	string name;
	long acctNum;
	double balance;
protected:
	struct Formatting
	{
		std::ios_base::fmtflags flag;
		std::streamsize pr;
	};
	const string& Name()const { return name; }
	const long AcctNum()const { return acctNum; }
	Formatting setFormat()const;
	void Restore(Formatting &f)const;
public:
	BaseBank(string na = "no body", long id = -1, double ba = 0.0);
	void SaveMoney(double mo);	//存钱
	double Balance()const { return balance; }	//查询余额
	virtual void Withdraw(double mo) = 0;	//取款,纯虚函数
	virtual void ViewAcct()const = 0;	//查询,纯虚函数
	virtual ~BaseBank() {};	//虚的析构函数
};

class Brass:public BaseBank
{
public:
	Brass(string na = "no body", int id = -1, double mo = 0);	//创建账户
	virtual void Withdraw(double mo);	//取款
	virtual void ViewAcct()const;	//显示账户信息
	virtual ~Brass() {};	//虚析构函数
};

class Brass_plus :public BaseBank
{
	double maxLoan;	//透支上限,loan是贷款的意思
	double rate;	//透支贷款利率
	double owesBank ;	//owes是欠,这个是欠银行多少钱(透支)
public:
	Brass_plus(string na = "no body", long id = -1, double mo = 0.0, double ma = 500, double ra = 0.1125);	//构造函数
	Brass_plus(const Brass& br,double ov_M = 500, double ov_R = 0.11125);
	void ResetLoan(double ov_M);	//设置透支上限
	void ResetRate(double ov_R);	//设置透支利率
	virtual void Withdraw(double mo);	//取款,透支保护
	virtual void ViewAcct()const;	//显示账号信息,更多
	void ResetOwes() { owesBank = 0; }	//设置欠款为0
};
//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);

//BaseBank类,抽象基类
BaseBank::BaseBank(string na, long id, double ba)
{
	name = na;
	acctNum = id;
	balance = ba;
}
void BaseBank::SaveMoney(double mo)
{
	if (mo < 0)
		cout << "你不能存入小于0的金钱。" << endl;
	else
	{
		balance += mo;
		cout << "存款成功。" << endl;
	}
}
void BaseBank::Withdraw(double mo)
{
	balance -= mo;
}
BaseBank::Formatting BaseBank::setFormat()const	//设置小数显示2位
{
	Formatting f;
	f.flag = cout.setf(std::ios_base::fixed, std::ios_base::floatfield);	//设置为显示小数形式(这里是6位小数)
	f.pr = cout.precision(2);	//cout.precision(2)表示从这行开始显示2位小数,
								//且其值为6(推测是因为之前是显示6位小数),因此相当于streamsize f.pr=6;(意味着f.pr=6)
	return f;	//返回结构对象
}
void BaseBank::Restore(Formatting &f)const	//函数作用是显示6位小数
{
	cout.setf(f.flag, std::ios_base::floatfield);
	cout.precision(f.pr);	//由于f.pr=6,因此从这行开始,显示6位小数
}

//Brass类,抽象基类的派生类
Brass::Brass(string na, int id, double mo):BaseBank(na,id,mo)	//构造函数
{
}
void Brass::Withdraw(double mo)	//取款
{
	if (mo < 0)
		cout << "你不能取出小于0的金钱。" << endl;
	else if (mo>Balance())
		cout << "余额不足。" << endl;
	else
		BaseBank::Withdraw(mo);		//表示使用基类的Withdraw方法
		cout << "取款成功。" << endl;
}
void Brass::ViewAcct()const
{
	Formatting f = setFormat();
	cout << "————账号信息显示(储蓄卡)————" << endl;
	cout << "用户名:" << Name() << endl;
	cout << "账  号:" << AcctNum() << endl;
	cout << "余  额:" << Balance() << "元" << endl;
	cout << "———————————————————" << endl;
	Restore(f);
}

//Brass_plus类
Brass_plus::Brass_plus(const Brass& br, double lo, double ra):BaseBank(br)	//构造函数,使用Brass类参数
{
	maxLoan = lo;
	rate = ra;
	owesBank = 0;
}
Brass_plus::Brass_plus(string na, long id, double mo, double lo, double ra):BaseBank(na,id,mo)	//构造函数,全参数
{
	maxLoan = lo;
	rate = ra;
	owesBank = 0;
}
void Brass_plus::ResetRate(double ra)	//设置透支利率
{
	Formatting f = setFormat();
	if (ra < 0)
		cout << "设置失败,不能设置为负数。" << endl;
	else
	{
		rate = ra;
		cout << "设置成功,新的利率为:" << rate * 100 << "%" << endl;
	}
	Restore(f);
}
void Brass_plus::ResetLoan(double ma)	//设置透支上限
{
	if (ma < 0)
	{
		cout << "设置失败,不能设置为负数。" << endl;
	}
	else
	{
		maxLoan = ma;
		cout << "设置成功,新的透支上限为:" << maxLoan << "元" << endl;
	}
}

void Brass_plus::ViewAcct()const	//显示账号信息,more
{
	Formatting f = setFormat();
	cout << "————账号信息显示(储蓄卡)————" << endl;
	cout << "用户名:" << Name() << endl;
	cout << "账  号:" << AcctNum() << endl;
	cout << "余  额:" << Balance() << "元" << endl;
	cout << "账户透支上限:" << maxLoan << " 元" << endl;
	cout << "透支偿还利率:" << rate * 100 << " %" << endl;
	cout << "当前透支额度为:" << owesBank << " 元" << endl;
	cout << "———————————————————" << endl;
	Restore(f);
}
void Brass_plus::Withdraw(double mo)	//取款,带有透支保护
{
	Formatting f = setFormat();
	double MO = Balance();
	if (mo <MO)	//不涉及透支的取款
	{
		BaseBank::Withdraw(mo);
	}
	else if (mo>MO+maxLoan-owesBank)	//透支程度大于限额
		cout << "超出限额,取款失败。" << endl;
	else
	{
		owesBank += mo - MO;
		BaseBank::Withdraw(MO);	//先取光余额
		cout << "取款成功,余额为:" << MO << ",透支额为:" << owesBank << " 元,最大透支额为: " << maxLoan << "元" << endl;
	}
	Restore(f);
}
//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.SaveMoney(a);
			break;
		case'l':cout << "输入取款金额:";
			cin >> a;
			two.Withdraw(a);
			break;
		case'c':two.ViewAcct();
			break;
		default:cout << "输入错误。" << endl;
			cin.clear();
			cin.sync();
			break;
		}
		cout << "s.存\tl.取.\tc.查询\tq.退出\n选择->";
	}
	cout << "设置利率(%):";
	double LiLv;
	cin >> LiLv;
	LiLv /= 100;
	two.ResetRate(LiLv);
	cout << "设置最大透支额度:";
	double Max;
	cin >> Max;
	two.ResetLoan(Max);
	cout << "再次查看账户信息:";
	two.ViewAcct();
	cout << "Done." << endl;
	system("pause");
	return 0;
}

总结:

①这个代码和之前的代码,主要是增添了抽象基类,更改了一些类定义,增添了protected保护方法。

 

②更改了显示的方法。 struct Formatting

{

std::ios_base::fmtflags flag;

std::streamsize pr;

};

 

而显示方法的2个类型被放在保护成员(protected)范围内,因此,其派生类BrassBrass_plus都可以直接访问。

注:以下两个都不是很明白。

ios_base::fmtflag是 用于指定输出外观的常数。它作为类型时,可以存储输出格式,比如ios_base::fixed,ios_base::floatfield以及其他

更多可见:https://msdn.microsoft.com/zh-cn/library/d2a1929w.aspx

http://www.cplusplus.com/reference/ios/ios_base/fmtflags/

 

streamsize表示流的大小(不懂),

参见:http://www.cplusplus.com/reference/ios/streamsize/

 

推测:代码:f.flag = cout.setf(std::ios_base::fixed, std::ios_base::floatfield);

f.flag存储了std::ios_base::fixed这个指令。

而cout.setf(f.flag, std::ios_base::floatfield); 就相当于用f.flag替代了std::ios_base::fixed 。

 

另外,也可以这个结构Formatting和两个函数(setFormat()和Restore()放在名称空间之中,然后使用的时候使用其名称空间即可。例如放在Namespace qqq中,然后qqq::Formatting f=qqq::setFormat()这样。

 

③由于BrassBrass_plus都是根据基类BaseBank派生而来的,因此Brass_plus并不能使用Brass的类方法,只能使用抽象基类中二者公有的方法。

 

 

④当调用基类方法时,使用BaseBank::方法名 的形式,来使用。例如:BaseBank::Withdraw(mo)来调用方法,由于加了类名,因此是该类的方法。

 

⑤当使用指针时,应该使用BaseBank*作为指针类型。只有这样,才能同时指向两个派生类。

 

 

 

ABC理念:

在设计ABC(抽象基类)前,首先应开发一个模型——指出编程问题所需的类以及他们之间相互关系。

一种学院派思路认为,如果要设计类继承层次,则只能将那些不会被用作基类的类设计为具体的类,这种方法的设计更清晰,复杂程度更低。——不懂

 

可以将ABC类看做是一种必须实施的接口。ABC要求具体派生类覆盖其虚函数——迫使派生类遵循ABC设置的接口规则。这种模型在基于组件的编程模式中很常见,在这种情况下,使用ABC 使得组件设计人员能够制定“接口约定”,这样确保了从ABC派生的所有组件,都至少支持ABC指定的功能。

上面这句话大概意思是:把抽象基类的几个功能,设置为纯虚函数,于是,如果要派生,那么必须在派生类里面具体化这些功能(于是这些功能必然有),否则派生类也有纯虚函数(因为没设计就没法覆盖)。

 

 

 


网友评论

登录后评论
0/500
评论
零零水
+ 关注