# 银行管理系统

开源注释详解:Github

# 功能

  • 添加账户
    • 储蓄账户
    • 信用卡账户
  • 存款
  • 取款
  • 当前账户余额
  • 指定时间内的账目信息
    • 利息
  • 本地化存储,二次启动同步
  • 所有账户总金额
  • 退出

# 数据存储思路

将每次运行的代码存储到本地 txt 文件当中,每次启动可以首先初始化文件内容,已达到文件储存的目的

# 缺点

  • 数据量过大数据初始化难
  • 业务逻辑越复杂也越难以保存
  • 用户交互需要按照文件保存书写指令,十分不便

或许可以使用 MySQL 数据库存储数据,用 C++ 与 MySQL 进行交互,达到相关查询,存储的功能。

# 类图呈现

银行管理系统类关系图

# 各文件功能

  • date.h 日期类头文件
  • date.cpp 日期类实现文件
  • account.h 账户类定义头文件
  • account.cpp 账户类实现文件以及记录
  • accumulator.h 对数据进行累加的相关计算
  • main.cpp 主函数文件
  • Array.h 数组模版的头文件
  • Controller 类保存账户列表,当前日期和处理指定命令

# 详解源代码

# date.h

#pragma once
/* 日期类头文件,对日期类文件进行处理 */
#ifndef _DATE_H_
#define _DATE_H_
#include <iostream>
class Date   // 日期类
{
private:
	int year;  // 年
	int month; // 月
	int day;  // 日
	int totalDays; // 该日期是从公元元年 1 月 1 日开始的第几天
public:
	Date(int year=1, int month=1, int day=1); // 用年,月,日构造日期
	// 专门从 cin 读入一个日期,赋值查询历史账目功能
	//static Date read();
	// 获取 Date 日期年份
	int getYear() const
	{
		return year;
	}
	// 获取 Date 日期月份
	int getMonth() const
	{
		return month;
	}
	// 获取 Date 日期天
	int getDay() const
	{
		return day;
	}
	// 获取 Date 当月的天数
	int getMaxDay() const;
	// 判断 Date 日期是否是闰年
	bool isLeapYear() const  // 判断是否为闰年
	{
		return year % 4 == 0 && year * 100 != 0 || year % 400 == 0;
	}
	// 输出 Date 日期
	void show() const;
	// 输出 Data 从公元元年 1 月 1 日到目前 date 的天数
	int distance(const Date& date) const
	{
		return totalDays - date.totalDays;
	}
	// 重载 Date 之前的 “-” 号,用公元元年到目前的日期相减
	int operator-(const Date& date) const
	{
		return totalDays - date.totalDays;
	}
	// 重载 Date 之间的 “<" 号,用永远元年到目前 date 的日期计算
	bool operator<(const Date& date) const
	{
		return totalDays < date.totalDays;
	}
};
// 重载 Date 类输入,格式化日期格式:year-month-day
std::istream& operator >>(std::istream& in, Date& date);
// 重载 Date 类输出,格式化日期格式:year-month-day
std::ostream& operator<<(std::ostream& out, const Date& date);
#endif // !_DATE_H_

# date.cpp

