“软件开发与创新课程设计”实验1
上海海洋大学 软件开发与创新课程设计 实验1 - 个人信息管理系统逆软件工程分析
本次随笔针对上海海洋大学信息学院“软件开发与创新课程设计”实验1展开,核心是对同院学号2452826同学的C++项目《基于C++的个人信息管理系统》进行逆软件工程的思考与实践。
1 开发设备运行环境
| 配置项 | 具体信息 |
|---|---|
| OS 名称 | Microsoft Windows 11 家庭中文版 |
| OS 版本 | 10.0.26200 暂缺 Build 26200 |
| 系统制造商 | LENOVO |
| 系统型号 | 83DF |
| 系统类型 | x64-based PC |
| 处理器 | 安装了1个处理器:[01]:Intel64 Family 6 Model 183 Stepping 1 GenuineIntel ~2200 Mhz |
| 物理内存总量 | 32,492 MB |
| 可用的物理内存 | 17,238 MB |
| 虚拟内存(最大值) | 34,540 MB |
| 虚拟内存(可用) | 12,397 MB |
| 虚拟内存(使用中) | 22,143 MB |
| 编译器 | Dev-C++ |
2 系统核心模块功能测试
本次测试覆盖系统三大核心模块:学生管理模块、银行账户管理模块、数据管理模块(以下图片依次为对应功能的源代码和测试结果截图)。
2.1 学生管理模块
2.1.1 添加学生
创建新节点并初始化输入的姓名、专业、生源地;将节点添加到链表尾部,同时为该学生创建农业银行和建设银行账户。
点击查看代码
void addStudent(string name, string major, string origin) {
Student* newStudent = new Student(name, major, origin);
if (!head) {
head = newStudent;
} else {
Student* temp = head;
while (temp->next) temp = temp->next;
temp->next = newStudent;
}
accounts.push_back(BankAccount()); // 创建对应的银行账户
cout << "学生 " << name << " 添加成功!" << endl;
}

2.1.2 删除学生
遍历链表查找目标姓名对应的节点;调整指针关系断开节点连接,释放节点内存,并删除关联的银行账户数据。
点击查看代码
void deleteStudent(string name) {
Student* curr = head;
Student* prev = nullptr;
int index = 0;
while (curr) {
if (curr->name == name) {
// 从链表中删除
if (prev) prev->next = curr->next;
else head = curr->next;
// 从账户容器中删除
accounts.erase(accounts.begin() + index);
delete curr;
cout << "学生 " << name << " 已删除" << endl;
return;
}
prev = curr;
curr = curr->next;
index++;
}
cout << "未找到学生: " << name << endl;
}

2.1.3 修改学生信息
按姓名查找节点后,直接更新专业和生源地字段值。
点击查看代码
void updateStudent(string name, string newMajor, string newOrigin) {
Student* curr = head;
while (curr) {
if (curr->name == name) {
curr->major = newMajor;
curr->origin = newOrigin;
cout << "学生信息已更新" << endl;
return;
}
curr = curr->next;
}
cout << "未找到学生: " << name << endl;
}

2.1.4 显示学生列表
遍历链表,按序号、姓名、专业、生源地格式生成表格输出。
点击查看代码
void displayAllStudents() {
cout << "\n====== 学生通讯录 ======\n";
Student* curr = head;
int index = 0;
while (curr) {
cout << "[" << index + 1 << "] " << curr->name << " | "
<< curr->major << " | " << curr->origin << endl;
curr = curr->next;
index++;
}
cout << "=======================\n";
}

2.2 银行账户管理模块
2.2.1 存款操作
验证学生存在性及存款金额有效性(>0);直接增加对应银行账户的余额,并实时显示更新后余额。
点击查看代码
void deposit(string name, string bank, double amount) {
int index = findAccountIndex(name);
if (index != -1) {
accounts[index].deposit(bank, amount);
cout << "存款成功! 当前" << bank << "余额: "
<< getBalance(name, bank) << endl;
} else {
cout << "学生不存在" << endl;
}
}

2.2.2 消费操作
检查消费金额是否超过账户余额,充足则扣减金额,否则提示 “余额不足”;操作后显示当前余额。
点击查看代码
void consume(string name, string bank, double amount) {
int index = findAccountIndex(name);
if (index != -1) {
if (accounts[index].consume(bank, amount)) {
cout << "消费成功! 当前" << bank << "余额: "
<< getBalance(name, bank) << endl;
} else {
cout << "消费失败! " << bank << "余额不足" << endl;
}
} else {
cout << "学生不存在" << endl;
}
}

2.3 数据管理模块
2.3.1 保存数据(二进制格式存储)
将学生链表数据写入contacts.dat文件,账户数据写入bank.dat文件;支持手动触发保存或退出时自动保存。
点击查看代码
void saveToFile(string contactFile, string bankFile) {
// 保存通讯录
ofstream outContact(contactFile, ios::binary);
Student* curr = head;
while (curr) {
size_t len = curr->name.size();
outContact.write(reinterpret_cast<char*>(&len), sizeof(len));
outContact.write(curr->name.c_str(), len);
len = curr->major.size();
outContact.write(reinterpret_cast<char*>(&len), sizeof(len));
outContact.write(curr->major.c_str(), len);
len = curr->origin.size();
outContact.write(reinterpret_cast<char*>(&len), sizeof(len));
outContact.write(curr->origin.c_str(), len);
curr = curr->next;
}
outContact.close();
// 保存银行数据
ofstream outBank(bankFile, ios::binary);
for (auto& acc : accounts) {
outBank << acc;
}
outBank.close();
cout<< ""
cout << "数据保存成功!" << endl;
}

