《C++ 小程序编写系列》(第五部):实战:多角色图书管理系统(继承与多态篇)

《C++ 小程序编写系列》(第五部):实战:多角色图书管理系统(继承与多态篇)

在第四部中,我们通过简易图书管理系统 掌握了类与对象的封装特性,实现了图书的基础管理功能。本次第五部,我们将进一步深入面向对象的核心特性 ------ 继承与多态 ,在原有系统基础上添加多角色用户管理(管理员、普通用户),不同角色拥有不同的操作权限,以此理解继承的代码复用性和多态的动态行为特性,让程序架构更灵活、可扩展。

一、核心知识点预热(进阶版)

本次是 C++ 面向对象编程的深化,核心知识点如下:

  1. 继承(公有继承) :定义基类(父类)User,派生类(子类)AdminUser(管理员)和NormalUser(普通用户),子类复用父类的属性和方法,同时扩展自身的特有功能。
  2. 多态(动态多态) :通过虚函数基类指针 / 引用实现动态绑定,不同子类对象调用同一基类接口时,执行各自的实现逻辑。
  3. 类的组合 :将Book类的对象集合与用户操作逻辑结合,体现 "整体 - 部分" 的关系。
  4. 权限控制:利用继承区分不同角色的操作权限(如管理员可添加 / 删除图书,普通用户仅可借阅 / 归还)。

二、系统需求分析(进阶版)

在第四部基础上,新增用户角色管理,核心需求如下:

  1. 用户登录:输入用户名和角色(管理员 / 普通用户),创建对应角色的用户对象。
  2. 管理员权限:添加图书、删除图书、查询图书、显示所有图书、退出系统(包含第四部所有图书管理功能)。
  3. 普通用户权限:查询图书、借阅图书、归还图书、退出系统(无修改图书数据的权限)。
  4. 数据持久化:保留图书数据的文件存储功能,启动加载、退出保存。
  5. 多态体现:不同角色用户调用同一 "操作接口",执行不同的功能逻辑。

三、完整代码实现(精简版)

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <iomanip>
#include <algorithm>

using namespace std;

// 图书类:复用第四部的封装逻辑,微调适配多角色场景
class Book {
private:
    string bookId;    // 图书编号(唯一)
    string bookName;  // 书名
    string author;    // 作者
    int stock;        // 库存数量

public:
    // 构造函数
    Book() = default;
    Book(string id, string name, string auth, int stk)
        : bookId(id), bookName(name), author(auth), stock(stk) {}

    // 公有访问接口
    string getBookId() const { return bookId; }
    string getBookName() const { return bookName; }
    string getAuthor() const { return author; }
    int getStock() const { return stock; }

    // 图书行为:借阅、归还
    bool borrowBook() {
        if (stock > 0) {
            stock--;
            return true;
        }
        return false;
    }

    void returnBook() { stock++; }

    // 设置信息(文件加载用)
    void setInfo(string id, string name, string auth, int stk) {
        bookId = id;
        bookName = name;
        author = auth;
        stock = stk;
    }

    // 显示单本图书信息
    void showInfo() const {
        cout << left << setw(12) << bookId
             << setw(20) << bookName
             << setw(15) << author
             << setw(8) << stock << endl;
    }
};

// 用户基类:定义抽象的用户行为(多态的核心)
class User {
protected:
    string userName;  // 用户名(保护成员:子类可访问,外部不可)
    vector<Book>& books; // 引用图书集合(避免拷贝,共享数据)

public:
    // 构造函数:初始化用户名和图书集合引用
    User(string name, vector<Book>& bs) : userName(name), books(bs) {}

    // 虚函数:用户操作菜单(多态的关键:基类声明,子类重写)
    virtual void showMenu() = 0; // 纯虚函数:基类成为抽象类,不能实例化

    // 虚函数:用户操作逻辑
    virtual void operate() = 0;

    // 析构函数:虚析构函数,避免子类对象析构不完整
    virtual ~User() = default;
};

// 管理员子类:继承User基类,实现管理员的权限逻辑
class AdminUser : public User {
public:
    // 构造函数:调用父类构造函数
    AdminUser(string name, vector<Book>& bs) : User(name, bs) {}

