《C++ 小程序编写系列》(第五部):实战:多角色图书管理系统(继承与多态篇)
在第四部中,我们通过简易图书管理系统 掌握了类与对象的封装特性,实现了图书的基础管理功能。本次第五部,我们将进一步深入面向对象的核心特性 ------ 继承与多态 ,在原有系统基础上添加多角色用户管理(管理员、普通用户),不同角色拥有不同的操作权限,以此理解继承的代码复用性和多态的动态行为特性,让程序架构更灵活、可扩展。
一、核心知识点预热(进阶版)
本次是 C++ 面向对象编程的深化,核心知识点如下:
- 继承(公有继承) :定义基类(父类)
User,派生类(子类)AdminUser(管理员)和NormalUser(普通用户),子类复用父类的属性和方法,同时扩展自身的特有功能。 - 多态(动态多态) :通过虚函数 和基类指针 / 引用实现动态绑定,不同子类对象调用同一基类接口时,执行各自的实现逻辑。
- 类的组合 :将
Book类的对象集合与用户操作逻辑结合,体现 "整体 - 部分" 的关系。 - 权限控制:利用继承区分不同角色的操作权限(如管理员可添加 / 删除图书,普通用户仅可借阅 / 归还)。
二、系统需求分析(进阶版)
在第四部基础上,新增用户角色管理,核心需求如下:
- 用户登录:输入用户名和角色(管理员 / 普通用户),创建对应角色的用户对象。
- 管理员权限:添加图书、删除图书、查询图书、显示所有图书、退出系统(包含第四部所有图书管理功能)。
- 普通用户权限:查询图书、借阅图书、归还图书、退出系统(无修改图书数据的权限)。
- 数据持久化:保留图书数据的文件存储功能,启动加载、退出保存。
- 多态体现:不同角色用户调用同一 "操作接口",执行不同的功能逻辑。
三、完整代码实现(精简版)
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:定义了用户的通用属性(userName、books引用)和纯虚函数(showMenu、operate)。纯虚函数使User成为抽象类,无法实例化,只能作为基类被继承。 - 子类
AdminUser和NormalUser:通过public继承User,复用父类的属性,同时重写父类的纯虚函数,实现各自的菜单和操作逻辑。 - 保护成员
protected:父类的userName和books被声明为protected,子类可直接访问,外部无法访问,兼顾了代码复用和数据隐藏。
2. 多态的实现(动态绑定)
这是本次代码的核心,多态的关键在于虚函数 和基类指针 / 引用:
- 虚函数 :基类中
virtual void showMenu() = 0和virtual 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;(虚析构函数),确保当通过基类指针删除子类对象时,子类的析构函数会被正确调用,避免内存泄漏。
五、功能扩展思路
在本基础版本上,可进一步深化面向对象的学习,扩展功能:
- 用户数据持久化 :添加用户文件(
users.txt),存储用户名、密码、角色,实现用户注册和密码验证功能。 - 多态的进一步体现 :添加
VipUser(VIP 用户)子类,继承NormalUser,实现 VIP 用户的特权(如借阅数量更多、借阅时间更长)。 - 异常处理:添加输入验证(如库存不能为负数、图书编号不能为空),抛出并捕获异常,提升程序稳定性。
- 图书分类与排序 :在
Book类中添加分类属性,实现按分类查询和排序功能。 - 借阅记录 :创建
BorrowRecord类,记录用户借阅的图书和时间,关联User和Book类。
六、总结
本次我们通过多角色图书管理系统 ,深入掌握了 C++ 面向对象的继承 和多态核心特性。继承实现了代码的复用,让不同角色的用户共享通用属性和接口;多态实现了动态行为,让程序能够根据不同的对象执行不同的逻辑,大幅提升了程序的灵活性和可扩展性。
与第四部的单类图书管理系统相比,本次的程序架构更贴近实际项目的设计模式(如简单工厂模式 :可封装用户对象的创建逻辑),为后续学习更复杂的设计模式和大型项目开发打下了基础。下一部我们将继续深入,学习模板与 STL 进阶,实现更通用、高效的程序。