(一四一)抽象基类

来源:互联网 发布:会计网络课程 编辑:程序博客网 时间:2024/05/10 13:16

抽象基类(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;elseBaseBank::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 0
原创粉丝点击