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