《C++ 小程序编写系列》(第四部):实战:简易图书管理系统(类与对象篇)
在前三部中,我们从基础语法到结构体、vector 容器、文件操作,实现了计算器和学生成绩管理系统(面向过程编程)。本次第四部,我们将跨越到面向对象编程(OOP)的核心 ------类与对象,通过实现简易图书管理系统,掌握类的封装、构造函数、成员函数等关键知识点,完成从 "面向过程" 到 "面向对象" 的思维转变。
一、核心知识点预热(进阶版)
本次是 C++ 面向对象编程的入门关键,核心知识点如下:
- 类与对象 :类是自定义的抽象数据类型,封装了成员变量(数据)和成员函数(行为);对象是类的实例化。
- 封装 :将成员变量私有化(
private),仅通过公有(public)成员函数访问 / 修改,避免数据被直接篡改,提升代码安全性。 - 构造函数:与类名相同,无返回值,用于初始化对象的成员变量,简化对象创建流程。
- const 成员函数 :在成员函数后加
const,保证函数不会修改成员变量,提升代码健壮性。 - 文件操作进阶:处理图书库存等数值型数据的持久化,兼容借阅 / 归还后的状态更新。
二、系统需求分析(精简核心功能)
我们实现的简易图书管理系统保留核心业务逻辑,兼顾面向对象特性:
- 添加图书:输入图书编号、书名、作者、库存数量,创建图书对象并存储。
- 查询图书:支持按图书编号(唯一)或书名(多结果)查询信息。
- 借阅图书:按编号找到图书,库存足够时减少库存,提示借阅成功。
- 归还图书:按编号找到图书,库存增加,提示归还成功。
- 显示所有图书:格式化输出所有图书的完整信息。
- 数据持久化:启动时加载文件中的图书数据,退出时保存数据。
- 退出系统:安全退出并自动保存数据。
三、完整代码实现(精简版)
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; } // const:不修改成员变量
string getBookName() const { return bookName; }
string getAuthor() const { return author; }
int getStock() const { return stock; }
// 借阅图书:库存减1(返回是否成功)
bool borrowBook() {
if (stock > 0) {
stock--;
return true;
}
return false;
}
// 归还图书:库存加1
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;
}
};
// 函数声明(核心功能)
void loadBooks(vector<Book>& books); // 从文件加载图书数据
void saveBooks(const vector<Book>& books); // 保存图书数据到文件
void addBook(vector<Book>& books); // 添加图书
void queryBook(const vector<Book>& books); // 查询图书
void borrowBook(vector<Book>& books); // 借阅图书
void returnBook(vector<Book>& books); // 归还图书
void showAllBooks(const vector<Book>& books); // 显示所有图书
void showMenu(); // 显示菜单
int main() {
vector<Book> books; // 用vector存储图书对象(类的实例集合)
loadBooks(books); // 启动时加载数据
int choice;
while (true) {
showMenu();
cout << "请选择功能(1-7):";
cin >> choice;
cin.ignore(); // 忽略换行符
switch (choice) {
case 1: addBook(books); break;
case 2: queryBook(books); break;
case 3: borrowBook(books); break;
case 4: returnBook(books); break;
case 5: showAllBooks(books); break;
case 6: saveBooks(books); cout << "数据已手动保存!" << endl; break;
case 7: saveBooks(books); cout << "数据已保存,再见!" << endl; return 0;
default: cout << "输入错误,请重新选择!" << endl;
}
cout << "\n按回车键继续...";
cin.get();
system("cls"); // Windows清屏,Linux/macOS用"clear"
}
return 0;
}
// 显示系统菜单
void showMenu() {
cout << "========== 图书管理系统 ==========" << endl;
cout << "1. 添加图书" << endl;
cout << "2. 查询图书" << endl;
cout << "3. 借阅图书" << endl;
cout << "4. 归还图书" << endl;
cout << "5. 显示所有图书" << endl;
cout << "6. 手动保存数据" << endl;
cout << "7. 退出系统" << endl;
cout << "==================================" << endl;
}
// 从文件加载图书数据(books.txt)
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();
}
// 添加图书(检查编号唯一性)
void addBook(vector<Book>& books) {
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;
// 创建图书对象并添加到vector
Book newBook(id, name, author, stock);
books.push_back(newBook);
cout << "图书添加成功!" << endl;
}
// 查询图书(按编号/书名)
void queryBook(const vector<Book>& books) {
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;
cout << "编号:" << book.getBookId() << "\t书名:" << book.getBookName() << endl;
cout << "作者:" << book.getAuthor() << "\t库存:" << book.getStock() << endl;
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 borrowBook(vector<Book>& books) {
if (books.empty()) {
cout << "暂无图书数据!" << endl;
return;
}
string id;
cout << "----- 借阅图书 -----" << endl;
cout << "请输入图书编号:";
getline(cin, id);
for (auto& book : books) { // 非const引用,可修改图书库存
if (book.getBookId() == id) {
if (book.borrowBook()) {
cout << "借阅成功!当前库存:" << book.getStock() << endl;
} else {
cout << "库存不足,借阅失败!" << endl;
}
return;
}
}
cout << "未找到该图书!" << endl;
}
// 归还图书
void returnBook(vector<Book>& books) {
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 showAllBooks(const vector<Book>& books) {
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();
}
}
四、代码解析(重点:面向对象思想)
1. 类的封装(Book 类)
这是本次代码的核心,体现了面向对象的封装性:
- 私有成员变量 :
bookId、bookName、author、stock被声明为private,外部无法直接访问或修改,避免了数据被恶意篡改(比如直接将库存改为负数)。 - 公有成员函数 :提供了访问(
getXXX)、修改(setInfo)和行为(borrowBook、returnBook)的接口,所有对私有数据的操作都通过这些函数完成,实现了 "数据隐藏"。
2. 构造函数
- 我们定义了默认构造函数 (
Book() = default;)和带参构造函数 ,用于初始化图书对象:- 带参构造函数通过初始化列表直接初始化成员变量,比在函数体内赋值更高效。
- 默认构造函数用于文件加载时创建空对象,再通过
setInfo方法赋值。
3. const 成员函数
- 如
getBookId() const、showInfo() const,函数后的const表示该函数不会修改类的任何成员变量,编译器会强制检查这一点,避免意外修改数据,提升代码的健壮性。
4. 类的实例化与存储
- 我们用
vector<Book> books存储多个图书对象(类的实例),vector 的动态特性与类的封装结合,既方便管理对象集合,又保证了数据安全。
5. 核心功能的面向对象实现
- 借阅 / 归还图书 :直接调用图书对象的
borrowBook和returnBook成员函数,将图书的行为封装在类内部,符合 "对象的行为由自身负责" 的面向对象思想(比如,图书自己知道如何处理借阅和归还,而不是在外部函数中直接修改库存)。
五、功能扩展思路
在本基础版本上,可进一步扩展功能,深化面向对象的学习:
- 添加图书分类 :在 Book 类中增加
category成员变量,支持按分类查询图书。 - 借阅记录管理 :创建
BorrowRecord类,记录借阅人、借阅时间、归还时间,关联 Book 类。 - 用户管理 :创建
User类(管理员、普通用户),实现不同权限的操作(如管理员可添加图书,普通用户仅可借阅)。 - 图书排序 :重载
<运算符,实现按书名、库存等条件排序图书。 - 异常处理:添加输入验证(如库存不能为负数),抛出并捕获异常,避免程序崩溃。
六、总结
本次我们通过简易图书管理系统,完成了从 "面向过程" 到 "面向对象" 的关键跨越,掌握了类的封装、构造函数、const 成员函数等核心知识点。面向对象编程是 C++ 的核心特性,它的封装、继承、多态三大特性将帮助我们编写更模块化、可维护、可扩展的代码。
与第三部的学生成绩管理系统(结构体 + 面向过程)相比,本次的类封装让代码的逻辑更清晰,数据更安全,更贴近实际项目的开发模式。下一部我们将继续深入,学习继承与多态,实现更复杂的面向对象程序。