上海海洋大学 软件开发与创新课程设计 实验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)」的手动操作,切换到
- 逆向软件工程核心思维:
- 以现有系统/代码为结果,反向推导其需求、架构、设计缺陷与优化逻辑,而非正向开发;
- 由表及里(功能表象→底层逻辑)、溯源归因(优化结果→原始问题)、抓核心关联(如以“姓名为键”推导存储/耦合规则),核心是还原“为什么这么设计” 而非仅“怎么做”。
