软件工程课程作业: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
原项目运行步骤(重构前)

  1. 确保上述目录存在并放置数据文件(可为空文件,但需存在以便保存)
  2. 编译运行 dazuoye.cpp
  3. 按菜单进行“显示图书/购书人、创建订单、添加/删除”等操作

截图1:程序启动主菜单界面
image

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

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

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

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

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 状态
修改后:若 !cinclearCinFail() + 提示 + 返回

修改点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,验证输入校验是否生效,程序没有崩溃,而是给出提示后回到菜单。
image

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

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

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

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


6)作业总结

6.1 本次重构中遇到的难点

对我来说,第一个难点是在尽量不改动界面和基本操作流程的前提下,把明显的逻辑错误修掉。比如贵宾折扣计算错误、同一购书人跨订单累计消费这些问题,表面上程序也能跑起来,但算出来的结果是错的,需要一边跟着代码走,一边想象真实用户的使用场景,才能比较有把握地下手修改。

另一个比较绕的地方是多态对象的内存管理。原来的写法是用 vector<buyer*> 存各种派生类指针,删除或者退出时再通过基类指针去 delete。如果没有虚析构,这种写法在工程里是有风险的,所以我在理解清楚继承关系之后,改成了带虚析构的基类配合 unique_ptr 的方式。

6.2 耗时比较多的环节

从时间上看,最花精力的不是具体哪一行代码,而是前期的“摸清楚原项目在干什么”。控制台程序表面上只有一个菜单,但实际涉及到文件读写、状态在不同函数之间流转、异常输入处理等很多细节。我花了不少时间画简单的流程图,把“图书—购书人—订单—文件”之间的数据关系理出来,再去对照源码,效率会高一些。

6.3 一些小的体会

做完这次二次开发,最大的感受是:读别人代码时,如果一开始就钻到细节里,很容易迷路;先理解业务模型,再回到具体实现,会更有条理。其次,重构不一定要大改特改,这次更多是围绕几个核心缺陷做有针对性的调整,再顺带把路径硬编码、缺少输入校验之类的地方顺一顺,整体风险会小很多。最后,一些看起来啰嗦的工程化习惯,比如统一管理文件路径、在关键修改点写简单注释、对边界输入做校验,其实在这种小项目里就已经能看出价值了。

posted @ 2026-03-05 19:42  Chloiris  阅读(6)  评论(0)    收藏  举报