通讯录管理系统的改进

一、 项目来源

项目名称:通讯录管理系统

获取渠道:用户提供的本地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::vector contacts;
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
cpp

include "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
cpp

include "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();
    }
}

}

六、 重构软件的测试截图

image

测试场景1:添加联系人并显示
输入:张三,男,25,13800001111,北京市海淀区

结果:成功添加,显示时包含所有字段。

image
image
image

测试场景2:同名联系人处理
添加两个“李四”(不同电话)

删除或查找时提示选择具体索引。
image
image

测试场景3:输入验证
年龄输入150以上或负数,提示重新输入。

性别输入字母,提示无效。

菜单输入字母,清空缓冲后重新提示。
image

测试场景4:文件持久化
添加联系人后退出程序,重新启动,数据自动加载。
七、总结
难点分析
输入验证的全面性:需要覆盖所有可能的非法输入,包括类型错误、范围错误、空输入等,确保程序在任何情况下都不会崩溃或进入死循环。其中处理 cin 错误状态和清空缓冲区是关键。

同名联系人的处理:原始设计基于姓名唯一,实际中同名很常见。需要设计一套用户选择机制,既要简单直观,又要保证操作的准确性。

跨平台清屏与暂停:避免使用平台相关的 system 调用,改用标准库函数实现类似效果,提高代码可移植性。模拟清屏(输出多个换行)虽然简单,但并不是完美的解决方案,真正的清屏仍需条件编译调用系统命令。

文件读写格式设计:需要决定如何存储联系人数据,既要便于读写,又要避免歧义(例如地址中可能包含换行符)。本版本采用每行一个字段的方式,假设字段内不含换行,但实际中地址可能含换行,更好的方案是使用 JSON 或 XML。

耗时较长的部分
输入验证与错误恢复:调试 cin 在各种错误场景下的行为,确保每次错误后都能正确恢复并重新提示,花费了不少时间。

同名选择交互设计:设计用户选择索引的方式,并处理输入无效索引的情况,确保交互流畅且不易误操作。

文件加载的健壮性:处理文件不存在、格式错误等情况,避免程序崩溃。

逆向软件工程的思考
通过分析和改进已有代码,我深刻体会到:

代码的可读性和可维护性是软件长期演化的基础。原始代码虽然功能完整,但命名不规范、结构混乱,导致修改和扩展困难。重构时优先考虑清晰的结构和命名,为后续功能添加打下基础。

用户输入是程序最大的敌人,任何不经校验的输入都可能导致程序崩溃。在实际开发中,输入验证必须作为首要任务,不能假设用户会按照预期操作。

数据持久化是软件实用性的关键。即使功能再强大,无法保存数据也毫无意义。文件读写看似简单,但需要考虑多种边界情况。

逆向工程不仅是修复缺陷,更是理解原作者思路、学习经验教训的过程。通过分析原始代码的不足,可以避免在自己未来的项目中犯类似错误。

本次重构不仅提升了通讯录管理系统的质量,也加深了我对C++编程、软件设计和工程实践的理解,是一次宝贵的实践经历。

posted @ 2026-03-11 16:42  hangcw  阅读(13)  评论(1)    收藏  举报