    // 重写:管理员菜单
    void showMenu() override {
        cout << "\n========== 管理员菜单 ==========" << endl;
        cout << "欢迎你,管理员:" << userName << endl;
        cout << "1. 添加图书" << endl;
        cout << "2. 删除图书" << endl;
        cout << "3. 查询图书" << endl;
        cout << "4. 显示所有图书" << endl;
        cout << "5. 退出系统" << endl;
        cout << "================================" << endl;
    }

    // 重写:管理员操作逻辑
    void operate() override;

    // 管理员特有功能:添加图书
    void addBook();

    // 管理员特有功能:删除图书
    void deleteBook();

    // 公共功能:查询图书(与普通用户共享,可抽离,此处简化)
    void queryBook();

    // 公共功能:显示所有图书
    void showAllBooks();
};

// 普通用户子类:继承User基类,实现普通用户的权限逻辑
class NormalUser : public User {
public:
    // 构造函数:调用父类构造函数
    NormalUser(string name, vector<Book>& bs) : User(name, bs) {}

    // 重写:普通用户菜单
    void showMenu() override {
        cout << "\n========== 普通用户菜单 ==========" << endl;
        cout << "欢迎你,用户:" << userName << endl;
        cout << "1. 查询图书" << endl;
        cout << "2. 借阅图书" << endl;
        cout << "3. 归还图书" << endl;
        cout << "4. 退出系统" << endl;
        cout << "==================================" << endl;
    }

    // 重写:普通用户操作逻辑
    void operate() override;

    // 普通用户功能:查询图书
    void queryBook();

    // 普通用户功能:借阅图书
    void borrowBook();

    // 普通用户功能:归还图书
    void returnBook();
};

// 全局函数:图书数据加载与保存(复用第四部逻辑)
void loadBooks(vector<Book>& books);
void saveBooks(const vector<Book>& books);

// 主函数:程序入口,处理用户登录和多态调用
int main() {
    vector<Book> books;
    loadBooks(books); // 加载图书数据

    // 用户登录
    string userName;
    int role;
    cout << "===== 图书管理系统 - 登录 =====" << endl;
    cout << "请输入用户名:";
    getline(cin, userName);
    cout << "请选择角色(1. 管理员  2. 普通用户):";
    cin >> role;
    cin.ignore(); // 忽略换行符

    // 多态:基类指针指向子类对象
    User* user = nullptr;
    if (role == 1) {
        user = new AdminUser(userName, books);
    } else if (role == 2) {
        user = new NormalUser(userName, books);
    } else {
        cout << "角色选择错误,程序退出!" << endl;
        return 0;
    }

    // 循环执行用户操作
    while (true) {
        user->showMenu(); // 动态绑定:调用子类的showMenu
        user->operate();  // 动态绑定:调用子类的operate

        // 检测退出条件:管理员选5,普通用户选4时退出
        // 此处通过操作逻辑中的返回值或标志位简化,直接在operate中处理退出
        // (代码简化:operate中退出时直接释放内存并return)
    }

    // 释放内存(实际在operate中已处理,此处为冗余保护)
    delete user;
    return 0;
}

// 管理员:添加图书
void AdminUser::addBook() {
    string id, name, author;
    int stock;

    cout << "----- 添加图书 -----" << endl;
    // 校验图书编号唯一性
    while (true) {
        cout << "请输入图书编号:";
        getline(cin, id);
        bool exist = false;
        for (const auto& book : books) {
            if (book.getBookId() == id) {
                exist = true;
                cout << "图书编号已存在,请重新输入!" << endl;
                break;
            }
        }
        if (!exist) break;
    }

    // 输入图书信息
    cout << "请输入图书名称:";
    getline(cin, name);
    cout << "请输入作者:";
    getline(cin, author);
    cout << "请输入库存数量:";
    cin >> stock;
    cin.ignore();

    // 创建图书对象并添加
    books.push_back(Book(id, name, author, stock));
    cout << "图书添加成功!" << endl;
}

