ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

抽象基类ABC

2021-11-08 11:31:40  阅读:96  来源: 互联网

标签:std ABC const cout double void 抽象 基类 AcctABC


Ellipse类

开发一个图形程序,显示圆和椭圆。圆是椭圆的一个特殊情况,长轴和短轴等长的椭圆,因此,所有的圆都是椭圆,可以从Ellipse类派生出Circle类。数据成员包括椭圆中心的坐标、半长轴、短半轴、方向角。还包括一些移动椭圆、返回椭圆面积、旋转椭圆、缩放长半轴和短半轴方法。

class Ellipse
{
private:
	double x; //椭圆中心x坐标
	double y; //椭圆中心y坐标
	double a; //半长轴
	double b; //短半轴
	double angle; //方向角
	...

public:
	void Move(int nx, int ny) { x = nx; y = ny; }
	virtual double Area() const { return 3.14159 * a * b; }
	virtual void Rotable(double nang) { angle += nang; }
	virtual void Scale(double sa, double sb) { a *= sa; b *= sb; }
	...
};

现在从Ellipse类派生出一个Circle类:

class Circle : public Ellipse
{
	...
};

虽然圆是一种椭圆,但是这种派生是笨拙的。例如,圆只需要一个值(半径)就可以描述大小和形状,并需要有长半轴、短半轴。Circle构造函数可以通过将同一个值赋给成员a和b来照顾这种情况,但这会导致信息冗余。angle参数和Rotate()方法对圆来说没有实际意义;而Scale()方法会将两个轴作不同的缩放,将圆变成椭圆。可以使用一些技巧来修正这些问题,例如在Circle类中的私有部分包含重新定义的Rotate()方法,使Rotate()不能以公有方式用于圆。但总的来说,不能使用继承,直接定义Circle类更简单:

class Circle : public Ellipse
{
private:
	double x;
	double y;
	double r;
	...

public:
	...
	void Move(int nx, int ny) { x = nx; y = ny; }
	double Area() const { return 3.14159 * r * r; }
	void Scale(double sr) { r *= sr; }
	...
};

类只包含所需的成员。但这种解决方法的效率也并不高。Circl和Ellipse类有很多的共同点,将他们分别定义则忽略这个事实。

还有一种解决放,从Ellipse和Circle类中抽象出它们的共性,将这些特性放到一个ABC中。然后从该ABC派生出Circle和Ellipse类。便可以使用基类指针数组同时管理Circle和Ellipse对象,即可以使用多态方法。这两个类的共同点是中心坐标、Move()方法(对于这两个类是相同的)和Area()方法(对于这两个类来说,是不同的)。甚至不能在ABC中实现Area()方法,因为它没有包含必要的数据成员。

C++通过使用纯虚函数提供未实现的函数。纯虚函数的声明的结尾处为=0。

class BaseEllipse //抽象基类
{
private:
	double x;
	double y;
	...

public:
	BaseEllipse(double x0 = 0, double y0 = 0) : x(x0),y(y0) {}
	virtual ~BaseEllipse() {}
	void Move(int nx, int ny) { x = nx; y = ny; }
	virtual double Area() const = 0; //纯虚函数
	...
};

当类声明中包含纯虚函数时,则不能创建该类的对象。包含纯虚函数的类只用作基类。要成为真正的ABC,必须至少包含一个纯虚函数。原则中=0使虚函数成为纯虚函数。这里的方法Area()没有定义,但C++甚至允许纯虚函数有定义。

例如,所有的基类方法都与Move()一样,可以在基类中进行定义,但仍需要将这个类声明为抽象的。这种情况下,可以将原型声明为虚的:

void Move(int nx, int ny) = 0;

这将使基类成为抽象的,但仍可以实现文件中提供方法的定义:

void BaseEllipse::Move(int nx, int ny) { x = nx; y = ny; }

在原型中使用=0指出类是一个抽象基类,在类中可以不定义该函数。

可以从BaseEllipse类派生出Ellipse类和Circle类,添加所需的成员来完成每个类。需要注意的是,Circle类总是表示圆,而Ellipse类总是表示椭圆。然而,Ellipse类圆可被重新缩放为非圆,而Ciecle类必须始终为圆。

