图书馆管理系统--C++程序设计

图书馆管理系统–C++程序设计 项目报告

1.项目摘要

  • 项目背景:现代图书馆管理需要高效的系统

  • 项目目标:开发一个基于C++的图书馆管理系统,实现图书和读者的信息化管理。

  • 项目成果

    • 完成支持5种图书类型和2种读者等级的系统
    • 完成对不同结束时长的管理和自动化罚款的计算
    • 支持文件读写
    • 实现面对对象编程

2.项目引言

​ 在现代社会,人们对于知识的渴望日渐增加,图书馆也成为了人们常去的地方。图书馆的人流量日渐剧增,随之而来的是图书馆的管理难度也逐步增加,于是选择开发这一图书馆管理系统来处理图书借阅、归还和罚款计算等任务,为减轻压力。

3.设计说明

系统架构处理

这是整个代码的系统架构图

系统架构图

本系统主要采取三层的结构设计,分别为表示层、逻辑层、数据层:

  • 表示层:通过命令行输入来执行用户所想要的效果,并对用户的输入做检查验证
  • 逻辑层:这里是整个系统运行的逻辑代码,所有的功能在这里实现,Library类为核心调度器
  • 数据层:这里是数据存放处,也有实现文件读写的功能,包含Book和Reader类及其派生类等

类图

类图

