c++的简单小项目——ATM(分析)
● 分析目的: 这是鄙人第一次系统分析项目代码,希望通过这次分析,自己能够夯实c++基础,找到项目中的基本思路,望之后自己能熟练地自建项目,找到其中的乐趣与精粹。(大家共同进步☺)
█提示█ :本次代码对于初学者(我或者还有你),都打在屏幕上会有一些迷离,你可以自己新建一个项目,将此项目拷贝到你的项目中,然后就会很方便的知道我码的是哪里,所以我把源码保存在了网盘:
链接:https://pan.baidu.com/s/1yoAlD0Qsj34EEN-HUVbuUA
提取码:rogb
如果失效了(sorry),可以在评论区留言,我会及时补发。
● 思路分析:ATM,大家都不陌生,可以用借记卡和信用卡等来存钱,取钱,转账,查明细等操作。
其步骤我分为三步曲:(非常的大白话)☺
第一步插卡,输入密码,第二步进行存钱等,第三步完成目的,退出。
● 开始分析:
拿到项目,我首先会看主函数main.cpp
,如下:
#include "ATM.h"
int main(){
ATM atm;
atm.run();
return 0;
}
全局声明了ATM的头文件,在main()函数中,我们可以清晰地看到只定义了一个ATM类的atm对象,并且只调用了ATM类中run()方法,为了了解其中的奥秘,让我们进入ATM类中的世界。(好期待)
我们先看ATM.h
#ifndef __ATM__ATM__
#define __ATM__ATM__
#include "Screen.h" //屏幕
#include "Keypad.h" //键盘
#include "CashDispenser.h" //取钱
#include "DepositSlot.h" //存钱
#include "BankDatabase.h" //银行数据库
class Transaction; //交易 类
class ATM
{
public:
ATM();
void run();
private:
bool isAuthenticated; //用户认证状态
int currentAccountNumber; //当前账号
Screen screen; //屏幕 类
Keypad keypad; //键盘 类
CashDispenser cashDispenser; //取钱 类
DepositSlot depositSlot; //存钱 类
BankDatabase bankDatabase; //银行数据 类
void authenticateUser(); //认证用户(函数)
void performTransactions(); //交易平台 (函数)
int displayMainMenu() const; //菜单 (函数)
Transaction *createTransaction(int);//做出交易的函数
};
#endif /* defined(__ATM__ATM__) */
直接看到这里,我也是一头雾水,这些数据成员和成员函数到底是干什么用的呢,怎么用呢,直接解释,可能接受不了,那我们Go And See.
转到ATM.cpp
文件
#include "ATM.h"
#include "Transaction.h" //交易
#include "BalanceInquiry.h" //查询余额
#include "Withdrawal.h" //取款
#include "Deposit.h" //存款
enum MenuOption { BALANCE_INQUIRY = 1, WITHDRAWAL, DEPOSIT, EXIT };
//1.余额查询 2. 取款 3. 存款 4. 退出
ATM::ATM():isAuthenticated(false), currentAccountNumber(0)
{
}
void ATM::run()
{
while (true)
{
while (!isAuthenticated) {
screen.displayMessageLine("");
screen.displayMessageLine("Wellcome");
authenticateUser();
}
performTransactions();
isAuthenticated = false;
currentAccountNumber = 0;
screen.displayMessageLine("");
screen.displayMessageLine("Thanks you, good bye!");
}
}
void ATM::authenticateUser()
{
screen.displayMessageLine("");
screen.displayMessageLine("Please enter your account number:");
int accountNumber = keypad.getInput();
screen.displayMessageLine("");
screen.displayMessageLine("Please enter your pin:");
int pin = keypad.getInput();
isAuthenticated = bankDatabase.authenticateUser(accountNumber, pin);
if (isAuthenticated)
currentAccountNumber = accountNumber;
else {
screen.displayMessageLine("");
screen.displayMessage("Invalid account number or pin. Please try again!");
}
}
void ATM::performTransactions()
{
Transaction * transactionPtr;
bool exited = false;
while (!exited) {
int type = displayMainMenu();
switch (type) {
case BALANCE_INQUIRY:
case WITHDRAWAL:
case DEPOSIT:
transactionPtr = createTransaction(type);
transactionPtr->execute();
delete transactionPtr;
break;
case EXIT:
screen.displayMessageLine("");
screen.displayMessageLine("Existing the system...");
exited = true;
break;
default:
screen.displayMessageLine("");
screen.displayMessageLine("Invalid option. Try Again!");
}
}
}
int ATM::displayMainMenu() const
{
screen.displayMessageLine("");
screen.displayMessageLine("Main menu:");
screen.displayMessageLine("1-Display Balance");
screen.displayMessageLine("2-Withdraw Cash");
screen.displayMessageLine("3-Deposit Funds");
screen.displayMessageLine("4-Exit");
screen.displayMessage("Enter choice:");
return keypad.getInput();
}
Transaction * ATM::createTransaction(int type)
{
Transaction * transactionPtr;
switch (type) {
case BALANCE_INQUIRY:
transactionPtr = new BalanceInquiry(currentAccountNumber, screen, bankDatabase);
break;
case WITHDRAWAL:
transactionPtr = new Withdrawal(currentAccountNumber, screen, bankDatabase, keypad, cashDispenser);
break;
case DEPOSIT:
transactionPtr = new Deposit(currentAccountNumber, screen, bankDatabase, keypad, depositSlot);
break;
default:
break;
}
return transactionPtr;
}
在ATM.cpp文件中,首先分别声明了Transaction"交易,"BalanceInquiry"查询余额,"Withdrawal.h"取款,“Deposit.h” 存款的头文件,品一下这四个头文件的名称,就会了解,交易里面好像包括查询余额,取款,存款的类型吧,这里先埋个伏笔,后面我会做详细解释(机智)。
在开头定义了一个MenuOption的枚举型函数,里面有余额查询,取款,存款,退出,这4个选项,(等等)这有三个名称与头文件的名称呼应上了,有点奇妙,带着奇妙,我们继续分析,ATM类的构造函数为两个成员变量进行了初始化,将isAuthenticated; //用户认证状态
默认为false,将currentAccountNumber; //当前用户账号
默认为0。
我们继续去品,我们来分析一下ATM类中重要的成员函数RUN()
,如果你想使用ATM,你一定是直接插卡就可以操作了,所以一定不希望要手动开机才能使用,所以要想一直运行,while(true)
就可以啦,使用ATM,首先要验证身份,如果身份不对,你就得继续验证,把你锁在while(!isAuthenticated)
中,这就是构造函数将你的用户认证状态为false的原因,在while(!isAuthenticated)
中,使用了screen类中displayMessageLine方法
,很显然screen类的目的就是把一些提示语打印到屏幕中。
在类中我还我们还发现了一个authenticateUser()方法
,我们来看一看到底是什么,如果找不到此方法的位置的话,可以使用Ctrl + 鼠标左键。
这是一个认证身份的一个方法,首先会调用screen类中的方法,提示你输入账号和密码,我们发现了一个新的类keypad调用getInput()方法
keypad类到底干什么的呢?我们看一下它的h文件和cpp文件就知道了,
#ifndef __ATM__Keypad__
#define __ATM__Keypad__
class Keypad
{
public:
int getInput() const;
};
#endif /* defined(__ATM__Keypad__) */
~~~~~~~~~~~~~~~~~~~~~~~~~~~~分割线~~~~~~~~~~~~~~~~~~~~~~~~~
#include <iostream>
#include "Keypad.h"
using namespace std;
int Keypad::getInput() const
{
int input;
cin >> input;
return input;
}
一目了然,类中的方法就是帮助用户键入自己的账号和密码,其中const作用,表明这个函数不会对这个类对象的数据成员(准确地说是非静态数据成员)作任何改变,如果将int input变量
定义为类中的私有变量你就会知道了。
将用户所输入的账号和密码储存到accountNumber和pin
变量中,再往下看,我们看到了isAuthenticated = bankDatabase.authenticateUser(accountNumber, pin);
这里出现了一个新的类bankDatabase
,我盲猜里面里面有用户的数据等(嘿嘿),我们来看一看它的h文件
#ifndef __ATM__BankDatabase__
#define __ATM__BankDatabase__
#include <vector>
#include <iostream>
#include "Account.h"
using namespace std;
class BankDatabase
{
public:
BankDatabase();
double getAvailableBalance(int); //获得可用余额
double getTotalBalance(int); // 获得总余额
bool authenticateUser(int, int); // 认证用户
void credit(int, double); // 存款
void debit(int, double); // 取款
private:
Account * getAccount(int);
vector<Account> accounts;
};
#endif /* defined(__ATM__BankDatabase__) */
看里面的头文件,有<vector>
,有"Account.h"
,vector我们先不管,遇到再说(嘻嘻),这里出现了一个新的类Account,看来Account类和BanDatabase有点瓜葛,话不多说,直接上cpp文件看看数据成员和成员函数都是什么作用
#include "BankDatabase.h"
BankDatabase::BankDatabase()
{
Account account1(12345, 54321, 1000.00, 1200.00);
Account account2(23456, 65432, 500.00, 800.00);
accounts.push_back(account1);
accounts.push_back(account2);
}
double BankDatabase::getAvailableBalance(int accountNumber)
{
Account * const account = getAccount(accountNumber);
return account->getAvailableBalance();
}
double BankDatabase::getTotalBalance(int accountNumber)
{
Account * const account = getAccount(accountNumber);
return account->getTotalBalance();
}
bool BankDatabase::authenticateUser(int accountNumber, int pin)
{
Account * const account = getAccount(accountNumber);
if (account != NULL)
return account->validatePin(pin);
else
return false;
}
void BankDatabase::credit(int accountNumber, double amount)
{
Account * account = getAccount(accountNumber);
account->credit(amount);
}
void BankDatabase::debit(int accountNumber, double amount)
{
Account * account = getAccount(accountNumber);
account->debit(amount);
}
Account * BankDatabase::getAccount(int accountNumber)
{
for (size_t index = 0; index < accounts.size(); ++index) {
if (accounts[index].getAccountNumber() == accountNumber)
return &accounts[index];
}
return NULL;
}
为了方便,我把Account类的h和cpp文件粘在下面。
#ifndef __ATM__Account__
#define __ATM__Account__
class Account
{
public:
Account(int, int, double, double);
int getAccountNumber() const;
double getAvailableBalance() const;
double getTotalBalance() const;
bool validatePin(int) const; //验证密码
void credit(double);
void debit(double);
private:
int accountNumber;
int pin;
double availableBalance;
double totalBalance;
};
#endif /* defined(__ATM__Account__) */
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#include "Account.h"
Account::Account(int accountNumber, int pin, double availableBalance, double totalBalance):
accountNumber(accountNumber), pin(pin), availableBalance(availableBalance), totalBalance(totalBalance)
{
}
int Account::getAccountNumber() const
{
return accountNumber;
}
double Account::getAvailableBalance() const
{
return availableBalance;
}
double Account::getTotalBalance() const
{
return totalBalance;
}
bool Account::validatePin(int userPin) const
{
if (userPin == pin)
return true;
else
return false;
}
void Account::credit(double amount)
{
totalBalance = totalBalance + amount;
}
void Account::debit(double amount)
{
availableBalance = availableBalance - amount;
totalBalance = totalBalance - amount;
}
步入正题,在BankDatabase()构造函数中,定义了两个Account类的对象,并且在里面有默认参数, Account account1(12345, 54321, 1000.00, 1200.00);
我们跳转到Account.cpp文件中,
Account::Account(int accountNumber, int pin, double availableBalance, double totalBalance):
accountNumber(accountNumber), pin(pin), availableBalance(availableBalance),totalBalance(totalBalance){}
从这er我们可以知道第一个参数是账号,依次是密码,可用余额,全部余额。既然这点清楚了,那下面的accounts.push_back(account1);
又是什么呢?
我们回到BankDatabase的h文件中vector<Account> accounts;
vector方法是在vector的头文件中的,这句话的意思是vector保存类型为Account的对象,默认构造函数accounts为空。
我说一下我对vector(向量)的理解,向量是表示可以改变大小的数组的序列容器,就像数组一样,向量对它们的元素使用连续的存储位置,这意味着它们的元素也可以使用指向其元素的常规指针上的偏移量来访问,并且效率与数组相同。但是与数组不同,它们的大小可以动态更改,其存储由容器自动处理(也可以自行某度,看更详细的介绍)。对vector有了一定的了解我们再来看看他的push_back用法,在向量的最后一个当前元素之后添加一个新元素。val的内容被复制(或移动)到新元素。这有效地将容器大小增加了一个,这会导致分配的存储空间的自动重新分配,前提是(且仅当)新向量大小超过当前向量容量。
我们继续看isAuthenticated = bankDatabase.authenticateUser(accountNumber, pin);
这里的参数账号和密码都是自己键盘打进去的,方法如下
bool BankDatabase::authenticateUser(int accountNumber, int pin)
{
Account * const account = getAccount(accountNumber);
if (account != NULL)
return account->validatePin(pin);
else
return false;
}
这里面的 getAccount是在BankDatabase.h里面类中的一个方法,此方法的内容为
Account * BankDatabase::getAccount(int accountNumber)
{
for (size_t index = 0; index < accounts.size(); ++index) {
if (accounts[index].getAccountNumber() == accountNumber)
return &accounts[index];
}
return NULL;
}
for循环是对accounts这个容器进行遍历,如果在容器中有对应账号的账户,则返回该账户的地址否则返回空,定义一个Account * const account
,(这里播个小插曲回忆一下const* 和 *const的区别:double const * a
这表示指针a所指向的地址可以变,但是所指向的那个值不能变。double *const a
我们都知道a的值其实是一个地址,这就表示a所保存的地址是不可以变的,但是这个地址对应的值是可以变的)
将getAccount函数返回的地址传给account,如果account不为空,继续执行return中的语句来判断密码是否正确,其中用到了validatePin方法,其全部内容为
bool Account::validatePin(int userPin) const
{
if (userPin == pin)
return true;
else
return false;
}
这里面的参数userPin是用户传进来的,Pin是Bankdatabase中定义的account中对应账号的相应密码。如果密码True,则True,反之亦然,
最终的true or false 传到 isAuthenticated中,if (isAuthenticated)currentAccountNumber = accountNumber;
(跳出循环)认证成功
else byebye,重新再来。(⭐╮( ̄﹏ ̄)╭⭐)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~终于认证成功了~终于可以进入下面的环节了(✪ω✪)
在run()
中,我们继续向下执行,看一下代码,看来离胜利不远了,不能掉以轻心
performTransactions();
isAuthenticated = false;
currentAccountNumber = 0;
screen.displayMessageLine("");
screen.displayMessageLine("Thanks you, good bye!");
简单分析一下,第一行是交易平台函数,第二行是将用户的认证状态设为false,第三行将当前账号设为0,伴随着4,5行的告别,你已经完成一轮ATM的体验。
void ATM::performTransactions()
{
Transaction * transactionPtr;
bool exited = false;
while (!exited) {
int type = displayMainMenu();
switch (type) {
case BALANCE_INQUIRY:
case WITHDRAWAL:
case DEPOSIT:
transactionPtr = createTransaction(type); //不明白
transactionPtr->execute();
delete transactionPtr;
break;
case EXIT:
screen.displayMessageLine("");
screen.displayMessageLine("Existing the system...");
exited = true;
break;
default:
screen.displayMessageLine("");
screen.displayMessageLine("Invalid option. Try Again!");
}
}
}
上面的代码是交易平台的方法的介绍,首先定义了一个Transaction
类的transactionPtr
指针,既然出现了交易,我就把交易的h和cpp文件打在下面,
#ifndef __ATM__Transaction__
#define __ATM__Transaction__
class Screen;
class BankDatabase;
class Transaction
{
public:
Transaction(int, Screen &, BankDatabase &);
virtual ~Transaction(){};
int getAccountNumber() const;
Screen & getScreen() const;
BankDatabase & getBankDatabase() const;
virtual void execute() = 0; //纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,
//而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。
private:
int accountNumber;
Screen & screen;
BankDatabase & bankDatabase;
};
#endif /* defined(__ATM__Transaction__) */
#include "Transaction.h"
#include "Screen.h"
#include "BankDatabase.h"
Transaction::Transaction(int accountNumber, Screen &screen, BankDatabase &bankDatabase):
accountNumber(accountNumber), screen(screen), bankDatabase(bankDatabase)
{
}
int Transaction::getAccountNumber() const
{
return accountNumber;
}
Screen & Transaction::getScreen() const
{
return screen;
}
BankDatabase & Transaction::getBankDatabase() const
{
return bankDatabase;
}
我们定义一个bool类型的exited,默认为false,(就是不退出),交易平台也是一个你可以驻留的地方,所以用while (!exited)
什么时候用户想退出,exited = true 就 OKK了,int type = displayMainMenu();
出现了一个displayMainMenu()的函数,它的原型是什么呢?
int ATM::displayMainMenu() const
{
screen.displayMessageLine("");
screen.displayMessageLine("Main menu:");
screen.displayMessageLine("1-Display Balance");
screen.displayMessageLine("2-Withdraw Cash");
screen.displayMessageLine("3-Deposit Funds");
screen.displayMessageLine("4-Exit");
screen.displayMessage("Enter choice:");
return keypad.getInput();
}
就是把各种功能打印在屏幕上,让你键入你想要的实现的操作,人性化ヽ( ̄▽ ̄)ノ,然后再用type接收,下面是switch语句,在程序开头的时候我们定义了一个enum函数
enum MenuOption { BALANCE_INQUIRY = 1, WITHDRAWAL, DEPOSIT, EXIT };
//1.余额查询 2. 取款 3. 存款 4. 退出
它和switch语句是配套使用的,当你选择,1,2,3的时候都会执行一样的语句
switch (type) {
case BALANCE_INQUIRY:
case WITHDRAWAL:
case DEPOSIT:
transactionPtr = createTransaction(type); //不明白
transactionPtr->execute();
delete transactionPtr;
break;
case EXIT:
screen.displayMessageLine("");
screen.displayMessageLine("Existing the system...");
exited = true;
break;
default:
screen.displayMessageLine("");
screen.displayMessageLine("Invalid option. Try Again!");
我们看一下transactionPtr = createTransaction(type;
我们跳转到ATM.cpp文件中,查看
Transaction * ATM::createTransaction(int type)
{
Transaction * transactionPtr;
switch (type) {
case BALANCE_INQUIRY:
transactionPtr = new BalanceInquiry(currentAccountNumber, screen, bankDatabase);
break;
case WITHDRAWAL:
transactionPtr = new Withdrawal(currentAccountNumber, screen, bankDatabase, keypad, cashDispenser);
break;
case DEPOSIT:
transactionPtr = new Deposit(currentAccountNumber, screen, bankDatabase, keypad, depositSlot);
break;
default:
break;
}
return transactionPtr;
}
函数的开始阶段,定义了一个交易类的指针对象,然后根据用户所键入的数字,继续进行stiwch()语句的操作,这次我们发现每个case下,的形式都差不多,只是交易类的指针的指向到了对应数字功能的类中,之后把交易类的指针返回出去
⊙(・◇・)?为什么交易类的指针可以指向其他类,难道它们之间有着不可告人的秘密,我们来看一下,就什么都清楚了。
#ifndef __ATM__BalanceInquiry__
#define __ATM__BalanceInquiry__
#include "Transaction.h"
class BalanceInquiry: public Transaction
{
public:
BalanceInquiry(int, Screen &, BankDatabase &);
virtual void execute();
};
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#ifndef __ATM__Withdrawal__
#define __ATM__Withdrawal__
#include "Transaction.h"
class Keypad;
class CashDispenser;
class Withdrawal: public Transaction
{
public:
Withdrawal(int, Screen &, BankDatabase &, Keypad &, CashDispenser &);
virtual void execute();
private:
int amount;
Keypad &keypad;
CashDispenser &cashDispenser;
int displayMenuOfAmounts() const;
};
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#ifndef __ATM__Deposit__
#define __ATM__Deposit__
#include "Transaction.h"
class Keypad;
class DepositSlot;
class Deposit: public Transaction
{
public:
Deposit(int, Screen &, BankDatabase &, Keypad &, DepositSlot &);
virtual void execute();
private:
double amount;
Keypad &keypad;
DepositSlot &depositSlot;
double promptForDepositAmout() const;
};
光看h文件,我们就知道了,原来交易类是他们的父亲(基类)丫,我们继续分析,继续执行transactionPtr->execute();
我们可以发现在父类和每个子类中都有一个execute()的函数,据我所知,在一般情况下,把父类的指针指向子类,然后父类调用和子类相同的函数,最终结果是调用父类的函数,这样显然不能达成我们的目的,所以引入了虚函数和纯虚函数,在交易类的中有个纯虚函数virtual void execute() = 0;
声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。
在完成execute()之后,delete transactionPtr;
这其中就会用到virtual ~Transaction(){};
C++中基类采用virtual虚析构函数是为了防止内存泄漏。具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。所以,为了防止这种情况的发生,C++中基类的析构函数应采用virtual虚析构函数。
如果你选择4的话,就会执行
case EXIT:
screen.displayMessageLine("");
screen.displayMessageLine("Existing the system...");
exited = true;
break;
然后你就可以安心的跳出循环,将
isAuthenticated = false;
currentAccountNumber = 0;
完成目的。
如果手抖,没有选1234,就会调用
default:
screen.displayMessageLine("");
screen.displayMessageLine("Invalid option. Try Again!");
这里有一个细节,default没有break,所以客户可以根据提示继续选择客户想要的服务(。◕ˇ∀ˇ◕)貌似整个ATM项目分析完了
码到这里,好像还有点东西没有分析,啊,对,就是三个派生类的实现还没有分析,我们继续凎(ノ゚∀゚)ノ
按顺序来,先看BalanceInquiry
#ifndef __ATM__BalanceInquiry__
#define __ATM__BalanceInquiry__
#include "Transaction.h"
class BalanceInquiry: public Transaction
{
public:
BalanceInquiry(int, Screen &, BankDatabase &);
virtual void execute();
};
#endif /* defined(__ATM__BalanceInquiry__) */
在h文件中,有构造函数,和execute函数,再看cpp文件,看他们是怎么实现的吧!
#include "BalanceInquiry.h"
#include "Screen.h"
#include "BankDatabase.h"
BalanceInquiry::BalanceInquiry(int accountNumber, Screen &screen, BankDatabase &bankDatabase)
:Transaction(accountNumber, screen, bankDatabase)
{
}
void BalanceInquiry::execute()
{
BankDatabase &bankDatabase = getBankDatabase();
Screen &screen = getScreen();
double availableBalance = bankDatabase.getAvailableBalance(getAccountNumber());
double totalBalance = bankDatabase.getTotalBalance(getAccountNumber());
screen.displayMessageLine("");
screen.displayMessageLine("Balance information:");
screen.displayMessage("-Available Balance: ");
screen.displayDollarAmount(availableBalance);
screen.displayMessageLine("");
screen.displayMessage("-Total Balance: ");
screen.displayDollarAmount(totalBalance);
screen.displayMessageLine("");
}
因为在执行查询余额的时候,会在数据库中找到对相应账号,才可以在屏幕上打印出来查询结果,又因为屏幕类和数据库类在交易类中是私有成员,得利用公有的成员函数才能那他们拿出来,所以先执行了
BankDatabase &bankDatabase = getBankDatabase();
Screen &screen = getScreen();
其中在等号右边的函数,返回的是对应类的对象的引用,然后再传到新建的对应类的对象的引用中,减少了拷贝构造函数的使用,大大提高了程序的运行效率(攒一个)。
double availableBalance = bankDatabase.getAvailableBalance(getAccountNumber());
double totalBalance = bankDatabase.getTotalBalance(getAccountNumber());
这两行代码形式上是一样的,偷一下懒,我就解释第一行
double BankDatabase::getAvailableBalance(int accountNumber)
{
Account * const account = getAccount(accountNumber);
return account->getAvailableBalance();
}
上面的代码是等式右边的具体操作,细心的程序员们已经意识到,这和前面验证账户情况差不多,向getAccount函数中传入账号,在accounts的容器中进行遍历,返回出拥有该账号全部内容的指针,传到新建的account指针中,最后返回account->getAvailableBalance()
getAvailableBalance()方法就是将用户的可用余额值返回出去,最后到了等式的左边。(搞定)(。◕ˇ∀ˇ◕)
同样方法,我们也可以得到全部余额,这类中剩下的操作就是打印在屏幕中啦,我就不做详细解释了。(等等)我无意中细细一品,screen类中还真有需要说的,在屏幕类中,有这样的语句
#include <iomanip>
void Screen::displayDollarAmount(double amount) const
{
cout << fixed << setprecision(2) << amount;
}
这是保存两位小数的部分方法,你想要看保留位数的功能,可以自行某度,也可以点击这个,我认为这位博主总结的很好。
https://blog.youkuaiyun.com/qq_36667170/article/details/79265224
既然我们应分析完查询余额这个类了,那我要车马加鞭的继续进行了
#include "Transaction.h"
class Keypad;
class CashDispenser;
class Withdrawal: public Transaction
{
public:
Withdrawal(int, Screen &, BankDatabase &, Keypad &, CashDispenser &);
virtual void execute();
private:
int amount;
Keypad &keypad;
CashDispenser &cashDispenser;
int displayMenuOfAmounts() const;
};
#endif /* defined(__ATM__Withdrawal__) */
上面是h文件,下面是cpp文件
//
// Withdrawal.cpp
// ATM
//
// Created by Xeasony on 11/23/14.
// Copyright (c) 2014 Xeasony. All rights reserved.
//
#include "Withdrawal.h"
#include "BankDatabase.h"
#include "Screen.h"
#include "Keypad.h"
#include "CashDispenser.h"
static const int CANCELED = 6;
Withdrawal::Withdrawal(int accountNumber, Screen &screen, BankDatabase &bankDatabase, Keypad &keypad, CashDispenser &cashDispenser):Transaction(accountNumber, screen, bankDatabase), keypad(keypad), cashDispenser(cashDispenser)
{
}
void Withdrawal::execute()
{
BankDatabase &bankDatabase = getBankDatabase();
Screen &screen = getScreen();
bool transactionCanceled = false;
bool cashDispensed = false;
while (!transactionCanceled && !cashDispensed) {
amount = displayMenuOfAmounts();
if (amount != CANCELED)
{
double availableBalance = bankDatabase.getAvailableBalance(getAccountNumber());
if (amount <= availableBalance) {
if (cashDispenser.isSufficientCashAvailable(amount)) {
bankDatabase.debit(getAccountNumber(), amount);
cashDispenser.dispenseCash(amount);
cashDispensed = true;
screen.displayMessageLine("Please take your cash from cash dispenser.");
}
else {
screen.displayMessageLine("Insufficient cash available in the ATM.");
screen.displayMessageLine("Please choose smaller amount.");
}
} else {
screen.displayMessageLine("Insufficient funds in your account.");
screen.displayMessageLine("Please choose smaller amount.");
}
} else {
screen.displayMessageLine("Canceling transaction...");
transactionCanceled = true;
}
}
}
int Withdrawal::displayMenuOfAmounts() const
{
Screen &screen = getScreen();
int amounts[] = {0, 20, 40, 60, 100, 200};
int amount = 0;
while (amount == 0) {
screen.displayMessageLine("");
screen.displayMessageLine("Withdrawal Options:");
screen.displayMessageLine("1 - 20");
screen.displayMessageLine("2 - 40");
screen.displayMessageLine("3 - 60");
screen.displayMessageLine("4 - 100");
screen.displayMessageLine("5 - 200");
screen.displayMessageLine("6 - Cancel");
screen.displayMessage("Choose withdrawal options (1-6): ");
int type = keypad.getInput();
switch (type) {
case 1:
case 2:
case 3:
case 4:
case 5:
amount = amounts[type];
break;
case CANCELED:
amount = CANCELED;
break;
default:
screen.displayMessageLine("Invalid selection. Try again!");
}
}
return amount;
}
乍看一下,有点长,不急,我们来慢慢分析
在h文件中,又声明了一个新的类,class CashDispenser;
我先码一下,遇到好分析(◕ᴗ◕✿)
#ifndef __ATM__CashDispenser__
#define __ATM__CashDispenser__
class CashDispenser
{
public:
CashDispenser();
void dispenseCash(int);
bool isSufficientCashAvailable(int) const;
private:
static const int INITIAL_COUNT = 500;
int count;
};
#endif /* defined(__ATM__CashDispenser__) */
#include "CashDispenser.h"
CashDispenser::CashDispenser()
{
count = INITIAL_COUNT;
}
void CashDispenser::dispenseCash(int amount)
{
int billsDispensed = amount / 20;
count = count - billsDispensed;
}
bool CashDispenser::isSufficientCashAvailable(int amount) const
{
int billsDispensed = amount / 20;
if (count >= billsDispensed)
return true;
else
return false;
}
我们理Withdrawal的cpp文件,Withdrawal::Withdrawal(int accountNumber, Screen &screen, BankDatabase &bankDatabase, Keypad &keypad, CashDispenser &cashDispenser):Transaction(accountNumber, screen, bankDatabase), keypad(keypad), cashDispenser(cashDispenser){}
这是构造函数,里面的参数有一个是新的,就是
CashDispenser::CashDispenser()
{
count = INITIAL_COUNT;
}
这里的INITIAL_COUNT
是static const int INITIAL_COUNT = 500;
,继续看execute()函数,首先
BankDatabase &bankDatabase = getBankDatabase();
Screen &screen = getScreen();
这里在前面已经见过了,我就不再做过多的解释了,后面 bool transactionCanceled = false;bool cashDispensed = false;
这两句别是取款结束的一般情况,你手动取消,取款完成,所以把取款的相关操作放在了while (!transactionCanceled && !cashDispensed)
中,我们深入while()继续分析,首先遇到amount = displayMenuOfAmounts();
,按照常规,了解一下displayMenuOfAmounts()
函数,这里的取款选项操作和之前的选择你目的的操作有着异曲同工之妙,我简单说明,将对应钱数打印在屏幕上,然后你把钱数的对应数字用键盘打出来,如果不打,可以取消,如果取的钱大于可用余额,那么只能继续做选择题了,所以我们现在能考虑取款合理的情况就可以了
if (cashDispenser.isSufficientCashAvailable(amount)) {
bankDatabase.debit(getAccountNumber(), amount);
cashDispenser.dispenseCash(amount);
cashDispensed = true;
screen.displayMessageLine("Please take your cash from cash dispenser.");
看if中的语句,是cashDispenser的一个方法,
bool CashDispenser::isSufficientCashAvailable(int amount) const
{
int billsDispensed = amount / 20;
if (count >= billsDispensed)
return true;
else
return false;
}
我认为这是一个判断你是否满足取款次数的方法,因为你选的金额
1 - 20
2 - 40
3 - 60
4 - 100
5 - 200
没20算一次取款额度,根据前面,一共可以取500次。如果你没有多少次数了,你选的钱数超过了这个次数,它就会
else {
screen.displayMessageLine("Insufficient cash available in the ATM.");
screen.displayMessageLine("Please choose smaller amount.");
}
进入了if里面,bankDatabase.debit(getAccountNumber(), amount);
我们来看debit方法
void BankDatabase::debit(int accountNumber, double amount)
{
Account * account = getAccount(accountNumber);
account->debit(amount);
}
第一条语句,就是将客户自己的账户找出来(前面有介绍,我就大概略过了),我们再来看第二条语句
void Account::debit(double amount)
{
availableBalance = availableBalance - amount;
totalBalance = totalBalance - amount;
}
它是一个取款计数的操作,说白了,就是将你取出的钱,从你账户中减掉,可用余额和总余额。next
void CashDispenser::dispenseCash(int amount)
{
int billsDispensed = amount / 20;
count = count - billsDispensed;
}
前面判断语句中用到了判断次数,上面的这个方法是将取款后次数记录下然后从总次数中减掉,就能达到检查你次数的目的。之后
cashDispensed = true;
screen.displayMessageLine("Please take your cash from cash dispenser.");
你就能看到钱了,你就可以可以退出while循环,可以继续进行行管操作啦。我们还差最后一个,加油ヾ(◍°∇°◍)ノ゙
既然有取款,那必定有存款,这两个操作我蒙一下,他们肯定有相似之处(◕ᴗ◕✿),放码过来!
#ifndef __ATM__Deposit__
#define __ATM__Deposit__
#include "Transaction.h"
class Keypad;
class DepositSlot;
class Deposit: public Transaction
{
public:
Deposit(int, Screen &, BankDatabase &, Keypad &, DepositSlot &);
virtual void execute();
private:
double amount;
Keypad &keypad;
DepositSlot &depositSlot;
double promptForDepositAmout() const;
};
#endif /* defined(__ATM__Deposit__) */
//
// Deposit.cpp
// ATM
//
// Created by Xeasony on 11/23/14.
// Copyright (c) 2014 Xeasony. All rights reserved.
//
#include "Deposit.h"
#include "Screen.h"
#include "BankDatabase.h"
#include "Keypad.h"
#include "DepositSlot.h"
static const int CANCELED = 0;
Deposit::Deposit(int accountNumber, Screen &screen, BankDatabase &bankDatabase, Keypad &keypad, DepositSlot &depositSlot):
Transaction(accountNumber, screen, bankDatabase), keypad(keypad), depositSlot(depositSlot)
{
}
void Deposit::execute()
{
BankDatabase &bankDatabase = getBankDatabase();
Screen &screen = getScreen();
amount = promptForDepositAmout();
if (amount != CANCELED) {
screen.displayMessage("Please insert deposit envelop containing ");
screen.displayDollarAmount(amount);
screen.displayMessageLine(" in deposit slot.");
bool envelopReceived = depositSlot.isEnvelopReceived();
if (envelopReceived) {
screen.displayMessageLine("Your envelop has been receiced.");
bankDatabase.credit(getAccountNumber(), amount);
} else {
screen.displayMessageLine("You did not insert the envelop, so the ATM has canceled your transaction.");
}
} else {
screen.displayMessageLine("Canceling transaction...");
}
}
double Deposit::promptForDepositAmout() const
{
Screen &screen = getScreen();
screen.displayMessageLine("Please enter deposit amount in CENT (or 0 to cancel):");
int input = keypad.getInput();
if (input == CANCELED) {
return CANCELED;
} else {
return static_cast<double>(input) / 100;
}
}
我们先分析一下h文件,声明了一个class DepositSlot;
,我们用到在介绍,Deposit的构造函数,有账号,屏幕,键盘,数据库,存款口。
再来看execute()
,前两句和前面介绍的两个类一样,将数据库和屏幕的对象的引用声明出来,amount = promptForDepositAmout();
具体实现
double Deposit::promptForDepositAmout() const
{
Screen &screen = getScreen();
screen.displayMessageLine("Please enter deposit amount in CENT (or 0 to cancel):");
int input = keypad.getInput();
if (input == CANCELED) {
return CANCELED;
} else {
return static_cast<double>(input) / 100;
}
}
借助屏幕类和键盘类的使用,提示你要存款的金额,CANCELED的值,在同级类中Withdrawal中有介绍static const int CANCELED = 6;
,这就是静态变量的好处,因为你输入的金额是按分计算的所以要除100,那么static_cast<double>(input)
这是啥?
tatic_cast 是“静态转换”的意思,也就是在编译期间转换,转换失败的话会抛出一个编译错误。我就不展开了可以自行某度。
既然我们已经选好金额了,我们就直接进入了if当中,
screen.displayMessage("Please insert deposit envelop containing ");
screen.displayDollarAmount(amount);
screen.displayMessageLine(" in deposit slot.");
将你完成操作的信息打入到屏幕上,bool envelopReceived = depositSlot.isEnvelopReceived();
bool DepositSlot::isEnvelopReceived() const
{
return true;
}
既然客户的款已经打进去了,会给收款处一个提示,为true 进入if
if (envelopReceived) {
screen.displayMessageLine("Your envelop has been receiced.");
bankDatabase.credit(getAccountNumber(), amount);
}
void BankDatabase::credit(int accountNumber, double amount)
{
Account * account = getAccount(accountNumber);
account->credit(amount);
}
上面这是完成进款的方法,
void Account::credit(double amount)
{
totalBalance = totalBalance + amount;
}
通过前面的介绍,这三段代码,不言而明。
到这里,整个ATM的项目,我做了大概的分析,经过了前面的分析和思考,我绘制了一张图可以有助大家理解!
看到这里大家一定会有收获,至少最少的收获就是复习一下c++喽,有不懂和我写错的地方可以在评论区留言。