软件工程课程作业:C++ 书店管理系统二次开发
1)项目来源
项目名称:书店管理系统
项目来源类型:同学项目
2)运行环境 + 原项目运行说明
2.1 编译/运行环境要求
操作系统:Windows 10/11
编译器(任选其一):
Visual Studio 2019/2022 (MSVC):建议使用 /std:c++17
MinGW-w64 g++:建议 g++ (>= 7.x),编译参数 -std:c++17
C++标准:建议 C++17
依赖库:仅使用标准库
2.2 原项目运行说明(重构前)
原项目可运行,但存在若干严重缺陷(见第 3 节)。其中最影响“可复现运行”的问题是:数据文件路径被硬编码为
E:\code\dazuoye25.6\。
原项目数据文件位置(重构前):
E:\code\dazuoye25.6\books.txt
E:\code\dazuoye25.6\buyers.txt
E:\code\dazuoye25.6\orders.txt
原项目运行步骤(重构前):
- 确保上述目录存在并放置数据文件(可为空文件,但需存在以便保存)
- 编译运行
dazuoye.cpp - 按菜单进行“显示图书/购书人、创建订单、添加/删除”等操作
截图1:程序启动主菜单界面

截图2:显示图书信息列表

截图3:添加购书人成功界面

截图4:创建订单并计算应付金额界面

截图5:订单历史查询界面