2.3.2 加载数据
从contacts.dat读取数据重建学生链表,从bank.dat读取数据填充账户容器;启动时自动加载或由用户手动触发加载。
点击查看代码
void loadFromFile(string contactFile, string bankFile) {
// 加载通讯录
ifstream inContact(contactFile, ios::binary);
if (!inContact) return;
while (true) {
size_t len;
if (!inContact.read(reinterpret_cast<char*>(&len), sizeof(len))) break;
string name(len, ' ');
inContact.read(&name[0], len);
inContact.read(reinterpret_cast<char*>(&len), sizeof(len));
string major(len, ' ');
inContact.read(&major[0], len);
inContact.read(reinterpret_cast<char*>(&len), sizeof(len));
string origin(len, ' ');
inContact.read(&origin[0], len);
addStudent(name, major, origin);
}
inContact.close();
// 加载银行数据
ifstream inBank(bankFile, ios::binary);
if (!inBank) return;
accounts.clear();
BankAccount acc;
while (inBank >> acc) {
accounts.push_back(acc);
}
inBank.close();
cout << "数据加载成功!" << endl;
}

补充说明
该系统的运行结果截图中的可视化列表均在主函数中编写,且代码结构均类似,以下以 “学生管理子菜单” 为例附源代码:
点击查看代码
// 学生管理子菜单
void displayStudentMenu() {
cout << "\n===== 学生管理 =====";
cout << "\n1. 添加学生";
cout << "\n2. 删除学生";
cout << "\n3. 修改学生信息";
cout << "\n4. 显示所有学生";
cout << "\n0. 返回主菜单";
cout << "\n请选择操作: ";
}
3 系统现存核心问题分析
- 查找效率低:原系统中
findAccountIndex/updateStudent等方法遍历单链表,时间复杂度为$O(n)$,学生数量较大时,查找、修改、删除的效率显著下降。 - 文件IO性能差:
saveToFile函数逐节点/逐账户写入文件,频繁的磁盘IO交互导致系统整体性能降低。 - 内存安全与数据一致性问题:使用裸指针易引发内存泄漏;
loadFromFile加载数据时未清空原有数据,导致新旧数据混杂,数据一致性差。
4 针对性优化建议
4.1 优化查找效率(解决问题1)
改用std::unordered_map<string, Student>(以学生姓名为Key),将查询、修改、删除的时间复杂度降至$O(1)$,核心代码如下:
点击查看代码
class PersonalInfoSystem {
private:
unordered_map<string, Student> studentMap; // 替代单链表
unordered_map<string, BankAccount> accountMap; // 替代账户向量
};
4.2 优化文件IO性能(解决问题2)
先写入“学生数量”到文件头部,再批量写入数据,减少磁盘交互次数,核心代码如下:
点击查看代码
void saveToFile(string contactFile, string bankFile) {
ofstream outContact(contactFile, ios::binary);
size_t studentCount = studentMap.size();//先写入学生的数量
outContact.write(reinterpret_cast<char*>(&studentCount),sizeof(studentCount));
for (unordered_map<string, Student>::iterator it = studentMap.begin(); it != studentMap.end(); ++it) {
string& name = it->first;
Student& stu = it->second;
// 写入stu信息...
}
}

4.3 优化内存与数据一致性(解决问题3)
在loadFromFile函数加载数据前,调用clear()清空原有数据,避免冗余,核心代码如下:
点击查看代码
void loadFromFile(string contactFile, string bankFile) {
studentMap.clear();
accountMap.clear();
// 原加载数据的代码逻辑
}

5 总结
- 所有优化中最难的点在于思维转变和技术壁垒:
- 习惯单链表「节点创建(new)→ 指针遍历→ 手动释放(delete)」的手动操作,切换到
unordered_map的「键值对存储 + 自动管理内存」时易出问题:- 学习并实践新语法/语句的时间成本较高;
- 忽略姓名作为键的唯一性约束,遗漏“添加前
find()校验唯一性”逻辑,导致业务逻辑错乱; - 迭代器使用出错(如用错
it->first/second、Dev-C++中advance()因缺<iterator>头文件编译报错)。
- 习惯单链表「节点创建(new)→ 指针遍历→ 手动释放(delete)」的手动操作,切换到
- 逆向软件工程核心思维:
- 以现有系统/代码为结果,反向推导其需求、架构、设计缺陷与优化逻辑,而非正向开发;
- 由表及里(功能表象→底层逻辑)、溯源归因(优化结果→原始问题)、抓核心关联(如以“姓名为键”推导存储/耦合规则),核心是还原“为什么这么设计” 而非仅“怎么做”。

浙公网安备 33010602011771号