实战项目 - 图书管理系统¶
📖 项目简介¶
本章将通过构建一个完整的图书管理系统,综合运用前面学到的C++知识。
上图给出了项目从 UI 到 Service、DAO、Data 的分层关系,帮助理解后续代码组织方式。
🎯 项目目标¶
完成本项目后,你将能够: - 构建完整的C++控制台应用 - 实现图书信息的增删改查 - 掌握数据结构的实际应用 - 理解面向对象编程的实际应用 - 掌握文件I/O操作
🏗️ 项目结构¶
Text Only
LibraryManagementSystem/
├── src/
│ ├── model/
│ │ ├── Book.h
│ │ └── User.h
│ ├── dao/
│ │ ├── BookDAO.h
│ │ └── BookDAO.cpp
│ ├── service/
│ │ ├── BookService.h
│ │ └── BookService.cpp
│ ├── util/
│ │ ├── FileUtil.h
│ │ ├── FileUtil.cpp
│ │ ├── InputUtil.h
│ │ └── InputUtil.cpp
│ └── main.cpp
├── data/
│ └── books.dat
└── README.md
📄 核心功能实现¶
1. 图书模型¶
C++
// Book.h
#ifndef BOOK_H
#define BOOK_H
#include <string>
using namespace std;
// Book类:使用封装(Encapsulation)将数据成员设为private,通过public接口访问
class Book {
private:
// 私有数据成员,外部只能通过getter/setter访问(数据隐藏原则)
string isbn;
string title;
string author;
string publisher;
int year;
double price;
int quantity;
public:
// 默认构造函数与带参构造函数(函数重载)
Book();
// const引用传参避免不必要的拷贝,提高效率
Book(const string& isbn, const string& title, const string& author, const string& publisher,
int year, double price, int quantity);
// Getter和Setter方法(const成员函数保证不修改对象状态)
string getIsbn() const;
void setIsbn(const string& isbn);
string getTitle() const;
void setTitle(const string& title);
string getAuthor() const;
void setAuthor(const string& author);
string getPublisher() const;
void setPublisher(const string& publisher);
int getYear() const;
void setYear(int year);
double getPrice() const;
void setPrice(double price);
int getQuantity() const;
void setQuantity(int quantity);
void display() const; // 格式化输出图书信息
string toString() const; // 序列化为CSV格式字符串,用于文件存储
};
#endif
C++
// Book.cpp
#include "Book.h"
#include <iostream>
// 默认构造函数:使用初始化列表(initializer list)初始化所有成员
// 初始化列表比在函数体内赋值更高效,避免先默认构造再赋值的开销
Book::Book() : isbn(""), title(""), author(""), publisher(""),
year(0), price(0.0), quantity(0) {}
// 带参构造函数:初始化列表中形参名与成员名相同,编译器能正确区分
Book::Book(const string& isbn, const string& title, const string& author, const string& publisher,
int year, double price, int quantity)
: isbn(isbn), title(title), author(author), publisher(publisher),
year(year), price(price), quantity(quantity) {}
string Book::getIsbn() const {
return isbn;
}
// this指针用于区分同名的成员变量和参数
void Book::setIsbn(const string& isbn) {
this->isbn = isbn;
}
string Book::getTitle() const {
return title;
}
void Book::setTitle(const string& title) {
this->title = title;
}
string Book::getAuthor() const {
return author;
}
void Book::setAuthor(const string& author) {
this->author = author;
}
string Book::getPublisher() const {
return publisher;
}
void Book::setPublisher(const string& publisher) {
this->publisher = publisher;
}
int Book::getYear() const {
return year;
}
void Book::setYear(int year) {
this->year = year;
}
double Book::getPrice() const {
return price;
}
void Book::setPrice(double price) {
this->price = price;
}
int Book::getQuantity() const {
return quantity;
}
void Book::setQuantity(int quantity) {
this->quantity = quantity;
}
// const成员函数:承诺不修改对象状态,可被const对象/引用调用
void Book::display() const {
cout << "ISBN: " << isbn << endl;
cout << "书名: " << title << endl;
cout << "作者: " << author << endl;
cout << "出版社: " << publisher << endl;
cout << "出版年份: " << year << endl;
cout << "价格: " << price << endl;
cout << "库存: " << quantity << endl;
}
// 将对象序列化为CSV格式字符串,用于文件持久化存储
// to_string()将数值类型转换为string
string Book::toString() const {
return isbn + "," + title + "," + author + "," + publisher + "," +
to_string(year) + "," + to_string(price) + "," + to_string(quantity);
}
2. 图书DAO¶
C++
// BookDAO.h
#ifndef BOOK_DAO_H
#define BOOK_DAO_H
#include <vector>
#include "Book.h"
using namespace std;
// DAO(Data Access Object)模式:将数据访问逻辑与业务逻辑分离
class BookDAO {
private:
vector<Book> books; // STL vector容器:动态数组,自动管理内存
string filename; // 数据文件路径
public:
BookDAO(const string& filename); // 构造时从文件加载数据
~BookDAO(); // 析构时自动保存数据(RAII思想)
void addBook(const Book& book);
bool removeBook(const string& isbn);
bool updateBook(const Book& book);
Book* findBookByIsbn(const string& isbn); // 返回指针:找到返回地址,未找到返回nullptr
vector<Book> getAllBooks();
vector<Book> searchBooks(const string& keyword);
vector<Book> getBooksByAuthor(const string& author);
int getBookCount();
bool saveToFile();
bool loadFromFile();
void displayAllBooks();
};
#endif
C++
// BookDAO.cpp
#include "BookDAO.h"
#include <fstream>
#include <algorithm>
#include <iostream>
#include <sstream>
// 构造函数:初始化文件名并加载已有数据
BookDAO::BookDAO(const string& filename) : filename(filename) {
loadFromFile(); // 程序启动时自动加载持久化数据
}
// 析构函数:对象销毁时自动保存数据(RAII:资源获取即初始化)
BookDAO::~BookDAO() {
saveToFile();
}
void BookDAO::addBook(const Book& book) {
books.push_back(book);
cout << "图书添加成功: " << book.getTitle() << endl;
}
bool BookDAO::removeBook(const string& isbn) {
// 使用迭代器遍历vector,erase()需要迭代器参数来定位删除元素
for (auto it = books.begin(); it != books.end(); ++it) {
if (it->getIsbn() == isbn) {
books.erase(it); // erase后迭代器失效,此处直接return避免问题
cout << "图书删除成功: " << isbn << endl;
return true;
}
}
cout << "图书不存在: " << isbn << endl;
return false;
}
bool BookDAO::updateBook(const Book& book) {
// 范围for循环使用引用(auto&),以便直接修改vector中的元素
for (auto& b : books) {
if (b.getIsbn() == book.getIsbn()) {
b = book; // 拷贝赋值运算符替换整个对象
cout << "图书更新成功: " << book.getTitle() << endl;
return true;
}
}
cout << "图书不存在: " << book.getIsbn() << endl;
return false;
}
Book* BookDAO::findBookByIsbn(const string& isbn) {
for (auto& book : books) {
if (book.getIsbn() == isbn) {
return &book;
}
}
return nullptr;
}
vector<Book> BookDAO::getAllBooks() {
return books;
}
// 关键词搜索:在书名、作者、出版社中模糊匹配
vector<Book> BookDAO::searchBooks(const string& keyword) {
vector<Book> result;
for (const auto& book : books) {
// string::find()返回匹配位置,string::npos表示未找到
if (book.getTitle().find(keyword) != string::npos ||
book.getAuthor().find(keyword) != string::npos ||
book.getPublisher().find(keyword) != string::npos) {
result.push_back(book); // 匹配成功则加入结果集
}
}
return result;
}
vector<Book> BookDAO::getBooksByAuthor(const string& author) {
vector<Book> result;
for (const auto& book : books) {
if (book.getAuthor() == author) {
result.push_back(book);
}
}
return result;
}
int BookDAO::getBookCount() {
return books.size();
}
// 数据持久化:将内存中的图书数据写入文件
bool BookDAO::saveToFile() {
ofstream outFile(filename); // 输出文件流,默认覆盖写入
if (!outFile) { // 检查文件是否成功打开
cout << "无法打开文件: " << filename << endl;
return false;
}
for (const auto& book : books) {
outFile << book.toString() << endl;
}
outFile.close();
cout << "数据保存成功" << endl;
return true;
}
// 从文件加载数据:逐行读取CSV格式并解析为Book对象
bool BookDAO::loadFromFile() {
ifstream inFile(filename); // 输入文件流
if (!inFile) {
cout << "无法打开文件: " << filename << endl;
return false;
}
string line;
books.clear();
while (getline(inFile, line)) {
// stringstream用于字符串分割,配合getline以逗号为分隔符解析CSV
stringstream ss(line);
string isbn, title, author, publisher, yearStr, priceStr, quantityStr;
getline(ss, isbn, ','); // 以','为分隔符逐字段提取
getline(ss, title, ',');
getline(ss, author, ',');
getline(ss, publisher, ',');
getline(ss, yearStr, ',');
getline(ss, priceStr, ',');
getline(ss, quantityStr);
// stoi/stod:字符串转整数/浮点数(string to int / string to double)
Book book(isbn, title, author, publisher,
stoi(yearStr), stod(priceStr), stoi(quantityStr));
books.push_back(book); // 将解析好的图书对象添加到vector尾部
}
inFile.close();
cout << "数据加载成功,共" << books.size() << "本图书" << endl;
return true;
}
void BookDAO::displayAllBooks() {
if (books.empty()) {
cout << "暂无图书信息" << endl;
return;
}
cout << "\n========== 图书列表 ===========" << endl;
cout << "ISBN\t书名\t作者\t出版社\t年份\t价格\t库存" << endl;
cout << "----------------------------------------" << endl;
for (const auto& book : books) {
cout << book.getIsbn() << "\t"
<< book.getTitle() << "\t"
<< book.getAuthor() << "\t"
<< book.getPublisher() << "\t"
<< book.getYear() << "\t"
<< book.getPrice() << "\t"
<< book.getQuantity() << endl;
}
}
3. 输入工具¶
C++
// InputUtil.h
#ifndef INPUT_UTIL_H
#define INPUT_UTIL_H
#include <string>
#include <iostream>
#include <limits>
using namespace std;
// 工具类:所有方法为static,无需实例化即可通过类名直接调用
class InputUtil {
public:
// 函数重载(Overloading):同名函数通过不同参数列表实现多种调用方式
// 获取整数输入(带提示、默认值和范围)
static int getIntInput(const string& prompt, int defaultVal, int minVal, int maxVal);
// 获取整数输入(带提示和范围)
static int getIntInput(const string& prompt, int minVal, int maxVal);
// 获取整数输入(仅范围)
static int getIntInput(int minVal, int maxVal);
// 获取浮点输入
static double getDoubleInput(const string& prompt, double minVal, double maxVal);
// 获取字符串输入(默认参数:调用时可省略defaultVal,自动使用空字符串)
static string getStringInput(const string& prompt, const string& defaultVal = "");
};
#endif
C++
// InputUtil.cpp
#include "InputUtil.h"
int InputUtil::getIntInput(int minVal, int maxVal) {
return getIntInput("", 0, minVal, maxVal);
}
int InputUtil::getIntInput(const string& prompt, int minVal, int maxVal) {
return getIntInput(prompt, 0, minVal, maxVal);
}
// 核心输入验证逻辑:循环直到获得合法输入(防御式编程)
int InputUtil::getIntInput(const string& prompt, int defaultVal, int minVal, int maxVal) {
int value;
while (true) { // 无限循环,只有合法输入才能通过return退出
if (!prompt.empty()) {
cout << "请输入" << prompt << " [" << minVal << "-" << maxVal << "]: ";
}
string line;
getline(cin, line);
if (line.empty()) return defaultVal;
try {
value = stoi(line); // stoi可能抛出invalid_argument或out_of_range异常
if (value >= minVal && value <= maxVal) return value;
cout << "输入超出范围,请重新输入" << endl;
} catch (...) { // catch(...)捕获所有类型的异常
cout << "输入无效,请输入整数" << endl;
}
}
}
double InputUtil::getDoubleInput(const string& prompt, double minVal, double maxVal) {
double value;
while (true) {
cout << "请输入" << prompt << ": ";
string line;
getline(cin, line);
try {
value = stod(line);
if (value >= minVal && value <= maxVal) return value;
cout << "输入超出范围,请重新输入" << endl;
} catch (...) {
cout << "输入无效,请输入数字" << endl;
}
}
}
string InputUtil::getStringInput(const string& prompt, const string& defaultVal) {
cout << "请输入" << prompt << ": ";
string line;
getline(cin, line);
return line.empty() ? defaultVal : line;
}
4. 图书服务¶
C++
// BookService.h
#ifndef BOOK_SERVICE_H
#define BOOK_SERVICE_H
#include "BookDAO.h"
#include <memory>
using namespace std;
// 服务层(Service Layer):封装业务逻辑,协调用户界面与数据访问层
class BookService {
private:
// unique_ptr智能指针:独占所有权,自动释放内存,避免内存泄漏
unique_ptr<BookDAO> bookDAO;
public:
BookService(const string& filename);
~BookService() = default; // 默认析构函数(unique_ptr自动清理所管理的资源)
void showMainMenu();
private:
void addBook();
void removeBook();
void updateBook();
void searchBooks();
void displayAllBooks();
void showStatistics();
void saveData();
void loadData();
};
#endif
C++
// BookService.cpp
#include "BookService.h"
#include "InputUtil.h"
// make_unique<T>:C++14引入,安全创建unique_ptr的推荐方式,避免裸new
BookService::BookService(const string& filename)
: bookDAO(make_unique<BookDAO>(filename)) {
}
void BookService::showMainMenu() {
while (true) {
cout << "\n========== 图书管理系统 ===========" << endl;
cout << "1. 添加图书" << endl;
cout << "2. 删除图书" << endl;
cout << "3. 更新图书" << endl;
cout << "4. 查询图书" << endl;
cout << "5. 显示所有图书" << endl;
cout << "6. 统计信息" << endl;
cout << "7. 保存数据" << endl;
cout << "8. 加载数据" << endl;
cout << "0. 退出" << endl;
cout << "请选择操作: ";
int choice = InputUtil::getIntInput(0, 8); // 静态方法调用,无需创建对象
switch (choice) { // switch-case实现菜单分发
case 1:
addBook();
break;
case 2:
removeBook();
break;
case 3:
updateBook();
break;
case 4:
searchBooks();
break;
case 5:
displayAllBooks();
break;
case 6:
showStatistics();
break;
case 7:
saveData();
break;
case 8:
loadData();
break;
case 0:
cout << "感谢使用图书管理系统!" << endl;
return;
default:
cout << "无效的选择!" << endl;
}
}
}
void BookService::addBook() {
cout << "\n--- 添加图书 ---" << endl;
string isbn = InputUtil::getStringInput("ISBN");
string title = InputUtil::getStringInput("书名");
string author = InputUtil::getStringInput("作者");
string publisher = InputUtil::getStringInput("出版社");
int year = InputUtil::getIntInput("出版年份", 1900, 2030);
double price = InputUtil::getDoubleInput("价格", 0, 10000);
int quantity = InputUtil::getIntInput("库存", 0, 10000);
Book book(isbn, title, author, publisher, year, price, quantity);
bookDAO->addBook(book);
}
void BookService::removeBook() {
cout << "\n--- 删除图书 ---" << endl;
string isbn = InputUtil::getStringInput("ISBN");
bookDAO->removeBook(isbn);
}
void BookService::updateBook() {
cout << "\n--- 更新图书 ---" << endl;
string isbn = InputUtil::getStringInput("ISBN");
Book* book = bookDAO->findBookByIsbn(isbn); // 返回指针,未找到为nullptr
if (book == nullptr) { // 空指针检查,防止访问无效内存
cout << "图书不存在!" << endl;
return;
}
cout << "当前信息:" << endl;
book->display();
string title = InputUtil::getStringInput("新书名(直接回车保持原值)", book->getTitle());
if (title.empty()) {
title = book->getTitle();
}
string author = InputUtil::getStringInput("新作者(直接回车保持原值)", book->getAuthor());
if (author.empty()) {
author = book->getAuthor();
}
int quantity = InputUtil::getIntInput("新库存(直接回车保持原值)", book->getQuantity(), 0, 10000);
Book updatedBook(isbn, title, author, book->getPublisher(),
book->getYear(), book->getPrice(), quantity);
bookDAO->updateBook(updatedBook);
}
void BookService::searchBooks() {
cout << "\n--- 查询图书 ---" << endl;
cout << "1. 按ISBN查询" << endl;
cout << "2. 按书名查询" << endl;
cout << "3. 按作者查询" << endl;
cout << "请选择查询方式: ";
int choice = InputUtil::getIntInput(1, 3);
switch (choice) {
case 1: {
string isbn = InputUtil::getStringInput("ISBN");
Book* book = bookDAO->findBookByIsbn(isbn);
if (book != nullptr) {
cout << "\n找到图书:" << endl;
book->display();
} else {
cout << "未找到图书!" << endl;
}
break;
}
case 2: {
string keyword = InputUtil::getStringInput("关键词");
vector<Book> books = bookDAO->searchBooks(keyword);
if (books.empty()) {
cout << "未找到匹配的图书" << endl;
} else {
cout << "\n找到" << books.size() << "本图书:" << endl;
for (const auto& book : books) {
book.display();
cout << endl;
}
}
break;
}
case 3: {
string author = InputUtil::getStringInput("作者");
vector<Book> books = bookDAO->getBooksByAuthor(author);
if (books.empty()) {
cout << "未找到该作者的图书" << endl;
} else {
cout << "\n" << author << "的" << books.size() << "本图书:" << endl;
for (const auto& book : books) {
book.display();
cout << endl;
}
}
break;
}
}
}
void BookService::displayAllBooks() {
bookDAO->displayAllBooks();
}
void BookService::showStatistics() {
cout << "\n--- 统计信息 ---" << endl;
cout << "图书总数: " << bookDAO->getBookCount() << endl;
vector<Book> books = bookDAO->getAllBooks();
if (books.empty()) {
return;
}
// 统计累加器:遍历所有图书计算汇总数据
double totalValue = 0;
double totalPrice = 0;
int totalQuantity = 0;
// const auto&:只读引用遍历,避免拷贝开销
for (const auto& book : books) {
totalValue += book.getPrice() * book.getQuantity();
totalPrice += book.getPrice();
totalQuantity += book.getQuantity();
}
cout << "图书总价值: " << totalValue << endl;
cout << "图书总库存: " << totalQuantity << endl;
// books.size()返回size_t类型,与double运算时自动类型转换
cout << "平均价格: " << (totalPrice / books.size()) << endl;
}
void BookService::saveData() {
bookDAO->saveToFile();
}
void BookService::loadData() {
bookDAO->loadFromFile();
}
4. 主程序¶
C++
// main.cpp
#include <iostream>
#include "BookService.h"
using namespace std;
// 程序入口:main函数是C++程序的起始执行点
int main() {
cout << "=========================================" << endl;
cout << " 图书管理系统" << endl;
cout << "=========================================" << endl;
// 创建服务对象,指定数据文件路径(栈上分配,函数结束自动销毁)
BookService service("data/books.dat");
service.showMainMenu(); // 启动主菜单交互循环
return 0; // 返回0表示程序正常退出
}
📝 项目总结¶
完成的功能¶
- ✅ 图书信息管理(增删改查)
- ✅ 数据持久化(文件存储)
- ✅ 多种查询方式
- ✅ 统计功能
- ✅ 用户友好的界面
- ✅ 输入验证
- ✅ 异常处理
学到的知识¶
通过这个项目,你实践了: - C++面向对象编程 - 数据结构的实际应用 - 文件I/O操作 - 异常处理机制 - 模块化程序设计
扩展建议¶
你可以继续扩展这个图书管理系统: - 添加用户管理功能 - 实现借阅和归还功能 - 添加图书分类功能 - 实现数据备份和恢复 - 添加图形用户界面(GUI) - 实现数据库存储 - 添加报表生成功能
📚 推荐资源¶
🎉 恭喜完成!¶
你已经成功构建了一个完整的C++图书管理系统!这个项目展示了C++编程的核心概念和最佳实践。继续学习和实践,你将能够构建更复杂、更强大的C++应用。
🔗 下一章¶
Effective C++基础 - 学习C++的最佳实践与资源管理。