/* 日期类实现函数 */
#include "date.h"
#include <iostream>
#include <cstdlib>
#include <stdexcept>  // 标准库表头,此头文件是错误处理库的一部分
using namespace std;
namespace  // 使用 namespace 使下面的定义只能在当前文件中有效
{
	// 存储平年中每个月 1 日之前有多少天,为便于 getMaxDay 函数的实现,
	const int DAYS_BEFORE_MONTH[] = { 0,31,59,90,120,151,181,212,143,273,304,334,365 };
}
Date::Date(int year, int month, int day) :year(year), month(month), day(day)
{
	if (day <= 0 || day > getMaxDay())
	{
		throw runtime_error("Invalid date"); //Date 发生异常,直接使用标准程序库构造异常抛出
	}
	// 减一是为了计算总天数,会加上月份和天数,年份日要减一
	int years = year - 1;
	// 计算总天数
	totalDays = years * 365 + years / 4 - years / 100 + years / 400 + DAYS_BEFORE_MONTH[month - 1] + day;
	// 如果是瑞年并且月份大约 2,总天数 + 1
	if (isLeapYear() && month > 2)
		totalDays++;
}
// 返回这月最大天数
int Date::getMaxDay() const
{
	if (isLeapYear() && month == 2)
	{
		return 29;
	}
	else
	{
		return DAYS_BEFORE_MONTH[month] - DAYS_BEFORE_MONTH[month - 1];
	}
}
// 输出 Date 的年 - 月 - 日
void Date::show() const
{
	cout << getYear() << "-" << getMonth() << "-" << getDay();
}
istream& operator>>(istream& in, Date& date) //istream 通用输入流
{
	int year, month, day;
	char c1, c2;
	cout << "类似输入格式为:2019-01-01" << endl;
	in >> year >> c1 >> month >> c2 >> day;
	// 对输入进行判断,若没有按照格式,返回错误
	if (c1 != '-' || c2 != '-')
		// 可用于运行时检测的异常类
		throw runtime_error("Bad time format");
	// 将所得的正确日期返回给 date,指针
	date = Date(year, month, day);
	// 返回用户的输入 此处
	return in;
}
// 虫重载输出流,使 date 按照标准格式输出
ostream& operator << (ostream& out, const Date& date) //ostream 通用输出流
{
	out << date.getYear() << "-" << date.getMonth() << "-" << date.getDay();
	return out;
}

# account.h