(这是根据先写好的代码再设计的类图)

  • 图书类

  • 类名 关系 关键成员 说明
    Book 抽象基类 totalBooks静态变量 定义所有图书共有的属性和接口
    Novel 继承Book getMaxBorrowDays()返回60 小说类(
    Textbook 继承Book getMaxBorrowDays()返回60 教科书类
    ScientificBook 继承Book getMaxBorrowDays()返回60 科学书籍类
    FairyTale 继承Book getMaxBorrowDays()返回60 童话书类
    HistoryBook 继承Book getMaxBorrowDays()返回60 历史书类
  • 读者类

  • 类名 关系 关键成员 说明
    Reader 抽象基类 borrowedBooks容器 管理借阅记录
    RegularReader 继承Reader getMaxBorrowDays()返回30 普通读者(借期30天)
    VIPReader 继承Reader getMaxBorrowDays()返回60 VIP读者(借期60天)
  • Library类

关键算法

借阅算法实现(Library类中实现)

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
33
34
35
36
37
38
39
40
41
void borrowBook(const string& readerID, const string& bookISBN) {
try {
// 查找读者
auto readerIt = find_if(readers.begin(), readers.end(),
[&](const unique_ptr<Reader>& r) { return r->getID() == readerID; });

// 查找图书
auto bookIt = find_if(books.begin(), books.end(),
[&](const unique_ptr<Book>& b) { return b->getISBN() == bookISBN; });

// 检查读者和图书是否存在
if (readerIt == readers.end() || bookIt == books.end()) {
throw runtime_error("读者或图书不存在");
}

// 检查图书是否可借
if (!(*bookIt)->checkAvailability()) {
throw runtime_error("图书已被借出");
}

// 设置应归还日期(当前时间 + 最大借阅天数)
time_t now = time(nullptr);
(*bookIt)->setDueDate(now + (*readerIt)->getMaxBorrowDays() * 24 * 3600);
(*bookIt)->setAvailability(false);
(*readerIt)->borrowBook(bookISBN);

cout << "借书成功!" << endl;
cout << "图书: " << (*bookIt)->getTitle() << endl;
cout << "读者: " << (*readerIt)->getName() << endl;

// 安全地输出时间
char buffer[26];
time_t dueDate = (*bookIt)->getDueDate();
ctime_s(buffer, sizeof(buffer), &dueDate);
cout << "应归还日期: " << buffer;

}
catch (const exception& e) {
cerr << "借书错误: " << e.what() << endl;
}
}

该函数可以判断图书和读者的输入数据是否正确,以及图书的状态,以此来确定是否可借,并且可以设置归还日期。

还书算法实现(Library类中实现)

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void returnBook(const string& readerID, const string& bookISBN) {
try {
// 查找读者
auto readerIt = find_if(readers.begin(), readers.end(),
[&](const unique_ptr<Reader>& r) { return r->getID() == readerID; });

// 查找图书
auto bookIt = find_if(books.begin(), books.end(),
[&](const unique_ptr<Book>& b) { return b->getISBN() == bookISBN; });

// 检查读者和图书是否存在
if (readerIt == readers.end() || bookIt == books.end()) {
throw runtime_error("读者或图书不存在");
}

// 检查图书是否被该读者借出
bool isBorrowed = false;
for (const auto& isbn : (*readerIt)->getBorrowedBooks()) {
if (isbn == bookISBN) {
isBorrowed = true;
break;
}
}

if (!isBorrowed) {
throw runtime_error("该读者未借阅此图书");
}

// 计算罚款
double fine = calculateFine(bookISBN);
if (fine > 0) {
cout << "图书逾期,罚款金额: " << fine << "元" << endl;
}

// 更新图书状态
(*bookIt)->setAvailability(true);
(*readerIt)->returnBook(bookISBN);

cout << "还书成功!" << endl;
cout << "图书: " << (*bookIt)->getTitle() << endl;
cout << "读者: " << (*readerIt)->getName() << endl;

}
catch (const exception& e) {
cerr << "还书错误: " << e.what() << endl;
}
}

与借阅算法实现相当,同时增加罚款算法

罚款算法实现(Library类中实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
double calculateFine(const string& bookISBN) const {
// 查找图书
auto bookIt = find_if(books.begin(), books.end(),
[&](const unique_ptr<Book>& b) { return b->getISBN() == bookISBN; });

if (bookIt == books.end()) {
throw runtime_error("图书不存在");
}

time_t now = time(nullptr);
time_t dueDate = (*bookIt)->getDueDate();

// 如果未逾期,返回0
if (now <= dueDate) return 0.0;

// 计算逾期天数
double daysOverdue = difftime(now, dueDate) / (24 * 3600);
return daysOverdue * dailyFine;
}

此函数在上面的还书算法中有运用到

文件读写算法实现(Library类中实现)

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
void saveToFile(const string& filename) const {
ofstream outFile(filename);
if (!outFile) {
throw runtime_error("无法打开文件进行写入");
}

// 保存图书信息
outFile << "BOOKS:" << endl;
for (const auto& book : books) {
outFile << book->getType() << ","
<< book->getISBN() << ","
<< book->getTitle() << ","
<< book->getAuthor() << ","
<< book->checkAvailability() << ","
<< book->getDueDate() << endl;
}

// 保存读者信息
outFile << "READERS:" << endl;
for (const auto& reader : readers) {
outFile << reader->getID() << ","
<< reader->getName() << ","
<< reader->getLevel() << endl;

// 保存读者借阅的图书
for (const auto& isbn : reader->getBorrowedBooks()) {
outFile << isbn << ",";
}
outFile << endl;
}

outFile.close();
cout << "数据已保存到文件: " << filename << endl;
}

// 从文件加载数据
void loadFromFile(const string& filename) {
ifstream inFile(filename);
if (!inFile) {
throw runtime_error("无法打开文件进行读取");
}

string line;
bool readingBooks = false;
bool readingReaders = false;

try {
while (getline(inFile, line)) {
if (line.empty()) continue;

if (line == "BOOKS:") {
readingBooks = true;
readingReaders = false;
continue;
}

if (line == "READERS:") {
readingBooks = false;
readingReaders = true;
continue;
}

if (readingBooks) {
// 解析图书信息
size_t pos = 0;
vector<string> tokens;
while ((pos = line.find(',')) != string::npos) {
tokens.push_back(line.substr(0, pos));
line.erase(0, pos + 1);
}
tokens.push_back(line);

if (tokens.size() != 6) {
throw runtime_error("图书数据格式错误");
}

// 根据类型创建图书对象
Book* book = nullptr;
if (tokens[0] == "Novel") {
book = new Novel(tokens[1], tokens[2], tokens[3]);
}
else if (tokens[0] == "Textbook") {
book = new Textbook(tokens[1], tokens[2], tokens[3]);
}
else if (tokens[0] == "ScientificBook") {
book = new ScientificBook(tokens[1], tokens[2], tokens[3]);
}
else if (tokens[0] == "FairyTale") {
book = new FairyTale(tokens[1], tokens[2], tokens[3]);
}
else if (tokens[0] == "HistoryBook") {
book = new HistoryBook(tokens[1], tokens[2], tokens[3]);
}
else {
throw runtime_error("未知的图书类型");
}

// 设置图书状态
book->setAvailability(tokens[4] == "1");
book->setDueDate(std::stol(tokens[5]));

books.emplace_back(book);
}
else if (readingReaders) {
// 解析读者信息
size_t pos = 0;
vector<string> tokens;
while ((pos = line.find(',')) != string::npos) {
tokens.push_back(line.substr(0, pos));
line.erase(0, pos + 1);
}
tokens.push_back(line);

if (tokens.size() < 3) {
throw runtime_error("读者数据格式错误");
}

// 创建读者对象
Reader* reader = nullptr;
if (tokens[2] == "1") {
reader = new RegularReader(tokens[0], tokens[1]);
}
else if (tokens[2] == "2") {
reader = new VIPReader(tokens[0], tokens[1]);
}
else {
throw runtime_error("未知的读者类型");
}

// 读取借阅的图书
getline(inFile, line);
if (!line.empty()) {
pos = 0;
while ((pos = line.find(',')) != string::npos) {
string isbn = line.substr(0, pos);
reader->borrowBook(isbn);
line.erase(0, pos + 1);
}
}

readers.emplace_back(reader);
}
}
}
catch (const exception& e) {
inFile.close();
throw runtime_error(string("加载数据错误: ") + e.what());
}

inFile.close();
cout << "已从文件加载数据: " << filename << endl;
}

4.实现过程

我的主函数是这样:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
int main()
{
try {
// 尝试从文件加载数据
library.loadFromFile("library_data.txt");
}
catch (const exception& e) {
cout << "初始化数据: " << e.what() << endl;

// 添加一些图书
library.addBook(new Novel("001", "三体", "刘慈欣"));
library.addBook(new Textbook("002", "C++ Primer", "Stanley Lippman"));
library.addBook(new ScientificBook("003", "时间简史", "霍金"));
library.addBook(new FairyTale("004", "安徒生童话", "安徒生"));
library.addBook(new HistoryBook("005", "明朝那些事儿", "当年明月"));

// 添加一些读者
library.addReader(new RegularReader("R001", "张三"));
library.addReader(new VIPReader("V001", "李四"));
}
bool running = true;
while (running)
{
cout << "\n==== 图书馆系统 ====" << endl;
string answer;
cout << "Are you in the Library readers?(y/n/exit)" << endl;
cin >> answer;

if (answer == "exit") {
running = false;
continue;
}
if (answer == "y")
{
string id;
cout << "Please enter your readerID";
cin >> id;
if (library.isReaderExist(id))
{
runReaderSession(library);
}
else
{
string name;
bool vip;
cout << "Your id is exist." << endl;
cout << "Please register" << endl;
registerNewReader(library);
runReaderSession(library);
}
}
else
{
string name;
bool vip;
cout << "Please register" << endl;
registerNewReader(library);
runReaderSession(library);
}

}

library.saveToFile("library_data.txt");
cout << "谢谢使用,数据已保存!" << endl;


return 0;
}

交互逻辑算法实现

在这里我做了丰富的设计,可以与用户做丰富的互动,在这里面同样有一些函数,声明如下:

1
2
3
4
5
6
7
8
9
10
//验证读者是否存在函数
bool isReaderExist(const std::string& readerID) const {
auto it = std::find_if(readers.begin(), readers.end(),
[&readerID](const std::unique_ptr<Reader>& reader) {
return reader->getID() == readerID;
});

// 如果找到则返回true,否则返回false
return it != readers.end();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//命令输入和循环函数
void runReaderSession(Library& lib) {
bool sessionActive = true;
while (sessionActive) {
cout << "\nPlease enter your command(help查看帮助): ";
string command;
cin >> command;

if (command == "exit") {
sessionActive = false;
}
else {
comment(lib, command);
}
}
}
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//命令函数
void comment(Library& library,const string& command)
{
string id;
string bookID;
string comment;
string type;
string isbn;
string title;
string author;
if (command == "borrow")
{
cout << "enter your ID: ";
cin >> id;
cout << "\nenter book ISBN: ";
cin >> bookID;
library.borrowBook(id, bookID);
}
else if (command == "return")
{
cout << "enter your ID: ";
cin >> id;
cout << "\nenter book ISBN: ";
cin >> bookID;
library.returnBook(id, bookID);
}
else if (command == "displayBooks")
{
library.displayAllBooks();
}
else if (command == "add")
{
cout << "Please enter type:";
cin >> type;
cout << "Please enter ISBN:";
cin >> isbn;
cout << "Please enter title:";
cin.ignore(); // 清除之前的换行符
getline(cin, title); // 允许输入空格
cout << "Please enter author:";
getline(cin, author);
library.addBook(type, isbn, title, author);
}
else if (command == "help")
{
cout << "Your command:\n"
<< "borrow - 借书\n"
<< "return - 还书\n"
<< "displayBooks - 显示所有图书\n"
<< "add - 添加图书\n"
<< "exit - 退出当前会话\n"
<< "help - 显示帮助\n";
}
else {
cout << "Your command error" << endl;
cout << "Your command:\n"
<< "borrow - 借书\n"
<< "return - 还书\n"
<< "displayBooks - 显示所有图书\n"
<< "add - 添加图书\n"
<< "exit - 退出当前会话\n"
<< "help - 显示帮助\n";
}
}
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
//注册函数
void registerNewReader(Library& lib) {
string name;
cout << "your name:";
cin.ignore(); // 清除缓冲区
getline(cin, name); // 允许包含空格的名字

cout << "Are you vip(true/false): ";
bool isVIP = getVIPStatus();

lib.registerReader(name, isVIP);
}
void registerReader(const std::string& name, bool isVIP) { //library类中
// 生成读者ID(格式:R+自动递增数字)
std::string readerID;

// 根据读者类型创建相应对象
if (isVIP) {
readerID = "V" + std::to_string(nextReaderIDv++);
readers.emplace_back(std::make_unique<VIPReader>(readerID, name));
}
else {
readerID = "R" + std::to_string(nextReaderIDr++);
readers.emplace_back(std::make_unique<RegularReader>(readerID, name));
}

std::cout << "成功注册" << (isVIP ? "VIP" : "普通") << "读者: "
<< name << " (ID: " << readerID << ")" << std::endl;
}

后改进方案

在完成整个代码后,我想了想,发现用文件读写存储麻烦而且很大可能会出问题,所以我想用个更稳妥方案,也更方便——数据库存储。

数据库来自于我自己的服务器

在原来的代码中加入新头文件

1
2
3
4
5
6
7
#include <mysql_driver.h>
#include <mysql_connection.h>
#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/resultset.h>
#include <cppconn/statement.h>
#include <cppconn/prepared_statement.h>

在Windows中在 c++ 中用数据库需要从MySQL官网下载Connector/C++并配置项目。

有点麻烦,不予演示了。

但经过尝试后我在本地Windows的环境无法连接到我的数据库,所以我换Linux系统环境来运行,最后成功。

数据库实现代码

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
class DatabaseManager {
private:
Driver* driver;
Connection* con;

public:
DatabaseManager() {
try {
driver = get_driver_instance();
// 明确指定主机地址
string connectionStr = "tcp://59.110.163.36:3306/";
cout << "尝试连接到: " << connectionStr << endl; // 添加调试输出

con = driver->connect(connectionStr, DB_USER, DB_PASS);
con->setSchema(DB_NAME);
initializeDatabase();
cout << "数据库连接成功!" << endl;
}
catch (SQLException& e) {
cerr << "数据库连接错误 (代码:" << e.getErrorCode() << "):" << endl;
cerr << "SQL状态: " << e.getSQLState() << endl;
cerr << "错误信息: " << e.what() << endl;
throw;
}
}

~DatabaseManager() {
delete con;
}

bool isConnected() const {
return con != nullptr && !con->isClosed();
}

void initializeDatabase() {
Statement* stmt = con->createStatement();

// 创建图书表
stmt->execute("CREATE TABLE IF NOT EXISTS Books ("
"ISBN VARCHAR(20) PRIMARY KEY, "
"Title VARCHAR(100) NOT NULL, "
"Author VARCHAR(50) NOT NULL, "
"Type VARCHAR(20) NOT NULL, "
"IsAvailable BOOLEAN NOT NULL, "
"DueDate BIGINT)");

// 创建读者表
stmt->execute("CREATE TABLE IF NOT EXISTS Readers ("
"ID VARCHAR(10) PRIMARY KEY, "
"Name VARCHAR(50) NOT NULL, "
"Level INT NOT NULL)");

// 创建借阅记录表
stmt->execute("CREATE TABLE IF NOT EXISTS BorrowRecords ("
"RecordID INT AUTO_INCREMENT PRIMARY KEY, "
"ReaderID VARCHAR(10) NOT NULL, "
"BookISBN VARCHAR(20) NOT NULL, "
"BorrowDate BIGINT NOT NULL, "
"DueDate BIGINT NOT NULL, "
"ReturnDate BIGINT, "
"FOREIGN KEY (ReaderID) REFERENCES Readers(ID), "
"FOREIGN KEY (BookISBN) REFERENCES Books(ISBN))");

delete stmt;
}

vector<unique_ptr<Book>> getAllBooks(int page = 1, int pageSize = 100) {
vector<unique_ptr<Book>> books;
try {
int offset = (page - 1) * pageSize;
unique_ptr<Statement> stmt(con->createStatement());
unique_ptr<ResultSet> res(stmt->executeQuery(
"SELECT * FROM Books LIMIT " + to_string(pageSize) + " OFFSET " + to_string(offset)));

while (res->next()) {
string type = res->getString("Type");
string isbn = res->getString("ISBN");
string title = res->getString("Title");
string author = res->getString("Author");
bool available = res->getBoolean("IsAvailable");
time_t dueDate = res->getInt64("DueDate");

unique_ptr<Book> book;
if (type == "Novel") {
book = make_unique<Novel>(isbn, title, author);
}
else if (type == "Textbook") {
book = make_unique<Textbook>(isbn, title, author);
}
else if (type == "ScientificBook") {
book = make_unique<ScientificBook>(isbn, title, author);
}
else if (type == "FairyTale") {
book = make_unique<FairyTale>(isbn, title, author);
}
else if (type == "HistoryBook") {
book = make_unique<HistoryBook>(isbn, title, author);
}
if (book) {
books.push_back(move(book));
}
}
}
catch (const SQLException& e) {
cerr << "获取图书失败: " << e.what() << endl;
throw;
}
return books;
}

vector<unique_ptr<Reader>> getAllReaders(int page = 1, int pageSize = 100) {
vector<unique_ptr<Reader>> readers;
try {
int offset = (page - 1) * pageSize;
unique_ptr<Statement> stmt(con->createStatement());
unique_ptr<ResultSet> res(stmt->executeQuery(
"SELECT * FROM Readers LIMIT " + to_string(pageSize) + " OFFSET " + to_string(offset)));

while (res->next()) {
string id = res->getString("ID");
string name = res->getString("Name");
int level = res->getInt("Level");

unique_ptr<Reader> reader;
if (level == 1) {
reader = make_unique<RegularReader>(id, name);
}
else if (level == 2) {
reader = make_unique<VIPReader>(id, name);
}

if (reader) {
// 加载借阅记录
loadBorrowedBooks(reader.get());
readers.push_back(move(reader));
}
}
}
catch (const SQLException& e) {
cerr << "获取读者失败: " << e.what() << endl;
throw;
}
return readers;
}

void loadBorrowedBooks(Reader* reader) {
try {
unique_ptr<PreparedStatement> pstmt(con->prepareStatement(
"SELECT BookISBN FROM BorrowRecords WHERE ReaderID=? AND ReturnDate IS NULL"));
pstmt->setString(1, reader->getID());
unique_ptr<ResultSet> res(pstmt->executeQuery());

while (res->next()) {
reader->borrowBook(res->getString("BookISBN"));
}
}
catch (const SQLException& e) {
cerr << "加载借阅记录失败: " << e.what() << endl;
throw;
}
}

void addBook(Book* book) {
try {
unique_ptr<PreparedStatement> pstmt(con->prepareStatement(
"INSERT INTO Books (ISBN, Title, Author, Type, IsAvailable, DueDate) "
"VALUES (?, ?, ?, ?, ?, ?)"));
pstmt->setString(1, book->getISBN());
pstmt->setString(2, book->getTitle());
pstmt->setString(3, book->getAuthor());
pstmt->setString(4, book->getType());
pstmt->setBoolean(5, book->checkAvailability());
pstmt->setInt64(6, book->getDueDate());
pstmt->executeUpdate();
}
catch (SQLException& e) {
cerr << "添加图书失败(错误#" << e.getErrorCode() << "): " << e.what()
<< "\nSQL状态: " << e.getSQLState() << endl;
throw; // 重新抛出异常让上层处理
}
}

void addReader(Reader* reader) {
try {
unique_ptr<PreparedStatement> pstmt(con->prepareStatement(
"INSERT INTO Readers (ID, Name, Level) VALUES (?, ?, ?)"));

pstmt->setString(1, reader->getID());
pstmt->setString(2, reader->getName());
pstmt->setInt(3, reader->getLevel());

pstmt->executeUpdate();
}
catch (SQLException& e) {
cerr << "添加读者失败(错误#" << e.getErrorCode() << "): " << e.what()
<< "\nSQL状态: " << e.getSQLState() << endl;
throw;
}
}

void borrowBook(const string& readerID, const string& bookISBN, time_t dueDate) {
try {
// 更新图书状态
unique_ptr<PreparedStatement> pstmt(con->prepareStatement(
"UPDATE Books SET IsAvailable=false, DueDate=? WHERE ISBN=?"));
pstmt->setInt64(1, dueDate);
pstmt->setString(2, bookISBN);
pstmt->executeUpdate();

// 添加借阅记录
pstmt = unique_ptr<PreparedStatement>(con->prepareStatement(
"INSERT INTO BorrowRecords (ReaderID, BookISBN, BorrowDate, DueDate) "
"VALUES (?, ?, ?, ?)"));
pstmt->setString(1, readerID);
pstmt->setString(2, bookISBN);
pstmt->setInt64(3, time(nullptr));
pstmt->setInt64(4, dueDate);
pstmt->executeUpdate();
}
catch (SQLException& e) {
cerr << "借书操作失败(错误#" << e.getErrorCode() << "): " << e.what()
<< "\nSQL状态: " << e.getSQLState() << endl;
throw;
}
}

void returnBook(const string& readerID, const string& bookISBN) {
try {
// 更新图书状态
unique_ptr<PreparedStatement> pstmt(con->prepareStatement(
"UPDATE Books SET IsAvailable=true, DueDate=0 WHERE ISBN=?"));
pstmt->setString(1, bookISBN);
pstmt->executeUpdate();

// 更新借阅记录
pstmt = unique_ptr<PreparedStatement>(con->prepareStatement(
"UPDATE BorrowRecords SET ReturnDate=? "
"WHERE ReaderID=? AND BookISBN=? AND ReturnDate IS NULL"));
pstmt->setInt64(1, time(nullptr));
pstmt->setString(2, readerID);
pstmt->setString(3, bookISBN);
pstmt->executeUpdate();
}
catch (SQLException& e) {
cerr << "还书操作失败(错误#" << e.getErrorCode() << "): " << e.what()
<< "\nSQL状态: " << e.getSQLState() << endl;
throw;
}
}

double calculateFine(const string& bookISBN) {
try {
unique_ptr<PreparedStatement> pstmt(con->prepareStatement(
"SELECT DueDate FROM Books WHERE ISBN=?"));
pstmt->setString(1, bookISBN);
unique_ptr<ResultSet> res(pstmt->executeQuery());

if (!res->next()) {
throw runtime_error("图书不存在");
}

time_t dueDate = res->getInt64("DueDate");

time_t now = time(nullptr);
if (now <= dueDate) return 0.0;

double daysOverdue = difftime(now, dueDate) / (24 * 3600);
return daysOverdue * 0.5; // 每日罚款0.5元
}
catch (SQLException& e) {
cerr << "计算罚款失败(错误#" << e.getErrorCode() << "): " << e.what()
<< "\nSQL状态: " << e.getSQLState() << endl;
throw;
}
}
};

该类中的各种函数都在后面的library类都有运用到,如:

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
// 添加图书
void addBook(Book* book) {
dbManager.addBook(book);
books.emplace_back(book);
}

// 添加读者
void addReader(Reader* reader) {
dbManager.addReader(reader);
readers.emplace_back(reader);
}

// 借书功能
void borrowBook(const string& readerID, const string& bookISBN) {
......
dbManager.borrowBook(readerID, bookISBN, dueDate);
......
}

// 还书功能
void returnBook(const string& readerID, const string& bookISBN) {
......
//计算罚款
double fine = dbManager.calculateFine(bookISBN);
......
}

.....................

运用数据库就是把原来从文件中调动数据改为从数据库中调动数据,简单方便,还不用担心文件被损坏,如果要添加书籍,我也写了添加书籍进数据库的函数。

实现数据库的流程和出现的问题

  • 在服务器上开放3306端口,开启MySQL服务,建库等等准备工作
  • 在代码中实现连接数据库的代码
  • 发现在本地Windows环境中无法连接到服务器端的3306端口(因为之前连都是在 wsl 中连接的,没有考虑到本地Windows环境连不上),于是切换到Linux环境编译
  • 切换到Linux环境存在编码问题,编译出来的结果变成了乱码,于是将 wsl 中的环境和代码的编码都换成 UTF-8,解决问题

5.结果与讨论

编译与运行

在 wsl 上的编译命令

1
g++ LibraryManagement.cpp -o library -lmysqlcppconn

运行命令

1
./library

运行结果

image-20250512205828516

不足之处

本次实验中,因为时间原因,没有解决本地上Windows环境无法连接到数据库的问题,现不能做到两端互通,仅能做到在Linux系统下编译和运行

6.结论

​ 通过本次图书馆管理系统的开发,我深入掌握了面向对象编程思想和数据库应用技术。项目采用三层架构设计,实现了图书借阅、归还、罚款计算等核心功能,通过抽象基类和派生类构建了5种图书类型和2种读者等级的分类体系,充分运用了封装、继承和多态等面向对象特性。

​ 在数据存储方面,我从最初的文件存储方案升级为MySQL数据库存储,掌握了数据库连接、SQL操作和事务处理等技术,并解决了跨平台兼容性和中文编码等实际问题。

​ 这次实践让我深刻理解了如何将理论知识转化为实际应用,也让我认识到系统健壮性和用户体验的重要性,为今后的软件开发积累了宝贵经验。


图书馆管理系统--C++程序设计
http://example.com/2025/05/08/图书馆管理系统-C-程序设计/
作者
yuhua
发布于
2025年5月8日
许可协议