本项目由本人基于程序设计项目作业中 3_2_2 编写,整体框架与函数逻辑相似,少部分思想存在创新。算是人生第一个完整的小项目,纯手撸 800 行代码,耗时大约 15 个小时。项目完整源码下载请前往 👉 项目源码下获取“银行管理系统源码”

项目介绍

开发环境

  • 代码编写:QT v5.2.1 非QT项目纯 C++ 语言项目

  • 绘图工具:processon

功能介绍

读入账户数据的时候采用自增 ID 的方法,添加账户业务采用由用户自行制定 ID 的方法

本人设计的项目主要是实现模拟银行系统处理账户业务的过程,其流程如上图:银行系统每次开始应先读取上次的账户数据,再开始根据用户的操作执行相应的功能,结束时保存账户的数据并退出

账户分类:现实生活中的账户多种多样,这里只是为了模拟只是设计以下三种不同的存款账户,他们首先都具有的公有属性是账户 ID、开户日期、存储金额和存储年利率并且每年都可以由存储金额作为本金产生额外的利息

  • 一般存款账户:模拟的是一般存储和支出的普通账户。独有的属性是奖金分红,因为奖金分红一般都是年末存储给账户所以我们将它单独拿出来处理,年末资金总额计算的时候不参与利息的产生
  • 基本存款账户:模拟的是提供借贷款功能的账户。它独有的属性是贷款金额和贷款年利率,在年末资金总额计算的时候需要扣除贷款金额及一年产生的贷款利息
  • 专用存款账户:模拟的是基金会或者公司的企业机构账户。它独有的属性的是基金金额和基金净利率,因为基金产品投资是有赔有赚的所以单独处理基金投资的盈亏

业务分类:主要是模拟银行的基本业务,即添加账户、修改账户、账户交易、注销账户、查看指定账户、查看所有账户、银行结算 7 个功能,其中账户交易我们不考虑政策规定保证账户交易无论是正常还是欠债都可以进行,每个账户都有唯一的 ID

演示说明

项目演示

用 QT 编辑器鼠标双击 BankManageSystem.pro 打开项目,Ctrl+R 运行程序进入菜单页面,此时成功载入D:\list.txt 下存储账户数据的文件

首次运行程序后该文件自动生成,不要手动生成会导致未知错误

为了项目演示,这里我们添加三种账户各一个。账户 id 和开户日期为整数,金额、利率为浮点数,请保证存入数据的格式正确以防止出现位置错误

账户类型 账户id 开户日期 存储金额 存储年利率 奖金红利
0(一般存款账户) 1 2020.1.1 100 0.5 50
账户类型 账户id 开户日期 存款金额 存款年利率 贷款金额 贷款年利率
1(基本存款账户) 2 2020.2.2 200 0.8 500 0.5
账户类型 账户id 开户日期 存款金额 存款年利率 基金金额 基金净利率
2(专用存款账户) 3 2020.3.3 300 1.0 400 0.4

假设一般存款账户存款金额变为 150 元,那么我们借助修改账户执行如下

假设基本存款账户从专用存款账户获取 50 元,那么我们借助交易账户执行如下

假设专用存款账户被所属公司注销,那么我们借助注销账户执行如下

假设我们要查询当前基本存款账户,那么我们借助查询账户执行如下

查看所有账户同上,我们选择按照存储年利率降序查看

银行结算显示所有账户的同时还会计算账户总和数据

退出系统同时会打开跳转链接至本文档

项目说明

  • 配色:紫色-项目提示、绿色-业务、蓝色-操作开始的选项提示和操作完成结束提示、黄色-账户信息、白色-业务选项提示与用户选项
  • 操作:完成的操作之间第一个空行分隔、每完成四次操作进行一次清屏询问

逻辑结构

左侧为结构设计、右侧为各 .h 文件与 .cpp 文件的内容,类中红色标识为特有成员,灰色为各自需要完成的函数,橘黄色为归类

类的设计:遵从“抽象到具体,局部到整体"的思想

  • 账户纯虚基类:存放账户的公有属性,供派生类继承
  • 账户派生三种类:存放每种账户的特有属性与特有的函数
  • 账户管理类:存储并管理上述的账户类实例化的对象并实现银行的主要业务

为了减少代码冗余能够实现动态管理不同种类的账户信息,选择借助 C++ 的类和对象、继承多态创建的不同的对象并借助虚函数和虚指针管理。另外借助 STL 的标准模板库减少底层代码的编写(查找、排序函数)优化性能

分层设计:客户端借助 function 中的函数执行不同的银行业务,header 容纳项目用到的所有头文件传给客户端。function 不同的函数调用账户管理类相应的功能。唯一的账户管理类控制放在不同文件下的类和对象实现函数功能。账户管理类借助 vector 存放各种对象的地址,并用基类指针来指向这个地址从而调用虚函数的时候实现动态处理的效果