#pragma once
/* 银行账户类头文件 */
#ifndef _ACCOUNT_H_
#define _ACCOUNT_H_
#include "date.h"
#include "accumulator.h"
#include <string>  // 字符串头文件
#include <istream>
#include <map>  // 引出关联容器
#include <stdexcept>
// 前置声明
class Account;
// 账目记录类
class AccountRecord
{
private:
	// 日期
	Date date;
	// 账户
	const Account* account;
	// 账户金额
	double amount;
	// 余额
	double balance;
	// 描述
	std::string desc;
public:
	// 构造函数
	AccountRecord(const Date& date, const Account* account, double amount, double balance, const std::string& desc);
	// 输出当前记录
	void show() const;
};
// 创建关联容器,建立日期和账户记录类的已排序列表
typedef std::multimap<Date, AccountRecord>RecordMap;
// 账户父类
class Account
{
public:
	std::string id; // 账户 Id 字符串
	double balance;  // 账户余额
	static double total;  // 所有账户的总金额静态变量
	static RecordMap recordMap;  // 账目记录
protected:
	// 提供派生类调用的构造函数:日期,id 为账户
	Account(const Date& date, const std::string& id);
	// 记录一笔账,date 为日期,amount 为金额,desc 为说明
	void record(const Date& date, double amount, const std::string& desc);
	// 报告错误信息
	void error(const std::string& msg) const;
public:
	// 获取账户 id
	const std::string& getId() const
	{
		return id;
	}
	// 获取账户余额
	double getBalance() const
	{
		return balance;
	}
	// 获取账户总金额
	static double getTotal()
	{
		return total;
	}
	// 纯虚函数,没有具体的操作内容,要求各派生类根据需要定义自己的版本
	// 存入现金,date 为日期,amount 为金额,desc 为款项说明
	virtual void deposit(const Date& date, double amount, const std::string& desc) = 0;
	// 纯虚函数,没有具体的操作内容,要求各派生类根据需要定义自己的版本
	// 取出现金,date 为日期,amount 为金额,desc 为款项说明
	virtual void withdraw(const Date& date, double amount, const std::string& desc) = 0;
	// 结算(计算利息、年费等),每月结算一次,date 为结算日期
	virtual void settle(const Date& date) = 0;
	// 显示账户信息
	virtual void show(std::ostream& out) const;
	// 查询指定时间内的账目记录,开始 Date,结束 Date
	static void query(const Date& begin, const Date& end);
};
// 内联重载:将 Account 输出调用 Account.show ()
inline std::ostream& operator<<(std::ostream& out, const Account& account)
{
	account.show(out);
	return out;
}
class SavingsAccount : public Account  // 存储账户类
{
private:
	Accumulator acc;  // 辅助计算利息的累加器
	double rate;	// 存款的年利率
public:
	// 储蓄账户,日期 Date, 账户 Account 的 id,存款利率
	SavingsAccount(const Date &date, const std::string &id, double rate);
	// 获取储蓄账户的存款利率
	double getRate() const
	{
		return rate;
	}
	// 存钱:参数:日期 Date, 金额,描述信息
	void deposit(const Date &date, double amount,const std::string &desc);// 存入现金
	// 取款:参数:日期 Date, 金额,描述信息
	void withdraw(const Date &date, double amount,const std::string &desc);// 取出现金
	// 结算利息:参数:Date 日期
	void settle(const Date &date);
};
class CreditAccount : public Account  // 信用账户类
{
private:
	Accumulator acc; // 辅助计算利息的累加器
	// 信用额度
	double credit;
	double rate;  // 欠款的日利率
	double fee;   // 信用卡年费
	// 获取欠款额
	double getDebt()
	{
		// 获取账户 Account 的余额,
		double balance = getBalance();
		// 如果余额大于 0 不欠钱,返回 0,否则返回 balance
		return (balance < 0 ? balance : 0);
	}
public:  // 构造函数
	// 信用卡类:参数:日期 Date, 账户 Account:id,信用额度,欠款日利率,信用卡年费
	CreditAccount(const Date& date, const std::string& id, double credit, double rate, double fee);
	// 获取信用卡额度
	double getCredit() const
	{
		return credit;
	}
	// 获取信用卡日利率
	double getRate() const
	{
		return rate;
	}
	// 获取信用卡年费
	double getFee() const
	{
		return fee;
	}
	// 返回信用卡还可用额度,若账户余额为负,用信用卡补上
	double getAvailableCredit() const
	{
		if (getBalance() < 0)
			return credit + getBalance();
		else
			return credit;
	}
	// 存入现金:参数:日期 Date, 账户 Account 金额,存款描述
	void deposit(const Date& date, double amount, const std::string& desc);
	// 取出现金,参数:取款日期 Date, 取款金额,取款描述
	void withdraw(const Date& date, double amount, const std::string& desc);
	// 结算利息和年费,参数 Date 日期
	void settle(const Date& date);// 结算利息和年费,每月 1 号调用一次该函数
	// 输出账户 id,账户金额,账户余额
	void show() const;
	// 虚函数输出
	virtual void show(std::ostream& out) const;
};
// 账户异常处理类
class AccountException :public std::runtime_error
{
private:
	// 账户
	const Account* account;
public:
	// 传递运行中的异常信息,以及账户 Account 类
	AccountException(const Account* account, const std::string& msg)
		:runtime_error(msg), account(account)
	{
	}
	// 获得异常账户类
	const Account* getAccount() const
	{
		return account;
	}
};
#endif //_ACCOUNT_H_

# account.cpp

