7、C++项目实战:图书管理系统(面向对象基础)

说实话,很多初学者学完C++语法后,最迷茫的就是——这些东西到底怎么串起来用?

我个人习惯是,每学完一个阶段,就做一个完整的小项目。图书管理系统,就是面向对象入门最经典的实战项目。它不复杂,但足够让你把类、对象、封装、继承、多态这些概念真正落地。

7.1 项目需求分析

先别急着写代码。我见过太多人上来就敲键盘,结果写到一半发现结构乱了。

这个系统要做什么?说白了就三件事:

  • 管理图书信息:书名、作者、ISBN、是否借出
  • 管理读者信息:姓名、编号、已借图书列表
  • 处理借还操作:借书、还书、查询

嗯,就这么简单。但我们要用面向对象的方式去设计它。

核心思路:把现实世界中的"书"和"读者"抽象成类,把操作封装在类的方法里。

7.2 类的设计

我一般先画一个简单的类图,理清楚关系再动手。这里我直接用SVG画给你看:

Book 类 - title: string - author: string - isbn: string - isBorrowed: bool + borrow() / returnBook() Reader 类 - name: string - id: string - borrowedBooks: vector<Book*> + borrowBook(Book*) + returnBook(Book*) Library 类 - books: vector<Book> - readers: vector<Reader> + addBook() + addReader() + searchBook() 关联 管理

你看,三个类之间的关系很清晰:Library 管理 BookReaderReader 关联 Book(借阅关系)。

7.3 代码实现

好,我们开始写代码。先从 Book 类开始:

// Book.h
#pragma once
#include <string>
using namespace std;

class Book {
private:
    string title;
    string author;
    string isbn;
    bool isBorrowed;

public:
    Book(string t, string a, string i)
        : title(t), author(a), isbn(i), isBorrowed(false) {}

    string getTitle() const { return title; }
    string getIsbn() const { return isbn; }
    bool getStatus() const { return isBorrowed; }

    void borrow() {
        if (isBorrowed) {
            cout << "这本书已被借出!" << endl;
            return;
        }
        isBorrowed = true;
        cout << "借书成功:" << title << endl;
    }

    void returnBook() {
        if (!isBorrowed) {
            cout << "这本书没有被借出!" << endl;
            return;
        }
        isBorrowed = false;
        cout << "还书成功:" << title << endl;
    }
};

小技巧:构造函数用初始化列表,比在函数体内赋值效率更高。我早期写代码时没注意这个,后来被同事review指出来才改的。

接下来是 Reader 类:

// Reader.h
#pragma once
#include <vector>
#include <algorithm>
#include "Book.h"

class Reader {
private:
    string name;
    string id;
    vector<Book*> borrowedBooks;  // 存指针,避免拷贝

public:
    Reader(string n, string i) : name(n), id(i) {}

    void borrowBook(Book* book) {
        if (borrowedBooks.size() >= 5) {
            cout << "每人最多借5本书!" << endl;
            return;
        }
        book->borrow();
        borrowedBooks.push_back(book);
    }

    void returnBook(Book* book) {
        auto it = find(borrowedBooks.begin(), borrowedBooks.end(), book);
        if (it != borrowedBooks.end()) {
            book->returnBook();
            borrowedBooks.erase(it);
        } else {
            cout << "你没有借这本书!" << endl;
        }
    }

    void showBorrowed() const {
        cout << name << " 已借图书:" << endl;
        for (auto book : borrowedBooks) {
            cout << "  - " << book->getTitle() << endl;
        }
    }
};

嗯,这里有个细节:borrowedBooks 存的是指针。为什么?因为 Book 对象由 Library 统一管理,读者只是"引用"它,不负责销毁。如果存对象副本,改状态就不同步了。

注意:用指针就要小心悬空指针。我这里假设 Library 的生命周期比 Reader 长,实际项目中建议用 shared_ptr

最后是 Library 类,负责统筹管理:

// Library.h
#pragma once
#include <vector>
#include <algorithm>
#include "Book.h"
#include "Reader.h"

class Library {
private:
    vector<Book> books;
    vector<Reader> readers;

public:
    void addBook(const string& title, const string& author, const string& isbn) {
        books.emplace_back(title, author, isbn);
    }

    void addReader(const string& name, const string& id) {
        readers.emplace_back(name, id);
    }

    Book* findBook(const string& isbn) {
        for (auto& book : books) {
            if (book.getIsbn() == isbn) {
                return &book;
            }
        }
        return nullptr;
    }

    Reader* findReader(const string& id) {
        for (auto& reader : readers) {
            if (reader.getId() == id) {
                return &reader;
            }
        }
        return nullptr;
    }

    void borrowBook(const string& readerId, const string& isbn) {
        Reader* reader = findReader(readerId);
        Book* book = findBook(isbn);
        if (!reader || !book) {
            cout << "读者或图书不存在!" << endl;
            return;
        }
        reader->borrowBook(book);
    }

    void returnBook(const string& readerId, const string& isbn) {
        Reader* reader = findReader(readerId);
        Book* book = findBook(isbn);
        if (!reader || !book) {
            cout << "读者或图书不存在!" << endl;
            return;
        }
        reader->returnBook(book);
    }
};

7.4 主函数测试

写完了,跑一下看看效果:

int main() {
    Library lib;

    // 添加图书
    lib.addBook("C++ Primer", "Lippman", "978-7-111-1");
    lib.addBook("Effective C++", "Meyers", "978-7-111-2");

    // 添加读者
    lib.addReader("张三", "R001");
    lib.addReader("李四", "R002");

    // 借书
    lib.borrowBook("R001", "978-7-111-1");
    lib.borrowBook("R001", "978-7-111-2");

    // 还书
    lib.returnBook("R001", "978-7-111-1");

    return 0;
}

输出结果:

借书成功:C++ Primer
借书成功:Effective C++
还书成功:C++ Primer

7.5 避坑指南

我曾经在项目里犯过一个低级错误——vector 存对象时,忘记考虑对象拷贝带来的副作用。比如 Library::addBook 里用 emplace_back,如果 Book 类里有指针成员,浅拷贝就会出问题。好在这个项目里 Book 只有 string 和 bool,没问题。

另外,你想想看,如果读者借书时直接修改 BookisBorrowed,而 Reader 里又存了一份副本,那状态就乱套了。所以我才用指针统一管理。

7.6 扩展思考

这个系统还能怎么改进?

  • 加上文件读写,让数据持久化
  • map 代替 vector 查找,效率更高
  • 引入继承,比如 StudentReaderTeacherReader 有不同的借书上限

嗯,这些就留给你自己去尝试了。面向对象的核心就是——把数据和操作绑在一起,让代码更贴近现实逻辑。

总结一句话:图书管理系统虽小,但封装、关联、生命周期管理这些概念全用上了。做完这个,你对面向对象的理解会上一个台阶。


公众号:蓝海资料掘金营,微信deep3321