// 管理员:删除图书
void AdminUser::deleteBook() {
    if (books.empty()) {
        cout << "暂无图书数据,无法删除!" << endl;
        return;
    }

    string id;
    cout << "----- 删除图书 -----" << endl;
    cout << "请输入要删除的图书编号:";
    getline(cin, id);

    for (auto it = books.begin(); it != books.end(); ++it) {
        if (it->getBookId() == id) {
            cout << "确定删除《" << it->getBookName() << "》吗?(y/n):";
            char confirm;
            cin >> confirm;
            cin.ignore();
            if (confirm == 'y' || confirm == 'Y') {
                books.erase(it);
                cout << "图书删除成功!" << endl;
            } else {
                cout << "取消删除!" << endl;
            }
            return;
        }
    }
    cout << "未找到该图书,删除失败!" << endl;
}

// 管理员:查询图书
void AdminUser::queryBook() {
    if (books.empty()) {
        cout << "暂无图书数据!" << endl;
        return;
    }

    int choice;
    cout << "----- 查询图书 -----" << endl;
    cout << "1. 按编号查询" << endl;
    cout << "2. 按书名查询" << endl;
    cout << "请选择查询方式:";
    cin >> choice;
    cin.ignore();

    if (choice == 1) {
        string id;
        cout << "请输入图书编号:";
        getline(cin, id);
        bool found = false;
        for (const auto& book : books) {
            if (book.getBookId() == id) {
                cout << "\n图书信息:" << endl;
                book.showInfo();
                found = true;
                break;
            }
        }
        if (!found) cout << "未找到该图书!" << endl;
    } else if (choice == 2) {
        string name;
        cout << "请输入图书名称:";
        getline(cin, name);
        bool found = false;
        cout << "\n查询结果:" << endl;
        cout << left << setw(12) << "编号" << setw(20) << "书名" << setw(15) << "作者" << setw(8) << "库存" << endl;
        cout << "-----------------------------------------------------------" << endl;
        for (const auto& book : books) {
            if (book.getBookName().find(name) != string::npos) {
                book.showInfo();
                found = true;
            }
        }
        if (!found) cout << "未找到相关图书!" << endl;
    } else {
        cout << "输入错误!" << endl;
    }
}

// 管理员:显示所有图书
void AdminUser::showAllBooks() {
    if (books.empty()) {
        cout << "暂无图书数据!" << endl;
        return;
    }

    cout << "----- 所有图书信息 -----" << endl;
    cout << left << setw(12) << "编号" << setw(20) << "书名" << setw(15) << "作者" << setw(8) << "库存" << endl;
    cout << "-----------------------------------------------------------" << endl;
    for (const auto& book : books) {
        book.showInfo();
    }
}

// 管理员:操作逻辑
void AdminUser::operate() {
    int choice;
    cout << "请选择操作(1-5):";
    cin >> choice;
    cin.ignore();

    switch (choice) {
        case 1: addBook(); break;
        case 2: deleteBook(); break;
        case 3: queryBook(); break;
        case 4: showAllBooks(); break;
        case 5:
            saveBooks(books);
            cout << "数据已保存,感谢使用!" << endl;
            delete this; // 释放当前对象
            exit(0); // 退出程序
        default: cout << "输入错误,请重新选择!" << endl;
    }

    // 暂停查看结果
    cout << "\n按回车键继续...";
    cin.get();
    system("cls"); // Windows清屏,Linux/macOS用"clear"
}