/*
project :Account 账户类实现文件 cpp
*/
#include "account.h"
#include <iostream>
#include <cmath>
#include <utility>
using namespace std;
using namespace std::rel_ops;
//AccountRecord 类的实现:参数日期 Date, 账户 Account 类,金额,余额,描述信息
AccountRecord::AccountRecord(const Date& date, const Account* account, double amount, double balance, const std::string& desc)
	:date(date), account(account), amount(amount), balance(balance), desc(desc)
{
}
// 展现出账户记录类的账户时间,Id,存款金额,余额(算上信用卡),描述信息
void AccountRecord::show() const
{
	//date.show();
	cout <<date<< "\t#" << account->getId() << "\t" << amount << "\t" << balance << "\t" << desc << endl;
}
// 存款总金额全局变量 (所有账户)
double Account::total = 0;
// Account 类中的账户记录
RecordMap Account::recordMap;
//Account 账户类构造函数
Account::Account(const Date& date, const string& id)
	:id(id), balance(0)
{
	//date.show();
	cout << date<<"\t#" << id << " created" << endl;
}
// 记账:参数:Date 时间,amount 金额,记账描述
void Account::record(const Date& date, double amount, const string& desc)
{
	amount = floor(amount * 100 + 0.5) / 100;  // 保留小数点后两位,
	balance += amount; // 余额增加
	total += amount;  // 账户总额增加
	//date.show ();  // 输出时间
	// 将添加的数据存入账目记录类
	AccountRecord record(date, this, amount, balance, desc);
	// 将数据插入到关联容器内部,,date 为主键
	recordMap.insert(make_pair(date, record));
	record.show();
	//cout << "\t#" << id << "\t" << amount << "\t" << balance << "\t" << desc << endl;
}
// 显示账户信息
void Account::show(ostream &out) const
{
	cout << "#" << id << "\tBalance:" << balance;
}
//SavingsAccount 类的实现,参数:Date 时间,Account 的 ID,利率
SavingsAccount::SavingsAccount(const Date& date, const string& id, double rate)  // 时间,账户,利率
	:Account(date, id), rate(rate), acc(date, 0)
{
}
// 存钱:参数:时间,金额,描述信息
void SavingsAccount::deposit(const Date& date, double amount,const string &desc)
{
	record(date, amount,desc); // 记录一笔账
	// 调佣 accumulator 函数,更新值,变更当前的时间,还有值
	acc.change(date, getBalance());
}
// 取钱
void SavingsAccount::withdraw(const Date& date, double amount,const string& desc)
{
	if (amount > getBalance())
	{
		cout << "Error: not enough money" << endl;
	}
	else
	{
		record(date, -amount, desc);
		acc.change(date, getBalance());
	}
}
// 计算利息:传递时间 Date
void SavingsAccount::settle(const Date& date)
{
	double interest = acc.getSum(date) * rate / date.distance(Date(date.getYear()-1,1,1)); // 计算年息
	if (interest != 0)
	{
		// 将利息记账
		record(date, interest,"interest");
	}
	// 初始化,日期,数值,累加器清零
	acc.reset(date, getBalance());
}
//CreditAccount 类默认构造函数的实现
CreditAccount::CreditAccount(const Date& date, const string& id, double credit, double rate, double fee)
	:Account(date,id),credit(credit),rate(rate),fee(fee),acc(date,0)
{
}
// 存入信用卡
void CreditAccount::deposit(const Date& date, double amount, const string& desc)
{
	record(date, amount, desc);
	acc.change(date, getDebt());
}
// 取出信用卡金额
void CreditAccount::withdraw(const Date& date, double amount, const string& desc)
{
	if (amount - getBalance() > credit)
	{
		error("nt enough credit");
	}
	else
	{
		// 使用负数 ,减
		record(date, -amount, desc);
		acc.change(date, getDebt());
	}
}
// 结算信用卡的利息
void CreditAccount::settle(const Date& date)
{
	double interest = acc.getSum(date) * rate;
	if (interest != 0)
		record(date, interest, "interest");
	if (date.getMonth() == 1)
		record(date, -fee, "annual fee");
	acc.reset(date, getDebt());
}
// 输出,包括信用卡额度
void CreditAccount::show(ostream& out) const
{
	Account::show(out);
	cout << "\tAvailable credit:" << getAvailableCredit();
}
// 异常
void Account::error(const string& msg) const
{
	throw AccountException(this, msg);
}
// 账户记录查询实现,参数:开始 Date, 结束 Date
void Account::query(const Date& begin, const Date& end)
{
	// 从开始到结尾遍历输出
	if (begin <= end)
	{
		//lower_bound 返回指向首个不小于给定键的元素的迭代器
		RecordMap::iterator iter1 = recordMap.lower_bound(begin);
		//upper_bound 返回指向首个大于给定键的元素的迭代器
		RecordMap::iterator iter2 = recordMap.upper_bound(end);
		for (RecordMap::iterator iter = iter1; iter != iter2; ++iter)
		{
			iter->second.show();
		}
	}
}

