抽象基类(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)范围内,因此,其派生类Brass和Brass_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()这样。
③由于Brass和Brass_plus都是根据基类BaseBank派生而来的,因此Brass_plus并不能使用Brass的类方法,只能使用抽象基类中二者公有的方法。
④当调用基类方法时,使用BaseBank::方法名 的形式,来使用。例如:BaseBank::Withdraw(mo)来调用方法,由于加了类名,因此是该类的方法。
⑤当使用指针时,应该使用BaseBank*作为指针类型。只有这样,才能同时指向两个派生类。
ABC理念:
在设计ABC(抽象基类)前,首先应开发一个模型——指出编程问题所需的类以及他们之间相互关系。
一种学院派思路认为,如果要设计类继承层次,则只能将那些不会被用作基类的类设计为具体的类,这种方法的设计更清晰,复杂程度更低。——不懂
可以将ABC类看做是一种必须实施的接口。ABC要求具体派生类覆盖其虚函数——迫使派生类遵循ABC设置的接口规则。这种模型在基于组件的编程模式中很常见,在这种情况下,使用ABC
使得组件设计人员能够制定“接口约定”,这样确保了从ABC派生的所有组件,都至少支持ABC指定的功能。
上面这句话大概意思是:把抽象基类的几个功能,设置为纯虚函数,于是,如果要派生,那么必须在派生类里面具体化这些功能(于是这些功能必然有),否则派生类也有纯虚函数(因为没设计就没法覆盖)。