C++面向对象实战:ATM机系统开发
简介:”C++实现ATM机”是一个典型的软件工程项目,旨在通过模拟自动取款机系统,帮助开发者掌握C++面向对象编程的核心技术。项目涵盖需求分析、系统设计、编码实现与测试,涉及类与对象、封装、继承、多态、异常处理、I/O操作、数据结构应用、设计模式、安全性控制及并发处理等关键技术。通过该项目,开发者可全面提升C++高级特性应用能力与实际项目开发经验。 
1. C++面向对象编程(OOP)基础
面向对象编程(Object-Oriented Programming,OOP)是C++语言的核心范式,它通过类(class)与对象(object)的机制,将现实世界中的实体抽象为程序中的逻辑结构。本章将从OOP的四大基本特性入手: 封装(Encapsulation)、继承(Inheritance)、多态(Polymorphism)与抽象(Abstraction) ,逐步引导读者理解其在ATM系统开发中的实际应用价值。
例如,ATM系统中的“用户”、“账户”、“交易”等实体,均可通过类进行建模,每个类包含属性(如用户名、账户余额)与行为(如存款、取款、登录等)。这种设计方式不仅增强了代码的可维护性,也为后续模块扩展提供了良好的架构基础。
2. 类与对象设计:用户、账户、ATM系统
在ATM系统的开发中,类与对象的设计是构建整个系统架构的核心。通过面向对象的思维方式,我们可以将现实世界中的实体(如用户、账户)抽象为程序中的类( User 、 Account ),并通过类的实例化生成具体的对象。此外,整个ATM系统本身也可以抽象为一个类 ATMSystem ,负责协调用户、账户之间的交互。本章将围绕这三个核心类的设计与实现展开,详细讲解每个类的属性、方法以及它们之间的协作关系。
2.1 用户类的设计与实现
用户类 User 是ATM系统中最基础的实体之一,用于存储与用户身份相关的信息,并定义用户在系统中的基本行为。通过对用户信息的封装和行为建模,可以实现安全、可扩展的系统设计。
2.1.1 用户信息的封装与属性定义
用户类的设计应包含用户的基本信息,如用户名、密码、用户ID、关联账户等。为了确保数据安全,这些信息应使用 private 访问修饰符进行封装,并通过公共的访问器(getter)和修改器(setter)方法进行操作。
class User {
private:
std::string userId; // 用户唯一标识
std::string username; // 用户名
std::string password; // 用户密码(加密存储)
std::vector<Account*> accounts; // 关联的账户列表
public:
// 构造函数
User(const std::string& id, const std::string& name, const std::string& pwd);
// Getter 和 Setter 方法
const std::string& getUserId() const;
const std::string& getUsername() const;
void setPassword(const std::string& pwd);
bool authenticate(const std::string& inputPassword) const;
// 账户管理
void addAccount(Account* account);
const std::vector<Account*>& getAccounts() const;
};
代码逻辑分析与参数说明:
userId:用户的唯一标识,用于系统内部识别不同用户。username:用户的登录名,用于界面显示或登录操作。password:用户密码,为保证安全性,通常在存储前进行加密处理(如SHA-256)。accounts:一个指向Account类的指针容器,表示该用户拥有的多个账户。authenticate方法:用于验证用户输入的密码是否正确,返回布尔值,常用于登录流程。addAccount方法:将新账户添加到用户账户列表中,支持多账户绑定。getAccounts方法:返回当前用户的所有账户指针,供后续操作使用。
数据封装与访问控制示意图(mermaid流程图):
classDiagram
class User {
-userId: string
-username: string
-password: string
-accounts: vector<Account*>
+User(id, name, pwd)
+getUserId(): string
+getUsername(): string
+setPassword(pwd)
+authenticate(input): bool
+addAccount(account)
+getAccounts(): vector<Account*>
}
2.1.2 用户行为建模:登录、查询余额、修改密码等
用户在ATM系统中的行为主要包括登录、查看账户信息、修改密码等。这些行为可以通过类的方法来实现,从而形成用户行为模型。
登录行为实现示例:
bool User::authenticate(const std::string& inputPassword) const {
// 使用加密函数比较密码(此处简化为明文比较)
return inputPassword == password;
}
- 逻辑分析 :该方法接收用户输入的密码
inputPassword,与类内部存储的密码进行比较。实际应用中应使用加密算法(如bcrypt)进行比对。 - 参数说明 :
inputPassword:用户在界面上输入的密码字符串。
修改密码行为实现示例:
void User::setPassword(const std::string& pwd) {
// 实际应加入密码复杂度校验和加密处理
this->password = pwd;
}
- 逻辑分析 :此方法允许用户更新其密码。为增强安全性,应在实际实现中加入密码复杂度验证逻辑,并使用加密函数存储密码。
- 参数说明 :
pwd:用户输入的新密码。
查询账户余额行为实现示例:
void displayUserAccounts(const User& user) {
const auto& accounts = user.getAccounts();
for (const auto& account : accounts) {
std::cout << "账户ID: " << account->getAccountId()
<< ",余额: " << account->getBalance() << std::endl;
}
}
- 逻辑分析 :该函数接收一个用户对象,调用其
getAccounts()方法获取账户列表,然后遍历每个账户并输出账户ID和余额。 - 参数说明 :
user:当前登录的用户对象。
2.2 账户类的设计与实现
账户类 Account 是ATM系统中另一个核心类,负责管理用户的资金信息与金融操作。它应具备账户的基本属性(如账户ID、余额、类型)和操作方法(如存款、取款、转账等),并支持多账户类型(如储蓄账户、信用卡账户)的扩展。
2.2.1 账户的基本属性与操作方法
账户类应包含账户的基础信息和核心操作,如下所示:
class Account {
private:
std::string accountId; // 账户唯一标识
double balance; // 账户余额
std::string accountType; // 账户类型(储蓄/信用卡等)
std::vector<std::string> transactionHistory; // 交易记录
public:
Account(const std::string& id, double initBalance, const std::string& type);
const std::string& getAccountId() const;
double getBalance() const;
const std::string& getAccountType() const;
bool deposit(double amount); // 存款
bool withdraw(double amount); // 取款
bool transfer(Account& target, double amount); // 转账
void recordTransaction(const std::string& record); // 记录交易
void printTransactionHistory() const; // 打印交易记录
};
代码逻辑分析与参数说明:
accountId:账户的唯一标识符,用于区分不同账户。balance:账户当前余额,支持浮点数操作。accountType:账户类型,用于区分储蓄账户、信用卡账户等。deposit:存款操作,增加账户余额。withdraw:取款操作,需判断余额是否足够。transfer:转账操作,将指定金额从当前账户转移到目标账户。recordTransaction:记录每次交易,便于后续查询。printTransactionHistory:输出交易历史,用于用户查看。
存款操作实现示例:
bool Account::deposit(double amount) {
if (amount <= 0) {
std::cerr << "存款金额必须大于0。" << std::endl;
return false;
}
balance += amount;
recordTransaction("存款: +" + std::to_string(amount));
return true;
}
- 逻辑分析 :检查金额是否合法(>0),若合法则更新余额并记录交易。
- 参数说明 :
amount:存款金额。
取款操作实现示例:
bool Account::withdraw(double amount) {
if (amount <= 0) {
std::cerr << "取款金额必须大于0。" << std::endl;
return false;
}
if (balance < amount) {
std::cerr << "余额不足,无法完成取款。" << std::endl;
return false;
}
balance -= amount;
recordTransaction("取款: -" + std::to_string(amount));
return true;
}
- 逻辑分析 :检查金额是否合法且账户余额是否足够,若条件满足则执行取款操作。
- 参数说明 :
amount:取款金额。
2.2.2 多账户支持与账户类型区分
在ATM系统中,用户可能拥有多个账户(如储蓄账户、信用卡账户)。账户类的设计应支持多类型账户的统一管理,并通过继承机制实现扩展。
多账户支持示例:
class User {
// ...
void addAccount(Account* account);
const std::vector<Account*>& getAccounts() const;
};
- 逻辑分析 :用户类中使用
std::vector<Account*>来管理多个账户指针,支持添加、查询账户。 - 参数说明 :
account:指向Account或其派生类的指针。
账户类型区分示例(使用继承):
class SavingsAccount : public Account {
public:
SavingsAccount(const std::string& id, double initBalance)
: Account(id, initBalance, "储蓄账户") {}
};
class CreditAccount : public Account {
private:
double creditLimit; // 信用额度
public:
CreditAccount(const std::string& id, double initBalance, double limit)
: Account(id, initBalance, "信用卡账户"), creditLimit(limit) {}
bool withdraw(double amount) override;
};
- 逻辑分析 :通过继承
Account类,定义不同的账户类型。例如,CreditAccount重写了withdraw方法以支持信用额度。 - 参数说明 :
creditLimit:信用卡账户的信用额度上限。
2.3 ATM系统类的设计与实现
ATM系统类 ATMSystem 是整个系统的控制中心,负责协调用户、账户之间的交互,并管理系统的运行状态和模块调用。
2.3.1 ATM系统的主控逻辑与状态管理
系统类应包含用户登录、操作菜单、退出系统等主控逻辑,并管理系统的运行状态(如是否登录、当前用户是谁)。
class ATMSystem {
private:
std::map<std::string, User*> users; // 用户数据库
User* currentUser; // 当前登录用户
bool isRunning; // 系统运行状态
public:
ATMSystem();
~ATMSystem();
void start(); // 启动系统
bool login(const std::string& username, const std::string& password); // 登录
void showMenu(); // 显示操作菜单
void run(); // 运行主循环
void logout(); // 注销当前用户
};
系统启动与主循环实现示例:
void ATMSystem::start() {
isRunning = true;
std::cout << "欢迎使用ATM系统,请登录。" << std::endl;
while (isRunning) {
if (!currentUser) {
// 登录流程
} else {
// 显示菜单并处理用户操作
}
}
}
- 逻辑分析 :启动系统后进入主循环,判断是否登录,若未登录则引导用户登录;若已登录则显示操作菜单。
- 参数说明 :
isRunning:控制主循环是否继续执行。
2.3.2 系统模块间的协作与调用关系
系统模块之间通过类的方法调用实现协作。例如,用户登录后,系统调用账户类的方法进行余额查询或转账操作。
模块协作流程图(mermaid):
sequenceDiagram
participant System as ATMSystem
participant User as User
participant Account as Account
System->>User: 输入用户名密码
User->>User: authenticate验证
User-->>System: 登录成功
System->>Account: 查询余额
Account-->>System: 返回余额
System->>Account: 调用withdraw
Account->>Account: 检查余额
Account-->>System: 返回操作结果
- 逻辑分析 :展示了用户登录后与账户进行交互的典型流程,包括余额查询和取款操作。
本章从用户类、账户类到ATM系统类的设计进行了系统性的讲解,涵盖了属性定义、方法实现、多账户支持、模块协作等多个方面。这些设计为后续章节中封装机制、继承多态、异常处理等内容的实现打下了坚实的基础。
3. 封装机制实现与访问控制
封装是面向对象编程的核心原则之一,它通过将数据和行为绑定在一起,并限制对内部状态的直接访问,来提高程序的安全性和可维护性。在ATM系统中,封装机制尤为重要,因为用户账户信息、操作日志等敏感数据必须受到严格保护,防止非法访问和篡改。本章将深入探讨C++中封装机制的实现方式,并结合ATM系统的实际需求,讲解如何通过访问控制、构造函数与析构函数的设计、以及操作日志的封装等手段,实现系统中关键数据的安全管理。
3.1 成员变量与成员函数的访问权限控制
在C++中,类的成员变量和成员函数可以通过访问修饰符( public 、 private 、 protected )来控制其可访问性。这些修饰符决定了类的哪些部分可以被外部访问,哪些部分只能被类的内部成员函数访问。
3.1.1 public、private、protected的使用场景
| 修饰符 | 可访问范围 | 使用场景示例 |
|---|---|---|
public |
类外部、类内部、派生类 | 公共接口方法,如 getBalance() |
private |
仅限类内部 | 敏感数据如账户余额、密码等 |
protected |
类内部、派生类 | 需要被子类访问的成员,如账户类型 |
在ATM系统中,账户类 Account 的余额属性应当设置为 private ,以防止外部直接修改。而 getBalance() 这样的查询接口则应设为 public ,供外部安全访问。
class Account {
private:
double balance; // 私有成员,外部不能直接访问
std::string password;
public:
double getBalance() const; // 公共接口,用于查询余额
void deposit(double amount); // 存款操作
bool withdraw(double amount); // 取款操作
};
代码逐行解读:
- 第1行:定义类
Account。 - 第2-4行:将
balance和password设为private,保证外部无法直接访问。 - 第6-9行:将
getBalance()、deposit()、withdraw()设为public,提供对外接口。
通过这种访问控制方式,我们确保了账户余额的安全性,所有对余额的操作都必须通过预定义的接口进行,从而实现数据的封装与保护。
3.1.2 访问器与修改器的设计原则
为了访问和修改私有成员变量,C++中通常会设计 访问器(getter) 和 修改器(setter) 方法。
class User {
private:
std::string username;
std::string password;
public:
// Getter
std::string getUsername() const {
return username;
}
// Setter
void setPassword(const std::string& newPassword) {
if (newPassword.length() >= 8) {
password = newPassword;
} else {
std::cout << "Password too short!" << std::endl;
}
}
};
逻辑分析:
- 第6-9行:
getUsername()作为访问器,返回用户名。 - 第11-17行:
setPassword()作为修改器,在设置新密码时进行长度验证,防止设置过于简单的密码。 const关键字用于getUsername(),表示该方法不会修改对象状态,有助于编译器优化和逻辑清晰。
这种设计不仅实现了封装,还增强了系统的安全性,防止非法或不符合规范的数据进入系统。
3.2 类内部数据的安全性保护
在封装机制中,除了访问控制,还需要通过构造函数和析构函数确保对象在生命周期内的数据安全。
3.2.1 数据隐藏与信息保护机制
数据隐藏(Data Hiding)是封装的另一个核心思想,它要求将类的实现细节隐藏起来,仅暴露必要的接口。例如,在ATM系统中,用户的密码不能以明文形式存储,可以使用加密或哈希处理后再存储。
#include <openssl/sha.h> // OpenSSL库用于哈希处理
class Account {
private:
std::string hashedPassword;
public:
void setPassword(const std::string& rawPassword) {
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256_CTX sha256;
SHA256_Init(&sha256);
SHA256_Update(&sha256, rawPassword.c_str(), rawPassword.size());
SHA256_Final(hash, &sha256);
// 将哈希值转为字符串
std::stringstream ss;
for(int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
}
hashedPassword = ss.str();
}
bool verifyPassword(const std::string& input) const {
// 对输入密码做哈希后与存储值比较
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256_CTX sha256;
SHA256_Init(&sha256);
SHA256_Update(&sha256, input.c_str(), input.size());
SHA256_Final(hash, &sha256);
std::stringstream ss;
for(int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
}
return ss.str() == hashedPassword;
}
};
逻辑分析:
- 第5-19行:
setPassword()方法使用SHA-256算法对原始密码进行哈希处理,再存储到hashedPassword中。 - 第21-35行:
verifyPassword()方法对输入密码再次哈希并与存储值比较,验证是否一致。 - 这种方式实现了数据隐藏,即使数据库被非法访问,也无法获取用户原始密码。
3.2.2 构造函数与析构函数的安全初始化与资源释放
构造函数用于对象初始化,析构函数用于资源释放。良好的构造函数设计可以防止对象处于无效状态,而析构函数则确保资源如内存、文件句柄等被正确释放。
class ATMLogger {
private:
FILE* logFile;
public:
// 构造函数:打开日志文件
ATMLogger(const std::string& filename) {
logFile = fopen(filename.c_str(), "a");
if (!logFile) {
throw std::runtime_error("Failed to open log file!");
}
}
// 析构函数:关闭日志文件
~ATMLogger() {
if (logFile) {
fclose(logFile);
}
}
void log(const std::string& message) {
fprintf(logFile, "%s\n", message.c_str());
}
};
逻辑分析:
- 第6-11行:构造函数尝试以追加模式打开日志文件,若失败则抛出异常。
- 第14-18行:析构函数确保在对象生命周期结束时关闭文件,防止资源泄漏。
- 第20-23行:
log()方法将日志信息写入文件。 - 使用RAII(Resource Acquisition Is Initialization)模式确保资源安全。
3.3 封装在ATM系统中的实践
在实际系统开发中,封装不仅体现在类的设计上,还体现在系统模块之间的隔离与数据保护上。
3.3.1 用户账户信息的封装与访问限制
在ATM系统中,用户账户信息应当由专门的类进行封装管理,外部只能通过接口访问。
class AccountManager {
private:
std::map<std::string, Account> accounts; // 账户信息存储
public:
bool addAccount(const std::string& userId, const Account& account) {
if (accounts.find(userId) == accounts.end()) {
accounts[userId] = account;
return true;
}
return false; // 用户ID已存在
}
Account* getAccount(const std::string& userId) {
auto it = accounts.find(userId);
if (it != accounts.end()) {
return &(it->second);
}
return nullptr;
}
};
逻辑分析:
- 第4行:使用
std::map存储账户信息,键为用户ID。 - 第6-11行:
addAccount()方法用于添加新账户,若用户ID已存在则返回失败。 - 第13-19行:
getAccount()方法返回账户指针,供外部调用者进行操作。
通过封装 AccountManager 类,我们将账户管理的逻辑集中处理,避免外部直接操作账户数据,提升了系统的可维护性和安全性。
3.3.2 操作日志的封装与记录机制
操作日志记录是ATM系统安全审计的重要组成部分。我们可以通过封装日志记录类来实现统一的日志管理。
class AuditLogger {
private:
std::ofstream logStream;
public:
AuditLogger(const std::string& filename) {
logStream.open(filename, std::ios::app);
if (!logStream) {
throw std::runtime_error("Log file open failed");
}
}
~AuditLogger() {
if (logStream.is_open()) {
logStream.close();
}
}
void logOperation(const std::string& operation, const std::string& user) {
logStream << "[" << getCurrentTime() << "] User: " << user
<< " - Operation: " << operation << std::endl;
}
private:
std::string getCurrentTime() const {
time_t now = time(nullptr);
tm* ltm = localtime(&now);
char buffer[80];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", ltm);
return std::string(buffer);
}
};
逻辑分析:
- 第6-10行:构造函数以追加模式打开日志文件,若失败则抛出异常。
- 第12-17行:析构函数确保日志文件正常关闭。
- 第19-25行:
logOperation()方法记录操作时间、用户ID和操作类型。 - 第27-34行:
getCurrentTime()方法获取当前系统时间,用于日志记录。
封装优势:
- 统一日志格式 :所有操作日志统一通过
logOperation()方法记录,确保格式一致。 - 安全可靠 :使用构造/析构机制确保资源释放。
- 可扩展性高 :未来可扩展为网络日志、加密日志等功能。
封装机制在ATM系统中的作用总结
graph TD
A[用户操作] --> B[调用封装接口]
B --> C{权限检查}
C -->|允许| D[执行操作]
C -->|拒绝| E[拒绝访问]
D --> F[日志记录]
F --> G[日志文件存储]
E --> H[安全审计]
通过上述封装机制的实践,我们确保了ATM系统中用户账户数据、操作日志等关键信息的安全性与可控性,使得整个系统具备良好的模块化设计和高可维护性。
4. 继承与多态:多种账户类型支持
在ATM系统中,账户类型并非单一,常见的包括储蓄账户(Savings Account)、信用卡账户(Credit Account)和支票账户(Checking Account)等。这些账户在基础功能上相似,但又各自具有特定的业务逻辑和限制条件。为了高效支持多种账户类型,C++的继承与多态机制成为不可或缺的设计手段。
本章将深入讲解如何利用继承建立账户类的继承结构,并通过多态机制实现账户操作的统一接口与差异化行为。同时,我们将结合ATM系统的实际需求,展示继承与多态在系统扩展性与维护性中的实际应用价值。
4.1 类继承的基本结构
4.1.1 基类与派生类的关系
在面向对象编程中,基类(Base Class)是定义通用属性和方法的类,而派生类(Derived Class)则是在基类基础上添加或修改功能的类。在ATM系统中,我们可以定义一个抽象的账户基类 Account ,然后派生出具体的账户类型类,如 SavingsAccount 、 CreditAccount 等。
示例:定义账户基类
// Account.h
#ifndef ACCOUNT_H
#define ACCOUNT_H
#include <string>
class Account {
protected:
std::string accountNumber; // 账户号码
double balance; // 账户余额
public:
Account(const std::string& accNum, double initialBalance);
virtual void deposit(double amount); // 存款
virtual void withdraw(double amount); // 取款
virtual void display() const; // 显示账户信息
double getBalance() const;
const std::string& getAccountNumber() const;
};
#endif
// Account.cpp
#include "Account.h"
#include <iostream>
Account::Account(const std::string& accNum, double initialBalance)
: accountNumber(accNum), balance(initialBalance) {}
void Account::deposit(double amount) {
if (amount > 0) {
balance += amount;
std::cout << "Deposit " << amount << " to account " << accountNumber << std::endl;
}
}
void Account::withdraw(double amount) {
if (amount > 0 && balance >= amount) {
balance -= amount;
std::cout << "Withdraw " << amount << " from account " << accountNumber << std::endl;
} else {
std::cout << "Insufficient funds or invalid amount." << std::endl;
}
}
void Account::display() const {
std::cout << "Account Number: " << accountNumber << std::endl;
std::cout << "Balance: $" << balance << std::endl;
}
double Account::getBalance() const {
return balance;
}
const std::string& Account::getAccountNumber() const {
return accountNumber;
}
逻辑分析:
Account类定义了账户的基本属性和操作。- 使用
virtual关键字将方法声明为虚函数,为后续的多态实现打下基础。 - 构造函数接受账户号码和初始余额,用于初始化账户。
4.1.2 继承方式与访问权限的控制
在C++中,继承方式决定了基类成员在派生类中的可访问性。常见的继承方式有 public 、 protected 和 private 。在ATM系统中,我们通常使用 public 继承方式,以保持接口的一致性和可扩展性。
示例:储蓄账户类的定义
// SavingsAccount.h
#ifndef SAVINGSACCOUNT_H
#define SAVINGSACCOUNT_H
#include "Account.h"
class SavingsAccount : public Account {
private:
double interestRate; // 利率
public:
SavingsAccount(const std::string& accNum, double initialBalance, double rate);
void addInterest(); // 添加利息
void display() const override; // 重写显示方法
};
#endif
// SavingsAccount.cpp
#include "SavingsAccount.h"
#include <iostream>
SavingsAccount::SavingsAccount(const std::string& accNum, double initialBalance, double rate)
: Account(accNum, initialBalance), interestRate(rate) {}
void SavingsAccount::addInterest() {
double interest = balance * interestRate / 100;
deposit(interest);
std::cout << "Interest added: $" << interest << std::endl;
}
void SavingsAccount::display() const {
Account::display();
std::cout << "Interest Rate: " << interestRate << "%" << std::endl;
}
逻辑分析:
SavingsAccount继承自Account,继承方式为public。- 新增了
interestRate成员变量,用于存储利率。 - 实现了
addInterest()方法,根据利率计算利息并调用基类的deposit()方法。 - 重写了
display()方法,添加了利率的显示。
4.2 多态性的实现
4.2.1 虚函数与动态绑定
多态性通过虚函数(Virtual Function)和动态绑定(Dynamic Binding)实现。在ATM系统中,不同的账户类型虽然继承自同一个基类,但它们的 withdraw() 和 deposit() 方法可能具有不同的实现逻辑。
示例:信用卡账户类
// CreditAccount.h
#ifndef CREDITACCOUNT_H
#define CREDITACCOUNT_H
#include "Account.h"
class CreditAccount : public Account {
private:
double creditLimit; // 信用额度
public:
CreditAccount(const std::string& accNum, double initialBalance, double limit);
void setCreditLimit(double limit); // 设置信用额度
void withdraw(double amount) override; // 重写取款方法
void display() const override; // 重写显示方法
};
#endif
// CreditAccount.cpp
#include "CreditAccount.h"
#include <iostream>
CreditAccount::CreditAccount(const std::string& accNum, double initialBalance, double limit)
: Account(accNum, initialBalance), creditLimit(limit) {}
void CreditAccount::setCreditLimit(double limit) {
creditLimit = limit;
}
void CreditAccount::withdraw(double amount) {
if (amount > 0 && (balance + creditLimit) >= amount) {
balance -= amount;
std::cout << "Withdraw " << amount << " from credit account " << accountNumber << std::endl;
} else {
std::cout << "Withdrawal denied: insufficient credit." << std::endl;
}
}
void CreditAccount::display() const {
Account::display();
std::cout << "Credit Limit: $" << creditLimit << std::endl;
}
逻辑分析:
CreditAccount继承自Account,并重写了withdraw()方法。- 允许在信用额度范围内透支,增加了信用账户的灵活性。
display()方法也进行了重写,用于显示信用额度。
4.2.2 抽象类与接口设计
在某些情况下,基类不需要具体的实现,而是作为接口存在。这种类称为 抽象类(Abstract Class) ,它至少包含一个纯虚函数(Pure Virtual Function)。在ATM系统中,可以将 Account 类改为抽象类,强制派生类实现关键方法。
示例:将Account改为抽象类
// Account.h
class Account {
protected:
std::string accountNumber;
double balance;
public:
Account(const std::string& accNum, double initialBalance);
virtual void deposit(double amount) = 0; // 纯虚函数
virtual void withdraw(double amount) = 0; // 纯虚函数
virtual void display() const = 0;
double getBalance() const;
const std::string& getAccountNumber() const;
};
注意:此时
Account类不能直接实例化,必须通过其派生类实现具体功能。
4.3 ATM系统中的账户类型扩展
4.3.1 不同账户类型的继承关系
在ATM系统中,账户类型之间的关系可以使用UML类图进行建模。以下是一个简单的mermaid流程图,展示账户类的继承结构:
classDiagram
class Account {
<<abstract>>
+string accountNumber
+double balance
+deposit(double)
+withdraw(double)
+display()
}
class SavingsAccount {
+double interestRate
+addInterest()
+display()
}
class CreditAccount {
+double creditLimit
+withdraw(double)
+display()
}
Account <|-- SavingsAccount
Account <|-- CreditAccount
说明:
Account是抽象基类,提供统一的账户接口。SavingsAccount和CreditAccount是具体的账户类型,分别实现不同的取款和显示逻辑。
4.3.2 账户操作的多态实现(如取款、转账)
通过多态机制,ATM系统可以统一处理不同账户类型的对象。例如,在执行取款操作时,系统无需关心具体是储蓄账户还是信用卡账户,只需调用 withdraw() 方法即可。
示例:使用多态统一处理账户
#include <vector>
#include "Account.h"
#include "SavingsAccount.h"
#include "CreditAccount.h"
int main() {
std::vector<Account*> accounts;
accounts.push_back(new SavingsAccount("SA001", 1000, 2.5));
accounts.push_back(new CreditAccount("CA001", 500, 1000));
for (Account* acc : accounts) {
acc->display();
acc->withdraw(200);
acc->display();
std::cout << "------------------------" << std::endl;
}
// 清理内存
for (Account* acc : accounts) {
delete acc;
}
return 0;
}
逻辑分析:
- 使用
Account*指针数组统一管理不同账户类型。 - 通过
for循环遍历账户,调用withdraw()方法,自动调用对应派生类的实现。 - 最后释放内存,避免内存泄漏。
4.4 多态在系统扩展性中的作用
多态机制不仅实现了账户操作的统一接口,还极大地提升了系统的扩展性。例如,未来新增一个 LoanAccount 类时,只需继承 Account 并实现相应方法,无需修改已有代码。
示例:新增贷款账户类
// LoanAccount.h
#ifndef LOANACCOUNT_H
#define LOANACCOUNT_H
#include "Account.h"
class LoanAccount : public Account {
private:
double loanAmount; // 贷款金额
double repaymentTerm; // 还款期限(月)
public:
LoanAccount(const std::string& accNum, double initialBalance, double loan, double term);
void repay(double amount); // 还款
void display() const override;
};
#endif
// LoanAccount.cpp
#include "LoanAccount.h"
#include <iostream>
LoanAccount::LoanAccount(const std::string& accNum, double initialBalance, double loan, double term)
: Account(accNum, initialBalance), loanAmount(loan), repaymentTerm(term) {}
void LoanAccount::repay(double amount) {
if (amount > 0 && balance >= amount) {
balance -= amount;
loanAmount -= amount;
std::cout << "Repayment of $" << amount << " processed." << std::endl;
}
}
void LoanAccount::display() const {
Account::display();
std::cout << "Loan Amount: $" << loanAmount << std::endl;
std::cout << "Repayment Term: " << repaymentTerm << " months" << std::endl;
}
逻辑分析:
LoanAccount继承自Account,并新增了贷款金额和还款期限。- 实现了
repay()方法,用于处理还款逻辑。 display()方法重写后显示贷款相关信息。
总结性表格:账户类型功能对比
| 账户类型 | 存款功能 | 取款功能 | 特殊功能 | 多态支持 |
|---|---|---|---|---|
| 储蓄账户 | 支持 | 支持(余额限制) | 计算利息 | ✅ |
| 信用卡账户 | 支持 | 支持(信用额度) | 透支功能 | ✅ |
| 贷款账户 | 支持 | 不支持(仅还款) | 还款逻辑、贷款额度控制 | ✅ |
本章通过详细讲解类继承和多态机制在ATM系统中的应用,展示了如何通过面向对象设计实现账户类型的统一管理与灵活扩展。下一章节将继续深入,探讨ATM系统中异常处理机制的设计与实现。
5. 异常处理机制设计与实现
在大型系统开发中,异常处理是保障程序稳定性和健壮性的核心机制之一。特别是在ATM这类涉及金融交易的系统中,任何异常都可能导致严重的数据不一致或资金损失。C++提供了基于 try 、 catch 和 throw 的异常处理机制,允许开发者在运行时捕获并处理异常,从而避免程序崩溃或数据损坏。
本章将深入探讨C++异常处理的基本结构,分析ATM系统中常见的异常场景,并展示如何通过设计合理的异常类体系来增强系统的健壮性与可维护性。
5.1 异常处理的基本结构
C++的异常处理机制主要依赖于三个关键字: throw (抛出异常)、 try (尝试执行代码块)和 catch (捕获异常)。这一机制使得程序可以在运行时检测错误并做出相应的处理,而不是让程序崩溃。
5.1.1 try、catch、throw的使用方式
异常处理的基本结构如下:
try {
// 可能会抛出异常的代码
throw ExceptionType();
}
catch (ExceptionType& e) {
// 捕获并处理特定类型的异常
}
catch (...) {
// 捕获所有未处理的异常
}
在ATM系统中,这种结构可以用于验证用户输入、检查账户余额是否足够、处理文件读写错误等。
示例:账户余额不足异常处理
#include <iostream>
#include <stdexcept>
class InsufficientFundsException : public std::runtime_error {
public:
InsufficientFundsException() : std::runtime_error("账户余额不足") {}
};
void withdraw(double balance, double amount) {
if (amount > balance) {
throw InsufficientFundsException();
}
std::cout << "取款成功,剩余余额:" << balance - amount << std::endl;
}
int main() {
try {
withdraw(1000.0, 1500.0);
}
catch (const InsufficientFundsException& e) {
std::cerr << "捕获异常:" << e.what() << std::endl;
}
catch (...) {
std::cerr << "未知异常发生" << std::endl;
}
return 0;
}
代码逻辑分析
- 第1~5行 :定义了一个继承自
std::runtime_error的自定义异常类InsufficientFundsException,用于表示账户余额不足的情况。 - 第7~11行 :
withdraw函数模拟取款操作。如果取款金额大于账户余额,则抛出异常。 - 第13~22行 :在
main函数中使用try-catch结构捕获异常,并输出对应的错误信息。 - 第20行 :捕获自定义异常类型
InsufficientFundsException。 - 第22行 :捕获所有其他未处理的异常,作为兜底处理。
参数说明
balance:当前账户余额。amount:用户试图取出的金额。InsufficientFundsException:自定义异常类,用于标识余额不足。
5.1.2 异常类的设计与继承体系
为了提高系统的可维护性和可扩展性,ATM系统中的异常应该采用类继承体系进行组织。例如:
classDiagram
class std::exception {
<<abstract>>
virtual const char* what() const throw();
}
class std::runtime_error
class std::logic_error
class AccountException
class InsufficientFundsException
class InvalidInputException
class AccountNotFoundException
std::runtime_error <|-- AccountException
AccountException <|-- InsufficientFundsException
AccountException <|-- InvalidInputException
AccountException <|-- AccountNotFoundException
解释:
- 所有自定义异常类都继承自
std::runtime_error,这是C++标准库推荐的做法。 - 定义一个基类
AccountException来统一管理与账户相关的异常。 - 派生出具体的异常类型,如余额不足、无效输入、账户不存在等,便于在不同业务逻辑中进行针对性处理。
5.2 ATM系统中的常见异常场景
在ATM系统的实际运行过程中,可能遇到多种异常情况,包括但不限于:
| 异常类型 | 描述 |
|---|---|
| 用户输入错误 | 密码错误、账户编号错误等 |
| 账户余额不足 | 取款金额超过账户余额 |
| 无效操作 | 非法转账、无效菜单选择等 |
| 文件读写失败 | 用户信息或交易记录读取失败 |
| 系统连接中断 | 与银行服务器通信失败 |
5.2.1 输入错误处理
在ATM系统中,输入验证是异常处理的第一道防线。例如用户登录时输入错误的密码或账户编号。
class InvalidInputException : public AccountException {
public:
InvalidInputException(const std::string& msg) : AccountException(msg) {}
};
bool login(const std::string& accountNumber, const std::string& password) {
if (accountNumber.length() != 10 || password.length() != 6) {
throw InvalidInputException("账户编号或密码格式错误");
}
// 实际验证逻辑
return true;
}
int main() {
try {
login("123456789", "123456");
}
catch (const InvalidInputException& e) {
std::cerr << "登录失败:" << e.what() << std::endl;
}
return 0;
}
代码解释:
login函数模拟用户登录过程。- 如果账户编号不是10位,或密码不是6位,则抛出
InvalidInputException异常。 main函数中捕获并处理异常,提示用户错误信息。
5.2.2 账户余额不足、无效操作等业务异常
这类异常通常发生在执行金融交易时,如取款、转账等。
class TransferException : public AccountException {
public:
TransferException(const std::string& msg) : AccountException(msg) {}
};
void transfer(Account& from, Account& to, double amount) {
if (from.getBalance() < amount) {
throw TransferException("转账失败:余额不足");
}
from.withdraw(amount);
to.deposit(amount);
}
参数说明:
from:转出账户。to:转入账户。amount:转账金额。
该函数在执行转账前检查账户余额,若余额不足则抛出异常,防止非法操作。
5.3 异常处理在系统健壮性中的作用
异常处理机制不仅仅是捕获错误,更重要的是提升系统的健壮性和容错能力。在ATM系统中,良好的异常处理可以带来以下好处:
5.3.1 错误信息的统一管理
通过统一的异常处理框架,可以集中管理错误信息的输出格式,便于后续日志记录和用户提示。
enum class ErrorCode {
INVALID_CREDENTIALS,
INSUFFICIENT_FUNDS,
ACCOUNT_NOT_FOUND,
IO_ERROR
};
std::string getErrorMessage(ErrorCode code) {
switch (code) {
case ErrorCode::INVALID_CREDENTIALS:
return "账户或密码错误";
case ErrorCode::INSUFFICIENT_FUNDS:
return "余额不足";
case ErrorCode::ACCOUNT_NOT_FOUND:
return "账户不存在";
case ErrorCode::IO_ERROR:
return "系统错误,请稍后重试";
default:
return "未知错误";
}
}
应用场景:
- 用户登录失败时返回
INVALID_CREDENTIALS; - 取款失败时返回
INSUFFICIENT_FUNDS; - 找不到目标账户时返回
ACCOUNT_NOT_FOUND; - 文件读写失败时返回
IO_ERROR。
5.3.2 系统崩溃预防与资源安全释放
异常处理还应确保在异常发生时,程序不会崩溃,并能安全释放已分配的资源(如文件句柄、网络连接等)。
class FileHandler {
public:
FileHandler(const std::string& filename) {
file = fopen(filename.c_str(), "r");
if (!file) {
throw std::runtime_error("无法打开文件");
}
}
~FileHandler() {
if (file) fclose(file);
}
FILE* get() { return file; }
private:
FILE* file;
};
void readUserData(const std::string& filename) {
FileHandler handler(filename);
// 读取数据逻辑
}
解释:
FileHandler类封装了文件操作,并在析构函数中自动关闭文件。- 即使在
readUserData中发生异常,析构函数仍会被调用,确保资源释放。 - 这种方式称为“RAII”(Resource Acquisition Is Initialization),是C++中管理资源的推荐方式。
总结与延伸
本章系统地讲解了C++异常处理机制在ATM系统中的设计与实现。通过自定义异常类、使用标准异常库、统一错误管理以及资源安全释放等手段,我们不仅提升了系统的稳定性,还增强了系统的可维护性与扩展性。
在后续章节中,我们将进一步探讨如何将异常处理与日志记录、用户界面交互等模块结合,打造一个更加完善的金融交易系统。
6. 输入输出(I/O)交互功能开发
在ATM系统中,用户与系统的交互是整个流程的关键环节。无论用户的操作是查询余额、取款、转账,还是修改密码,都需要通过输入输出接口完成信息的获取与反馈。本章将围绕C++标准I/O流的基本使用,详细讲解如何设计ATM系统的用户交互界面,并通过输入验证机制提升系统的安全性和稳定性。
6.1 C++标准I/O流的基本使用
C++标准库提供了强大的输入输出(I/O)支持,主要通过 <iostream> 头文件定义的 cin 和 cout 对象来实现控制台的输入和输出操作。此外, <fstream> 头文件支持文件输入输出流的处理。
6.1.1 cin 、 cout 的基本操作
cin 和 cout 是C++标准库中最常用的输入输出流对象。 cin 用于从标准输入(通常是键盘)读取数据,而 cout 用于将数据输出到标准输出(通常是控制台)。
#include <iostream>
int main() {
int age;
std::cout << "请输入您的年龄:"; // 输出提示信息
std::cin >> age; // 从标准输入读取整数
std::cout << "您输入的年龄是:" << age << std::endl; // 输出结果
return 0;
}
代码逻辑分析:
std::cout:输出流对象,用于将信息打印到控制台。<<:流插入运算符,用于将数据发送到输出流。std::cin:输入流对象,用于从控制台获取用户输入。>>:流提取运算符,用于从输入流中提取数据。std::endl:换行并刷新输出缓冲区。
参数说明:
age:用于存储用户输入的整数,类型为int。
💡 提示:对于字符串输入,推荐使用
std::getline()函数,可以避免因空格导致的截断问题。
6.1.2 文件输入输出流的使用
除了控制台输入输出,ATM系统可能需要将用户操作日志或账户信息保存到文件中,这就需要使用文件流( ifstream 和 ofstream )。
#include <iostream>
#include <fstream>
int main() {
std::ofstream outFile("account_log.txt"); // 创建并打开文件输出流
if (outFile.is_open()) {
outFile << "用户进行了登录操作" << std::endl;
outFile.close(); // 关闭文件
}
std::ifstream inFile("account_log.txt"); // 打开文件输入流
std::string line;
while (getline(inFile, line)) {
std::cout << "读取日志内容: " << line << std::endl;
}
inFile.close();
return 0;
}
代码逻辑分析:
std::ofstream outFile("account_log.txt"):创建一个输出文件流对象,并打开名为account_log.txt的文件,若文件不存在则自动创建。outFile.is_open():判断文件是否成功打开。outFile << "...":向文件中写入一行日志信息。outFile.close():关闭文件流,确保数据写入磁盘。std::ifstream inFile("account_log.txt"):打开文件用于读取。getline(inFile, line):逐行读取文件内容。
参数说明:
outFile:用于写入数据的文件输出流对象。inFile:用于读取数据的文件输入流对象。line:用于存储读取的每一行内容。
📌 应用场景:ATM系统可使用文件流记录用户操作日志,便于后续审计和调试。
6.2 ATM系统的用户交互界面设计
ATM系统的交互界面通常采用菜单驱动的方式,用户通过选择菜单项来执行相应的操作。良好的交互设计不仅提升用户体验,也增强系统的可维护性和扩展性。
6.2.1 主菜单与子菜单的构建
ATM系统常见的主菜单包括登录、注册、退出等选项。一旦用户登录成功,系统会跳转到用户主菜单,包含余额查询、取款、转账、修改密码等功能。
void showMainMenu() {
std::cout << "===== ATM 系统主菜单 =====" << std::endl;
std::cout << "1. 登录" << std::endl;
std::cout << "2. 注册新账户" << std::endl;
std::cout << "3. 退出系统" << std::endl;
std::cout << "请选择操作:";
}
void showUserMenu() {
std::cout << "===== 用户操作菜单 =====" << std::endl;
std::cout << "1. 查询余额" << std::endl;
std::cout << "2. 取款" << std::endl;
std::cout << "3. 转账" << std::endl;
std::cout << "4. 修改密码" << std::endl;
std::cout << "5. 返回主菜单" << std::endl;
std::cout << "请选择操作:";
}
代码逻辑分析:
showMainMenu():展示系统主菜单,包括登录、注册和退出选项。showUserMenu():展示用户登录后的操作菜单,提供账户相关功能。
参数说明:
- 无参数,直接输出菜单内容。
📌 实现思路:通过函数调用方式组织菜单结构,便于后续添加新的菜单项或调整界面逻辑。
6.2.2 用户指令的解析与执行
用户输入的菜单选择需要通过条件语句(如 switch 或 if-else )进行解析,并调用对应的功能函数。
int main() {
int choice;
while (true) {
showMainMenu();
std::cin >> choice;
switch (choice) {
case 1:
// 调用登录函数
login();
break;
case 2:
// 调用注册函数
registerUser();
break;
case 3:
std::cout << "感谢使用ATM系统,再见!" << std::endl;
return 0;
default:
std::cout << "无效的选择,请重新输入!" << std::endl;
}
}
return 0;
}
代码逻辑分析:
std::cin >> choice:读取用户输入的菜单选项。switch语句:根据用户输入执行对应操作。login()、registerUser():功能函数,需在其他部分实现。
参数说明:
choice:存储用户选择的菜单编号,类型为int。
📌 实现建议:可在输入前添加清除输入缓冲区的操作,防止非法输入导致死循环。
6.3 输入验证与格式处理
在ATM系统中,用户输入可能包含非法字符、超出范围的数值或格式错误的数据。为防止程序崩溃或数据异常,必须对用户输入进行严格的验证和格式处理。
6.3.1 用户输入的合法性检查
用户输入验证主要包括数据类型检查、范围检查和格式检查。
bool isValidPIN(const std::string& pin) {
if (pin.length() != 6) return false; // PIN码必须为6位
for (char ch : pin) {
if (!isdigit(ch)) return false; // 每个字符必须是数字
}
return true;
}
int getValidInteger(int min, int max) {
int value;
while (true) {
std::cin >> value;
if (std::cin.fail() || value < min || value > max) {
std::cin.clear(); // 清除错误状态
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // 清除缓冲区
std::cout << "输入无效,请重新输入(范围:" << min << "-" << max << "): ";
} else {
return value;
}
}
}
代码逻辑分析:
isValidPIN():验证PIN码是否符合要求(6位数字)。getValidInteger():获取合法整数输入,确保在指定范围内。std::cin.fail():检测输入是否失败(如输入字符而非数字)。std::cin.clear():清除输入流错误标志。std::cin.ignore():跳过缓冲区中的非法字符。
参数说明:
pin:待验证的PIN码字符串。value:用户输入的整数值。min、max:允许的最小和最大值。
📌 应用场景:用户登录时验证PIN码,取款时限制输入金额范围。
6.3.2 错误输入的处理与提示机制
错误输入不仅影响用户体验,还可能导致程序异常终止。通过统一的错误提示机制,可以提高系统的容错能力。
void handleInvalidInput() {
std::cin.clear(); // 清除错误标志
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // 清除缓冲区
std::cout << "检测到非法输入,请重新输入!" << std::endl;
}
代码逻辑分析:
std::cin.clear():恢复输入流的状态。std::cin.ignore():跳过当前行的非法输入。std::cout:输出错误提示信息。
参数说明:
- 无参数,直接处理当前输入流。
📌 实现建议:可在所有用户输入操作前调用此函数,统一处理非法输入。
表格:常用输入验证函数与用途对比
| 函数名 | 功能描述 | 使用场景 |
|---|---|---|
isValidPIN() |
验证6位数字PIN码 | 用户登录时输入PIN码验证 |
getValidInteger() |
获取合法整数输入(带范围限制) | 取款金额、菜单选项输入验证 |
handleInvalidInput() |
处理非法输入并提示用户重新输入 | 所有用户输入操作 |
Mermaid 流程图:用户输入处理流程
graph TD
A[开始] --> B{用户输入}
B --> C[验证输入是否合法]
C -- 合法 --> D[执行对应操作]
C -- 不合法 --> E[提示错误并重新输入]
D --> F[返回主菜单]
E --> B
📌 说明:该流程图展示了ATM系统中用户输入的处理逻辑,强调输入验证的重要性。
小结
第六章围绕ATM系统的输入输出交互功能展开,从C++标准I/O流的基本使用入手,逐步讲解了菜单界面的设计、用户指令的解析与执行,以及输入验证机制的实现。通过结构化的代码示例和流程图,帮助读者理解如何构建一个稳定、安全且用户友好的ATM交互系统。下一章将继续深入ATM系统的数据管理部分,探讨如何使用C++标准库中的数据结构高效处理用户信息和账户数据。
7. 数据结构应用:用户信息与账户管理
7.1 用户信息的存储与管理
在ATM系统中,用户信息(如用户名、用户ID、密码、关联账户等)需要被高效地存储和访问。C++标准库提供了多种数据结构,其中最常用的是 std::vector 和 std::map 。
7.1.1 使用 vector 或 map 存储用户数据
- std::vector :适合顺序访问和遍历,适用于用户数量较小的情况。
- std::map :适合根据唯一键(如用户名或用户ID)快速查找用户信息。
下面是一个使用 std::map 存储用户信息的示例:
#include <iostream>
#include <map>
#include <string>
#include "User.h" // 假设 User 类已定义
class UserManager {
private:
std::map<std::string, User> users; // 键为用户名,值为用户对象
public:
void addUser(const std::string& username, const User& user) {
users[username] = user;
}
bool findUser(const std::string& username, User& outUser) {
auto it = users.find(username);
if (it != users.end()) {
outUser = it->second;
return true;
}
return false;
}
void removeUser(const std::string& username) {
users.erase(username);
}
};
参数说明 :
-username:用户的唯一标识符,作为键。
-User:用户类,包含用户的基本信息。
-users:存储用户信息的映射表。执行逻辑说明 :
-addUser:将用户添加到映射表中。
-findUser:通过用户名查找用户并赋值给输出参数。
-removeUser:删除指定用户名的用户。
7.1.2 用户信息的查找与更新
在ATM系统中,用户信息可能需要频繁更新(如修改密码、绑定新账户等)。使用 std::map 可以高效地完成更新操作。
bool UserManager::updateUserPassword(const std::string& username, const std::string& newPassword) {
auto it = users.find(username);
if (it != users.end()) {
it->second.setPassword(newPassword); // 调用 User 类的 setPassword 方法
return true;
}
return false;
}
执行逻辑说明 :
- 查找用户是否存在。
- 如果存在,调用setPassword方法进行更新。
- 返回操作是否成功。
7.2 账户数据的组织与访问
账户信息(如账户号、余额、账户类型、交易记录)是ATM系统的核心数据之一。为了实现高效访问和管理,通常采用嵌套的数据结构进行组织。
7.2.1 账户与用户的关联管理
每个用户可以拥有多个账户,因此可以采用 std::map<std::string, std::vector<Account>> 来表示用户与账户的多对多关系。
#include <map>
#include <vector>
#include "Account.h"
class AccountManager {
private:
std::map<std::string, std::vector<Account>> userAccounts; // 用户ID -> 账户列表
public:
void addAccount(const std::string& userId, const Account& account) {
userAccounts[userId].push_back(account);
}
std::vector<Account> getAccounts(const std::string& userId) {
return userAccounts[userId];
}
bool findAccount(const std::string& accountId, Account& outAccount) {
for (const auto& pair : userAccounts) {
for (const auto& acc : pair.second) {
if (acc.getAccountId() == accountId) {
outAccount = acc;
return true;
}
}
}
return false;
}
};
执行逻辑说明 :
-addAccount:将账户添加到指定用户ID的账户列表中。
-getAccounts:获取某个用户的所有账户。
-findAccount:通过账户ID查找账户对象。
7.2.2 账户余额与交易记录的维护
账户余额的维护需要在每次交易(如存款、取款、转账)后更新。交易记录可以使用 std::list<Transaction> 来存储,并在类中提供查询接口。
class Account {
private:
std::string accountId;
double balance;
std::list<Transaction> transactionHistory;
public:
void deposit(double amount) {
balance += amount;
transactionHistory.push_back(Transaction("Deposit", amount));
}
bool withdraw(double amount) {
if (amount > balance) {
throw std::runtime_error("Insufficient balance");
}
balance -= amount;
transactionHistory.push_back(Transaction("Withdraw", amount));
return true;
}
void showTransactionHistory() const {
for (const auto& t : transactionHistory) {
std::cout << t.getType() << ": " << t.getAmount() << std::endl;
}
}
};
执行逻辑说明 :
-deposit:增加余额并记录交易。
-withdraw:检查余额后扣减,并记录交易。
-showTransactionHistory:输出交易历史。
7.3 数据结构对系统性能的影响
选择合适的数据结构不仅影响代码的可读性和可维护性,也直接影响ATM系统的性能表现。
7.3.1 查找效率与内存占用的平衡
| 数据结构 | 查找效率 | 插入/删除效率 | 内存开销 | 适用场景 |
|---|---|---|---|---|
std::vector |
O(n) | O(n)(尾部O(1)) | 低 | 数据量小、顺序访问 |
std::map |
O(log n) | O(log n) | 中 | 快速查找、有序 |
std::unordered_map |
平均 O(1) | 平均 O(1) | 高 | 快速随机访问 |
std::list |
O(n) | O(1) | 高 | 频繁插入删除 |
建议 :
- 对用户信息和账户信息使用std::unordered_map提高查找效率。
- 对交易记录使用std::list以支持频繁的插入操作。
7.3.2 数据结构在多线程环境下的安全性处理
在并发访问的ATM系统中(如多个终端同时操作),数据结构必须保证线程安全。
#include <mutex>
#include <map>
class ThreadSafeUserManager {
private:
std::map<std::string, User> users;
std::mutex mtx;
public:
void addUser(const std::string& username, const User& user) {
std::lock_guard<std::mutex> lock(mtx);
users[username] = user;
}
bool findUser(const std::string& username, User& outUser) {
std::lock_guard<std::mutex> lock(mtx);
auto it = users.find(username);
if (it != users.end()) {
outUser = it->second;
return true;
}
return false;
}
};
执行逻辑说明 :
- 使用std::mutex加锁,防止多个线程同时访问。
-std::lock_guard自动管理锁的释放。扩展讨论 :
- 如果系统并发量极高,可考虑使用无锁数据结构或使用读写锁(std::shared_mutex)提升读操作效率。
- 对于交易记录等写操作频繁的数据,可考虑使用队列(std::queue+std::mutex)进行异步持久化处理。下一章将继续探讨ATM系统的持久化机制设计,包括文件存储与数据库集成等内容,敬请期待。
简介:”C++实现ATM机”是一个典型的软件工程项目,旨在通过模拟自动取款机系统,帮助开发者掌握C++面向对象编程的核心技术。项目涵盖需求分析、系统设计、编码实现与测试,涉及类与对象、封装、继承、多态、异常处理、I/O操作、数据结构应用、设计模式、安全性控制及并发处理等关键技术。通过该项目,开发者可全面提升C++高级特性应用能力与实际项目开发经验。
更多推荐



所有评论(0)