// 普通用户:查询图书(与管理员逻辑一致,可抽离为全局函数,此处简化)
void NormalUser::queryBook() {
    if (books.empty()) {
        cout << "暂无图书数据!" << endl;
        return;
    }

    int choice;
    cout << "----- 查询图书 -----" << endl;
    cout << "1. 按编号查询" << endl;
    cout << "2. 按书名查询" << endl;
    cout << "请选择查询方式:";
    cin >> choice;
    cin.ignore();

    if (choice == 1) {
        string id;
        cout << "请输入图书编号:";
        getline(cin, id);
        bool found = false;
        for (const auto& book : books) {
            if (book.getBookId() == id) {
                cout << "\n图书信息:" << endl;
                book.showInfo();
                found = true;
                break;
            }
        }
        if (!found) cout << "未找到该图书!" << endl;
    } else if (choice == 2) {
        string name;
        cout << "请输入图书名称:";
        getline(cin, name);
        bool found = false;
        cout << "\n查询结果:" << endl;
        cout << left << setw(12) << "编号" << setw(20) << "书名" << setw(15) << "作者" << setw(8) << "库存" << endl;
        cout << "-----------------------------------------------------------" << endl;
        for (const auto& book : books) {
            if (book.getBookName().find(name) != string::npos) {
                book.showInfo();
                found = true;
            }
        }
        if (!found) cout << "未找到相关图书!" << endl;
    } else {
        cout << "输入错误!" << endl;
    }
}

// 普通用户:借阅图书
void NormalUser::borrowBook() {
    if (books.empty()) {
        cout << "暂无图书数据!" << endl;
        return;
    }

    string id;
    cout << "----- 借阅图书 -----" << endl;
    cout << "请输入图书编号:";
    getline(cin, id);

    for (auto& book : books) {
        if (book.getBookId() == id) {
            if (book.borrowBook()) {
                cout << "借阅成功!当前库存:" << book.getStock() << endl;
            } else {
                cout << "库存不足,借阅失败!" << endl;
            }
            return;
        }
    }
    cout << "未找到该图书,借阅失败!" << endl;
}

// 普通用户:归还图书
void NormalUser::returnBook() {
    if (books.empty()) {
        cout << "暂无图书数据!" << endl;
        return;
    }

    string id;
    cout << "----- 归还图书 -----" << endl;
    cout << "请输入图书编号:";
    getline(cin, id);

    for (auto& book : books) {
        if (book.getBookId() == id) {
            book.returnBook();
            cout << "归还成功!当前库存:" << book.getStock() << endl;
            return;
        }
    }
    cout << "未找到该图书,归还失败!" << endl;
}

// 普通用户:操作逻辑
void NormalUser::operate() {
    int choice;
    cout << "请选择操作(1-4):";
    cin >> choice;
    cin.ignore();

    switch (choice) {
        case 1: queryBook(); break;
        case 2: borrowBook(); break;
        case 3: returnBook(); break;
        case 4:
            saveBooks(books);
            cout << "数据已保存,感谢使用!" << endl;
            delete this; // 释放当前对象
            exit(0); // 退出程序
        default: cout << "输入错误,请重新选择!" << endl;
    }

    // 暂停查看结果
    cout << "\n按回车键继续...";
    cin.get();
    system("cls"); // Windows清屏,Linux/macOS用"clear"
}

// 加载图书数据
void loadBooks(vector<Book>& books) {
    ifstream inFile("books.txt");
    if (!inFile.is_open()) {
        cout << "首次使用,暂无图书数据文件!" << endl;
        return;
    }

    string id, name, author;
    int stock;
    while (inFile >> id >> name >> author >> stock) {
        Book book;
        book.setInfo(id, name, author, stock);
        books.push_back(book);
    }
    inFile.close();
    cout << "成功加载" << books.size() << "本图书数据!" << endl;
}

// 保存图书数据
void saveBooks(const vector<Book>& books) {
    ofstream outFile("books.txt");
    if (!outFile.is_open()) {
        cout << "文件打开失败,保存失败!" << endl;
        return;
    }

    for (const auto& book : books) {
        outFile << book.getBookId() << " "
                << book.getBookName() << " "
                << book.getAuthor() << " "
                << book.getStock() << endl;
    }
    outFile.close();
}

四、代码解析(重点:继承与多态)

1. 继承的实现(User 基类与子类)

  • 基类User :定义了用户的通用属性(userNamebooks引用)和纯虚函数(showMenuoperate)。纯虚函数使User成为抽象类,无法实例化,只能作为基类被继承。
  • 子类AdminUserNormalUser :通过public继承User,复用父类的属性,同时重写父类的纯虚函数,实现各自的菜单和操作逻辑。
  • 保护成员protected :父类的userNamebooks被声明为protected,子类可直接访问,外部无法访问,兼顾了代码复用和数据隐藏。