2.3 原项目完整源码(重构前)
点击展开:原项目完整源码(重构前)
#include <string>
#include <iostream>
#include <vector>
#include <cstdlib>
#include <fstream>
using namespace std;
class buyer {
protected:
string name;
int buyerID;
string address;
double pay;
public:
buyer();
buyer(string n, int b, string a, double p);
string getbuyname();
string getaddress();
double getpay();
int getid();
virtual void display() = 0;
virtual void setpay(double = 0) = 0;
};
class member : public buyer {
private:
int leaguer_grade;
public:
member(string n, int b, int l, string a, double p) : buyer(n, b, a, p) { leaguer_grade = l; }
void display();
void setpay(double p);
int getGrade() const { return leaguer_grade; }
};
class honoured_guest : public buyer {
double discount_rate;
public:
honoured_guest(string n, int b, double r, string a, double p) : buyer(n, b, a, p) { discount_rate = r; }
void display();
void setpay(double p);
double getDiscountRate() const { return discount_rate; }
};
class layfolk : public buyer {
public:
layfolk(string n, int b, string a, double p) : buyer(n, b, a, p) { }
void display();
void setpay(double p);
};
buyer::buyer() {
name = "";
buyerID = 0;
address = "";
pay = 0;
}
buyer::buyer(string n, int b, string a, double p) {
name = n;
buyerID = b;
address = a;
pay = p;
}
double buyer::getpay() {
return pay;
}
string buyer::getaddress() {
return address;
}
string buyer::getbuyname() {
return name;
}
int buyer::getid() {
return buyerID;
}
void member::display() {
cout << "购书人姓名:" << name << "\t";
cout << "购书人编号:" << buyerID << "\n";
cout << "购书人会员,级别:" << leaguer_grade << "\n";
cout << "地址:" << address << "\n";
}
void member::setpay(double p) {
if (leaguer_grade == 1)
pay = 0.95 * p + pay;
else if (leaguer_grade == 2)
pay = 0.90 * p + pay;
else if (leaguer_grade == 3)
pay = 0.85 * p + pay;
else if (leaguer_grade == 4)
pay = 0.80 * p + pay;
else if (leaguer_grade == 5)
pay = 0.70 * p + pay;
else
cout << "级别错误!";
}
void honoured_guest::display() {
cout << "购书人姓名:" << name << "\t";
cout << "购书人编号:" << buyerID << "\t";
cout << "购书人为贵宾!折扣率为:" << (discount_rate * 100) << "%\n";
cout << "地址:" << address << "\n\n";
}
void honoured_guest::setpay(double p) {
pay = pay + (1 - discount_rate) * p;
}
void layfolk::display() {
cout << "购书人姓名:" << name << "\t";
cout << "购书人编号:" << buyerID << "\t";
cout << "购书人为普通人" << "\t";
cout << "地址:" << address << "\n";
}
void layfolk::setpay(double p) {
pay = pay + p;
}
class book {
protected:
string book_ID;
string book_name;
string author;
string publishing;
double price;
public:
book();
book(string b_id, string b_n, string au, string pu, double pr);
void display();
string getbook_ID();
string getbook_name();
string getauthor();
string getpublishing();
double getprice();
};
book::book(string b_id, string b_n, string au, string pu, double pr) {
book_ID = b_id;
book_name = b_n;
author = au;
publishing = pu;
price = pr;
}
book::book() {
book_ID = "";
book_name = "";
author = "";
publishing = "";
price = 0;
}
void book::display() {
cout << "书号:" << book_ID << "\t";
cout << "书名:" << book_name << "\t";
cout << "作者:" << author << "\n";
cout << "出版社:" << publishing << "\t";
cout << "定价:" << price << "\n";
}
string book::getbook_ID() {
return book_ID;
}
string book::getbook_name() {
return book_name;
}
string book::getauthor() {
return author;
}
string book::getpublishing() {
return publishing;
}
double book::getprice() {
return price;
}
class order {
private:
static int ordercount;
int orderID;
int buyerID;
int listcount;
string orderlist[20];
public:
friend void loadOrderCountFromFile();
void setorder(int bid, int lc, string ol[]) {
ordercount++;
orderID = ordercount;
buyerID = bid;
listcount = lc;
for (int i = 0; i < listcount; i++) {
orderlist[i] = ol[i];
}
}
void showorder() {
cout << "订单编号:" << orderID << "\n";
cout << "购书人编号:" << buyerID << "\n";
cout << "购书数量:" << listcount << "\n";
cout << "书号列表:";
for (int i = 0; i < listcount; i++) {
cout << orderlist[i] << " ";
}
cout << "\n";
}
// 添加获取当前订单编号的公共方法
int getOrderID() const {
return orderID;
}
// 添加获取订单总数的静态方法
static int getOrderCount() {
return ordercount;
}
};
int order::ordercount = 0;
// 全局变量
vector<book> books;
vector<buyer*> buyers;
// 函数声明
void menu();
void Wrong();
void displayBooks();
void displayBuyers();
void createOrder();
void showAllOrders();
void addBook();
void addBuyer();
void deleteBook();
void deleteBuyer();
void loadBooksFromFile();
void loadBuyersFromFile();
void saveBooksToFile();
void saveBuyersToFile();
void loadOrderCountFromFile();
void loadBooksFromFile() {
ifstream fin("E:\\code\\dazuoye25.6\\books.txt");
if (!fin) return;
string id, name, author, pub;
double price;
while (fin >> id >> ws && getline(fin, name) && getline(fin, author) && getline(fin, pub) && fin >> price) {
books.push_back(book(id, name, author, pub, price));
fin.ignore(); // 读取 price 后忽略换行
}
fin.close();
}
void loadBuyersFromFile() {
ifstream fin("E:\\code\\dazuoye25.6\\buyers.txt");
if (!fin) return;
int type;
while (fin >> type) {
string name, address;
int id;
double extra; // grade or discount
fin >> name >> id >> ws;
getline(fin, address);
fin >> extra;
fin.ignore();
if (type == 1)
buyers.push_back(new layfolk(name, id, address, 0));
else if (type == 2)
buyers.push_back(new member(name, id, (int)extra, address, 0));
else if (type == 3)
buyers.push_back(new honoured_guest(name, id, extra, address, 0));
}
fin.close();
}
int main() {
// 初始化默认数据
loadBooksFromFile();
loadBuyersFromFile();
loadOrderCountFromFile();
int select;
while(1) {
system("cls"); // 清屏
menu();
cout << "\n 请输入您的选择(0~8):";
cin >> select;
if(!select) {
cout << "=====>感谢使用书店管理系统!" << endl;
// 保存所有数据到文件
saveBooksToFile();
saveBuyersToFile();
// 清理内存
for(auto& buyer : buyers) {
delete buyer;
}
break;
}
switch(select) {
case 1: displayBooks(); break; // 显示图书信息
case 2: displayBuyers(); break; // 显示购书人信息
case 3: createOrder(); break; // 创建订单
case 4: showAllOrders(); break; // 订单历史
case 5: addBook(); break; // 添加图书
case 6: addBuyer(); break; // 添加购书人
case 7: deleteBook(); break; // 删除图书
case 8: deleteBuyer(); break; // 删除购书人
default: Wrong(); cin.get(); break;
}
}
return 0;
}
void menu() {
cout << " 书店管理系统 \n";
cout << " **********************菜单********************\n";
cout << " * 1 显示图书信息 2 显示购书人信息 *\n";
cout << " * 3 创建购书订单 4 订单历史查询 *\n";
cout << " * 5 添加图书信息 6 添加购书人 *\n";
cout << " * 7 删除图书信息 8 删除购书人 *\n";
cout << " * 0 退出系统 *\n";
cout << " **********************************************\n";
}
void Wrong() {
cout << "\n\n\n**********错误:输入数据错误,按下任意键继续**********\n";
cin.get();
}
void displayBooks() {
system("cls");
cout << "\n==================== 图书信息 ====================\n";
if(books.empty()) {
cout << "暂无图书信息!\n";
} else {
for(size_t i = 0; i < books.size(); i++) {
cout << "图书 " << (i+1) << ":\n";
books[i].display();
cout << "----------------------------------------\n";
}
}
cout << "\n按任意键返回主菜单...";
cin.get();
cin.get();
}
void displayBuyers() {
system("cls");
cout << "\n==================== 购书人信息 ====================\n";
if(buyers.empty()) {
cout << "暂无购书人信息!\n";
} else {
for(size_t i = 0; i < buyers.size(); i++) {
cout << "购书人 " << (i+1) << ":\n";
buyers[i]->display();
cout << "----------------------------------------\n";
}
}
cout << "\n按任意键返回主菜单...";
cin.get();
cin.get();
}
void createOrder() {
system("cls");
cout << "\n==================== 创建订单 ====================\n";
// 显示现有图书和购书人信息
cout << "可选图书:\n";
for(size_t i = 0; i < books.size(); i++) {
cout << (i+1) << ". ";
books[i].display();
cout << "\n";
}
cout << "\n可选购书人:\n";
for(size_t i = 0; i < buyers.size(); i++) {
cout << (i+1) << ". ";
buyers[i]->display();
cout << "\n";
}
int buyerid, listcount;
cout << "\n请输入购书人编号:";
cin >> buyerid;
// 查找购书人
buyer* selectedBuyer = nullptr;
int buyerIndex = -1;
for(size_t i = 0; i < buyers.size(); i++) {
if(buyers[i]->getid() == buyerid) {
selectedBuyer = buyers[i];
buyerIndex = i;
break;
}
}
if(!selectedBuyer) {
cout << "购书人编号不存在!\n";
cout << "按任意键返回...";
cin.get();
cin.get();
return;
}
cout << "请输入购书数量(最多20本):";
cin >> listcount;
if(listcount <= 0 || listcount > 20) {
cout << "购书数量无效!\n";
cout << "按任意键返回...";
cin.get();
cin.get();
return;
}
string orderlist[20];
cout << "请输入购书书号:\n";
for(int i = 0; i < listcount; i++) {
cout << "第" << (i+1) << "本书号:";
cin >> orderlist[i];
// 检查书号是否存在
bool bookExists = false;
for(size_t j = 0; j < books.size(); j++) {
if(books[j].getbook_ID() == orderlist[i]) {
bookExists = true;
break;
}
}
if(!bookExists) {
cout << "书号不存在!\n";
cout << "按任意键返回...";
cin.get();
cin.get();
return;
}
}
// 创建订单
order o;
o.setorder(buyerid, listcount, orderlist);
// 计算费用
double totalPrice = 0;
for(int i = 0; i < listcount; i++) {
for(size_t j = 0; j < books.size(); j++) {
if(books[j].getbook_ID() == orderlist[i]) {
selectedBuyer->setpay(books[j].getprice());
totalPrice += books[j].getprice();
break;
}
}
}
cout << "\n==================== 订单详情 ====================\n";
o.showorder();
cout << "原价总计:" << totalPrice << " 元\n";
cout << "购书人需要付费:" << selectedBuyer->getpay() << " 元\n";
cout << "====================================================\n";
cout << "\n按任意键返回主菜单...";
cin.get();
cin.get();
ofstream fout("E:\\code\\dazuoye25.6\\orders.txt", ios::app);
if (fout) {
fout << "订单编号:" << o.getOrderID() << "\n";
fout << "购书人编号:" << buyerid << "\n";
fout << "购书数量:" << listcount << "\n";
fout << "书号列表:";
for (int i = 0; i < listcount; i++) fout << orderlist[i] << " ";
fout << "\n原价总计:" << totalPrice << " 元\n";
fout << "购书人需付:" << selectedBuyer->getpay() << " 元\n";
fout << "==============================\n";
fout.close();
}
}
void addBook() {
system("cls");
cout << "\n==================== 添加图书 ====================\n";
string book_id, book_name, author, publishing;
double price;
cout << "请输入图书信息:\n";
cout << "书号:";
cin >> book_id;
cin.ignore(); // 清除缓冲区
cout << "书名:";
getline(cin, book_name);
cout << "作者:";
getline(cin, author);
cout << "出版社:";
getline(cin, publishing);
cout << "价格:";
cin >> price;
// 检查书号是否已存在
for(size_t i = 0; i < books.size(); i++) {
if(books[i].getbook_ID() == book_id) {
cout << "该书号已存在,添加失败!\n";
cout << "按任意键返回主菜单...";
cin.get();
cin.get();
return;
}
}
books.push_back(book(book_id, book_name, author, publishing, price));
cout << "\n图书添加成功!\n";
cout << "按任意键返回主菜单...";
cin.get();
cin.get();
}
void addBuyer() {
system("cls");
cout << "\n==================== 添加购书人 ====================\n";
string name, address;
int buyerID, type;
cout << "请输入购书人信息:\n";
cout << "姓名:";
cin >> name;
cout << "编号:";
cin >> buyerID;
cin.ignore();
bool exists = false;
for (auto& b : buyers) {
if (b->getid() == buyerID) {
exists = true;
break;
}
}
if (exists) {
cout << "该编号已存在,添加失败!\n";
cout << "按任意键返回主菜单...";
cin.get(); cin.get();
return;
}
cout << "地址:";
getline(cin, address);
cout << "购书人类型:\n";
cout << "1. 普通人\n";
cout << "2. 会员\n";
cout << "3. 贵宾\n";
cout << "请选择(1-3):";
cin >> type;
buyer* newBuyer = nullptr;
switch(type) {
case 1:
newBuyer = new layfolk(name, buyerID, address, 0);
break;
case 2: {
int grade;
cout << "请输入会员级别(1-5):";
cin >> grade;
newBuyer = new member(name, buyerID, grade, address, 0);
break;
}
case 3: {
double discount;
cout << "请输入折扣率(0-1):";
cin >> discount;
newBuyer = new honoured_guest(name, buyerID, discount, address, 0);
break;
}
default:
cout << "类型选择错误!\n";
cout << "按任意键返回...";
cin.get();
cin.get();
return;
}
buyers.push_back(newBuyer);
cout << "\n购书人添加成功!\n";
cout << "按任意键返回主菜单...";
cin.get();
cin.get();
}
void deleteBook() {
system("cls");
cout << "\n==================== 删除图书 ====================\n";
if(books.empty()) {
cout << "暂无图书信息可删除!\n";
cout << "按任意键返回主菜单...";
cin.get();
cin.get();
return;
}
// 显示所有图书
cout << "当前图书列表:\n";
for(size_t i = 0; i < books.size(); i++) {
cout << "序号: " << (i+1) << "\n";
books[i].display();
cout << "----------------------------------------\n";
}
int choice;
cout << "\n请输入要删除的图书序号(1-" << books.size() << "):";
cin >> choice;
if(choice < 1 || choice > (int)books.size()) {
cout << "序号无效!\n";
cout << "按任意键返回主菜单...";
cin.get();
cin.get();
return;
}
// 显示要删除的图书信息
cout << "\n即将删除以下图书:\n";
books[choice-1].display();
char confirm;
cout << "\n确认删除?(y/n):";
cin >> confirm;
if(confirm == 'y' || confirm == 'Y') {
books.erase(books.begin() + choice - 1);
cout << "\n图书删除成功!\n";
} else {
cout << "\n取消删除操作!\n";
}
cout << "按任意键返回主菜单...";
cin.get();
cin.get();
}
void deleteBuyer() {
system("cls");
cout << "\n==================== 删除购书人 ====================\n";
if(buyers.empty()) {
cout << "暂无购书人信息可删除!\n";
cout << "按任意键返回主菜单...";
cin.get();
cin.get();
return;
}
// 显示所有购书人
cout << "当前购书人列表:\n";
for(size_t i = 0; i < buyers.size(); i++) {
cout << "序号: " << (i+1) << "\n";
buyers[i]->display();
cout << "----------------------------------------\n";
}
int choice;
cout << "\n请输入要删除的购书人序号(1-" << buyers.size() << "):";
cin >> choice;
if(choice < 1 || choice > (int)buyers.size()) {
cout << "序号无效!\n";
cout << "按任意键返回主菜单...";
cin.get();
cin.get();
return;
}
// 显示要删除的购书人信息
cout << "\n即将删除以下购书人:\n";
buyers[choice-1]->display();
char confirm;
cout << "\n确认删除?(y/n):";
cin >> confirm;
if(confirm == 'y' || confirm == 'Y') {
delete buyers[choice-1]; // 释放内存
buyers.erase(buyers.begin() + choice - 1);
cout << "\n购书人删除成功!\n";
} else {
cout << "\n取消删除操作!\n";
}
cout << "按任意键返回主菜单...";
cin.get();
cin.get();
}
void showAllOrders() {
system("cls");
cout << "\n==================== 订单历史 ====================\n";
ifstream fin("E:\\code\\dazuoye25.6\\orders.txt");
if (!fin) {
cout << "暂无历史订单记录!\n";
} else {
string line;
while (getline(fin, line)) {
cout << line << "\n";
}
fin.close();
}
cout << "\n按任意键返回主菜单...";
cin.get(); cin.get();
}
void saveBooksToFile() {
ofstream fout("E:\\code\\dazuoye25.6\\books.txt");
if(fout) {
for(size_t i = 0; i < books.size(); i++) {
fout << books[i].getbook_ID() << "\n";
fout << books[i].getbook_name() << "\n";
fout << books[i].getauthor() << "\n";
fout << books[i].getpublishing() << "\n";
fout << books[i].getprice() << "\n";
}
fout.close();
}
}
void saveBuyersToFile() {
ofstream fout("E:\\code\\dazuoye25.6\\buyers.txt");
if(fout) {
for(size_t i = 0; i < buyers.size(); i++) {
// 判断购书人类型并写入相应格式
member* m = dynamic_cast<member*>(buyers[i]);
honoured_guest* h = dynamic_cast<honoured_guest*>(buyers[i]);
layfolk* l = dynamic_cast<layfolk*>(buyers[i]);
if(l && !m && !h) { // 普通人
fout << "1\n";
fout << buyers[i]->getbuyname() << "\n";
fout << buyers[i]->getid() << "\n";
fout << buyers[i]->getaddress() << "\n";
fout << "0\n"; // 普通人没有额外信息,写0
} else if(m) { // 会员
fout << "2\n";
fout << buyers[i]->getbuyname() << "\n";
fout << buyers[i]->getid() << "\n";
fout << buyers[i]->getaddress() << "\n";
fout << m->getGrade() << "\n";
} else if(h) { // 贵宾
fout << "3\n";
fout << buyers[i]->getbuyname() << "\n";
fout << buyers[i]->getid() << "\n";
fout << buyers[i]->getaddress() << "\n";
fout << h->getDiscountRate() << "\n";
}
}
fout.close();
}
}
void loadOrderCountFromFile() {
ifstream fin("E:\\code\\dazuoye25.6\\orders.txt");
if (!fin) return; // 文件不存在就返回,ordercount保持默认值0
string line;
int orderCount = 0;
while (getline(fin, line)) {
// 每个订单结尾都有分隔符
if (line.find("==============================") != string::npos) {
orderCount++;
}
}
fin.close();
order::ordercount = orderCount; // 设置为已有订单数量
}
3)原项目问题列表与改进重构思路(按P0~P3分级)
固定格式:【问题所在文件/行号】【问题等级】【问题类型】
文件均为:dazuoye.cpp(重构前行号)
P0 致命问题(可能崩溃/未定义行为)
【dazuoye.cpp L8-L24 + L310-L312 + L705-L706】【P0】【内存/多态释放】
描述:buyers 使用 vector<buyer*> 保存派生类对象指针,但 buyer 基类没有虚析构函数;程序退出/删除购书人时对 buyer* 执行 delete 会触发未定义行为(可能导致崩溃、资源未释放或数据损坏)。
思路:为 buyer 增加 virtual ~buyer();并进一步将容器改为 vector<unique_ptr<buyer>>,由 RAII 自动释放,消除手动 delete 风险。
P1 严重问题(逻辑错误/健壮性差/核心功能不可靠)
【dazuoye.cpp L113-L115】【P1】【业务逻辑错误:折扣计算】
描述:贵宾应付金额计算写成 pay += (1 - discount_rate) * p,把“折扣率”当成“减免比例”。例如折扣率 0.8(打8折)时,程序只收 0.2p,明显错误。
思路:修正为 pay += discount_rate * p,并补充注释解释语义。
【dazuoye.cpp L468-L479】【P1】【业务逻辑错误:跨订单累计】
描述:pay 保存在购书人对象里,创建订单时直接累加,导致同一购书人第二次下单时“应付金额”包含历史订单,展示错误。
思路:创建订单前重置 pay=0(新增 resetPay()),保证 pay 表示“本次订单应付”。
【dazuoye.cpp L256-L257/L268-L270/L485/L720/L734/L748/L781】【P1】【可移植性/持久化失效:硬编码路径】
描述:数据文件路径硬编码为 E:\code\dazuoye25.6\...,换电脑或目录后无法读写数据,导致“持久化”核心能力在多数环境失效。
思路:改为相对路径常量(books.txt / buyers.txt / orders.txt),默认与程序运行目录一致;必要时可扩展为配置项。
【dazuoye.cpp 多处 cin >> 输入】【P1】【输入无校验/异常处理缺失】
描述:菜单选择、编号、数量、价格、折扣等均未处理 cin 失败(例如输入字母),会导致 cin 进入 fail 状态,后续读取全部失败,程序表现异常甚至陷入错误循环。
思路:每次读取后检查 if(!cin),调用 cin.clear() + ignore 清空缓冲并提示重新输入或返回菜单。
【dazuoye.cpp L509/L550 附近】【P1】【字符串读取边界问题】
描述:cin.ignore() 仅忽略 1 个字符,若缓冲区残留不止一个字符(或输入格式变化),后续 getline 可能读到空串,造成信息丢失。
思路:改为 cin.ignore(numeric_limits<streamsize>::max(), '\n') 清空整行。
P2 规范/可维护性问题
【dazuoye.cpp 全局结构】【P2】【结构设计:高耦合/低内聚】
描述:大量全局变量(books/buyers)+ 全局函数混杂 I/O、业务、持久化逻辑,后期扩展困难。
思路:课程作业范围内不做大拆分,但通过“常量集中管理/工具函数封装/RAII”降低耦合、提高可读性。
【dazuoye.cpp L406-L412】【P2】【死代码/可读性】
描述:buyerIndex 赋值后未使用,增加理解成本。
思路:删除无用变量,保持函数目的单一。
【多处 magic number】【P2】【可维护性】
描述:如最多 20 本、菜单 0~8 等散落在代码里。
思路:可进一步提取为常量。
P3 性能可优化点
【dazuoye.cpp L441-L472】【P3】【算法复杂度】
描述:创建订单时每输入一本书号都遍历 books 查找,计算价格时再次遍历,整体接近 $O(m*n)$。
思路:在 createOrder() 内构建 unordered_map<书号, 价格> 索引,查找与计价均为均摊 $O(1)$。
4)改进后代码
4.1 修改点代码片段:修改前/修改后对比
修改点A:修复多态删除未定义行为(P0)
修改前:buyer 基类无虚析构,且用 vector<buyer*> 保存派生类对象并手动 delete,存在未定义行为风险。
修改后:补充 virtual ~buyer() = default;,并将容器改为 vector<unique_ptr<buyer>> 由 RAII 自动释放,消除手动 delete 风险。
修改点B:修复贵宾折扣计算(P1)
修改前:应付金额计算写成 pay += (1 - discount_rate) * p,把“折扣率”(如 0.8)误当成“减免比例”,导致少收钱。
修改后:修正为 pay += discount_rate * p(例如 8 折:付 0.8p),语义与业务一致。
修改点C:修复跨订单累计(P1)
修改前:pay 不重置,导致本单应付包含历史订单
修改后:创建订单前 selectedBuyer->resetPay();
修改点D:消除硬编码路径(P1)
修改前:ifstream fin("E:\\code\\dazuoye25.6\\books.txt");
修改后:统一常量 kBooksFile/kBuyersFile/kOrdersFile 使用相对路径
修改点E:输入失败保护(P1)
修改前:cin >> select; 不判断 cin 状态
修改后:若 !cin 则 clearCinFail() + 提示 + 返回
修改点F:订单创建性能优化(P3)
修改前:多次遍历 books 查找书号与价格
修改后:构建 unordered_map<string,double> bookPriceById,查找/计价均摊 O(1)
4.2 重构后全量代码(可编译运行)
点击展开:重构后全量源码
#include <string>
#include <iostream>
#include <vector>
#include <cstdlib>
#include <fstream>
#include <limits> // 修改点:新增头文件;修改原因:用于安全清理输入缓冲,避免cin失败导致死循环
#include <memory> // 修改点:新增头文件;修改原因:使用unique_ptr管理多态对象,避免手动delete与内存泄漏/UB
#include <utility> // 修改点:新增头文件;修改原因:std::move标准头文件,提升可移植性
#include <unordered_map> // 修改点:新增头文件;修改原因:创建订单时构建书号索引,优化查找性能
using namespace std;
class buyer {
protected:
string name;
int buyerID;
string address;
double pay;
public:
buyer();
buyer(string n, int b, string a, double p);
// 修改点:补充虚析构函数;修改原因:buyers里通过buyer*删除派生类对象,否则delete基类指针为未定义行为(可能崩溃)
// 修改前:~buyer() 默认非虚
// 修改后:virtual ~buyer() = default;
virtual ~buyer() = default;
string getbuyname();
string getaddress();
double getpay();
int getid();
virtual void display() = 0;
virtual void setpay(double = 0) = 0;
// 修改点:新增重置本次订单应付金额的方法;修改原因:原实现pay会跨订单累计,导致“本单应付”计算错误
// 修改前:createOrder() 直接在pay基础上累加
// 修改后:每次创建订单前先resetPay(),保证pay表示“本次订单应付”
void resetPay() { pay = 0.0; }
};
class member : public buyer {
private:
int leaguer_grade;
public:
member(string n, int b, int l, string a, double p) : buyer(n, b, a, p) { leaguer_grade = l; }
void display();
void setpay(double p);
int getGrade() const { return leaguer_grade; }
};
class honoured_guest : public buyer {
double discount_rate;
public:
honoured_guest(string n, int b, double r, string a, double p) : buyer(n, b, a, p) { discount_rate = r; }
void display();
void setpay(double p);
double getDiscountRate() const { return discount_rate; }
};
class layfolk : public buyer {
public:
layfolk(string n, int b, string a, double p) : buyer(n, b, a, p) { }
void display();
void setpay(double p);
};
buyer::buyer() {
name = "";
buyerID = 0;
address = "";
pay = 0;
}
buyer::buyer(string n, int b, string a, double p) {
name = n;
buyerID = b;
address = a;
pay = p;
}
double buyer::getpay() {
return pay;
}
string buyer::getaddress() {
return address;
}
string buyer::getbuyname() {
return name;
}
int buyer::getid() {
return buyerID;
}
void member::display() {
cout << "购书人姓名:" << name << "\t";
cout << "购书人编号:" << buyerID << "\n";
cout << "购书人会员,级别:" << leaguer_grade << "\n";
cout << "地址:" << address << "\n";
}
void member::setpay(double p) {
if (leaguer_grade == 1)
pay = 0.95 * p + pay;
else if (leaguer_grade == 2)
pay = 0.90 * p + pay;
else if (leaguer_grade == 3)
pay = 0.85 * p + pay;
else if (leaguer_grade == 4)
pay = 0.80 * p + pay;
else if (leaguer_grade == 5)
pay = 0.70 * p + pay;
else
cout << "级别错误!";
}
void honoured_guest::display() {
cout << "购书人姓名:" << name << "\t";
cout << "购书人编号:" << buyerID << "\t";
cout << "购书人为贵宾!折扣率为:" << (discount_rate * 100) << "%\n";
cout << "地址:" << address << "\n\n";
}
void honoured_guest::setpay(double p) {
// 修改点:修复贵宾应付金额计算公式;修改原因:原实现把“折扣率”当成“减免比例”,逻辑错误
// 修改前:pay += (1 - discount_rate) * p (折扣率0.8时只付0.2p,明显不符合“打8折”语义)
// 修改后:pay += discount_rate * p (折扣率0.8时付0.8p)
pay = pay + discount_rate * p;
}
void layfolk::display() {
cout << "购书人姓名:" << name << "\t";
cout << "购书人编号:" << buyerID << "\t";
cout << "购书人为普通人" << "\t";
cout << "地址:" << address << "\n";
}
void layfolk::setpay(double p) {
pay = pay + p;
}
class book {
protected:
string book_ID;
string book_name;
string author;
string publishing;
double price;
public:
book();
book(string b_id, string b_n, string au, string pu, double pr);
void display();
string getbook_ID() const;
string getbook_name();
string getauthor();
string getpublishing();
double getprice() const;
};
book::book(string b_id, string b_n, string au, string pu, double pr) {
book_ID = b_id;
book_name = b_n;
author = au;
publishing = pu;
price = pr;
}
book::book() {
book_ID = "";
book_name = "";
author = "";
publishing = "";
price = 0;
}
void book::display() {
cout << "书号:" << book_ID << "\t";
cout << "书名:" << book_name << "\t";
cout << "作者:" << author << "\n";
cout << "出版社:" << publishing << "\t";
cout << "定价:" << price << "\n";
}
string book::getbook_ID() const {
return book_ID;
}
string book::getbook_name() {
return book_name;
}
string book::getauthor() {
return author;
}
string book::getpublishing() {
return publishing;
}
double book::getprice() const {
return price;
}
class order {
private:
static int ordercount;
int orderID;
int buyerID;
int listcount;
string orderlist[20];
public:
friend void loadOrderCountFromFile();
void setorder(int bid, int lc, string ol[]) {
ordercount++;
orderID = ordercount;
buyerID = bid;
listcount = lc;
for (int i = 0; i < listcount; i++) {
orderlist[i] = ol[i];
}
}
void showorder() {
cout << "订单编号:" << orderID << "\n";
cout << "购书人编号:" << buyerID << "\n";
cout << "购书数量:" << listcount << "\n";
cout << "书号列表:";
for (int i = 0; i < listcount; i++) {
cout << orderlist[i] << " ";
}
cout << "\n";
}
// 添加获取当前订单编号的公共方法
int getOrderID() const {
return orderID;
}
// 添加获取订单总数的静态方法
static int getOrderCount() {
return ordercount;
}
};
int order::ordercount = 0;
// 全局变量
vector<book> books;
// 修改点:buyers改为unique_ptr容器;修改原因:避免手动delete造成遗漏/重复释放,并提升异常安全性
vector<unique_ptr<buyer>> buyers;
// 函数声明
void menu();
void Wrong();
void displayBooks();
void displayBuyers();
void createOrder();
void showAllOrders();
void addBook();
void addBuyer();
void deleteBook();
void deleteBuyer();
void loadBooksFromFile();
void loadBuyersFromFile();
void saveBooksToFile();
void saveBuyersToFile();
void loadOrderCountFromFile();
// 修改点:统一文件路径为相对路径常量;修改原因:原代码硬编码E:\...导致换电脑/换目录后数据读写失效
static const string kBooksFile = "books.txt";
static const string kBuyersFile = "buyers.txt";
static const string kOrdersFile = "orders.txt";
// 修改点:封装等待用户回车;
static void waitForEnter() {
cout << "\n按回车键继续...";
cin.clear();
if (cin.rdbuf()->in_avail() > 0) {
cin.ignore(numeric_limits<streamsize>::max(), '\n');
}
cin.get();
cin.get();
}
// 修改点:封装清理输入流失败状态;修改原因:防止输入非数字导致cin进入fail状态后无限错误
static void clearCinFail() {
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(), '\n');
}
void loadBooksFromFile() {
ifstream fin(kBooksFile);
if (!fin) return;
string id, name, author, pub;
double price;
while (fin >> id >> ws && getline(fin, name) && getline(fin, author) && getline(fin, pub) && fin >> price) {
books.push_back(book(id, name, author, pub, price));
// 修改点:忽略整行换行;修改原因:Windows下可能为\r\n,忽略1个字符会留下残余
fin.ignore(numeric_limits<streamsize>::max(), '\n');
}
fin.close();
}
void loadBuyersFromFile() {
ifstream fin(kBuyersFile);
if (!fin) return;
int type;
while (fin >> type) {
string name, address;
int id;
double extra; // grade or discount
fin >> name >> id >> ws;
getline(fin, address);
fin >> extra;
// 修改点:忽略整行换行;修改原因:兼容\r\n
fin.ignore(numeric_limits<streamsize>::max(), '\n');
if (type == 1)
buyers.emplace_back(make_unique<layfolk>(name, id, address, 0));
else if (type == 2)
buyers.emplace_back(make_unique<member>(name, id, (int)extra, address, 0));
else if (type == 3)
buyers.emplace_back(make_unique<honoured_guest>(name, id, extra, address, 0));
}
fin.close();
}
int main() {
// 初始化默认数据
loadBooksFromFile();
loadBuyersFromFile();
loadOrderCountFromFile();
int select;
while(1) {
system("cls"); // 清屏
menu();
cout << "\n 请输入您的选择(0~8):";
cin >> select;
if (!cin) { // 修改点:输入校验;修改原因:输入非数字会导致cin失败并进入死循环
cout << "\n输入不是数字!";
clearCinFail();
waitForEnter();
continue;
}
if(!select) {
cout << "=====>感谢使用书店管理系统!" << endl;
// 保存所有数据到文件
saveBooksToFile();
saveBuyersToFile();
break;
}
switch(select) {
case 1: displayBooks(); break; // 显示图书信息
case 2: displayBuyers(); break; // 显示购书人信息
case 3: createOrder(); break; // 创建订单
case 4: showAllOrders(); break; // 订单历史
case 5: addBook(); break; // 添加图书
case 6: addBuyer(); break; // 添加购书人
case 7: deleteBook(); break; // 删除图书
case 8: deleteBuyer(); break; // 删除购书人
default: Wrong(); break;
}
}
return 0;
}
void menu() {
cout << " 书店管理系统 \n";
cout << " **********************菜单********************\n";
cout << " * 1 显示图书信息 2 显示购书人信息 *\n";
cout << " * 3 创建购书订单 4 订单历史查询 *\n";
cout << " * 5 添加图书信息 6 添加购书人 *\n";
cout << " * 7 删除图书信息 8 删除购书人 *\n";
cout << " * 0 退出系统 *\n";
cout << " **********************************************\n";
}
void Wrong() {
cout << "\n\n\n**********错误:输入数据错误,按下任意键继续**********\n";
waitForEnter();
}
void displayBooks() {
system("cls");
cout << "\n==================== 图书信息 ====================\n";
if(books.empty()) {
cout << "暂无图书信息!\n";
} else {
for(size_t i = 0; i < books.size(); i++) {
cout << "图书 " << (i+1) << ":\n";
books[i].display();
cout << "----------------------------------------\n";
}
}
waitForEnter();
}
void displayBuyers() {
system("cls");
cout << "\n==================== 购书人信息 ====================\n";
if(buyers.empty()) {
cout << "暂无购书人信息!\n";
} else {
for(size_t i = 0; i < buyers.size(); i++) {
cout << "购书人 " << (i+1) << ":\n";
buyers[i]->display();
cout << "----------------------------------------\n";
}
}
waitForEnter();
}
void createOrder() {
system("cls");
cout << "\n==================== 创建订单 ====================\n";
// 修改点:边界校验;修改原因:无图书/无购书人时创建订单无意义且易误操作
if (books.empty()) {
cout << "当前没有图书,无法创建订单。\n";
waitForEnter();
return;
}
if (buyers.empty()) {
cout << "当前没有购书人,无法创建订单。\n";
waitForEnter();
return;
}
// 显示现有图书和购书人信息
cout << "可选图书:\n";
for(size_t i = 0; i < books.size(); i++) {
cout << (i+1) << ". ";
books[i].display();
cout << "\n";
}
// 修改点:构建书号->价格索引;修改原因:原实现每录入一本书号都遍历books全表查找,时间复杂度较高
// 修改前:对每个orderlist[i],循环books[j]做匹配(O(m*n))
// 修改后:先建unordered_map索引(O(n)),再O(1)查询(整体约O(m+n))
unordered_map<string, double> bookPriceById;
bookPriceById.reserve(books.size());
for (const auto& b : books) {
bookPriceById.emplace(b.getbook_ID(), b.getprice());
}
cout << "\n可选购书人:\n";
for(size_t i = 0; i < buyers.size(); i++) {
cout << (i+1) << ". ";
buyers[i]->display();
cout << "\n";
}
int buyerid, listcount;
cout << "\n请输入购书人编号:";
cin >> buyerid;
if (!cin) { // 修改点:输入校验
cout << "购书人编号输入无效!\n";
clearCinFail();
waitForEnter();
return;
}
// 查找购书人
buyer* selectedBuyer = nullptr;
for(size_t i = 0; i < buyers.size(); i++) {
if(buyers[i]->getid() == buyerid) {
selectedBuyer = buyers[i].get();
break;
}
}
if(!selectedBuyer) {
cout << "购书人编号不存在!\n";
waitForEnter();
return;
}
cout << "请输入购书数量(最多20本):";
cin >> listcount;
if (!cin) { // 修改点:输入校验
cout << "购书数量输入无效!\n";
clearCinFail();
waitForEnter();
return;
}
if(listcount <= 0 || listcount > 20) {
cout << "购书数量无效!\n";
waitForEnter();
return;
}
string orderlist[20];
cout << "请输入购书书号:\n";
for(int i = 0; i < listcount; i++) {
cout << "第" << (i+1) << "本书号:";
cin >> orderlist[i];
if (!cin) { // 修改点:输入校验
cout << "书号输入无效!\n";
clearCinFail();
waitForEnter();
return;
}
// 检查书号是否存在
if(bookPriceById.find(orderlist[i]) == bookPriceById.end()) {
cout << "书号不存在!\n";
waitForEnter();
return;
}
}
// 创建订单
order o;
o.setorder(buyerid, listcount, orderlist);
// 计算费用
// 修改点:重置pay;修改原因:原实现pay跨订单累计,导致本订单应付错误
selectedBuyer->resetPay();
double totalPrice = 0;
for(int i = 0; i < listcount; i++) {
const double price = bookPriceById[orderlist[i]];
selectedBuyer->setpay(price);
totalPrice += price;
}
cout << "\n==================== 订单详情 ====================\n";
o.showorder();
cout << "原价总计:" << totalPrice << " 元\n";
cout << "购书人需要付费:" << selectedBuyer->getpay() << " 元\n";
cout << "====================================================\n";
waitForEnter();
ofstream fout(kOrdersFile, ios::app);
if (fout) {
fout << "订单编号:" << o.getOrderID() << "\n";
fout << "购书人编号:" << buyerid << "\n";
fout << "购书数量:" << listcount << "\n";
fout << "书号列表:";
for (int i = 0; i < listcount; i++) fout << orderlist[i] << " ";
fout << "\n原价总计:" << totalPrice << " 元\n";
fout << "购书人需付:" << selectedBuyer->getpay() << " 元\n";
fout << "==============================\n";
fout.close();
}
}
void addBook() {
system("cls");
cout << "\n==================== 添加图书 ====================\n";
string book_id, book_name, author, publishing;
double price;
cout << "请输入图书信息:\n";
cout << "书号:";
cin >> book_id;
// 修改点:清除整行缓冲区;修改原因:避免只ignore 1个字符导致getline读到空行
cin.ignore(numeric_limits<streamsize>::max(), '\n');
cout << "书名:";
getline(cin, book_name);
cout << "作者:";
getline(cin, author);
cout << "出版社:";
getline(cin, publishing);
cout << "价格:";
cin >> price;
if (!cin || price < 0) { // 修改点:输入校验;修改原因:非数字/负价格不合法
cout << "价格输入无效!\n";
clearCinFail();
waitForEnter();
return;
}
// 检查书号是否已存在
for(size_t i = 0; i < books.size(); i++) {
if(books[i].getbook_ID() == book_id) {
cout << "该书号已存在,添加失败!\n";
waitForEnter();
return;
}
}
books.push_back(book(book_id, book_name, author, publishing, price));
cout << "\n图书添加成功!\n";
waitForEnter();
}
void addBuyer() {
system("cls");
cout << "\n==================== 添加购书人 ====================\n";
string name, address;
int buyerID, type;
cout << "请输入购书人信息:\n";
cout << "姓名:";
cin >> name;
cout << "编号:";
cin >> buyerID;
if (!cin) { // 修改点:输入校验
cout << "编号输入无效!\n";
clearCinFail();
waitForEnter();
return;
}
// 修改点:清除整行缓冲区;修改原因:避免后续getline读到空地址
cin.ignore(numeric_limits<streamsize>::max(), '\n');
bool exists = false;
for (auto& b : buyers) {
if (b->getid() == buyerID) {
exists = true;
break;
}
}
if (exists) {
cout << "该编号已存在,添加失败!\n";
waitForEnter();
return;
}
cout << "地址:";
getline(cin, address);
cout << "购书人类型:\n";
cout << "1. 普通人\n";
cout << "2. 会员\n";
cout << "3. 贵宾\n";
cout << "请选择(1-3):";
cin >> type;
if (!cin) { // 修改点:输入校验
cout << "类型输入无效!\n";
clearCinFail();
waitForEnter();
return;
}
unique_ptr<buyer> newBuyer;
switch(type) {
case 1:
newBuyer = make_unique<layfolk>(name, buyerID, address, 0);
break;
case 2: {
int grade;
cout << "请输入会员级别(1-5):";
cin >> grade;
if (!cin || grade < 1 || grade > 5) { // 修改点:输入校验
cout << "会员级别输入无效!\n";
clearCinFail();
waitForEnter();
return;
}
newBuyer = make_unique<member>(name, buyerID, grade, address, 0);
break;
}
case 3: {
double discount;
cout << "请输入折扣率(0-1):";
cin >> discount;
if (!cin || discount < 0 || discount > 1) { // 修改点:输入校验
cout << "折扣率输入无效!\n";
clearCinFail();
waitForEnter();
return;
}
newBuyer = make_unique<honoured_guest>(name, buyerID, discount, address, 0);
break;
}
default:
cout << "类型选择错误!\n";
waitForEnter();
return;
}
buyers.emplace_back(std::move(newBuyer));
cout << "\n购书人添加成功!\n";
waitForEnter();
}
void deleteBook() {
system("cls");
cout << "\n==================== 删除图书 ====================\n";
if(books.empty()) {
cout << "暂无图书信息可删除!\n";
waitForEnter();
return;
}
// 显示所有图书
cout << "当前图书列表:\n";
for(size_t i = 0; i < books.size(); i++) {
cout << "序号: " << (i+1) << "\n";
books[i].display();
cout << "----------------------------------------\n";
}
int choice;
cout << "\n请输入要删除的图书序号(1-" << books.size() << "):";
cin >> choice;
if (!cin) { // 修改点:输入校验
cout << "序号输入无效!\n";
clearCinFail();
waitForEnter();
return;
}
if(choice < 1 || choice > (int)books.size()) {
cout << "序号无效!\n";
waitForEnter();
return;
}
// 显示要删除的图书信息
cout << "\n即将删除以下图书:\n";
books[choice-1].display();
char confirm;
cout << "\n确认删除?(y/n):";
cin >> confirm;
if(confirm == 'y' || confirm == 'Y') {
books.erase(books.begin() + choice - 1);
cout << "\n图书删除成功!\n";
} else {
cout << "\n取消删除操作!\n";
}
waitForEnter();
}
void deleteBuyer() {
system("cls");
cout << "\n==================== 删除购书人 ====================\n";
if(buyers.empty()) {
cout << "暂无购书人信息可删除!\n";
waitForEnter();
return;
}
// 显示所有购书人
cout << "当前购书人列表:\n";
for(size_t i = 0; i < buyers.size(); i++) {
cout << "序号: " << (i+1) << "\n";
buyers[i]->display();
cout << "----------------------------------------\n";
}
int choice;
cout << "\n请输入要删除的购书人序号(1-" << buyers.size() << "):";
cin >> choice;
if (!cin) { // 修改点:输入校验
cout << "序号输入无效!\n";
clearCinFail();
waitForEnter();
return;
}
if(choice < 1 || choice > (int)buyers.size()) {
cout << "序号无效!\n";
waitForEnter();
return;
}
// 显示要删除的购书人信息
cout << "\n即将删除以下购书人:\n";
buyers[choice-1]->display();
char confirm;
cout << "\n确认删除?(y/n):";
cin >> confirm;
if(confirm == 'y' || confirm == 'Y') {
// 修改点:unique_ptr自动释放;修改原因:避免手动delete造成重复释放/遗漏
buyers.erase(buyers.begin() + choice - 1);
cout << "\n购书人删除成功!\n";
} else {
cout << "\n取消删除操作!\n";
}
waitForEnter();
}
void showAllOrders() {
system("cls");
cout << "\n==================== 订单历史 ====================\n";
ifstream fin(kOrdersFile);
if (!fin) {
cout << "暂无历史订单记录!\n";
} else {
string line;
while (getline(fin, line)) {
cout << line << "\n";
}
fin.close();
}
waitForEnter();
}
void saveBooksToFile() {
ofstream fout(kBooksFile);
if(fout) {
for(size_t i = 0; i < books.size(); i++) {
fout << books[i].getbook_ID() << "\n";
fout << books[i].getbook_name() << "\n";
fout << books[i].getauthor() << "\n";
fout << books[i].getpublishing() << "\n";
fout << books[i].getprice() << "\n";
}
fout.close();
}
}
void saveBuyersToFile() {
ofstream fout(kBuyersFile);
if(fout) {
for(size_t i = 0; i < buyers.size(); i++) {
// 判断购书人类型并写入相应格式
member* m = dynamic_cast<member*>(buyers[i].get());
honoured_guest* h = dynamic_cast<honoured_guest*>(buyers[i].get());
layfolk* l = dynamic_cast<layfolk*>(buyers[i].get());
if(l && !m && !h) { // 普通人
fout << "1\n";
fout << buyers[i]->getbuyname() << "\n";
fout << buyers[i]->getid() << "\n";
fout << buyers[i]->getaddress() << "\n";
fout << "0\n"; // 普通人没有额外信息,写0
} else if(m) { // 会员
fout << "2\n";
fout << buyers[i]->getbuyname() << "\n";
fout << buyers[i]->getid() << "\n";
fout << buyers[i]->getaddress() << "\n";
fout << m->getGrade() << "\n";
} else if(h) { // 贵宾
fout << "3\n";
fout << buyers[i]->getbuyname() << "\n";
fout << buyers[i]->getid() << "\n";
fout << buyers[i]->getaddress() << "\n";
fout << h->getDiscountRate() << "\n";
}
}
fout.close();
}
}
void loadOrderCountFromFile() {
ifstream fin(kOrdersFile);
if (!fin) return; // 文件不存在就返回,ordercount保持默认值0
string line;
int orderCount = 0;
while (getline(fin, line)) {
// 每个订单结尾都有分隔符
if (line.find("==============================") != string::npos) {
orderCount++;
}
}
fin.close();
order::ordercount = orderCount; // 设置为已有订单数量
}
5)重构后软件的测试情况
这一部分主要是对核心功能做一次走查,确认前面修改的地方没有引入新的问题。
启动阶段,我主要看了菜单是否正常显示,输入 0~8 不同选项时有没有异常退出;同时特意在菜单输入里敲了几次字母,例如 abc,验证输入校验是否生效,程序没有崩溃,而是给出提示后回到菜单。

