0%

BookManager

json文件,RAII,多态

源代码目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
BookManager/
├── CMakeLists.txt # 构建配置(C++17、UTF-8、可执行文件、头文件路径)
├── include/ # 头文件
│ ├── Book.h # 图书基类 + FictionBook / TechnicalBook / ReferenceBook
│ ├── FileManager.h # FileManager / FileReader / FileWriter
│ └── Library.h # 图书馆类(书目管理、保存/加载)
├── src/ # 源文件
│ ├── main.cpp # 程序入口、菜单、用户输入
│ ├── Book.cpp # Book 及各派生类实现(display、toJson、fromJson)
│ ├── FileManager.cpp # 文件打开/关闭、读一行、flush
│ └── Library.cpp # 添加/删除/查询/借还、saveToFile、loadFromFile、createBookFromJson
└── third_party/
└── json/
└── single_include/
└── nlohmann/ # nlohmann/json 头文件(本地,不联网)
├── json.hpp
└── json_fwd.hpp

Book类

.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected:
int id_;
std::string title_;
std::string author_;
int year_;
bool borrowed_;
static int nextId_

public:
Book(const std::string& title, const std::string& author, int year);
virtual ~Book() = default;

virtual std::string getType() const = 0; // 纯虚函数
virtual void display() const; // 可被派生类重写
virtual nlohmann::json toJson() const = 0; // JSON 序列化
virtual void fromJson(const nlohmann::json& j); // JSON 反序列化(基类读公共字段,派生类 override 读自身)

其余是通用的get方法

1
2
3
4
5
6
7
8
9
10
11
12
13
class FictionBook : public Book {
public:
FictionBook(const std::string& title, const std::string& author, int year,
const std::string& genre = "未分类");
std::string getType() const override { return "文学"; }
void display() const override;
nlohmann::json toJson() const override;
void fromJson(const nlohmann::json& j) override;
const std::string& getGenre() const { return genre_; }

private:
std::string genre_;
};

​ override关键字标明这个成员函数是重写基类里的虚函数,不是新函数。如果基类里没有同名、同参数、可被重写的虚函数,编译器会报错。

1
2
3
4
5
6
7
8
class TechnicalBook : public Book{
private:
std::string field_;
};
class ReferenceBook : public Book{
private:
bool encyclopedia_;
};

.cpp

1
int Book::nextId_ = 1;
1
2
3
4
5
6
7
8
void Book::fromJson(const nlohmann::json& j) {
if (j.contains("id")) id_ = j["id"].get<int>();
if (j.contains("title")) title_ = j["title"].get<std::string>();
if (j.contains("author")) author_ = j["author"].get<std::string>();
if (j.contains("year")) year_ = j["year"].get<int>();
if (j.contains("borrowed")) borrowed_ = j["borrowed"].get<bool>();
if (id_ >= nextId_) nextId_ = id_ + 1;
}

​ 根据一个 nlohmann::json 对象,把里面的 id、title、author、year、borrowed 填到当前 Book 的成员里;如果 JSON 里没有某个键就不改对应成员。

1
2
3
4
5
6
7
8
9
10
11
nlohmann::json FictionBook::toJson() const {
nlohmann::json j = nlohmann::json::object();
j["type"] = "FICTION";
j["id"] = id_;
j["title"] = title_;
j["author"] = author_;
j["year"] = year_;
j["borrowed"] = borrowed_;
j["genre"] = genre_;
return j;
}
1
2
3
4
void FictionBook::fromJson(const nlohmann::json& j) {
Book::fromJson(j);
if (j.contains("genre")) genre_ = j["genre"].get<std::string>();
}

​ 其余子类同理

FileManager

.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Book;  // 前向声明
class FileManager {
public:
explicit FileManager(const std::string& path);
~FileManager();

FileManager(const FileManager&) = delete;
FileManager& operator=(const FileManager&) = delete;

bool isOpen() const { return file_.is_open(); }
bool good() const { return file_.good(); }
std::fstream& stream() { return file_; }
const std::string& path() const { return path_; }

private:
std::string path_;
std::fstream file_;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class FileReader {
public:
explicit FileReader(const std::string& path);
~FileReader();

FileReader(const FileReader&) = delete;
FileReader& operator=(const FileReader&) = delete;

bool isOpen() const { return file_.is_open(); }
std::ifstream& stream() { return file_; }
std::string readLine();
bool eof() const { return file_.eof(); }

private:
std::string path_;
std::ifstream file_;
};
class FileWriter {
public:
explicit FileWriter(const std::string& path);
~FileWriter();

FileWriter(const FileWriter&) = delete;
FileWriter& operator=(const FileWriter&) = delete;

bool isOpen() const { return file_.is_open(); }
std::ofstream& stream() { return file_; }

private:
std::string path_;
std::ofstream file_;
};

​ 删除了拷贝构造与复制=

.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
FileManager::FileManager(const std::string& path) : path_(path) {
file_.open(path, std::ios::in | std::ios::out | std::ios::app);
if (!file_.is_open()) {
file_.clear();
file_.open(path, std::ios::out);
}
}

FileManager::~FileManager() {
if (file_.is_open()) {
file_.close();
}
}

​ RAII:构造时拿资源,析构里还资源,文件的生命周期与FileManager对象绑定。读写器同理。

1
2
3
4
5
6
7
8
9
10
11
12
13
std::string FileReader::readLine() {
std::string line;
if (file_.is_open() && std::getline(file_, line)) {
return line;
}
return "";
}
FileWriter::~FileWriter() {
if (file_.is_open()) {
file_.flush();//将缓冲区写进文件
file_.close();
}
}

class Library

.h

1
2
3
4
5
6
7
8
9
10
11
#include <nlohmann/json.hpp>
class Library{
private:
std::vector<std::unique_ptr<Book>> books_; // RAII: unique_ptr 管理动态内存
std::string dataPath_;

std::unique_ptr<Book> createBookFromJson(const nlohmann::json& j);
public:
bool loadFromFile(const std::string& path);
bool saveToFile(const std::string& path) const;
}

.cpp

1
2
3
4
5
6
void Library::addBook(std::unique_ptr<Book> book) {
if (book) {
books_.push_back(std::move(book));
std::cout << "添加成功\n";
}
}

​ 这里必须使用move,unique_ptr禁用了拷贝,直接push_back会编译报错,需要用move转移所有权。

1
2
3
4
5
6
7
8
9
10
void Library::removeBook(int id) {
auto it = std::find_if(books_.begin(), books_.end(),
[id](const std::unique_ptr<Book>& b) { return b->getId() == id; });
if (it != books_.end()) {
books_.erase(it);
std::cout << "删除成功\n";
} else {
std::cout << "未找到该图书\n";
}
}

​ lambda表达式。[id]是捕获的变量,从外层作用域拿到的,(const std::unique_ptr& b)是参数,调用时find_if传进来的。