1. 项目概述
这个C++通讯录管理系统是我在大学期间完成的第一个综合性编程项目,也是让我真正理解指针和结构体应用的实战案例。通过数组存储联系人数据,结合函数进行模块化开发,再运用指针实现高效的内存操作,最终构建出一个具备增删改查功能的完整系统。
这个项目特别适合刚学完C++基础语法,想要通过实际开发巩固指针、结构体等核心概念的同学。我在开发过程中踩过不少坑,比如指针越界导致的程序崩溃、结构体成员访问错误等问题,这些经验都会在后续内容中详细分享。
2. 核心数据结构设计
2.1 联系人结构体定义
通讯录的核心是联系人信息的存储,我设计了一个Person结构体来封装所有字段:
cpp复制struct Person {
string name; // 姓名
string gender; // 性别
int age; // 年龄
string phone; // 电话
string address; // 地址
};
选择结构体而不是类,主要是考虑到:
- 联系人数据是纯数据集合,不需要复杂的成员方法
- 结构体默认public访问权限,操作更直接
- 内存连续分配,配合数组使用效率更高
2.2 通讯录数组实现
通讯录使用Person类型的数组存储联系人:
cpp复制#define MAX_SIZE 1000
Person contacts[MAX_SIZE];
int currentSize = 0; // 当前联系人数量
这里有几个关键设计点:
- 数组大小通过宏定义,方便统一修改
- 使用currentSize变量记录实际存储的联系人数量
- 数组下标从0开始,最大可用到MAX_SIZE-1
注意:数组实现简单但扩展性有限,当联系人超过MAX_SIZE时会丢失数据。实际项目中建议改用vector动态数组。
3. 核心功能实现
3.1 添加联系人功能
cpp复制void addContact(Person* contacts, int& size) {
if (size >= MAX_SIZE) {
cout << "通讯录已满!" << endl;
return;
}
Person p;
cout << "请输入姓名:";
cin >> p.name;
// 其他字段输入类似...
contacts[size] = p;
size++;
cout << "添加成功!" << endl;
}
关键点解析:
- 使用指针参数contacts直接操作原数组
- size参数使用引用传递,确保修改能同步到调用处
- 先检查数组是否已满,避免越界
3.2 查询联系人功能
查询功能实现了按姓名查找:
cpp复制Person* findContact(Person* contacts, int size, string name) {
for (int i = 0; i < size; i++) {
if (contacts[i].name == name) {
return &contacts[i]; // 返回找到的联系人指针
}
}
return nullptr; // 未找到返回空指针
}
这个函数展示了指针的典型应用:
- 遍历数组查找匹配项
- 找到后返回该元素的地址
- 未找到返回nullptr,调用方需要检查返回值
3.3 删除联系人功能
删除操作需要移动数组元素:
cpp复制void deleteContact(Person* contacts, int& size, string name) {
int index = -1;
for (int i = 0; i < size; i++) {
if (contacts[i].name == name) {
index = i;
break;
}
}
if (index == -1) {
cout << "未找到该联系人!" << endl;
return;
}
// 将后续元素前移
for (int i = index; i < size - 1; i++) {
contacts[i] = contacts[i + 1];
}
size--;
cout << "删除成功!" << endl;
}
提示:数组删除操作时间复杂度是O(n),频繁删除时性能较差。可以考虑标记删除而不是物理删除。
4. 指针操作技巧与陷阱
4.1 指针与数组的关系
在C++中,数组名本质上是指向数组首元素的指针。以下两种写法是等价的:
cpp复制contacts[i].name;
(contacts + i)->name;
但要注意区别:
- 数组名是常量指针,不能修改指向
- sizeof(数组名)返回数组总大小,而sizeof(指针)返回指针大小
4.2 常见指针问题
- 空指针解引用:
cpp复制Person* p = findContact(...);
cout << p->name; // 如果p是nullptr会崩溃
安全做法:
cpp复制if (p != nullptr) {
cout << p->name;
}
- 指针越界:
cpp复制Person* p = &contacts[MAX_SIZE]; // 越界访问
- 野指针:
cpp复制Person* p;
cout << p->name; // p未初始化
5. 功能扩展与优化建议
5.1 增加文件存储功能
当前程序关闭后数据会丢失,可以增加文件读写功能:
cpp复制void saveToFile(Person* contacts, int size) {
ofstream out("contacts.dat");
for (int i = 0; i < size; i++) {
out << contacts[i].name << " "
<< contacts[i].phone << "\n";
}
out.close();
}
5.2 改用动态数组
使用vector替代原生数组:
cpp复制vector<Person> contacts;
// 添加联系人
contacts.push_back(p);
// 删除联系人
contacts.erase(contacts.begin() + index);
vector自动管理内存,无需担心数组越界问题。
5.3 增加多条件查询
扩展查询功能,支持按姓名、电话等多条件查询:
cpp复制vector<Person*> findContacts(vector<Person>& contacts,
string name = "",
string phone = "") {
vector<Person*> result;
for (auto& p : contacts) {
if ((name.empty() || p.name == name) &&
(phone.empty() || p.phone == phone)) {
result.push_back(&p);
}
}
return result;
}
6. 项目总结与心得
这个项目让我深刻理解了指针和内存管理的重要性。几个关键收获:
- 指针操作必须谨慎,解引用前一定要检查是否为nullptr
- 数组越界是常见错误,使用vector等容器更安全
- 结构体适合组织相关数据,比单独变量更清晰
- 函数应该尽量单一职责,比如查询和修改分开
一个特别容易忽视的问题是:当结构体包含string等动态内存成员时,直接内存拷贝可能导致问题。这种情况下应该实现拷贝构造函数和赋值运算符。
这个通讯录系统虽然基础,但涵盖了C++的核心概念。后续可以扩展为带UI的完整应用,或者改用数据库存储。对于初学者来说,理解这个项目的每个细节,对掌握C++编程思维非常有帮助。