围绕图书管理,我测试了添加新图书、重复添加同一个书号、显示当前图书列表等场景。正常添加时,新书可以在列表里看到;当输入已经存在的书号时,程序会提示书号已存在,不会重复插入。

购书人相关的测试,分别覆盖了普通用户、会员和贵宾三种类型。对会员,我主要关注等级在合法范围内时是否能正常保存;当输入非法等级时,程序会提示错误并返回。对贵宾,则重点看折扣率的合法性,例如输入 0.8 这样的折扣能否被正确接受,非法折扣会被拦截。

订单部分,我做了两类检查:一是贵宾折扣是否真正生效,比如定价 200 的图书,贵宾折扣为 0.8 时,最终实付是否变成 160;二是同一购书人连续下两单时,每一单的应付金额是不是只和本单的书目有关,不会把历史订单的消费累计到一起。

最后,我简单看了删除购书人、删除图书以及订单历史展示功能。删除后,列表中的数量会减少,程序没有出现越界或崩溃;创建新订单后,再打开历史订单,可以在 orders.txt 里看到追加的一条新记录。

6)作业总结
6.1 本次重构中遇到的难点
对我来说,第一个难点是在尽量不改动界面和基本操作流程的前提下,把明显的逻辑错误修掉。比如贵宾折扣计算错误、同一购书人跨订单累计消费这些问题,表面上程序也能跑起来,但算出来的结果是错的,需要一边跟着代码走,一边想象真实用户的使用场景,才能比较有把握地下手修改。
另一个比较绕的地方是多态对象的内存管理。原来的写法是用 vector<buyer*> 存各种派生类指针,删除或者退出时再通过基类指针去 delete。如果没有虚析构,这种写法在工程里是有风险的,所以我在理解清楚继承关系之后,改成了带虚析构的基类配合 unique_ptr 的方式。
6.2 耗时比较多的环节
从时间上看,最花精力的不是具体哪一行代码,而是前期的“摸清楚原项目在干什么”。控制台程序表面上只有一个菜单,但实际涉及到文件读写、状态在不同函数之间流转、异常输入处理等很多细节。我花了不少时间画简单的流程图,把“图书—购书人—订单—文件”之间的数据关系理出来,再去对照源码,效率会高一些。
6.3 一些小的体会
做完这次二次开发,最大的感受是:读别人代码时,如果一开始就钻到细节里,很容易迷路;先理解业务模型,再回到具体实现,会更有条理。其次,重构不一定要大改特改,这次更多是围绕几个核心缺陷做有针对性的调整,再顺带把路径硬编码、缺少输入校验之类的地方顺一顺,整体风险会小很多。最后,一些看起来啰嗦的工程化习惯,比如统一管理文件路径、在关键修改点写简单注释、对边界输入做校验,其实在这种小项目里就已经能看出价值了。

浙公网安备 33010602011771号