(一四一)抽象基类

抽象基类(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指定的功能。

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

 

 

 

时间: 2024-07-29 20:18:34

(一四一)抽象基类的相关文章

C++中的纯虚函数(pure virtual) 和抽象基类(abstract base class)

纯虚函数(pure virtual), 是一个基类中的方法, 仅仅是声明, 而不包括定义, 是一个泛化概念(general concept); 是把相应的虚函数, 末尾添加"=0",该虚函数就变为纯虚函数, 可以不用添加定义; 如果是其他虚函数, 即使不使用, 也必须定义(define); 包含纯虚函数的基类, 是抽象基类(abstract base class),不能定义(define)对象(object), 仅可以作为继承使用; 代码: /* * CppPrimer.cpp * *

thrift的TTransport类体系原理及源码详细解析1-类结构和抽象基类

本章主要介绍Thrift的传输层功能的实现,传输的方式多种多样,可以采用压缩.分帧等,而这些 功能的实现都是相互独立,和上一章介绍的协议类实现方式比较雷同,还是先看看这部分的类关系图, 如下: 由上面的类关系图可以看出,这部分的功能是相当的强大,所以类比较多且关系错综复杂.但是如 果理解清楚了这些类直接的关系就很容易掌握这部分的实现技术和这部分实现的功能.我们把这个类关 系图分为三部分来看,第一部分看抽象基类TTransport类,它是所有传输类的基类,有很大一部分类直 接从它继承实现它提供或者

python抽象基类用法实例分析

  本文实例讲述了python抽象基类用法.分享给大家供大家参考.具体如下: 定义抽象类,需要使用abc模块,该模块定义了一个元类(ABCMeata),和装饰器 @abstractmethod, @abstractproperty 如果要实例化继承了Foo 的子类,子类必须实现了Foo所有的抽象方法(跟java一样),否则实例化报错. 抽象类不能直接实例化 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #!coding=utf-8 from abc

构建自己的PHP框架--抽象Controller的基类

我们将简单的路由解析和执行,从入口文件public/index.php中移入到框架中.入口文件顿时变得清爽无比--   但是,去我们的controller里看一下,会看到如下的code:       public function actionView()     {         $body = 'Test body information';         require '../views/site/view.php';     } 难道我们每写一个要去渲染页面的action,都要去找

thrift的TServer类体系原理及源码详解:服务器基类TServer

这一部分主要实现了底层IO通信,还涉及到通信服务器的堵塞.非堵塞.单线程.多线程 等运行模式,所以实现比较复杂.这一部分涉及到的类的关系图如下: 由上面的 类关系图可以看出,这一部分的类关系比较复杂,复杂的不是继承关系,而是相互之间的依 赖关系.因为服务器需要处理很多的任务,也需要处理多个客户端的连接,这就涉及到多线 程编程以及多线程之间通信及并发的情况.这一部分涉及到的并发编程的类容将在后面章节 单独分析,本章主要介绍服务器模型和IO通信的具体细节. 第一节 服务器基类 TServer 所有具

《Python面向对象编程指南》——第1部分 用特殊方法实现Python风格的类 第1章 __init__()方法 1.1 隐式的基类——object

第1部分 用特殊方法实现Python风格的类 init()方法 与Python无缝集成--基本特殊方法 属性访问.特性和修饰符 抽象基类设计的一致性 可调用对象和上下文的使用 创建容器和集合 创建数值类型 装饰器和Mixins--横切方面 用特殊方法实现 Python风格的类 通过重写特殊方法来完成对Python内部机制的调用,在Python中是很普遍的.例如len()函数就可以重写一个类的__len__()方法. 这意味着对于像(len(x))这样的通用公共接口,任何类(例如,声明一个类叫ti

第13周-任务3-抽象基类Shape及派生类Circle、Rectangle和Triangle

[题目]写一个程序,定义抽象基类Shape,由它派生出3个派生类,Circle(圆形).Rectangle(矩形).Triangle(三角形).用如下的mian()函数,求出定义的几个几何体的面积和. int main() { Circle c1(12.6),c2(4.9); //建立Circle类对象c1,c2,参数为圆半径 Rectangle r1(4.5,8.4),r2(5.0,2.5); //建立Rectangle类对象r1,r2,参数为矩形长.宽 Triangle t1(4.5,8.4

Microsoft .NET 中的基类继承

接口继承 创建抽象类时,请使用关键字 Interface 而不是 Class.为接口命名,然后定义需要子类实现的所有属性和方法.这是因为基类中没有可以实现的属性和方法,它只包含一般数据,而不包含方法.您所创建的只是一个合约,它规定所有使用此接口的子类都必须遵循一定的规则. 1.        现在,请在已创建的项目中添加一个新类. 2.        从 Visual Studio 菜单中,单击 Project(项目),然后单击 Add Class(添加类). 3.        在类中添加以下

基类构造函数和初始化器的执行顺序

标题比较抽象,所以我写了一个Demo来说明问题: public class A { public A() { Console.WriteLine("A的构造函数被调用"); } } public class B : A { private X x = new X(); //初始化器 } public class X { public X() { Console.WriteLine("X的构造函数调用"); } } static void Main(string[]