命名规则:采用驼峰命名法,即类名各单词首字母大写,数据成员以及函数第一个单词的首字母小写其他单词的首字母大写。同时注意命名的语义化,虽然不是必须的但是可以提高项目的生产力

代码分析

项目代码一般将类、函数的声明与定义分开写放在不同的文件夹中。头文件就像是菜单只是用来作为浏览参考,而 cpp 文件则更像是真真正正的实物。这样写利于维护与可以让引用头文件的程序员清楚的看到某个类的全部属性

这里只讲述具有核心思想的代码以及本人掌握欠缺的地方,具体查看请下载源码自行浏览

账户纯虚基类 Account

  • 一般声明的头文件中会尽量少用 namespace,减小其他程序员引用你的头文件造成的代价,我们就不要嫌麻烦加上命名的作用域 std::
  • 因为我们读入账户数据的时候是采用自增 ID 的形式所以重载两个构造函数一个用于我们在系统中人为创建账户时使用(需要 ID),一个用于读入文件的时候自动创建账户时使用(不需要 ID)
  • 虚类一般析构函数也声明为虚函数防止意外的内存泄漏
  • 辅助函数一般只是实现程序的时候内部使用客户端不能随意调用所以我们将它设置为私有成员
  • 不修改数据成员的函数一般声明为 const 形成良好的契约
  • cpp 文件是可以使用命名空间,需要的头文件最好调用库的和自己写的分开,这样后期修改维护也清楚
  • 自增 ID 的构造函数我们借助 C++ 11 的委托构造函数实现
  • 静态数据成员定义是不能带上 static 关键字的但是要标明作用域

  • 我们将公有类的函数定义好这样后面的派生类公有属性可以借助基类的函数减少代码冗余
  • 内部的数据成员是可以直接访问的,合理的使用 this 可以标识出函数中哪些数据是当前类的成员更好阅读

账户派生类 NormalAccount

三个派生类的方法类似,就以 NormalAccount 类为例讲一下

派生类一般就是声明自己的特有属性以及需要进行自定义的虚函数与纯虚函数,可以不加 virtual 关键字同样是为了阅读方便一般优秀的程序员都不嫌麻烦 😁

派生类的构造函数初始化列表是先借助基类的构造函数将继承的公有属性构造然后在初始化特有的属性

账户管理类 AccountManage

  • 前面也说了账户管理类肯定是唯一的,所以我们是不允许进行赋值和拷贝构造的,将这两个函数设置为 delete
  • 开一个基类指针类型的vector 动态存储每个账户对象的地址,实现存储不同类型的账户并且可以直接用于后序的调用虚函数实现动态效果
  • 注销账户操作时释放完对象的空间后还需要 删除其存储在向量中的地址,而调用 vectorerase()函数需要指明迭代器的位置,所以我们需要一个辅助函数能够根据传入的账户地址得到在 vector 中迭代器的辅助函数

在 const 函数中我们的操作是不能修改成员的,但是查看账户时是应该在之前进行一遍排序的,为了去掉这种 const 限制我们可以使用 const_cast 去掉指针的常类型

需要修改成员那为什么还要多此一举的声明函数为 const,这并不矛盾从函数功能来看他本身就是不能修改成员的,而我们也确实并没有修改只是想要将 vector 内的成员重新排序,所以在不影响本质的时候借助 const_cast 可以更加灵活

常函数内部使用指针、迭代器等,因为防止他们修改所以编译器要求他们也必须是指向常量的类型,否则报错

合理借助 Lambda 表达式完成少量代码的函数对象

注销账户函数借助了上述刚刚讲到的 getIterator 辅助函数

调用管理类的函数 function

除去菜单函数和用于美化控制台的函数,都是直接对账户管理类的操作

先确定好业务操作的账户对象,然后调用账户管理类的函数完成相应的功能,层层递进,逻辑分明

不足总结

项目缺陷

  • 对于未知选项没有做到全面防护,不慎的错误未知选项操作就可能导致程序的意外崩溃
  • 修改账户的内容不应该是客户自己计算结果修改,而是应该提供更加细化的存储于支出功能
  • 注销账户应该显示一下注销的用户信息的
  • 银行结算页面对于大数会出现布局凌乱

个人总结

  1. 对于类和对象、继承多态、封装的思想的掌握还是不足经常犯傻,语法的掌握与应用程度明显不够
  2. 项目开发过程中总是临时停止跑去修改其他函数,犯小错误并且经常看不出来,缺少逻辑思维与细心
  3. 缺少审美,设计的页面总是很丑很别扭,另外码字仍然停留在二指禅阶段 😟

老师说:“C++ 的学习对于我们只是刚刚开始。短短的几十节课时只能帮助我们整体的了解 C++ 的各个功能,想要更加的熟练的使用只能靠今后个人的学习”