一、项目概述:为什么要做这个图书管理系统?
说实话,我见过太多学C++的同学,语法背得滚瓜烂熟,一到写项目就懵了。为什么?因为知识点是散的,就像一堆零件堆在桌上,不知道怎么组装成一台机器。
这个图书管理系统,就是咱们的「组装练习」。它会用到类与对象、继承、多态、文件I/O、STL,甚至设计模式。做完它,你就能把这些知识点串起来了。
我在公司带新人时,经常让他们先写一个类似的小系统。不是为了炫技,而是为了理解「面向对象到底在解决什么问题」。你想想看,如果只是写几个函数也能跑,为什么要用类?为什么要继承?
嗯,这个项目会给你答案。
二、项目需求:我们要做什么?
2.1 功能清单
一个简单的图书管理系统,至少需要这些功能:
- 图书管理:添加、删除、修改、查询图书
- 借阅管理:借书、还书、查看借阅记录
- 用户管理:管理员和普通读者两种角色
- 数据持久化:程序关闭后数据不丢失(文件I/O)
- 统计功能:统计图书数量、借阅次数等
2.2 技术栈
| 技术点 | 用途 |
|---|---|
| 类与对象 | 封装图书、用户、借阅记录等实体 |
| 继承 | 管理员和读者继承自基类User |
| 多态 | 统一的接口处理不同用户的操作 |
| 文件I/O | 读写图书和用户数据到文件 |
| STL | vector存储图书列表,map管理用户 |
| 设计模式 | 单例模式管理图书馆实例 |
三、架构设计:怎么搭架子?
3.1 整体架构图
先别急着写代码。我习惯先画图,把结构理清楚。下面这张图展示了系统的核心模块和它们之间的关系:
3.2 核心类设计
咱们先定义几个核心类。我习惯从最底层的实体类开始写:
// Book.h - 图书实体类
class Book {
private:
string m_id; // 图书编号
string m_title; // 书名
string m_author; // 作者
bool m_borrowed; // 是否已借出
int m_borrowCount; // 借阅次数
public:
Book() : m_borrowed(false), m_borrowCount(0) {}
Book(string id, string title, string author)
: m_id(id), m_title(title), m_author(author),
m_borrowed(false), m_borrowCount(0) {}
// getter/setter 略...
virtual void display() const; // 多态:显示图书信息
};
3.3 继承与多态:用户体系
用户分为管理员和普通读者。它们有很多共同属性(姓名、ID、密码),但权限不同。这时候继承就派上用场了:
// User.h - 用户基类
class User {
protected:
string m_name;
string m_userId;
string m_password;
public:
virtual void showMenu() = 0; // 纯虚函数:不同用户显示不同菜单
virtual ~User() {} // 虚析构,防止内存泄漏
string getName() const { return m_name; }
string getUserId() const { return m_userId; }
bool checkPassword(const string& pwd) const { return m_password == pwd; }
};
// Admin.h - 管理员
class Admin : public User {
public:
void showMenu() override {
cout << "1. 添加图书 2. 删除图书 3. 查看所有图书" << endl;
cout << "4. 查看所有用户 5. 退出" << endl;
}
};
// Reader.h - 读者
class Reader : public User {
public:
void showMenu() override {
cout << "1. 查询图书 2. 借书 3. 还书" << endl;
cout << "4. 查看我的借阅记录 5. 退出" << endl;
}
};
四、单例模式:图书馆只有一个
一个系统里只能有一个「图书馆」实例。如果有人不小心new了两个Library对象,数据就乱了。单例模式就是解决这个问题的:
// Library.h - 单例模式
class Library {
private:
vector<Book> m_books;
map<string, User*> m_users; // key: userId, value: User指针
string m_bookFile;
string m_userFile;
// 构造函数私有化,外部不能直接new
Library() : m_bookFile("books.txt"), m_userFile("users.txt") {
loadData(); // 启动时加载数据
}
// 禁止拷贝
Library(const Library&) = delete;
Library& operator=(const Library&) = delete;
public:
static Library& getInstance() {
static Library instance; // C++11保证线程安全
return instance;
}
void loadData(); // 从文件加载数据
void saveData(); // 保存数据到文件
void addBook(const Book& book);
void removeBook(const string& bookId);
Book* findBook(const string& bookId);
// ... 其他方法
};
五、文件I/O:让数据活下来
程序关了数据就没了?那不行。咱们得把数据存到文件里。我习惯用简单的文本格式,方便调试:
void Library::saveData() {
ofstream out(m_bookFile);
if (!out) {
cerr << "无法打开文件: " << m_bookFile << endl;
return;
}
for (const auto& book : m_books) {
// 格式:ID|书名|作者|是否借出|借阅次数
out << book.getId() << "|"
<< book.getTitle() << "|"
<< book.getAuthor() << "|"
<< book.isBorrowed() << "|"
<< book.getBorrowCount() << endl;
}
out.close();
}
void Library::loadData() {
ifstream in(m_bookFile);
if (!in) return; // 文件不存在就跳过
string line;
while (getline(in, line)) {
// 解析每一行,构造Book对象
// 用stringstream或find+substr分割字符串
// ... 解析代码略
}
in.close();
}
六、STL容器:选对工具干对活
咱们用到了两个STL容器:
- vector<Book>:存储图书列表。为什么用vector?因为图书数量会动态变化,vector支持随机访问,查找也方便。
- map<string, User*>:管理用户。用用户ID作为key,查找用户时O(log n)复杂度,比遍历vector快多了。
你可能会问:为什么不用unordered_map?嗯,对于几十个用户的小系统,map就够用了。unordered_map虽然更快,但需要提供哈希函数,有点杀鸡用牛刀的感觉。
七、完整流程:从启动到退出
咱们捋一遍程序的完整生命周期:
- 启动:Library::getInstance() 被调用,触发构造函数,自动加载文件数据。
- 登录:用户输入ID和密码,从m_users中查找并验证。
- 显示菜单:根据用户类型,通过多态调用showMenu()。
- 操作循环:用户选择功能,调用对应的Manager方法。
- 退出:程序结束前,Library的析构函数自动调用saveData()。
八、总结:你学到了什么?
这个项目虽然小,但五脏俱全。你想想看,我们用了:
- 类与对象:把图书、用户抽象成类
- 继承:Admin和Reader继承User
- 多态:通过虚函数实现不同用户的菜单
- 文件I/O:数据持久化,程序重启后数据还在
- STL:vector和map管理数据
- 设计模式:单例模式保证Library唯一
说实话,这个架构拿到小公司里直接就能用。我当年第一个正式项目,就是从类似的结构开始的。当然,生产环境会更复杂,要考虑并发、数据库、网络等等。但核心思想是一样的——面向对象的封装、继承、多态,加上合适的设计模式,能让代码清晰、可维护。
下一章,咱们会真正动手写代码。我会带着你一步步实现每个类,从Book到Library,从文件读写到用户交互。准备好了吗?