2. 多态的实现(动态绑定)

这是本次代码的核心,多态的关键在于虚函数基类指针 / 引用

  • 虚函数 :基类中virtual void showMenu() = 0virtual void operate() = 0是纯虚函数,子类必须重写这些函数。override关键字显式声明重写,提高代码可读性,编译器会检查重写的正确性。
  • 基类指针 :主函数中User* user = nullptr;定义基类指针,根据用户选择的角色,让指针指向子类对象(new AdminUser(...)new NormalUser(...))。
  • 动态绑定 :调用user->showMenu()user->operate()时,编译器不会在编译期确定调用哪个函数,而是在运行期根据指针指向的实际子类对象,调用对应的重写函数(管理员调用AdminUser的方法,普通用户调用NormalUser的方法),这就是动态多态

3. 类的组合(Book 与 User 的关联)

  • User类中通过引用 vector<Book>& books关联图书集合,避免了拷贝整个 vector 的性能开销,同时让所有用户对象共享同一批图书数据,体现了 "类的组合" 设计思想(用户对象包含图书集合的引用,而非继承)。

4. 权限控制

  • 管理员子类AdminUser实现了添加、删除图书的功能,而普通用户子类NormalUser仅实现查询、借阅、归还功能,通过继承和重写,自然区分了不同角色的权限,符合实际业务逻辑。

5. 虚析构函数

  • 基类User的析构函数被声明为virtual ~User() = default;(虚析构函数),确保当通过基类指针删除子类对象时,子类的析构函数会被正确调用,避免内存泄漏。

五、功能扩展思路

在本基础版本上,可进一步深化面向对象的学习,扩展功能:

  1. 用户数据持久化 :添加用户文件(users.txt),存储用户名、密码、角色,实现用户注册和密码验证功能。
  2. 多态的进一步体现 :添加VipUser(VIP 用户)子类,继承NormalUser,实现 VIP 用户的特权(如借阅数量更多、借阅时间更长)。
  3. 异常处理:添加输入验证(如库存不能为负数、图书编号不能为空),抛出并捕获异常,提升程序稳定性。
  4. 图书分类与排序 :在Book类中添加分类属性,实现按分类查询和排序功能。
  5. 借阅记录 :创建BorrowRecord类,记录用户借阅的图书和时间,关联UserBook类。

六、总结

本次我们通过多角色图书管理系统 ,深入掌握了 C++ 面向对象的继承多态核心特性。继承实现了代码的复用,让不同角色的用户共享通用属性和接口;多态实现了动态行为,让程序能够根据不同的对象执行不同的逻辑,大幅提升了程序的灵活性和可扩展性。

与第四部的单类图书管理系统相比,本次的程序架构更贴近实际项目的设计模式(如简单工厂模式 :可封装用户对象的创建逻辑),为后续学习更复杂的设计模式和大型项目开发打下了基础。下一部我们将继续深入,学习模板与 STL 进阶,实现更通用、高效的程序。

相关推荐
虾..2 小时前
Linux 进程池小程序
linux·c++·小程序
CC.GG2 小时前
【Qt】信号和槽
开发语言·数据库·qt
是席木木啊2 小时前
基于MinIO Java SDK实现ZIP文件上传的方案与实践
java·开发语言
一起养小猫2 小时前
《Java数据结构与算法》第四篇(四):二叉树的高级操作查找与删除实现详解
java·开发语言·数据结构·算法
ALex_zry2 小时前
C++20/23标准对进程间共享信息的优化:从传统IPC到现代C++的演进
开发语言·c++·c++20
IMPYLH3 小时前
Lua 的 OS(操作系统) 模块
开发语言·笔记·后端·游戏引擎·lua
YGGP3 小时前
【Golang】LeetCode 287. 寻找重复数
开发语言·leetcode·golang
郝学胜-神的一滴3 小时前
深入解析Linux的`pthread_create`函数:从原理到实践
linux·服务器·c++·程序人生
吴佳浩 Alben3 小时前
Go 1.22 通关讲解
开发语言·数据库·golang