简介:配套《C++大学教程(第九版)》的可直接编译运行的课后习题代码,从第2章基础输出程序(如fig02_01.cpp、fig02_05.cpp)开始,覆盖变量、控制结构、函数、数组、指针、类与对象、继承、多态等全部核心章节。重点包含ATM自动柜员机综合项目,提供ATM、BankDatabase、Account、Transaction等主类及Withdrawal、Deposit、BalanceInquiry、CashDispenser、Keypad、Screen、DepositSlot等模块化实现文件,所有源码按教材章节结构组织在ch02至ch07等目录下,并配有appF、appH、appI、appJ等应用级示例。代码命名规范、注释清晰、接口明确,支持主流C++编译器(如g++、MSVC),无需额外配置即可调试运行。适合初学者对照课本逐章练习,理解类封装原则、对象协作机制和面向对象系统设计流程,也便于教师布置实验或学生完成课程设计。
1. 这不是“代码搬运工”,而是一套可落地的C++面向对象训练体系
你手头那本《C++大学教程(第九版)》翻到第2章,写着“Hello, World!”;翻到第6章,开始讲类与对象;翻到第7章,突然冒出一个ATM系统案例——但教材里只有UML图、伪代码和零散片段。你照着敲,编译报错;你查资料,发现教材没告诉你BankDatabase::getAccount(int)该返回什么、Transaction基类的纯虚函数怎么被Withdrawal具体实现、Keypad类为何要设计成单例……这不是你学得不够努力,而是教材天然存在“教学留白”:它负责讲清概念,但不负责把概念缝合成能跑起来的系统。
这套习题源码合集,就是专门来填这个坑的。它不是简单地把书上代码打出来,而是以一个真实软件工程师构建小型系统的方式,重构了全书所有关键练习。从fig02_01.cpp那个两行输出程序开始,每一行代码都经过实测验证;到ch07下的ATM工程,它是一个完整、解耦、可调试的C++项目,包含12个.h/.cpp文件,严格遵循教材的类图设计,但补全了所有教材省略的“血肉”:内存管理策略、异常处理边界、构造函数初始化列表顺序、虚析构函数的必要性、头文件卫士宏的命名规范、甚至#include路径的相对写法——这些细节,恰恰是初学者在IDE里点开一个红波浪线时最需要的答案。
关键词里的“C++习题代码”不是指一堆孤立的.cpp文件,而是指一套按认知曲线递进的训练路径:第2章练输入输出格式控制,第3章练if/else嵌套逻辑与布尔表达式陷阱,第4章练函数重载与默认参数的调用歧义,第5章练一维数组越界访问的调试技巧,第6章练class定义中公有/私有成员的访问权限边界,第7章则直接把你扔进ATM系统的多文件协作现场。而“ATM系统源码”也不是一个黑盒Demo,它是教材第7章“面向对象设计”理念的实体化呈现:Account类封装余额与透支逻辑,BankDatabase类封装账户集合与查找算法,Transaction作为抽象基类强制派生类实现execute(),Withdrawal与Deposit各自处理资金流转与状态校验——这种设计不是为了炫技,而是让你亲手触摸到“封装隔离变化”、“继承复用行为”、“多态消除条件分支”的真实手感。如果你正卡在“知道概念但不会写类”,或者“写了类但不知道怎么组织多个类协同工作”,这套代码就是你缺的那块拼图。
2. 全书结构拆解:为什么代码必须按章节+应用双轨组织?
2.1 教材章节代码:从语法肌肉记忆到设计直觉养成
教材的章节推进,本质是一条精心设计的认知阶梯。这套源码严格对应ch02至ch07目录,但绝非机械复制。以ch03(控制结构)为例,教材可能只给一个fig03_12.cpp计算阶乘的循环示例,而源码包里你会看到:
fig03_12_loop.cpp:基础for循环版本,重点演示unsigned int防止负数输入的边界处理;fig03_12_recursion.cpp:递归版本,刻意在main()中加入try/catch捕获栈溢出异常,并注释说明“此处递归深度受限于系统栈大小,实际项目中应避免无限制递归”;fig03_12_input_validation.cpp:增强版,用while循环持续提示用户输入合法整数,直到输入有效值才退出——这补全了教材常忽略的“用户输入不可信”这一工程铁律。
这种“一题多解+工程加固”的做法,在ch05(函数)中更明显。教材讲函数重载,源码就提供print(int), print(double), print(const std::string&)三个重载版本,并在main()中故意调用print(3.14f)(float字面量),触发编译器报错,再通过注释解释“float会优先匹配double而非int,这是重载解析规则”。你看的不是答案,而是编译器思考的过程。
提示:
ch06(类与对象)目录下的fig06_09.cpp(Time类)是理解封装的分水岭。教材只定义了setTime()和getTime(),源码却额外增加了isValidTime()私有成员函数,并在setTime()中调用它进行参数校验。这意味着:封装不仅是隐藏数据,更是将校验逻辑与数据绑定,确保对象永远处于有效状态。初学者常犯的错误是把校验写在main()里,而这里教会你“校验即封装的一部分”。
2.2 应用级工程(appF/appH/appI/appJ):从单文件练习到多文件协作的跃迁
如果章节代码是“单兵技能训练”,那么appF(GradeBook)、appH(Date)、appI(Employee)、appJ(Invoice)就是“小队战术演练”。它们规模不大(通常3-5个文件),但已具备真实项目的骨架:
- 头文件依赖管理:
appJ/Invoice.h中只声明class Invoice,不包含<iostream>,而appJ/Invoice.cpp中才#include <iostream>。这教会你“头文件只暴露接口,实现文件才引入依赖”,避免头文件污染。 - 构造函数初始化列表实战:
appI/Employee.cpp中,Employee::Employee(string n, double s)的初始化列表写作: name{n}, salary{s},而非在函数体内赋值。注释明确指出:“对于const成员或引用成员,必须使用初始化列表,否则编译失败;即使非const,初始化列表也比赋值更高效,因避免了默认构造+赋值两次调用”。 - const成员函数的契约精神:
appH/Date.h中,int getDay() const被标记为const,意味着它承诺不修改对象状态。源码在main()中创建const Date d{1,1,2023};后,只能调用d.getDay(),不能调用任何非const成员函数——这是C++类型系统对“只读”语义的硬性保障。
这些appX工程,是通往ATM系统的必经桥梁。它们让你习惯在多个.h/.cpp文件间跳转,理解#include "Account.h"与#include <vector>的本质区别(前者是本地头文件,后者是标准库头文件),掌握g++ -c Account.cpp生成目标文件、g++ main.o Account.o -o atm链接可执行文件的底层流程——这些知识,教材不会细讲,但面试官会问。
2.3 ATM综合项目:面向对象设计原则的集中检验场
ch07下的ATM系统,是整套资源的皇冠。它不是玩具代码,而是严格遵循教材UML图构建的、可交互运行的系统。其目录结构本身就是一门设计课:
atm/
├── ATM.h/cpp // 系统入口,协调所有组件
├── BankDatabase.h/cpp // 账户数据库,单例模式管理账户集合
├── Account.h/cpp // 银行账户,封装余额、透支额度等核心数据
├── Transaction.h/cpp // 抽象交易基类,定义execute()纯虚函数
├── Withdrawal.h/cpp // 取款交易,实现execute()并调用CashDispenser
├── Deposit.h/cpp // 存款交易,实现execute()并调用DepositSlot
├── BalanceInquiry.h/cpp // 余额查询,只读操作,不修改状态
├── CashDispenser.h/cpp // 现金出钞机,模拟硬件交互
├── Keypad.h/cpp // 键盘输入,返回整数PIN/金额
├── Screen.h/cpp // 屏幕显示,提供统一UI接口
└── DepositSlot.h/cpp // 存款插槽,接收现金并通知系统
这个结构完美诠释了单一职责原则(SRP):每个类只做一件事。CashDispenser不关心账户余额,只负责“吐钱”;Keypad不验证PIN,只负责“读数字”;ATM类才是总指挥,它组合(Composition)所有组件,决定何时调用Keypad::getInput()、何时调用BankDatabase::authenticateUser()、何时调用Withdrawal::execute()。当你在ATM.cpp中看到transactionPtr->execute();这一行时,背后是多态(Polymorphism) 在起作用:transactionPtr是Transaction*类型,但实际指向Withdrawal或Deposit对象,运行时自动调用对应子类的execute()——这消除了if (type == WITHDRAWAL) ... else if (type == DEPOSIT) ...这类脆弱的条件分支。
注意:
BankDatabase采用单例模式(Singleton),其getInstance()静态方法确保全局唯一实例。源码中特意在BankDatabase.cpp的构造函数里添加了std::cout << "BankDatabase initialized.\n";,当你首次调用BankDatabase::getInstance()时,你会看到这行输出——这是验证单例是否生效的最直观方式。很多初学者写单例失败,就是因为忘了将构造函数设为private,而这里的代码已帮你踩过这个坑。
3. 核心模块深度解析:从代码行到设计思想的穿透式阅读
3.1 Account类:封装的终极实践与内存安全边界
Account.h看似简单,但每一行都暗藏深意:
#ifndef ACCOUNT_H
#define ACCOUNT_H
#include <string>
class Account {
public:
explicit Account(int accountNumber, double initialBalance, double allowedOverdraft);
// 访问器(Getter)
int getAccountNumber() const { return accountNumber; }
double getBalance() const { return balance; }
double getAllowedOverdraft() const { return allowedOverdraft; }
// 修改器(Setter)——带校验!
bool setBalance(double newBalance); // 返回bool表示操作是否成功
void setAllowedOverdraft(double newOverdraft);
// 业务方法
bool credit(double amount); // 存入
bool debit(double amount); // 取出(支持透支)
private:
const int accountNumber; // const成员,初始化后不可变
double balance; // 当前余额
double allowedOverdraft; // 允许透支额度
// 私有辅助函数:校验余额变更是否合法
bool isValidBalance(double proposedBalance) const;
};
#endif
这段代码的教学价值远超语法:
explicit Account(...):阻止隐式转换。若去掉explicit,Account acc = 12345;会意外调用单参数构造函数,这是易错点,源码已规避。getBalance() const:const修饰符不仅保证函数不修改对象,更向调用者承诺“此操作绝对安全,可放心在多线程中调用”。setBalance(double)返回bool:教材可能只写void setBalance(...),但真实项目中,设置余额可能失败(如传入负数)。返回bool是防御性编程的体现,迫使调用者处理失败情况。const int accountNumber:账户号一旦创建绝不改变,用const修饰是编译期强制保障,比注释“请勿修改”可靠一万倍。isValidBalance()私有函数:将校验逻辑封装在类内部,确保无论从何处修改balance,都经过同一套规则检查。这是“封装=数据+操作”的教科书级实现。
在Account.cpp中,debit(double amount)的实现尤为精妙:
bool Account::debit(double amount) {
if (amount <= 0.0) return false; // 参数校验
double newBalance = balance - amount;
if (!isValidBalance(newBalance)) {
// 透支超出额度,拒绝操作
return false;
}
balance = newBalance; // 确保校验通过后才修改状态
return true;
}
注意balance = newBalance;这行代码的位置——它在isValidBalance()校验通过之后。这保证了对象状态的原子性(Atomicity):要么完整更新成功,要么保持原状,绝不会出现“余额已扣但校验失败”的中间态。这种思维,是区分“会写代码”和“会写健壮代码”的关键。
3.2 Transaction继承体系:多态如何消灭if-else地狱
Transaction.h定义了整个ATM业务逻辑的骨架:
#ifndef TRANSACTION_H
#define TRANSACTION_H
#include <string>
#include "Account.h"
#include "BankDatabase.h"
class Transaction {
public:
Transaction(int userAccountNumber, BankDatabase& bankDB);
virtual ~Transaction() = default; // 必须有虚析构函数!
virtual void execute() = 0; // 纯虚函数,强制派生类实现
int getAccountNumber() const { return accountNumber; }
protected:
const int accountNumber;
BankDatabase& bankDatabase;
};
#endif
关键点在于virtual ~Transaction() = default;。很多初学者忽略这点,导致delete transactionPtr;时只调用Transaction的析构函数,而不调用Withdrawal的析构函数,引发资源泄漏。源码用= default显式声明虚析构,是C++面向对象的黄金法则。
Withdrawal.h继承Transaction,并添加专属成员:
#ifndef WITHDRAWAL_H
#define WITHDRAWAL_H
#include "Transaction.h"
#include "CashDispenser.h"
class Withdrawal : public Transaction {
public:
Withdrawal(int userAccountNumber, BankDatabase& bankDB,
CashDispenser& cashDispenser);
virtual void execute() override; // override关键字,编译器检查是否正确重写
private:
CashDispenser& cashDispenser;
double amount; // 用户请求取款金额
};
#endif
execute()的实现(Withdrawal.cpp)展示了多态的威力:
void Withdrawal::execute() {
// 1. 获取账户
Account& userAccount = bankDatabase.getAccount(accountNumber);
// 2. 检查余额是否足够(含透支额度)
if (userAccount.getBalance() + userAccount.getAllowedOverdraft() >= amount) {
// 3. 执行取款:先更新账户,再出钞
userAccount.debit(amount);
cashDispenser.dispenseCash(amount);
// 4. 显示成功信息
std::cout << "Withdrawal successful. Amount: $" << amount << std::endl;
} else {
std::cout << "Insufficient funds." << std::endl;
}
}
现在看ATM.cpp中的调度逻辑:
void ATM::performTransactions() {
// ... 用户登录后,获取交易类型 ...
Transaction* transactionPtr = nullptr;
switch (transactionType) {
case WITHDRAWAL:
transactionPtr = new Withdrawal(accountNumber, bankDatabase, cashDispenser);
break;
case DEPOSIT:
transactionPtr = new Deposit(accountNumber, bankDatabase, depositSlot);
break;
case BALANCE_INQUIRY:
transactionPtr = new BalanceInquiry(accountNumber, bankDatabase);
break;
default:
return;
}
// 关键:统一调用execute(),无需关心具体类型!
transactionPtr->execute();
delete transactionPtr; // 注意内存释放
}
这里没有if (transactionType == WITHDRAWAL) { doWithdrawal(); } else if (...)。transactionPtr->execute()这一行,编译器在编译时只知道它是Transaction*,但运行时根据transactionPtr实际指向的对象类型,动态绑定到Withdrawal::execute()或Deposit::execute()。这就是运行时多态(Runtime Polymorphism) ——它让新增交易类型(如Transfer)只需新增一个派生类,完全不用修改ATM::performTransactions(),符合开闭原则(OCP)。
3.3 BankDatabase单例与内存管理:全局状态的安全访问
BankDatabase是ATM系统的数据中枢,必须全局唯一且线程安全(虽本项目未涉及多线程,但设计需预留扩展)。源码采用经典的Meyers单例(C++11线程安全):
// BankDatabase.h
class BankDatabase {
public:
static BankDatabase& getInstance(); // 返回引用,避免拷贝
Account& getAccount(int accountNumber);
bool authenticateUser(int accountNumber, int pin);
private:
BankDatabase(); // 私有构造
BankDatabase(const BankDatabase&) = delete; // 禁止拷贝
BankDatabase& operator=(const BankDatabase&) = delete; // 禁止赋值
std::map<int, Account> accounts; // 账户映射表
};
// BankDatabase.cpp
BankDatabase& BankDatabase::getInstance() {
static BankDatabase instance; // C++11保证此静态局部变量的初始化是线程安全的
return instance;
}
这个实现的精妙之处在于:
static BankDatabase instance;:静态局部变量,首次调用getInstance()时初始化,后续调用直接返回引用。C++11标准保证:编译器自动生成必要的锁机制,确保多线程环境下instance只被初始化一次,无需手动加锁。返回引用而非指针:避免nullptr检查,语义更清晰(单例必然存在)。
在main()中,你只需写BankDatabase& db = BankDatabase::getInstance();,即可获得全局唯一的数据库实例。这种设计,比在main()中创建一个BankDatabase对象然后层层传递参数,要简洁、安全得多。
4. 实操指南:从零开始编译、调试与定制你的ATM系统
4.1 编译环境准备与一键构建脚本
这套代码兼容主流编译器,但配置细节决定成败。以下是针对不同平台的实操建议:
Linux/macOS(推荐g++):
- 确保安装g++ 7.0+(支持C++17):g++ --version
- 进入atm/目录(即包含ATM.cpp等文件的目录)
- 手动编译(理解过程):
```bash
# 1. 编译所有.cpp文件为对象文件(-c选项)
g++ -c -std=c++17 ATM.cpp
g++ -c -std=c++17 BankDatabase.cpp
g++ -c -std=c++17 Account.cpp
# … 依次编译所有.cpp文件
# 2. 链接所有对象文件生成可执行文件
g++ -std=c++17 ATM.o BankDatabase.o Account.o Transaction.o \
Withdrawal.o Deposit.o BalanceInquiry.o CashDispenser.o \
Keypad.o Screen.o DepositSlot.o -o atm_system
# 3. 运行
./atm_system
- **强烈推荐使用Makefile**(源码包中已提供`Makefile`):makefile
CXX = g++
CXXFLAGS = -std=c++17 -Wall -Wextra
SOURCES = $(wildcard *.cpp)
OBJECTS = $(SOURCES:.cpp=.o)
TARGET = atm_system
$(TARGET): $(OBJECTS)
$(CXX) $(CXXFLAGS) $^ -o $@
%.o: %.cpp
$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
rm -f $(OBJECTS) $(TARGET)
.PHONY: clean
`` 执行make即可一键编译,make clean清理。-Wall -Wextra`开启所有警告,能提前发现潜在问题(如未使用的变量、隐式类型转换)。
Windows(MSVC):
- 使用Visual Studio 2019+,新建“空项目”,将所有.h/.cpp文件拖入解决方案资源管理器。
- 在“项目属性”→“常规”→“C++语言标准”中选择“ISO C++17标准”。
- 关键:在“项目属性”→“C/C++”→“预编译头”中,将所有.cpp文件的“预编译头”设为“不使用预编译头”,避免stdafx.h冲突。
- 编译后生成atm_system.exe,双击运行。
实操心得:第一次编译失败?90%的原因是
#include路径错误。检查ATM.cpp中#include "BankDatabase.h",确保BankDatabase.h与ATM.cpp在同一目录,或使用#include "../ch07/BankDatabase.h"(若目录结构不同)。源码包中所有#include均采用相对路径,这是跨平台可移植的关键。
4.2 调试ATM系统:从“程序崩溃”到“逻辑清晰”的三步法
ATM系统交互性强,调试不能只靠cout。以下是高效调试路径:
第一步:断点定位崩溃点
- 在VS Code中,打开ATM.cpp,在main()函数第一行设断点。
- 按F5启动调试,程序停在断点。
- 按F10逐过程(Step Over),观察变量窗口中accountNumber、bankDatabase等变量的初始值。
- 当执行到bankDatabase.authenticateUser(accountNumber, pin)时,按F11进入(Step Into),跳转到BankDatabase.cpp,查看accounts.find(accountNumber)是否返回end()(即账户不存在)——这是登录失败的常见原因。
第二步:日志追踪对象生命周期
在Account.cpp的构造函数和析构函数中添加日志:
Account::Account(int accNum, double initBal, double overdraft)
: accountNumber{accNum}, balance{initBal}, allowedOverdraft{overdraft} {
std::cout << "Account " << accountNumber << " constructed.\n";
}
Account::~Account() {
std::cout << "Account " << accountNumber << " destructed.\n";
}
运行程序,你会看到账户对象的创建与销毁顺序。若看到“constructed”但没看到“destructed”,说明内存泄漏——这通常是因为new分配但忘记delete。源码中ATM::performTransactions()末尾有delete transactionPtr;,正是为解决此问题。
第三步:单元测试驱动功能验证
不要等到整个ATM跑通才验证Account类。单独测试它:
// test_Account.cpp
#include "Account.h"
#include <cassert>
#include <iostream>
int main() {
Account acc{12345, 1000.0, 500.0};
// 测试正常取款
assert(acc.debit(200.0) == true);
assert(acc.getBalance() == 800.0);
// 测试透支取款
assert(acc.debit(900.0) == true); // 800 - 900 = -100, 在500透支额度内
assert(acc.getBalance() == -100.0);
// 测试超额透支(应失败)
assert(acc.debit(600.0) == false); // -100 - 600 = -700 < -500
std::cout << "All Account tests passed!\n";
return 0;
}
编译运行g++ test_Account.cpp Account.cpp -o test_Account && ./test_Account。通过assert快速验证核心逻辑,比在ATM界面里反复输入PIN高效得多。
4.3 定制化扩展:从“运行教材代码”到“打造你的银行系统”
这套代码是起点,不是终点。以下是几个低门槛、高价值的扩展方向:
扩展1:添加交易历史记录
- 在Account.h中添加std::vector<std::string> transactionHistory;
- 在Account::credit()和Account::debit()中,追加日志字符串:transactionHistory.push_back("CREDIT: $" + std::to_string(amount));
- 新增void printTransactionHistory() const,遍历并打印历史。
- 收获:实践std::vector动态数组、std::string拼接、const成员函数访问非常量成员(需将transactionHistory声明为mutable)。
扩展2:持久化账户数据到文件
- 在BankDatabase.cpp的构造函数中,添加loadAccountsFromFile("accounts.txt");
- 实现loadAccountsFromFile():按行读取,每行格式accountNumber,balance,overdraft,用std::stoi和std::stod解析。
- 实现saveAccountsToFile():遍历accounts,将每个账户信息写入文件。
- 收获:掌握<fstream>文件流、字符串分割(std::stringstream)、异常处理(文件打开失败)。
扩展3:图形界面(GUI)雏形
- 使用轻量级库如imgui或nana,替换Screen.cpp中的std::cout。
- 将Screen::displayMessage()改为调用GUI库的文本显示函数。
- 收获:理解“抽象接口”(Screen类)与“具体实现”(控制台vs GUI)的分离,为学习Qt或wxWidgets打下基础。
这些扩展,都不需要重写整个ATM,只需在现有架构上“插拔”新模块。这正是良好面向对象设计的魅力:变化被隔离在最小的单元内,系统整体稳定如磐石。
5. 常见问题排查与避坑指南:那些教材不会告诉你的“血泪教训”
5.1 编译错误高频问题速查表
| 错误现象 | 可能原因 | 解决方案 | 经验备注 |
|---|---|---|---|
error: 'Account' does not name a type | Transaction.h中#include "Account.h"缺失,或路径错误 | 检查Transaction.h顶部是否有#include "Account.h",确认文件名大小写(Linux敏感) | C++中#include "xxx"搜索当前目录,#include <xxx>搜索系统路径,混淆会导致找不到头文件 |
undefined reference to 'Account::Account(int, double, double)' | Account.cpp未被编译进项目,或g++命令漏掉Account.o | 确保Account.cpp在Makefile的SOURCES中,或手动编译时包含Account.cpp | 链接阶段报错,说明符号定义缺失,一定是某个.cpp文件没参与编译 |
error: no matching function for call to 'BankDatabase::getAccount(int)' | BankDatabase.h中getAccount()声明为const,但ATM.cpp中在非const成员函数里调用 | 检查ATM::performTransactions()是否声明为const,若否,将getAccount()调用移出const上下文,或修改BankDatabase::getAccount()为非const | const成员函数只能调用其他const成员函数,这是编译器强制的契约 |
Segmentation fault (core dumped) | 访问了空指针(如transactionPtr未初始化就调用->execute())或数组越界 | 在调用指针前加assert(transactionPtr != nullptr);,或用gdb调试:gdb ./atm_system → run → bt看崩溃栈 | 段错误是运行时错误,gdb是Linux下最强大的调试利器,务必学会bt(backtrace)和p variable(print变量) |
5.2 运行时逻辑陷阱与调试技巧
陷阱1:浮点数精度导致的余额不一致
- 现象:存入100.01元,取出100.01元,余额显示-0.0000001而非0.0。
- 原因:double二进制无法精确表示十进制小数(如0.01),累积误差。
- 解决:金融系统必须用定点数。将余额单位改为“分”,用int存储:int balanceInCents = 10001;。所有运算在整数层面进行,显示时除以100.0。
- 源码启示:当前代码用double是教学简化,实际项目必须规避。
陷阱2:构造函数中调用虚函数
- 现象:Account构造函数中调用isValidBalance()(假设它是虚函数),结果调用的是Account::isValidBalance(),而非派生类重写的版本。
- 原因:构造期间,对象类型被视为当前正在构造的类,虚函数表尚未完全建立。
- 解决:永远不要在构造/析构函数中调用虚函数。isValidBalance()应为普通私有函数,非虚。
- 经验:这是C++高级陷阱,初学者极易中招。源码中isValidBalance()是普通函数,已规避。
陷阱3:头文件重复包含导致重定义
- 现象:error: redefinition of 'class Account'。
- 原因:Account.h被多个.cpp文件#include,且无卫士宏。
- 解决:确认Account.h开头有#ifndef ACCOUNT_H、#define ACCOUNT_H,结尾有#endif。源码包中所有头文件均已添加,但若你新建头文件,务必手动添加。
- 工具:现代编辑器(VS Code, CLion)可一键生成卫士宏,快捷键通常是Ctrl+Shift+P → “Insert Header Guard”。
5.3 学习路径优化建议:如何用这套代码最大化学习效率
- 不要从ATM开始:这是最大误区。先从
ch02/fig02_01.cpp(Hello World)开始,逐行理解,然后ch03/fig03_05.cpp(if语句),确保每章代码都能独立编译运行。ATM是终点,不是起点。 - 动手改,而不是只看:对每个
figXX_YY.cpp,尝试修改一个数字、删掉一行cout、把int换成double,观察编译错误和运行结果。错误是最好的老师。 - 善用IDE的“转到定义”:在
ATM.cpp中右键点击bankDatabase.authenticateUser(),选择“Go to Definition”,直接跳转到BankDatabase.cpp的实现。这是理解类间调用关系的最快方式。 - 画类图:用纸笔或draw.io,根据
ch07下的头文件,画出ATM、BankDatabase、Account、Transaction及其派生类的继承与组合关系。画一遍,胜过看十遍文字描述。 - 定期“破坏性测试”:在
main()中故意传入非法参数,如Account acc{123, -100.0, 500.0};,观察程序是否崩溃或静默接受。好的代码应该优雅地拒绝非法输入。
6. 最后一点个人体会:为什么这套代码值得你花时间吃透
我带过十几届C++课程设计,见过太多学生:教材概念背得滚瓜烂熟,一写class就不知如何组织头文件与实现文件,一涉及多个类协作就陷入“我不知道该在哪个文件里写哪段代码”的混乱。这套《C++大学教程(第九版)》习题源码,本质上是一份可执行的C++工程实践手册。它不教你“什么是封装”,而是让你亲手写出Account类,看到const成员如何保护数据,看到private函数如何将校验逻辑锁死在类内部;它不空谈“多态的好处”,而是让你在ATM.cpp中写下transactionPtr->execute();,然后在调试器里亲眼见证,同一行代码,如何根据transactionPtr实际指向的对象,调用完全不同的一段逻辑。
你可能会说,“ATM系统太老了,现在都用区块链了”。但我想说,技术框架日新月异,而软件工程的核心原则亘古不变:封装隔离变化,继承复用行为,多态消除分支,单一职责降低耦合。这套代码的价值,不在于它实现了ATM,而在于它用最朴实的C++语法,将这些原则刻进了每一行可运行的代码里。当你把ch02到ch07的代码全部亲手编译、调试、修改过一遍,你会发现,面对任何新框架——无论是Qt的信号槽、React的组件化,还是Spring的IoC容器——你都能迅速识别出其中的设计模式影子,因为底层的思维范式早已内化。
所以,别把它当成一份“答案”,而把它当作一面镜子。照见自己对C++的理解盲区,照见教材与工程实践之间的鸿沟,照见从“会写代码”到“会设计系统”的那条必经之路。路就在那里,代码已经铺好,剩下的,就是你敲下g++命令、按下F5键、然后开始思考的那一刻。
简介:配套《C++大学教程(第九版)》的可直接编译运行的课后习题代码,从第2章基础输出程序(如fig02_01.cpp、fig02_05.cpp)开始,覆盖变量、控制结构、函数、数组、指针、类与对象、继承、多态等全部核心章节。重点包含ATM自动柜员机综合项目,提供ATM、BankDatabase、Account、Transaction等主类及Withdrawal、Deposit、BalanceInquiry、CashDispenser、Keypad、Screen、DepositSlot等模块化实现文件,所有源码按教材章节结构组织在ch02至ch07等目录下,并配有appF、appH、appI、appJ等应用级示例。代码命名规范、注释清晰、接口明确,支持主流C++编译器(如g++、MSVC),无需额外配置即可调试运行。适合初学者对照课本逐章练习,理解类封装原则、对象协作机制和面向对象系统设计流程,也便于教师布置实验或学生完成课程设计。

被折叠的 条评论
为什么被折叠?



