通讯录管理系统的改进
一、 项目来源
项目名称:通讯录管理系统
获取渠道:用户提供的本地C语言源代码文件。
原始功能:基于C语言,在Windows控制台环境下运行,实现对通讯录联系人的添加、显示、删除、查找、修改、清空。
二、系统运行环境
| 环境类别 | 具体配置 |
|---|---|
| 操作系统 | Microsoft Windows 10/11 |
| 开发工具 | 任意C语言IDE (如 Code::Blocks, Dev-C++) 或文本编辑器+命令行 |
| 编程语言 | C语言 (C99标准) |
| 编译命令 | gcc 111(2).c -o class_contact.exe |
三、原始项目代码
点击查看完整源代码(原始版本)
#include#include using namespace std;
define MAX 1000 // 最大人数
// 通讯录管理系统
// 主菜单
void showMenu() {
cout << "" << endl;
cout << " 1、添加联系人 " << endl;
cout << " 2、显示联系人 " << endl;
cout << " 3、删除联系人 " << endl;
cout << " 4、查找联系人 " << endl;
cout << " 5、修改联系人 " << endl;
cout << " 6、清空联系人 " << endl;
cout << " 0、退出通讯录 " << endl;
cout << "*******" << endl;
}// 联系人结构体
struct Person {
string m_Name; // 姓名
int m_Sex; // 性别:1男,2女
int m_Age; // 年龄
string m_Phone; // 手机
string m_Addr; // 住址
};// 通讯录结构体
struct Addressbooks {
struct Person personArray[MAX]; // 通讯录中保存的联系人数组
int m_Size; // 通讯录中人员个数
};// 添加联系人
void addPerson(Addressbooks* abs) {
// 判断通讯录是否已满,如果满了就不再添加
if (abs->m_Size == MAX) {
cout << "通讯录已满,无法添加!" << endl;
return;
}
else {
// 添加具体联系人// 姓名 string name; cout << "请输入姓名:" << endl; cin >> name; abs->personArray[abs->m_Size].m_Name = name; // 性别 int sex = 0; cout << "请输入性别:" << endl; cout << "1 --- 男 2 --- 女" << endl; while (true) { // 如果输入的是1或2可以退出循环,因为输入的是正确值 // 如果输入有误,重新输入 cin >> sex; if (sex == 1 || sex == 2) { abs->personArray[abs->m_Size].m_Sex = sex; break; } cout << "输入有误,请重新输入!" << endl; } // 年龄 cout << "请输入年龄:" << endl; int age = 0; cin >> age; abs->personArray[abs->m_Size].m_Age = age; // 手机号 cout << "请输入电话:" << endl; string phone; cin >> phone; abs->personArray[abs->m_Size].m_Phone = phone; // 地址 cout << "请输入住址:" << endl; string addr; cin >> addr; abs->personArray[abs->m_Size].m_Addr = addr; // 更新通讯录人数 abs->m_Size++; cout << "添加成功!" << endl; system("pause"); system("cls"); }}
// 显示联系人
void showPerson(Addressbooks* abs) {
// 判断通讯录中人数是否为0,如果为0,提示记录为空
// 如果不为0,显示记录的联系人信息
if (abs->m_Size == 0) {
cout << "当前记录为空" << endl;
}
else
{
for (int i = 0; i < abs->m_Size; i++) {
cout << "姓名:" << abs->personArray[i].m_Name << "\t";
cout << "性别:" << (abs->personArray[i].m_Sex == 1 ? "男" : "女") << "\t";
cout << "年龄:" << abs->personArray[i].m_Age << "\t";
cout << "电话:" << abs->personArray[i].m_Phone << "\t";
cout << "住址:" << abs->personArray[i].m_Addr << endl;
}
}system("pause"); system("cls");}
// 删除联系人
// 1.检测联系人是否存在
// 存在,返回联系人在数组中的位置,不存在返回-1
int isExit(Addressbooks* abs, string name) {
// 参数解释:参数1:通讯录; 参数2:对比姓名
for (int i = 0; i < abs->m_Size; i++) {
// 寻找用户输入的姓名
if (abs->personArray[i].m_Name == name) {
return i; // 找到了,返回该联系人在数组中的下标编号
}
}
return -1; // 遍历完,没找到,返回-1;
}// 2.删除指定联系人信息
void deletePerson(Addressbooks* abs) {
cout << "请输入要删除的联系人姓名:" << endl;
string name;
cin >> name;// ret == -1 未查到; ret != -1 查到了 int ret = isExit(abs, name); if (ret != -1) { // 查到了,进行删除 for (int i = ret; i < abs->m_Size; i++) { // 数据前移,实现删除 abs->personArray[i] = abs->personArray[i + 1]; } abs->m_Size--; // 更新通讯录的人数 cout << "删除成功!" << endl; } else { cout << "查无此人!" << endl; } system("pause"); system("cls");}
// 查找联系人
void findPerson(Addressbooks* abs) {
cout << "请输入查找的联系人姓名:" << endl;
string name;
cin >> name;// 判断指定的联系人是否在通讯录中 int ret = isExit(abs, name); if (ret != -1) { // 找到联系人 cout << "姓名:" << abs->personArray[ret].m_Name << "\t"; cout << "性别:" << (abs->personArray[ret].m_Sex == 1 ? "男" : "女") << "\t"; cout << "年龄:" << abs->personArray[ret].m_Age << "\t"; cout << "电话:" << abs->personArray[ret].m_Phone << "\t"; cout << "住址:" << abs->personArray[ret].m_Addr << endl; } else // 找不到联系人 { cout << "查无此人!" << endl;; } system("pause"); system("cls");}
// 修改联系人
void modifyPerson(Addressbooks* abs) {
cout << "请输入要修改的联系人姓名:" << endl;
string name;
cin >> name;// 查找要修改的联系人是否存在 int ret = isExit(abs, name); if (ret != -1) { // 找到了联系人 // 姓名 string name; cout << "请输入姓名:" << endl; cin >> name; abs->personArray[ret].m_Name = name; // 性别 int sex = 0; cout << "请输入性别:" << endl; cout << "1 --- 男 2 --- 女" << endl; while (true) { // 如果输入的是1或2可以退出循环,因为输入的是正确值 // 如果输入有误,重新输入 cin >> sex; if (sex == 1 || sex == 2) { abs->personArray[ret].m_Sex = sex; break; } cout << "输入有误,请重新输入!" << endl; } // 年龄 cout << "请输入年龄:" << endl; int age = 0; cin >> age; abs->personArray[ret].m_Age = age; // 手机号 cout << "请输入电话:" << endl; string phone; cin >> phone; abs->personArray[ret].m_Phone = phone; // 地址 cout << "请输入住址:" << endl; string addr; cin >> addr; abs->personArray[ret].m_Addr = addr; cout << "修改成功!" << endl; } else { // 未找到联系人 cout << "查无此人!" << endl; } system("pause"); system("cls");}
// 清空联系人
void cleanPerson(Addressbooks* abs) {
abs->m_Size = 0;
cout << "通讯录已清空" << endl;
system("pause");
system("cls");
}int main() {
// 创建通讯录结构体变量 Addressbooks abs; // 初始化通讯录中当前人员个数 abs.m_Size = 0; // 创建用户选择变量 int select = 0; while (true) { // 主菜单调用 showMenu(); // 功能选择 cin >> select; switch (select) { case 1: // 1、添加联系人 addPerson(&abs); // ;利用地址传递,可以修饰实参 break; case 2: // 2、显示联系人 showPerson(&abs); break; case 3: // 3、删除联系人 deletePerson(&abs); break; case 4: // 4、查找联系人 findPerson(&abs); break; case 5: // 5、修改联系人 modifyPerson(&abs); break; case 6: // 6、清空联系人 cleanPerson(&abs); break; case 0: // 0、退出通讯录 cout << "欢迎下次使用!" << endl; system("pause"); return 0; break; default: break; } } system("pause"); return 0;
}
四、主要问题列表、针对问题的改善和重构思路
(一)核心问题清单
问题1:数据结构容量固定,扩展性差
使用宏 #define MAX 1000 定义固定大小的数组,一旦通讯录人数超过1000则无法添加,且造成内存浪费(即使只有几个人也占用固定空间)。无法灵活应对不同规模的需求。
问题2:输入验证严重不足,程序易崩溃或行为异常
年龄未做范围限制(可输入负数、超大数)。
性别输入若误输字母或符号,程序陷入死循环(cin 进入错误状态)。
电话和住址使用 cin >> 读取,无法包含空格(如“北京市海淀区”只能读入“北京市”)。
菜单选择输入非数字字符时,cin 进入错误状态,导致无限循环。
问题3:无数据持久化,程序关闭后数据全部丢失
每次运行都从零开始,无法保存已添加的联系人,实用性大打折扣。
问题4:交互体验依赖系统命令,跨平台性差
使用 system("pause") 和 system("cls") 实现暂停和清屏,仅能在 Windows 环境下运行,且频繁清屏导致界面闪烁,用户体验不佳。
问题5:功能单一,无法处理同名联系人
查找、删除、修改均基于姓名,且只处理第一个匹配项,若存在多个同名联系人无法区分操作。
无排序功能(如按姓名、年龄排序)。
无多条件查询(如按性别、年龄段筛选)。
问题6:代码可维护性存在隐患
函数命名不规范:isExit 拼写错误(应为 isExist)。
性别用 int 表示,可读性差,且输入逻辑在添加和修改中重复(违反 DRY 原则)。
删除操作存在潜在越界风险(循环条件 i < abs->m_Size 中最后一次访问 abs->personArray[i+1] 越界)。
所有功能函数均依赖全局结构体,未采用面向对象封装,模块耦合度较高。
(二)改善与重构思路
改善1:改用动态数据结构,突破容量限制
用 std::vector
移除 MAX 宏,利用 vector 的 push_back 和 erase 灵活管理内存。
改善2:全方位加强输入验证
年龄限制为 0~150,输入非数字或超出范围时提示重新输入。
性别使用枚举 enum class Sex { Male, Female },并封装独立输入函数,循环校验直到输入合法。
电话和地址改用 getline(cin, str) 读取,支持空格。
菜单选择前检查 cin 状态,失败时清空缓冲区并重新提示。
改善3:增加文件读写,实现数据持久化
在程序启动时自动从 contacts.txt 加载联系人数据(loadFromFile)。
程序退出时(或每次修改后)自动保存数据到文件(saveToFile)。
采用简单格式存储(每行一个字段,便于读写),也可考虑 JSON 或二进制格式。
改善4:优化交互体验,提高跨平台性
用 cin.get() 或 std::this_thread::sleep_for 替代 system("pause")。
清屏改用输出多个换行(或根据平台条件编译调用 clear/cls)。
减少不必要的清屏,只在菜单切换时适当清理,保持界面简洁。
改善5:扩展功能,提升实用性
同名处理:查找时返回所有匹配项索引,让用户选择要操作的具体联系人。
排序功能:增加按姓名、年龄升序/降序排序(可借助 std::sort 和自定义比较函数)。
多条件查询:支持按姓名、性别、年龄范围、电话片段等进行模糊搜索。
修改优化:允许只修改部分字段,而非全部重新输入。
改善6:代码重构,提高可维护性
将通讯录封装为类 AddressBook,成员变量私有,对外提供公共接口。
修正拼写错误,统一命名规范。
抽取重复代码(如性别输入)为独立成员函数。
修复删除操作的越界问题(循环条件改为 i < abs->m_Size - 1)。
用 const 修饰只读成员函数(如显示、查找),明确函数意图。
五、二次开发的新代码
点击查看改进后完整源代码
addressbook.h cpp #pragma once #include#include enum class Sex { Male = 1, Female = 2 };
struct Person {
std::string name;
Sex sex;
int age;
std::string phone;
std::string address;
};class AddressBook {
private:
std::vectorcontacts;
const std::string filename = "contacts.txt";Sex inputSex() const; std::vector<size_t> findByName(const std::string& name) const;public:
AddressBook();
~AddressBook();void addPerson(); void showAll() const; void deletePerson(); void findPerson() const; void modifyPerson(); void clearAll(); void loadFromFile(); void saveToFile() const; static void pause(); static void clearScreen();};
addressbook.cpp
cppinclude "addressbook.h"
include
include
include
include
using namespace std;
AddressBook::AddressBook() { loadFromFile(); }
AddressBook::~AddressBook() { saveToFile(); }Sex AddressBook::inputSex() const {
int s;
while (true) {
cout << "性别(1-男,2-女):";
cin >> s;
if (cin.fail() || (s != 1 && s != 2)) {
cin.clear();
cin.ignore(numeric_limits::max(), '\n');
cout << "输入无效,请重新输入!" << endl;
} else {
return static_cast(s);
}
}
}vector<size_t> AddressBook::findByName(const string& name) const {
vector<size_t> indices;
for (size_t i = 0; i < contacts.size(); ++i) {
if (contacts[i].name == name) indices.push_back(i);
}
return indices;
}void AddressBook::addPerson() {
Person p;
cout << "请输入姓名:";
cin.ignore();
getline(cin, p.name);
p.sex = inputSex();
cout << "请输入年龄(0-150):";
while (!(cin >> p.age) || p.age < 0 || p.age > 150) {
cin.clear();
cin.ignore(numeric_limits::max(), '\n');
cout << "年龄无效,请重新输入(0-150):";
}
cout << "请输入电话:";
cin.ignore();
getline(cin, p.phone);
cout << "请输入地址:";
getline(cin, p.address);
contacts.push_back(p);
cout << "添加成功!" << endl;
pause();
clearScreen();
}void AddressBook::showAll() const {
if (contacts.empty()) {
cout << "通讯录为空。" << endl;
} else {
for (size_t i = 0; i < contacts.size(); ++i) {
const auto& p = contacts[i];
cout << "索引:" << i << "\t姓名:" << p.name
<< "\t性别:" << (p.sex == Sex::Male ? "男" : "女")
<< "\t年龄:" << p.age
<< "\t电话:" << p.phone
<< "\t地址:" << p.address << endl;
}
}
pause();
clearScreen();
}void AddressBook::deletePerson() {
cout << "请输入要删除的联系人姓名:";
string name;
cin.ignore();
getline(cin, name);
auto indices = findByName(name);
if (indices.empty()) {
cout << "查无此人!" << endl;
} else if (indices.size() == 1) {
contacts.erase(contacts.begin() + indices[0]);
cout << "删除成功!" << endl;
} else {
cout << "找到多个同名联系人:" << endl;
for (size_t i = 0; i < indices.size(); ++i) {
const auto& p = contacts[indices[i]];
cout << i << ":姓名 " << p.name << ",电话 " << p.phone << endl;
}
cout << "请选择要删除的序号(-1取消):";
int choice;
cin >> choice;
if (choice >= 0 && choice < static_cast(indices.size())) {
contacts.erase(contacts.begin() + indices[choice]);
cout << "删除成功!" << endl;
} else {
cout << "取消删除。" << endl;
}
}
pause();
clearScreen();
}void AddressBook::findPerson() const {
cout << "请输入要查找的姓名:";
string name;
cin.ignore();
getline(cin, name);
auto indices = findByName(name);
if (indices.empty()) {
cout << "查无此人!" << endl;
} else {
for (size_t i : indices) {
const auto& p = contacts[i];
cout << "姓名:" << p.name
<< "\t性别:" << (p.sex == Sex::Male ? "男" : "女")
<< "\t年龄:" << p.age
<< "\t电话:" << p.phone
<< "\t地址:" << p.address << endl;
}
}
pause();
clearScreen();
}void AddressBook::modifyPerson() {
cout << "请输入要修改的联系人姓名:";
string name;
cin.ignore();
getline(cin, name);
auto indices = findByName(name);
if (indices.empty()) {
cout << "查无此人!" << endl;
pause();
clearScreen();
return;
}
size_t idx;
if (indices.size() == 1) {
idx = indices[0];
} else {
cout << "找到多个同名联系人:" << endl;
for (size_t i = 0; i < indices.size(); ++i) {
const auto& p = contacts[indices[i]];
cout << i << ":姓名 " << p.name << ",电话 " << p.phone << endl;
}
cout << "请选择要修改的序号(-1取消):";
int choice;
cin >> choice;
if (choice < 0 || choice >= static_cast(indices.size())) {
cout << "取消修改。" << endl;
pause();
clearScreen();
return;
}
idx = indices[choice];
}
Person& p = contacts[idx];
cout << "开始修改联系人(留空表示不修改):" << endl;
// 姓名
cout << "当前姓名:" << p.name << ",新姓名:";
string newName;
cin.ignore();
getline(cin, newName);
if (!newName.empty()) p.name = newName;
// 性别
cout << "当前性别:" << (p.sex == Sex::Male ? "男" : "女") << ",是否修改?(y/n):";
char ch;
cin >> ch;
if (ch == 'y' || ch == 'Y') {
p.sex = inputSex();
}
// 年龄
cout << "当前年龄:" << p.age << ",新年龄(输入负数跳过):";
int newAge;
cin >> newAge;
if (newAge >= 0 && newAge <= 150) p.age = newAge;
// 电话
cout << "当前电话:" << p.phone << ",新电话:";
cin.ignore();
getline(cin, newName);
if (!newName.empty()) p.phone = newName;
// 地址
cout << "当前地址:" << p.address << ",新地址:";
getline(cin, newName);
if (!newName.empty()) p.address = newName;
cout << "修改成功!" << endl;
pause();
clearScreen();
}void AddressBook::clearAll() {
contacts.clear();
cout << "通讯录已清空!" << endl;
pause();
clearScreen();
}void AddressBook::loadFromFile() {
ifstream in(filename);
if (!in.is_open()) return;
contacts.clear();
Person p;
int sexInt;
while (in >> ws, in.peek() != EOF) {
getline(in, p.name);
in >> sexInt; in.ignore();
p.sex = static_cast(sexInt);
in >> p.age; in.ignore();
getline(in, p.phone);
getline(in, p.address);
contacts.push_back(p);
}
in.close();
}void AddressBook::saveToFile() const {
ofstream out(filename);
for (const auto& p : contacts) {
out << p.name << '\n'
<< static_cast(p.sex) << '\n'
<< p.age << '\n'
<< p.phone << '\n'
<< p.address << '\n';
}
out.close();
}void AddressBook::pause() {
cout << "按回车键继续...";
cin.ignore(numeric_limits::max(), '\n');
cin.get();
}void AddressBook::clearScreen() {
// 跨平台清屏:输出多个换行
for (int i = 0; i < 30; ++i) cout << endl;
}
main.cpp
cppinclude "addressbook.h"
include
include
using namespace std;
void showMenu() {
cout << "" << endl;
cout << " 1、添加联系人 " << endl;
cout << " 2、显示联系人 " << endl;
cout << " 3、删除联系人 " << endl;
cout << " 4、查找联系人 " << endl;
cout << " 5、修改联系人 " << endl;
cout << " 6、清空联系人 " << endl;
cout << " 0、退出通讯录 " << endl;
cout << "*******" << endl;
}int main() {
AddressBook book;
int choice;while (true) { showMenu(); cout << "请选择:"; cin >> choice; if (cin.fail()) { cin.clear(); cin.ignore(numeric_limits<streamsize>::max(), '\n'); cout << "输入无效,请重新输入!" << endl; AddressBook::pause(); AddressBook::clearScreen(); continue; } switch (choice) { case 1: book.addPerson(); break; case 2: book.showAll(); break; case 3: book.deletePerson(); break; case 4: book.findPerson(); break; case 5: book.modifyPerson(); break; case 6: book.clearAll(); break; case 0: cout << "欢迎下次使用!" << endl; return 0; default: cout << "无效选项,请重新选择!" << endl; AddressBook::pause(); AddressBook::clearScreen(); } }
}
六、 重构软件的测试截图

测试场景1:添加联系人并显示
输入:张三,男,25,13800001111,北京市海淀区
结果:成功添加,显示时包含所有字段。



测试场景2:同名联系人处理
添加两个“李四”(不同电话)
删除或查找时提示选择具体索引。


测试场景3:输入验证
年龄输入150以上或负数,提示重新输入。
性别输入字母,提示无效。
菜单输入字母,清空缓冲后重新提示。

测试场景4:文件持久化
添加联系人后退出程序,重新启动,数据自动加载。
七、总结
难点分析
输入验证的全面性:需要覆盖所有可能的非法输入,包括类型错误、范围错误、空输入等,确保程序在任何情况下都不会崩溃或进入死循环。其中处理 cin 错误状态和清空缓冲区是关键。
同名联系人的处理:原始设计基于姓名唯一,实际中同名很常见。需要设计一套用户选择机制,既要简单直观,又要保证操作的准确性。
跨平台清屏与暂停:避免使用平台相关的 system 调用,改用标准库函数实现类似效果,提高代码可移植性。模拟清屏(输出多个换行)虽然简单,但并不是完美的解决方案,真正的清屏仍需条件编译调用系统命令。
文件读写格式设计:需要决定如何存储联系人数据,既要便于读写,又要避免歧义(例如地址中可能包含换行符)。本版本采用每行一个字段的方式,假设字段内不含换行,但实际中地址可能含换行,更好的方案是使用 JSON 或 XML。
耗时较长的部分
输入验证与错误恢复:调试 cin 在各种错误场景下的行为,确保每次错误后都能正确恢复并重新提示,花费了不少时间。
同名选择交互设计:设计用户选择索引的方式,并处理输入无效索引的情况,确保交互流畅且不易误操作。
文件加载的健壮性:处理文件不存在、格式错误等情况,避免程序崩溃。
逆向软件工程的思考
通过分析和改进已有代码,我深刻体会到:
代码的可读性和可维护性是软件长期演化的基础。原始代码虽然功能完整,但命名不规范、结构混乱,导致修改和扩展困难。重构时优先考虑清晰的结构和命名,为后续功能添加打下基础。
用户输入是程序最大的敌人,任何不经校验的输入都可能导致程序崩溃。在实际开发中,输入验证必须作为首要任务,不能假设用户会按照预期操作。
数据持久化是软件实用性的关键。即使功能再强大,无法保存数据也毫无意义。文件读写看似简单,但需要考虑多种边界情况。
逆向工程不仅是修复缺陷,更是理解原作者思路、学习经验教训的过程。通过分析原始代码的不足,可以避免在自己未来的项目中犯类似错误。
本次重构不仅提升了通讯录管理系统的质量,也加深了我对C++编程、软件设计和工程实践的理解,是一次宝贵的实践经历。
浙公网安备 33010602011771号