“软件开发与创新课程设计”实验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;
}

image

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;
}

image

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;
}

image

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";
}

image

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;
	}
}

image

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;
	}
}

image

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;
}

image

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;
}

image

补充说明

该系统的运行结果截图中的可视化列表均在主函数中编写,且代码结构均类似,以下以 “学生管理子菜单” 为例附源代码:

点击查看代码
// 学生管理子菜单
void displayStudentMenu() {
	cout << "\n===== 学生管理 =====";
	cout << "\n1. 添加学生";
	cout << "\n2. 删除学生";
	cout << "\n3. 修改学生信息";
	cout << "\n4. 显示所有学生";
	cout << "\n0. 返回主菜单";
	cout << "\n请选择操作: ";
}

3 系统现存核心问题分析

  1. 查找效率低:原系统中findAccountIndex/updateStudent等方法遍历单链表,时间复杂度为$O(n)$,学生数量较大时,查找、修改、删除的效率显著下降。
  2. 文件IO性能差saveToFile函数逐节点/逐账户写入文件,频繁的磁盘IO交互导致系统整体性能降低。
  3. 内存安全与数据一致性问题:使用裸指针易引发内存泄漏;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信息...
}
}
测试结果截图:

image

4.3 优化内存与数据一致性(解决问题3)

loadFromFile函数加载数据前,调用clear()清空原有数据,避免冗余,核心代码如下:

点击查看代码
void loadFromFile(string contactFile, string bankFile) {
    studentMap.clear();
    accountMap.clear();
    // 原加载数据的代码逻辑
}
测试结果截图:

image

5 总结

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