这些类的程序能够创建Ellipse对象和Circle对象,但是不能创建BaseEllipse对象。由于Circle和Ellipse对象的基类相同,因此可以用BaseEllipse指针数组同时管理这两种对象。像Circle和Ellipse这样的类有时被称为具体类,这表示可以创建这些类型的对象。

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

ABC

首先定义一个名为AcctABC的ABC。这个类包含Brass和BrassPlus类共有的所有方法和数据成员,而那些在BrassPlus类和Brass类中的行为不同的方法应被声明为虚函数。至少应有一个虚函数是纯虚函数,这样才能使AcctABC成为抽象类。

acctabc.h

为帮助派生类访问基类数据,AcctABC提供一些保护方法;派生类方法可以调用这些方法,但它们并不是派生类对象的公有接口的组成部分。AcctABC提供了一个保护成员函数,用于处理格式化。另外AcctABC类还有两个纯虚函数,所以它确实是抽象类。

#ifndef ACCTABC_H_
#define ACCTABC_H_
#include <iostream>
#include <string>

//抽象基类
class AcctABC
{
private:
	std::string fullName;
	long acctNum;
	double balance;

protected:
	struct Formatting
	{
		std::ios_base::fmtflags flag;
		std::streamsize pr;
	};
	const std::string & FullName() const { return fullName; }
	long AcctNum() const { return acctNum; }
	Formatting SetFormat() const;
	void Restore(Formatting & f) const;

public:
	AcctABC(const std::string & s = "Nullbody", long an = -1, double bal = 0.0);
	void Deposit(double amt);
	virtual void Withdraw(double amt) = 0;
	double Balance() const { return balance; };
	virtual void ViewAcct() const = 0;
	virtual ~AcctABC() {}
};

//Brass Account 类
class Brass :public AcctABC
{
public:
	Brass(const std::string & s = "Nullbody", long an = -1, double bal = 0.0) : AcctABC(s, an, bal) {}
	virtual void Withdraw(double amt);
	virtual void ViewAcct() const;
	virtual ~Brass() {}
};

//Brass Plus Account 类
class BrassPlus :public AcctABC
{
private:
	double maxLoan;
	double rate;
	double owesBank;

public:
	BrassPlus(const std::string & s = "Nullbody", long an = -1, double bal = 0.0, double ml = 500, double r = 0.10);
	BrassPlus(const Brass & ba, double ml = 500, double r = 0.1);
	virtual void ViewAcct() const;
	virtual void Withdraw(double amt);
	void ResetMax(double m) { maxLoan = m; }
	void ResetRate(double r) { rate = r; }
	void ResetOwes() { owesBank = 0; }
};

#endif

acctabc.cpp

#include <iostream>
#include "acctabc.h"
using std::cout;
using std::ios_base;
using std::endl;
using std::string;

AcctABC::AcctABC(const std::string & s, long an, double bal)
{
	fullName = s;
	acctNum = an;
	balance = bal;
}

void AcctABC::Deposit(double amt)
{
	if (amt < 0)
		cout << "Negative deposit not allowed; "
		<< "deposit is cancelled.\n";
	else
		balance += amt;
}

void AcctABC::Withdraw(double amt)
{
	balance -= amt;
}

AcctABC::Formatting AcctABC::SetFormat() const
{
	Formatting f;
	f.flag = cout.setf(ios_base::fixed, ios_base::floatfield);
	f.pr = cout.precision(2);
	return f;
}

void AcctABC::Restore(Formatting & f) const
{
	cout.setf(f.flag, ios_base::floatfield);
	cout.precision(f.pr);
}

void Brass::Withdraw(double amt)
{
	if (amt < 0)
		cout << "Withdrawal amount must be positive; "
		<< "withdrawal canceled.\n";
	else if (amt <= Balance())
		AcctABC::Withdraw(amt);
	else
		cout << "Withdrawal amount of $" << amt
		<< " exceeds your balance.\n"
		<< "Withdrawal canceled.\n";
}