# accumulator.h

/* 将 accumulation.h 按日将数字累加的 Accumulatior 类的头文件 */
#ifndef _ACCUMULATOR_H_
#define _ACCUMULATOR_H_
#include "date.h"
class Accumulator // 将某个数值按日累加
{
private:
	Date lastDate;  // 上次变更数值的日期
	double value;   // 数值的当前值
	double sum;     // 数值按日累加之和
public:
	// 构造函数,date 为 开始累加的日期,value 为初始值
	Accumulator(const Date& date, double value)
		:lastDate(date), value(value), sum(0)
	{
	}
	// 获取日期 date 的累加结果
	double getSum(const Date& date) const
	{
		return sum + value * date.distance(lastDate);
	}
	// 在 date 将数值表更为 value
	void change(const Date& date, double value)
	{
		sum = getSum(date);
		lastDate = date;
		this->value = value;
	}
	// 初始化,将日期变为 date,数值变为 value,累加器清零
	void reset(const Date& date, double value)
	{
		lastDate = date;
		this->value = value;
		sum = 0;
	}
};
#endif // !_ACCUMULATOR_H_

# main.cpp

/*
Project: 存款类主函数文件
environment:Visual studio 2022
*/
#include "account.h"
#include "Array.h"
#include <iostream>
#include <vector>
#include <algorithm>
#include <fstream>
#include <sstream>
#include <string>
using namespace std;
// 释放空间结构体,用于析构函数
struct deleter
{
	template<class T>void operator() (T* p)
	{
		delete p;
	}
};
// 建立类,用来存储账户列表和处理命令
class Controller
{
private:
	Date date;  // 当前日期
	vector<Account*>accounts; // 账户列表
	bool end;  // 用户是够输入了退出命令
public:
	// 存储用户数据,参数:Date 时间
	Controller(const Date& date) :date(date), end(false)
	{
	}
	// 析构函数
	~Controller();
	// 返回当前的时间
	const Date& getDate() const
	{
		return date;
	}
	//bool 值,返回是否结束
	bool isEnd() const
	{
		return end;
	}
	// 执行一条命令,返回该命令是否改变了当前状态 (即是否需要保存该命令)
	bool runCommand(const string& cmdLine);
};
// 释放从账户开始到结束的所有内存
Controller::~Controller()
{
	//for_each 遍历:开始,结束,执行
	// 按照顺序应用给定的函数对象 f 到接引到范围 [first,last] 中每个迭代器的结果
	for_each(accounts.begin(), accounts.end(), deleter());
}
bool Controller::runCommand(const string& cmdLine)
{
	/*
	* 指令内容
	* a s S3755217 0.015
	* a s 02342342 0.015
	* a c C5392394 10000 0.0005 50
	* c 5
	*/
	// 读取是有顺序要求的,根据指针向后,异常赋值
	// 第一个赋值给 cmd 判断进入那个 case 开面
	// 后面根据不同 case 依次赋值
	istringstream str(cmdLine);
	char cmd, type;
	int index, day;
	double amount, credit, rate, fee;
	string id, desc;
	Account* account;
	Date date1, date2;
	str >> cmd;
	switch (cmd)
	{
	case 'a':	// 增加账户
		str >> type >> id;
		if (type == 's')
		{
			str >> rate;
			account = new SavingsAccount(date, id, rate);
		}
		else
		{
			str >> credit >> rate >> fee;
			account = new CreditAccount(date, id, credit, rate, fee);
		}
		accounts.push_back(account);
		return true;
	case 'd':	// 存入现金
		str >> index >> amount;
		getline(str, desc);
		accounts[index]->deposit(date, amount, desc);
		return true;
	case 'w':	// 取出现金
		str >> index >> amount;
		getline(str, desc);
		accounts[index]->withdraw(date, amount, desc);
		return true;
	case 's':	// 查询各账户信息
		for (size_t i = 0; i < accounts.size(); i++)
			cout << "[" << i << "] " << *accounts[i] << endl;
		return false;
	case 'c':	// 改变日期
		str >> day;
		if (day < date.getDay())
			cout << "You cannot specify a previous day";
		else if (day > date.getMaxDay())
			cout << "Invalid day";
		else
			date = Date(date.getYear(), date.getMonth(), day);
		return true;
	case 'n':	// 进入下个月
		if (date.getMonth() == 12)
			date = Date(date.getYear() + 1, 1, 1);
		else
			date = Date(date.getYear(), date.getMonth() + 1, 1);
		for (vector<Account*>::iterator iter = accounts.begin(); iter != accounts.end(); ++iter)
			(*iter)->settle(date);
		return true;
	case 'q':	// 查询一段时间内的账目
		str >> date1 >> date2;
		Account::query(date1, date2);
		return false;
	case 'e':	// 退出
		end = true;
		return false;
	}
	cout << "Inavlid command: " << cmdLine << endl;
	return false;
}
int main(void)
{
	Date date(2022,11,1); // 起始日期
	cout << "输出化本地化数据库数据" << endl;
	//	初始化命令
	Controller controller(date);
	string cmdLine;
	// 打开文件名称
	const char* FILE_NAME = "commands.txt";
	ifstream fileIn(FILE_NAME); // 以读模式打开文件
	if (fileIn) // 直到执行到没有指令
	{	// 如果正常打开,就执行文件中的每一条命令
		while (getline(fileIn, cmdLine))
		{
			try
			{
				controller.runCommand(cmdLine);
			}
			catch (exception& e)
			{
				cout << "Bad line in" << FILE_NAME << ":" << cmdLine << endl;
				cout << "Error:" << e.what() << endl;
				return 1;
			}
		}
		fileIn.close();	// 关闭文件
	}
	// 初始化指令结束
	// 追加模式写入
	ofstream fileOut(FILE_NAME, ios_base::app);	// 以追加模式
	// 选择框
	cout << "\n\n\t\t***由于银行信息系统使用命令行存储数据形式,请严格按照输入格式进行输入:***" << endl;
	cout << "\t\t\t\t******命令选项*******"
		<<"\n\t\t\t\t\t(a)add account: a s S3755217 0.015      //a代表添加用户,s代表储蓄卡,c代表信用卡,S3755217为账户id,0.015为利率 "
		<<"\n\t\t\t\t\t(d)deposit    :d 0 5000 salary          //d代表存钱,0代表第0个账户,5000代表金额,salary为存储说明"
		<< "\n\t\t\t\t\t(w)withdraw : w 2 2000 buy a cell     //w为取钱,2代表第二个账户,2000金额,buy a cell说明"
		<<"\n\t\t\t\t\t(s)show      : s                        //查看账户"
		<< "\n\t\t\t\t\t(c)change day :c 15                   //c为改变日期,15为日 "
		<< "\n\t\t\t\t\t(n)next month :n                       //进入下一个月"
		<<"\n\t\t\t\t\t(q)query      :q 2022-11-01 2022-12-01   //遍历从11月1号到12月1号的账目数据"
		<<"\n\t\t\t\t\t(e)exit        :e                       //退出"
		<< endl;
	while (!controller.isEnd())
	{	// 从标准输入读入命令并执行,直到退出
		cout << controller.getDate() << "\tTotal: " << Account::getTotal() << "\t\t\t\t\ncommand> ";
		cout << "命令输入规范:" << endl;
		string cmdLine;
		// 读入命令,和文件存储格式相同
		getline(cin, cmdLine);
		try
		{
			// 执行命令
			if (controller.runCommand(cmdLine))
				// 并记录文件内容
				fileOut << cmdLine << endl;	// 将命令写入文件
		}
		catch (AccountException& e)
		{
			cout << "Erroe(#" << e.getAccount()->getId() << "):" << e.what() << endl;
		}
		catch (exception& e)
		{
			cout << "Error: " << e.what() << endl;
		}
	}
	return 0;
}