小白学习顺序表 之 通讯录实现
#前言
在日常编程学习中,实现一个联系人管理系统。
它不仅能让我们熟悉结构体、动态内存分配、文件操作等知识点,还能帮助我们更好地理解程序的模块化设计。
一、整体架构
这个联系人管理系统主要由以下几个文件组成:
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
函数进行文件的读写操作。
模块化设计:将不同的功能封装成不同的函数,提高了代码的可读性和可维护性。