跳转至

实战项目 - 图书管理系统

📖 项目简介

本章将通过构建一个完整的图书管理系统,综合运用前面学到的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表示程序正常退出
}

📝 项目总结

完成的功能

  1. ✅ 图书信息管理(增删改查)
  2. ✅ 数据持久化(文件存储)
  3. ✅ 多种查询方式
  4. ✅ 统计功能
  5. ✅ 用户友好的界面
  6. ✅ 输入验证
  7. ✅ 异常处理

学到的知识

通过这个项目,你实践了: - C++面向对象编程 - 数据结构的实际应用 - 文件I/O操作 - 异常处理机制 - 模块化程序设计

扩展建议

你可以继续扩展这个图书管理系统: - 添加用户管理功能 - 实现借阅和归还功能 - 添加图书分类功能 - 实现数据备份和恢复 - 添加图形用户界面(GUI) - 实现数据库存储 - 添加报表生成功能

📚 推荐资源

🎉 恭喜完成!

你已经成功构建了一个完整的C++图书管理系统!这个项目展示了C++编程的核心概念和最佳实践。继续学习和实践,你将能够构建更复杂、更强大的C++应用。

🔗 下一章

Effective C++基础 - 学习C++的最佳实践与资源管理。