void Brass::ViewAcct() const
{
	Formatting f = SetFormat();
	cout << "Brass Client: " << FullName() << endl;
	cout << "Account Number: " << AcctNum() << endl;
	cout << "Balance: $" << Balance() << endl;
	Restore(f);
}

BrassPlus::BrassPlus(const string & s, long an, double bal, double ml, double r) : AcctABC(s, an, bal)
{
	maxLoan = ml;
	owesBank = 0.0;
	rate = r;
}

BrassPlus::BrassPlus(const Brass & ba, double ml, double r) : AcctABC(ba)
{
	maxLoan = ml;
	owesBank = 0.0;
	rate = r;
}

void BrassPlus::ViewAcct() const
{
	Formatting f = SetFormat();
	cout << "Brass Client: " << FullName() << endl;
	cout << "Account Number: " << AcctNum() << endl;
	cout << "Balance: $" << Balance() << endl;
	cout << "Maximum loan: $" << maxLoan << endl;
	cout << "Owed to bank: $" << owesBank << endl;
	cout.precision(3);
	cout << "Loan Rate: " << 100 * rate << "%\n";
	Restore(f);
	Restore(f);
}

void BrassPlus::Withdraw(double amt)
{
	Formatting f = SetFormat();
	double bal = Balance();
	if (amt <= bal)
		AcctABC::Withdraw(amt);
	else if (amt <= bal + maxLoan - owesBank)
	{
		double advance = amt - bal;
		owesBank += advance * (1.0 + rate);
		cout << "Bank advance: $" << advance << endl;
		cout << "Finance charge: $" << advance * rate << endl;
		Deposit(advance);
		AcctABC::Withdraw(amt);
	}
	else
		cout << "Credit limit exceeded. Transaction cancelled.\n";
	Restore(f);
}

保护方法FullName()和AcctNum()提供了对数据成员fullName和acctNum的只读访问,使得可以进一步定制每个派生类的ViewAcct()。

在设置输出格式进行改进,定义了一个结构,用于存储两项格式设置;并使用该结构来设置和恢复格式,因此只需两个函数调用:

struct Formatting
{
	std::ios_base::fmtflags flag;
	std::streamsize pr;
};
....
Formatting f = SetFormat();
...
Restore(f);

将这些函数以及结构Formatting放在一个独立的名称空间中,但它们是保护访问权限,因此将这些结构和函数放在了类定义的保护部分。这使得它们对基类和派生类可用,同时向外隐藏了它们。

main.cpp

#include <iostream>
#include <string>
#include "acctabc.h"

const int CLIENTS = 4;

int main()
{
	using std::cin;
	using std::cout;
	using std::endl;

	AcctABC * p_clients[CLIENTS];
	std::string temp;
	long tempnum;
	double tempbal;
	char kind;

	for (int i = 0; i < CLIENTS; i++)
	{
		cout << "Enter client's name: ";
		getline(cin, temp);
		cout << "Enter client's account number: ";
		cin >> tempnum;
		cout << "Enter opening balance: $";
		cin >> tempbal;
		cout << "Enter 1 for Brass Account or "
			<< "2 for BrassPlus Account: ";
		while (cin >> kind && (kind != '1' && kind != '2'))
			cout << "Enter either 1 or 2: ";
		if (kind == '1')
			p_clients[i] = new Brass(temp, tempnum, tempbal);
		else
		{
			double tmax, trate;
			cout << "Enter the overdraft limit: $";
			cin >> tmax;
			cout << "Enter the interest rate "
				<< "as a decimal fraction: ";
			cin >> trate;
			p_clients[i] = new BrassPlus(temp, tempnum, tempbal, tmax, trate);
		}
		while (cin.get() != '\n')
			continue;
	}
	cout << endl;
	for (int i = 0; i < CLIENTS; i++)
	{
		p_clients[i]->ViewAcct();
		cout << endl;
	}
	for (int i = 0; i < CLIENTS; i++)
	{
		delete p_clients[i];
	}
	cout << "Done.\n";
	return 0;
}

在这里插入图片描述

标签:std,ABC,const,cout,double,void,抽象,基类,AcctABC
来源: https://blog.csdn.net/qq_36314864/article/details/121168700

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有