简介:本项目通过模拟ATM取款机操作流程,帮助初学者掌握C++基础和面向对象编程概念。学生将学习如何利用C++的类、模板、异常处理等特性来构建ATM系统模型,包括实现账户管理、用户交互以及安全性考虑。同时,项目也涉及输入验证、异常处理、封装和多态性等编程实践。
1. C++编程语言特性
C++作为一门古老而强大的编程语言,它在IT行业中占据着不可动摇的地位。自1985年由Bjarne Stroustrup在贝尔实验室开始开发以来,C++以其高性能、多功能、灵活的编程范式而闻名,广泛应用于软件开发、游戏制作、实时物理模拟以及许多其他需要高效率执行的领域。
1.1 C++的多范式编程能力
C++支持多种编程范式,包括过程化、面向对象和泛型编程。这使得开发者可以根据项目需求灵活选择合适的编程方法。例如,在处理底层系统资源时,开发者可能会倾向于使用过程化编程;而在开发涉及复杂数据结构和算法的应用时,则可能采用泛型编程。
1.2 C++的性能优势
C++语言的性能优势主要体现在接近硬件层面的操作能力和出色的编译优化。其指针操作和内存管理功能允许程序员直接控制内存使用,这在性能敏感的应用中至关重要。同时,现代C++标准库提供了大量优化过的数据结构和算法,能够在不牺牲代码清晰度的同时,保持高效的运行时表现。
通过本章,我们将深入探索C++的核心特性,并理解它们是如何支持复杂系统设计和性能优化的。这将为后续章节中介绍的面向对象编程以及更高级的应用打下坚实的基础。
2. 面向对象编程基础
2.1 面向对象的基本概念
2.1.1 类与对象
在面向对象编程(OOP)中,类(Class)是定义了一组属性(数据)和方法(函数)的模板或蓝图,用于创建具有相同属性和行为的具体对象。对象(Object)是类的一个实例,它包含类中定义的属性值和方法的实现。
class Car {
public:
void start() {
// Code to start the car
}
private:
std::string color;
int year;
};
在上述代码中,我们定义了一个名为 Car
的类,它有两个私有成员变量 color
和 year
以及一个公共成员函数 start()
。一个对象可以是这个类的实例,比如 Car myCar;
创建了一个 Car
类的对象 myCar
。
2.1.2 封装、继承与多态
封装 :是一种将数据(或状态)和操作数据的代码绑定在一起的机制,隐藏对象的内部细节,只暴露必要的接口给外部使用。在C++中,我们使用 public
、 protected
和 private
访问修饰符来实现封装。
继承 :是一种机制,使得一个类可以继承另一个类的属性和方法。继承有助于代码复用,并可以创建一个类的层次结构。C++中的继承通过使用冒号 :
和访问修饰符(public, protected, private)来实现。
class Vehicle {
public:
int getNumberOfWheels() {
return numberOfWheels;
}
protected:
int numberOfWheels;
};
class Car : public Vehicle {
public:
Car() : numberOfWheels(4) {}
};
多态 :指的是一种能力,允许不同的类对同一个消息做出响应。在C++中,多态通常通过虚函数实现。虚函数允许在派生类中重写基类中的方法,从而根据对象的实际类型调用相应的方法版本。
class Shape {
public:
virtual void draw() = 0;
};
class Circle : public Shape {
public:
void draw() override {
std::cout << "Drawing a circle." << std::endl;
}
};
class Rectangle : public Shape {
public:
void draw() override {
std::cout << "Drawing a rectangle." << std::endl;
}
};
2.2 C++中的类和对象
2.2.1 类的定义与实现
类的定义包括数据成员和成员函数。数据成员是类的变量,用于存储对象的状态;成员函数则是类的方法,定义了对象的行为。在C++中,类的定义通常以关键字 class
开头。
class Date {
private:
int month, day, year;
public:
void setValues(int m, int d, int y) {
month = m;
day = d;
year = y;
}
void print() {
std::cout << year << "-" << month << "-" << day << std::endl;
}
};
在上面的例子中, Date
类有三个私有成员变量 month
、 day
和 year
,以及两个公共成员函数 setValues
和 print
。 setValues
用于设置日期,而 print
用于打印日期。
2.2.2 对象的创建和使用
创建类的对象称为实例化。一旦实例化,我们就可以调用对象的方法或访问其数据成员。创建对象时,通常需要调用构造函数,构造函数是一个特殊的方法,用于初始化对象的状态。
int main() {
Date today;
today.setValues(12, 25, 2023);
today.print(); // 输出: 2023-12-25
return 0;
}
以上代码展示了如何创建 Date
类的对象 today
,并通过调用 setValues
和 print
方法来使用它。这种创建和使用对象的方式是面向对象编程中最基本的操作。
3. 类的设计与实现(Account, ATM, User)
3.1 Account类的设计与实现
3.1.1 账户信息管理
在面向对象编程中,管理账户信息是一个核心功能。Account类通常会包含有关银行账户的基本属性,例如账户余额、账户号码、户名等。在设计Account类时,需要考虑到数据的封装性,以确保其对外是隐藏的,并且通过函数来操作这些数据。
class Account {
private:
std::string accountNumber;
std::string accountName;
double balance;
public:
Account(std::string num, std::string name, double bal) : accountNumber(num), accountName(name), balance(bal) {}
void deposit(double amount);
bool withdraw(double amount);
double getBalance() const;
};
- 构造函数初始化账户信息。
-
deposit
函数用于存款操作。 -
withdraw
函数用于取款操作,需要检查余额是否足够。 -
getBalance
函数提供获取账户余额的功能。
3.1.2 交易处理逻辑
在实现Account类的交易处理逻辑时,需要确保数据的一致性和准确性。在存款和取款操作中,要保证对账户余额的修改是原子性的,即在多线程环境下也不会出现数据不一致的情况。
void Account::deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
bool Account::withdraw(double amount) {
if (amount > balance) {
return false; // 不允许透支
}
balance -= amount;
return true;
}
-
deposit
方法增加账户余额,接受正数金额。 -
withdraw
方法减少账户余额,如果金额大于余额则返回false。
3.2 ATM类的设计与实现
3.2.1 ATM状态管理
ATM类负责管理ATM机的状态以及处理用户的请求。一个ATM机在任何时候都有一个特定的状态,例如等待用户输入、处理交易、退出等。
class ATM {
private:
Account *currentAccount;
std::string atmState;
public:
ATM(Account *account) : currentAccount(account), atmState("waiting") {}
void handleUserInput(const std::string &input);
void transitionToState(const std::string &newState);
};
-
currentAccount
指向当前用户账户。 -
atmState
表示ATM当前状态。 -
handleUserInput
方法处理用户输入。 -
transitionToState
方法改变ATM状态。
3.2.2 用户交互与请求处理
ATM类通过用户交互来执行不同的操作,例如存款、取款、查询余额等。在处理这些请求时,ATM机需要与Account类进行交互。
void ATM::handleUserInput(const std::string &input) {
if (atmState == "waiting") {
if (input == "deposit") {
transitionToState("depositing");
} else if (input == "withdraw") {
transitionToState("withdrawing");
} else if (input == "balance") {
std::cout << "Your current balance is: " << currentAccount->getBalance() << std::endl;
}
}
}
- 根据用户输入,调用对应的方法或改变ATM状态。
- 显示余额的示例操作。
3.3 User类的设计与实现
3.3.1 用户信息与权限管理
User类的设计用来管理用户的个人信息以及用户权限。在实际的ATM系统中,User类可能还会涉及到密码验证和操作权限的设置。
class User {
private:
std::string username;
std::string password;
bool isAdmin;
public:
User(std::string uname, std::string pwd, bool admin) : username(uname), password(pwd), isAdmin(admin) {}
bool checkCredentials(const std::string &inputPassword) {
return password == inputPassword;
}
bool hasAdminRights() const {
return isAdmin;
}
};
-
username
和password
存储用户信息。 -
isAdmin
标记是否为管理员。 -
checkCredentials
方法验证用户密码。 -
hasAdminRights
检查是否为管理员用户。
3.3.2 用户操作界面设计
用户操作界面应该清晰直观,易于操作。设计时应遵循用户中心设计原则,确保用户能够轻松地完成他们的任务。
// 假设这是一个控制台界面
void printMenu(const User &user) {
std::cout << "Welcome, " << user.username << std::endl;
if (user.hasAdminRights()) {
std::cout << "1. Deposit\n2. Withdraw\n3. Check Balance\n4. User Management\n";
} else {
std::cout << "1. Deposit\n2. Withdraw\n3. Check Balance\n";
}
std::cout << "5. Logout" << std::endl;
}
- 打印欢迎信息和菜单选项。
- 根据用户权限显示或隐藏特定选项。
在实际的开发过程中,类的设计与实现应遵循面向对象的原则,如单一职责、开闭原则、里氏替换原则等,以确保软件的可维护性和可扩展性。每个类都应该清晰地定义其职责,并保持良好的封装性,使得外部用户只能通过提供的接口与类进行交互。
4. 输入验证和异常处理
4.1 输入数据的验证机制
在任何软件系统中,输入数据的验证都扮演着至关重要的角色。验证过程确保了用户输入的数据是合法和预期的,这有助于防止恶意攻击、数据损坏或系统错误。在C++中,这一机制可以包括检查输入数据类型、格式和范围。
4.1.1 验证用户输入的合法性
合法性的检查是确认用户输入是否符合预定规则的过程。例如,一个银行系统需要确保用户输入的账号是有效的数字串,且长度符合要求。以下是一个简单的C++函数,它演示了如何验证用户输入的账号是否是有效的。
#include <iostream>
#include <string>
#include <regex>
bool isValidAccountNumber(const std::string& accountNumber) {
// 正则表达式,匹配16位数字的账号
std::regex accountNumberPattern("^[0-9]{16}$");
return std::regex_match(accountNumber, accountNumberPattern);
}
int main() {
std::string userAccountNumber;
std::cout << "请输入账号: ";
std::cin >> userAccountNumber;
if (isValidAccountNumber(userAccountNumber)) {
std::cout << "账号输入合法!" << std::endl;
// 进行后续操作...
} else {
std::cout << "账号输入非法,请重新输入!" << std::endl;
// 提示重新输入或错误处理...
}
return 0;
}
4.1.2 输入错误的处理策略
一旦检测到输入错误,有效的处理策略是提供用户友好的反馈,并给予他们更正的机会。下面的代码展示了在用户输入无效账号时,如何要求他们重新输入。
// ...之前的isValidAccountNumber函数保持不变
int main() {
std::string userAccountNumber;
while (true) {
std::cout << "请输入账号: ";
std::cin >> userAccountNumber;
if (isValidAccountNumber(userAccountNumber)) {
std::cout << "账号输入合法!" << std::endl;
break;
} else {
std::cout << "账号输入非法,请重新输入!" << std::endl;
}
}
// 进行后续操作...
return 0;
}
4.2 异常处理机制
异常处理是程序中用于处理不正常情况的机制。C++通过try、catch和throw关键字来实现异常处理。
4.2.1 C++异常处理基础
在C++中,异常处理是通过以下几种方式实现的:
-
throw
:抛出异常。 -
try
:块中编写可能抛出异常的代码。 -
catch
:块捕获并处理异常。
下面的例子演示了如何在C++中使用这些关键字来处理可能的异常。
#include <iostream>
#include <stdexcept>
void riskyOperation(int x) {
if (x == 0) {
throw std::runtime_error("不能除以零");
}
std::cout << "结果: " << (10 / x) << std::endl;
}
int main() {
try {
riskyOperation(0);
} catch (const std::runtime_error& e) {
std::cerr << "发生异常: " << e.what() << std::endl;
}
return 0;
}
4.2.2 设计自定义异常类
在某些情况下,你可能需要设计一个自定义异常类来提供更详细的错误信息。创建一个继承自 std::exception
的类是一个很好的开始。下面是一个示例:
#include <iostream>
#include <exception>
class InvalidAccountNumberException : public std::exception {
public:
const char* what() const throw() {
return "账号无效";
}
};
// ...isValidAccountNumber函数保持不变
int main() {
std::string userAccountNumber;
while (true) {
std::cout << "请输入账号: ";
std::cin >> userAccountNumber;
try {
if (!isValidAccountNumber(userAccountNumber)) {
throw InvalidAccountNumberException();
}
std::cout << "账号输入合法!" << std::endl;
break;
} catch (const InvalidAccountNumberException& e) {
std::cerr << "发生异常: " << e.what() << std::endl;
}
}
// 进行后续操作...
return 0;
}
通过定义 InvalidAccountNumberException
类,我们能够提供更为具体的错误信息。异常类中重载的 what()
方法返回了一个C风格的字符串,这个字符串描述了异常的原因。
异常处理是保证程序健壮性的重要机制之一,它能够帮助我们在发生错误时维持程序的正常运行,或者至少优雅地终止程序。在实际开发中,合理地设计异常处理流程,可以帮助开发者提高代码的可维护性和可读性。
5. 系统安全性(密码加密等)
系统安全是任何应用程序的根基,特别是在处理敏感信息如密码时,保护数据的完整性、保密性和可用性至关重要。C++作为一门功能强大的编程语言,在设计需要高度安全性的应用时,提供了丰富的工具和方法。本章节将探讨系统安全性的考虑以及密码加密的实现,确保开发者能够在构建安全应用方面做出明智的决策。
5.1 系统安全性的考虑
在设计和实现系统时,首先需要识别并分析安全需求,然后根据这些需求选择合适的加密技术来实现安全性目标。
5.1.1 安全性需求分析
安全性需求分析是确定系统需要满足哪些安全性属性的过程,包括但不限于:
- 保密性 : 确保敏感信息如密码、交易记录等不被未授权用户访问。
- 完整性 : 确保数据在存储或传输过程中未被非法篡改。
- 可用性 : 确保授权用户能够按需访问所需信息。
- 身份验证 : 确保用户身份的真实性,防止未授权访问。
- 授权 : 确保只有经过授权的用户才能执行特定操作。
5.1.2 加密技术的应用
加密技术是保证数据安全的主要手段。以下几种加密技术常用于提高系统的安全性:
- 对称加密 : 使用相同的密钥进行加密和解密操作。例如:AES、DES。
- 非对称加密 : 使用一对密钥,一个用于加密(公钥),另一个用于解密(私钥)。例如:RSA、ECC。
- 哈希函数 : 将任意长度的输入转换为固定长度的输出,通常用于验证数据的完整性。例如:SHA系列。
5.2 密码加密的实现
密码是安全应用中最敏感的信息之一。下面将详细介绍密码加密的两个主要方面:哈希函数与加密算法的应用和安全存储、传输密码的策略。
5.2.1 哈希函数与加密算法
哈希函数是密码学中用于生成消息摘要的一种函数,它可以检测数据的任何改动,使得原始数据即使被篡改也能被检测出来。
代码块示例
#include <iostream>
#include <openssl/sha.h> // 引入OpenSSL库的SHA函数
std::string sha256(const std::string& data) {
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256_CTX sha256;
SHA256_Init(&sha256);
SHA256_Update(&sha256, data.c_str(), data.size());
SHA256_Final(hash, &sha256);
std::stringstream ss;
for(int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
ss << std::hex << (int)hash[i];
}
return ss.str();
}
int main() {
std::string message = "Hello, World!";
std::string hash = sha256(message);
std::cout << "Original Message: " << message << std::endl;
std::cout << "SHA256 Hash: " << hash << std::endl;
return 0;
}
逻辑分析
- 引入OpenSSL库的SHA函数模块,确保在编译时链接了相应的库。
- 定义一个
sha256
函数,接受一个std::string
类型的消息作为输入。 - 创建一个
SHA256_CTX
结构体实例,用于存储中间哈希值。 - 使用
SHA256_Init
初始化上下文,SHA256_Update
将数据分段送入哈希函数,SHA256_Final
完成哈希计算并输出结果。 - 将输出的哈希值转换为16进制字符串。
非对称加密算法如RSA可用于加密数据或安全地交换对称密钥。例如,可以使用RSA公钥加密数据,然后使用私钥解密。
5.2.2 安全存储和传输密码
存储和传输密码时,需要采取额外措施以防止中间人攻击、重放攻击等安全威胁。
密码传输
传输密码时,最理想的做法是使用HTTPS协议,它结合了SSL/TLS加密技术,可以保证数据在传输过程中的机密性和完整性。
密码存储
存储密码时,推荐使用加盐的哈希技术。”盐”是指随机生成的字符串,它会与密码一起被哈希处理,使得即使是相同密码生成的哈希值也会不同。
表格示例:密码加盐技术比较
技术 | 描述 | 优点 | 缺点 |
---|---|---|---|
原生哈希 | 对密码直接进行哈希处理 | 实现简单 | 易受彩虹表攻击 |
带盐的哈希 | 在密码中添加随机盐值后哈希处理 | 防止彩虹表攻击 | 计算成本高 |
密钥拉伸 | 使用大量计算资源进行哈希处理,如PBKDF2 | 高安全性和可调整的计算强度 | 资源消耗大 |
代码块示例
#include <iostream>
#include <openssl/rand.h> // 引入OpenSSL库的随机数生成器
#include <openssl/evp.h> // 引入OpenSSL库的加密算法
std::string generateSalt(size_t length) {
unsigned char salt[32]; // 生成32字节的盐值
RAND_bytes(salt, length);
std::stringstream ss;
for (size_t i = 0; i < length; ++i) {
ss << std::hex << (int)salt[i];
}
return ss.str();
}
std::string hashPassword(const std::string& password, const std::string& salt) {
unsigned char hash[EVP_MAX_MD_SIZE];
unsigned int len;
std::string hashStr;
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL);
EVP_DigestUpdate(mdctx, salt.c_str(), salt.size());
EVP_DigestUpdate(mdctx, password.c_str(), password.size());
EVP_DigestFinal_ex(mdctx, hash, &len);
EVP_MD_CTX_free(mdctx);
for (unsigned int i = 0; i < len; i++)
hashStr += "0123456789ABCDEF"[hash[i] >> 4 & 0xF] + "0123456789ABCDEF"[hash[i] & 0xF];
return hashStr;
}
int main() {
std::string password = "MySecurePassword123";
std::string salt = generateSalt(16); // 生成16字节盐值
std::string hashedPassword = hashPassword(password, salt);
std::cout << "Original Password: " << password << std::endl;
std::cout << "Salt: " << salt << std::endl;
std::cout << "Hashed Password: " << hashedPassword << std::endl;
return 0;
}
逻辑分析
- 引入OpenSSL库的随机数生成器和加密算法。
- 定义
generateSalt
函数,生成指定长度的随机盐值。 - 定义
hashPassword
函数,使用盐值和密码生成加盐的哈希值。 - 在
main
函数中,生成盐值和哈希密码,分别输出原始密码、盐值和哈希密码。
通过以上示例,我们可以看到如何安全地处理密码。加密技术和合理的设计是保护系统安全性的关键。
6. 控制台用户界面设计
在软件开发中,用户界面(UI)的友好性对用户体验(UX)有着直接的影响。尤其是在控制台应用程序中,用户界面设计显得尤为重要,因为它直接关系到用户与程序交互的效率和愉悦度。本章将深入探讨控制台用户界面设计的原则和交互流程设计,确保开发者能够设计出既高效又易用的控制台应用程序。
6.1 控制台界面设计原则
6.1.1 用户体验的重要性
在控制台应用开发中,用户体验应当是设计的核心要素。虽然控制台界面无法提供图形用户界面(GUI)那样的直观性和视觉吸引力,但它应该尽可能简洁明了,逻辑清晰。用户体验的优化可以提高用户的工作效率,减少输入错误,从而提升整体的工作满意度。为了达到这一目标,设计时需要关注以下几个方面:
- 界面简洁性 :避免不必要的装饰和复杂的设计,确保用户能快速找到他们需要的功能。
- 易读性 :确保文本清晰,色彩对比合理,字体大小适宜。
- 一致性 :整个应用程序中应保持布局和命令的一致性,减少用户的学习成本。
- 错误容忍度 :设计时要考虑到用户可能的误操作,并提供清晰的错误提示。
6.1.2 界面布局与信息展示
用户界面布局是用户体验的关键。良好的界面布局可以引导用户高效地完成任务。下面是一些布局设计上的建议:
- 命令提示 :对于命令行界面,应当有一个清晰的提示符,让用户知道可以输入什么,以及程序的当前状态。
- 菜单选择 :使用菜单驱动的交互可以减少用户记忆负担,提高操作直观性。
- 帮助信息 :提供简洁的帮助信息和命令手册,使用户在遇到困难时能够快速解决问题。
- 反馈与确认 :对于用户的重要操作,提供明确的反馈和确认步骤,避免误操作。
6.2 控制台交互流程设计
6.2.1 用户操作流程图
设计用户操作流程图是控制台界面设计的一个重要步骤。流程图可以帮助设计者可视化用户的操作路径,并确保所有功能都易于访问。下面是一个简单的流程图示例,用于描述用户在ATM机上的操作流程:
graph LR
A[开始] --> B{登录账户}
B -->|成功| C[主菜单]
B -->|失败| D[输入错误]
D --> E[重新输入]
E --> B
C --> F[选择服务]
F --> G[余额查询]
F --> H[取款操作]
F --> I[存款操作]
F --> J[转账操作]
F --> K[退出系统]
G --> C
H --> L[确认金额]
L --> M[取款成功]
M --> K
I --> K
J --> K
6.2.2 功能模块的划分与实现
在设计控制台界面时,合理地划分功能模块是至关重要的。每个模块都应当有明确的目的,并通过清晰的界面元素展示给用户。模块化的设计不仅有助于用户理解和使用程序,也便于开发者进行维护和扩展。
模块化设计通常遵循以下原则:
- 单一职责 :每个模块应只负责一个功能或一组相关的功能。
- 接口清晰 :模块之间的交互应当通过明确的接口进行,隐藏内部实现细节。
- 松耦合 :减少模块之间的依赖,使得在不影响其他模块的情况下可以修改或替换一个模块。
以下是一个简单的示例代码块,展示如何在C++中实现一个简单的控制台菜单:
#include <iostream>
#include <string>
void showMenu() {
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;
}
void processChoice(int choice) {
switch (choice) {
case 1:
// 处理余额查询逻辑
std::cout << "处理余额查询..." << std::endl;
break;
case 2:
// 处理取款操作逻辑
std::cout << "处理取款操作..." << std::endl;
break;
// ... 其他case处理
case 5:
// 处理退出系统逻辑
std::cout << "退出系统..." << std::endl;
break;
default:
std::cout << "无效的选项,请重新输入" << std::endl;
}
}
int main() {
int choice;
do {
showMenu();
std::cout << "请输入您的选择: ";
std::cin >> choice;
processChoice(choice);
} while (choice != 5);
return 0;
}
在这个示例中, showMenu
函数负责显示菜单选项,而 processChoice
函数根据用户输入处理不同的操作。 main
函数则是程序的主入口,负责循环显示菜单,获取用户输入,并调用相应的处理函数。这样的模块化设计使得程序结构清晰,易于理解和维护。
在本章中,我们已经详细探讨了控制台用户界面设计的原则和流程。下一章,我们将进入多态性应用、封装原则与结构化编程方法的深入分析。
7. 多态性应用、封装原则与结构化编程方法
在软件开发中,多态性、封装原则和结构化编程方法是构建高效、可维护代码的关键。这三个概念虽然不同,但它们在现代软件工程中相互补充,共同为软件的设计和实现提供了强大的支持。
7.1 多态性的应用
多态性是面向对象编程中的一个核心概念,它允许使用同一接口来表示不同的底层形式(或类)。多态的实现可以极大地增强代码的可扩展性和灵活性。
7.1.1 函数重载与覆盖
在C++中,函数重载(Overloading)和覆盖(Overriding)是多态性的两种表现形式。
- 函数重载 是指在同一个作用域内可以声明几个功能类似的同名函数,但这些函数的参数类型、个数或顺序至少有一个不同。编译器根据函数声明时的参数类型和个数,决定调用哪个函数。
void print(int a) {
std::cout << "Print integer: " << a << std::endl;
}
void print(double a) {
std::cout << "Print double: " << a << std::endl;
}
// 测试重载函数
print(10); // 输出: Print integer: 10
print(3.14); // 输出: Print double: 3.14
- 函数覆盖 是指派生类重新定义继承自基类的方法。在运行时,调用的是派生类的版本。
class Base {
public:
virtual void show() { std::cout << "Base class show method" << std::endl; }
};
class Derived : public Base {
public:
void show() override { std::cout << "Derived class show method" << std::endl; }
};
// 测试覆盖函数
Base* b = new Base();
b->show(); // 输出: Base class show method
Base* d = new Derived();
d->show(); // 输出: Derived class show method
7.1.2 虚函数与多态的实现
虚函数是实现多态的关键。通过声明一个函数为虚函数,我们可以让派生类在运行时覆盖基类的方法。使用 virtual
关键字声明的函数在派生类中可以被覆盖,从而实现多态。
class Base {
public:
virtual void func() {
std::cout << "Base class func method" << std::endl;
}
};
class Derived : public Base {
public:
void func() override {
std::cout << "Derived class func method" << std::endl;
}
};
void callFunc(Base& b) {
b.func(); // 这里将调用派生类的func方法
}
Base b;
Derived d;
callFunc(b); // 输出: Base class func method
callFunc(d); // 输出: Derived class func method
7.2 封装原则的实践
封装是指隐藏对象的属性和实现细节,只对外提供公共访问方式,是一种“信息隐藏”的概念。
7.2.1 封装的作用与实现
封装可以减少代码的耦合度,提高安全性,并增加代码的可复用性。在C++中,封装通常是通过类来实现的。
class Account {
private:
double balance; // 私有成员变量,外部不可访问
public:
Account(double initialBalance) : balance(initialBalance) {}
void deposit(double amount) {
balance += amount;
}
double getBalance() const {
return balance;
}
};
7.2.2 访问权限的控制
在C++中,我们通过访问修饰符来控制类成员的访问权限。通常有三种访问权限:
-
public
:公有成员可以在任何地方被访问。 -
protected
:保护成员可以被派生类访问。 -
private
:私有成员只能被类本身访问。
class Base {
public:
void publicMethod() { /* ... */ } // 公有方法
protected:
void protectedMethod() { /* ... */ } // 保护方法
private:
void privateMethod() { /* ... */ } // 私有方法
};
7.3 结构化编程方法
结构化编程是通过使用顺序、选择(分支)和重复(循环)三种基本控制结构来组织程序结构的方法。
7.3.1 模块化编程思想
模块化编程是将程序分解为独立、可重用的代码块(模块),通过这些模块的协同工作来完成整个程序的功能。
// 模块化函数示例
// 一个模块化的加法函数
int add(int a, int b) {
return a + b;
}
// 一个模块化的打印函数
void printResult(int result) {
std::cout << "Result: " << result << std::endl;
}
// 主函数,调用模块化函数
int main() {
int sum = add(3, 4);
printResult(sum);
return 0;
}
7.3.2 代码的可读性与可维护性
提高代码的可读性和可维护性是每个开发人员的责任。良好的代码风格、注释和文档可以帮助其他开发者更快地理解和维护代码。
/**
* 计算并打印两个数的和
* @param a 第一个加数
* @param b 第二个加数
*/
int add(int a, int b) {
return a + b; // 计算两数之和并返回
}
int main() {
int sum = add(1, 2); // 计算1+2的和
std::cout << "The sum is: " << sum << std::endl; // 打印结果
return 0;
}
代码的可读性和可维护性是通过遵循一定的编码标准和最佳实践来实现的,包括但不限于合理命名、编写文档注释、避免过于复杂的逻辑以及保持函数的简短等。
Mermaid 流程图示例
下面是一个使用Mermaid语法创建的简单流程图,展示了如何处理用户输入的一个基本例子。
graph TD
A[开始] --> B{用户输入}
B --> |合法| C[处理输入]
B --> |非法| D[显示错误]
C --> E[继续操作]
D --> F[提示用户重新输入]
F --> B
E --> G[结束]
在这个流程中,系统会首先检查用户输入是否合法。如果是合法的输入,系统将处理该输入并继续后续操作;如果是非法的输入,系统将显示错误消息,并提示用户重新输入。
简介:本项目通过模拟ATM取款机操作流程,帮助初学者掌握C++基础和面向对象编程概念。学生将学习如何利用C++的类、模板、异常处理等特性来构建ATM系统模型,包括实现账户管理、用户交互以及安全性考虑。同时,项目也涉及输入验证、异常处理、封装和多态性等编程实践。