#前言
在日常编程学习中,实现一个联系人管理系统。
它不仅能让我们熟悉结构体、动态内存分配、文件操作等知识点,还能帮助我们更好地理解程序的模块化设计。
一、整体架构
这个联系人管理系统主要由以下几个文件组成:
contactTest.cpp:主函数所在文件,负责程序的入口和菜单的显示,以及用户操作的分发。
SeqList.h:定义了顺序表的结构和相关操作的函数声明。
contact.h:定义了联系人的信息结构和联系人管理的相关函数声明。
SeqList.cpp:实现了顺序表的各种操作,如初始化、插入、删除等。
contact.cpp:实现了联系人的添加、删除、修改、查找等具体功能,以及联系人信息的文件读写操作。
二、核心数据结构
1. 联系人信息结构体(Info
)
typedef struct SLcontact {
char name[NAME_MAX];
int age;
char gender[SEX_MAX];
char phone[TEL_MAX];
char addr[ADDR_MAX];
} Info;
这个结构体用于存储联系人的基本信息,包括姓名、年龄、性别、电话和地址。
2. 顺序表结构体(SL
)
typedef struct SeqList
{
SLDataType* a;
int size; // 元素个数
int capacity; // 容量
} SL;
顺序表是一种线性数据结构,这里用它来存储联系人信息。
a
是一个指向 SLDataType
类型的指针,用于动态分配内存;
size
表示当前顺序表中元素的个数;capacity
表示顺序表的容量。
三、主要操作及原理
1. 顺序表初始化(SLInit
)
void SLInit(SL* ps) {
ps->a = NULL;
ps->capacity = ps->size = 0;
}
这个函数将顺序表的指针 a 置为 NULL,并将容量和元素个数都初始化为 0。
2. 顺序表扩容(SLdepend
)
void SLdepend(SL* ps) {
if (ps->capacity == ps->size) {
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDataType* ret = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));
if (ret == NULL) {
perror("realloc");
exit(1);
}
ps->a = ret;
ps->capacity = newcapacity;
}
}
当顺序表的容量和元素个数相等时,需要进行扩容。
这里采用的是每次扩容为原来的 2 倍。
使用 realloc 函数重新分配内存,如果分配失败,会输出错误信息并退出程序。
3. 顺序表尾部插入(SLPushBack
)
void SLPushBack(SL* ps, SLDataType x) {
assert(ps);
SLdepend(ps);
ps->a[ps->size++] = x;
}
在顺序表的尾部插入一个元素。首先使用 assert 函数确保指针 ps 不为 NULL,然后调用 SLdepend 函数检查是否需要扩容,最后将元素插入到顺序表的尾部,并将元素个数加 1。
4. 顺序表头部插入(SLPushFront
)
void SLPushFront(SL* ps, SLDataType x) {
assert(ps);
SLdepend(ps);
for (int i = ps->size; i > 0; i--) {
ps->a[i] = ps->a[i - 1];
}
ps->a[0] = x;
ps->size++;
}
在顺序表的头部插入一个元素。同样先确保指针不为 NULL 并检查是否需要扩容,然后将顺序表中的所有元素依次向后移动一位,最后将元素插入到头部,并将元素个数加 1。
例如,原始顺序表:[A, B, C]
步骤 1:i=3,a [3] = a [2] → [A, B, C, C]
步骤 2:i=2,a [2] = a [1] → [A, B, B, C]
步骤 3:i=1,a [1] = a [0] → [A, A, B, C]
插入元素 X 后:[X, A, B, C]
5. 顺序表尾部删除(SLPopBack
)
void SLPopBack(SL* ps) {
assert(ps);
assert(ps->size > 0);
ps->size--; // 逻辑删除,只减少元素个数
}
删除顺序表的尾部元素。
使用 assert 函数确保指针不为 NULL 且顺序表中至少有一个元素,然后将元素个数减 1。
6. 顺序表头部删除(SLPopFront
)
void SLPopFront(SL* ps) {
assert(ps);
assert(ps->size > 0);
// 将后面的元素依次向前移动一位
for (int i = 0; i < ps->size - 1; i++) {
ps->a[i] = ps->a[i + 1];
}
ps->size--; // 减少元素个数
}
删除顺序表的头部元素。
先确保指针不为 NULL 且顺序表中至少有一个元素,然后将顺序表中的所有元素依次向前移动一位,最后将元素个数减 1。
例如,原始顺序表:[A, B, C]
步骤 1:i=0,a [0] = a [1] → [B, B, C]
步骤 2:i=1,a [1] = a [2] → [B, C, C]
删除后:[B, C]
7. 联系人添加(Addcontact
)
void Addcontact(Cont* pcon) {
Info info;
printf("请输入联系人的姓名:");
scanf_s("%s", info.name, (unsigned)_countof(info.name));
printf("请输入联系人的年龄:");
scanf_s("%d", &info.age);
printf("请输入联系人的性别:");
scanf_s("%s", info.gender, (unsigned)_countof(info.gender));
printf("请输入联系人的电话:");
scanf_s("%s", info.phone, (unsigned)_countof(info.phone));
printf("请输入联系人的地址:");
scanf_s("%s", info.addr, (unsigned)_countof(info.addr));
SLPushBack(pcon, info);
}
该函数用于添加联系人信息。
首先创建一个 Info 类型的变量 info,然后通过 scanf_s 函数从用户输入中获取联系人的姓名、年龄、性别、电话和地址,
最后调用 SLPushBack 函数将联系人信息插入到顺序表的尾部。
8. 联系人删除(Delcontact
)
void Delcontact(Cont* pcon) {
printf("请输入要删除的联系人的姓名:\n");
char name[NAME_MAX];
scanf_s("%s", name, (unsigned)_countof(name));
int findIndex = FindByName(pcon, name);
if (findIndex < 0) {
printf("要删除的联系人不存在!\n");
return;
}
SLErase(pcon, findIndex);
printf("联系人删除成功!\n");
}
该函数用于删除指定姓名的联系人。
首先获取用户输入的联系人姓名,然后调用 FindByName 函数查找该联系人在顺序表中的位置,
如果找不到则输出提示信息并返回;
如果找到,则调用 SLErase 函数删除该联系人,并输出删除成功的提示信息。
9. 联系人修改(Modifycontact
)
void Modifycontact(Cont* pcon) {
char name[NAME_MAX];
printf("请输入要修改的联系人的姓名:\n");
scanf_s("%s", name);
int findIndex = FindByName(pcon, name);
if (findIndex < 0) {
printf("要修改的联系人不存在!\n");
return;
}
printf("请输入新的姓名:\n");
scanf_s("%s", pcon->a[findIndex].name);
printf("请输入新的年龄:\n");
scanf_s("%d", &pcon->a[findIndex].age);
printf("请输入新的性别:\n");
scanf_s("%s", pcon->a[findIndex].gender);
printf("请输入新的电话:\n");
scanf_s("%s", pcon->a[findIndex].phone);
printf("请输入新的地址:\n");
scanf_s("%s", pcon->a[findIndex].addr);
printf("联系人修改成功!\n");
}
该函数用于修改指定姓名的联系人信息。
首先获取用户输入的联系人姓名,然后调用 FindByName 函数查找该联系人在顺序表中的位置,
如果找不到则输出提示信息并返回;
如果找到,则通过 scanf_s 函数获取用户输入的新信息,并更新该联系人的信息,最后输出修改成功的提示信息。
10. 联系人查找(Findcontact
)
void Findcontact(Cont* pcon) {
char name[NAME_MAX];
printf("请输入要查找的用户的姓名:\n");
scanf_s("%s", name);
int findIndex = FindByName(pcon, name);
if (findIndex < 0) {
printf("该联系人不存在!\n");
return;
}
// 找到了,打印一下找到的联系人的信息
printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "地址");
printf("%s %s %d %s %s\n",
pcon->a[findIndex].name,
pcon->a[findIndex].gender,
pcon->a[findIndex].age,
pcon->a[findIndex].phone,
pcon->a[findIndex].addr
);
}
该函数用于查找指定姓名的联系人信息。
首先获取用户输入的联系人姓名,然后调用 FindByName 函数查找该联系人在顺序表中的位置,
如果找不到则输出提示信息并返回;如果找到,则输出该联系人的信息。
11. 联系人信息保存到文件(SaveContactToFile
)
void SaveContactToFile(Cont* pcon, const char* filename) {
FILE* file = fopen(filename, "wb"); // 以二进制写模式打开文件
if (file == NULL) {
perror("无法打开文件");
return;
}
// 写入联系人数量
fwrite(&pcon->size, sizeof(int), 1, file);
// 写入每个联系人
for (int i = 0; i < pcon->size; i++) {
fwrite(&pcon->a[i], sizeof(Info), 1, file);
}
fclose(file);
}
该函数用于将联系人信息保存到文件中。
首先以二进制写模式打开文件,如果打开失败则输出错误信息并返回;
然后写入联系人的数量,再依次写入每个联系人的信息;最后关闭文件。
12. 从文件中加载联系人信息(LoadContactFromFile
)
void LoadContactFromFile(Cont* pcon, const char* filename) {
FILE* file = fopen(filename, "rb"); // 以二进制读模式打开文件
if (file == NULL) {
printf("文件不存在,初始化通讯录\n");
return;
}
// 销毁原有数据
SLDestroy(pcon);
SLInit(pcon);
// 读取联系人数量
int count;
fread(&count, sizeof(int), 1, file);
// 读取每个联系人
for (int i = 0; i < count; i++) {
Info info;
fread(&info, sizeof(Info), 1, file);
SLPushBack(pcon, info);
}
fclose(file);
}
该函数用于从文件中加载联系人信息。
首先以二进制读模式打开文件,如果文件不存在则输出提示信息并返回;
然后销毁原有数据并重新初始化顺序表,接着读取联系人的数量,再依次读取每个联系人的信息并插入到顺序表中;最后关闭文件。
四、注意事项
动态内存管理 :在使用 realloc 函数进行内存扩容时,要注意检查内存分配是否成功,避免出现内存泄漏。
文件操作 :在进行文件读写操作时,要确保文件能够正常打开和关闭,避免数据丢失。
输入验证 :在获取用户输入时,要考虑输入的合法性,避免出现程序崩溃或数据错误。
函数调用 :在调用函数时,要确保传递的参数类型和数量正确,避免出现编译错误或运行时错误。
五、总结知识点
结构体 :使用结构体来组织和存储联系人的信息,方便对联系人进行管理。
顺序表 :顺序表是一种线性数据结构,通过动态内存分配实现了可变长度的存储。
动态内存管理 :使用 malloc、realloc 和 free 函数进行动态内存的分配和释放。
文件操作 :使用 fopen、fread、fwrite 和 fclose 函数进行文件的读写操作。
模块化设计 :将不同的功能封装成不同的函数,提高了代码的可读性和可维护性。