目录
2)显示通讯录中所有人的信息(遍历)ShowContact函数
3)显示指定联系人的信息(遍历)SearchContact函数
8)将通讯录中的数据保存到文件中 SaveContact 函数
源代码:https://gitee.com/epiphanywh/c_project/tree/master/Contact_link/test_5_30
当我们学习过链表相关知识过后,我们可以利用链表实习通讯录的相关操作;
相比较与前两版,三者有相同的部分也有些许差异:我们不需要自己写代码实现通讯录增容,因为当我们每增加一个联系人后就可以创建一个节点(在内存中开辟一块空间)来保存联系人的信息,所以就不存在通讯录已满的问题。
如果你还对链表的相关知识不太了解的话,你可以看一下这位大佬的博客:链表基础知识详解(非常详细简单易懂)_不秃也很强的博客-CSDN博客
下面我们就开始实现通讯录的第三个版本:
一、结构体相关结构的变化
由于一个节点有一个数据域和指针域,所以一个节点如以下结构:
typedef struct PoInfo
{
char name[20];
int units;
char QQ[20];
char tele[20];
char addr[30];
}PoInfo;
typedef struct contact
{
PoInfo data;//通讯录联系人信息包括(name,qq...)
struct contact* next;
}contact;
这里我们用data作为数据域,next作为指针域(PoInfo是一个自定义结构,其里面是联系人的相关信息)。
二、相关函数的实现
这里函数与前面的版本相比,我增加了文件相关的操作,这样我们就可以将通讯录里的数据保存下来了。
首先,我们先创建一个头节点head:
contact* head = NULL;
首先说一下各个函数的参数,这里我们每一个函数都将head的地址作为参数,以便我们我们对链表结构的变动。
LoadContact(&head);
int input = 0;
do
{
MakeMenu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
AddContact(&head);//增加
break;
case 2:
DelContact(&head);//删除
break;
case 3:
SortContact(&head);//排序
break;
case 4:
SearchContact(&head);//查找
break;
case 5:
ShowContact(&head);//显示所有联系人
break;
case 6:
ModifyContact(&head);//修改信息
break;
case 0:
SaveContact(&head);//保存通讯录到文件中
DestroyContact(&head);//销毁通讯录
printf("退出通讯录!\n");
break;
default:
printf("选择错误,请重新选择!\n");
break;
}
} while (input);
1)增加联系人(增加节点)AddContact函数
增加联系人其实就是增加一个节点;
增加节点有两种情况:
①当头节点没有数据时,我们将第一个数据放到头节点中;
②当头节点中已有数据时,我们通过头节点找到尾节点,然后将尾节点的指针域指向新增节点
所以有以下代码:
void AddContact(contact** head)
{
assert(head);//进行断言防止pc为空指针,它的头文件为<assert.h>
contact* pnew = *head;
contact* pmove = *head;
pnew = (contact*)malloc(sizeof(contact));
//写入信息
printf("请输入姓名:>");
scanf("%s", pnew->data.name);
printf("请输入单位:>");
scanf("%d", &(pnew->data.units));
printf("请输入QQ:>");
scanf("%s", pnew->data.QQ);
printf("请输入电话:>");
scanf("%s", pnew->data.tele);
printf("请输入地址:>");
scanf("%s", pnew->data.addr);
pnew->next = NULL;
if (*head == NULL)//第一次添加
{
*head = pnew;
pnew->next = NULL;
}
else
{
while (pmove->next != NULL)//第二次及以后添加节点
{
pmove = pmove->next;//找到尾节点
}
pmove->next = pnew;
pnew->next = NULL;
}
}
2)显示通讯录中所有人的信息(遍历)ShowContact函数
既然要遍历所有节点,我们就需要一个指针进行遍历,当这个指针指向的空间为NULL时,完成遍历。
void ShowContact(const contact** head)
{
system("cls");
contact* pmove = *head;
printf("%10s\t%5s\t%10s\t%12s\t%20s\n", "姓名", "单位", "QQ", "电话", "地址");//打印一个表头在屏幕上
while (pmove != NULL)//循环到最后一个节点停止
{
printf("%10s\t%5d\t%10s\t%12s\t%20s\n", pmove->data.name,
pmove->data.units,
pmove->data.QQ,
pmove->data.tele,
pmove->data.addr);
pmove = pmove->next;
}
}
3)显示指定联系人的信息(遍历)SearchContact函数
需要一个指针进行遍历所有节点,直到找到目标节点,遍历结束,然后直接将节点内的信息打印出来即可。代码如下:
void SearchContact(const contact** head)
{
char name[20];
printf("请输入你要查找联系人的姓名:>");
scanf("%s", name);
contact* pos = *head;
//找到要查看到节点
while (strcmp(pos->data.name, name) != 0 && pos->next != NULL)
{
pos = pos->next;
}
if (strcmp(pos->data.name, name) == 0)
{
printf("%10s\t%5s\t%10s\t%12s\t%20s\n", "姓名", "单位", "QQ", "电话", "地址");//打印一个表头,要注意打印形式
printf("%10s\t%5d\t%10s\t%12s\t%20s\n", pos->data.name,
pos->data.units,
pos->data.QQ,
pos->data.tele,
pos->data.addr);
}
else
printf("无此联系人!\n");
}
4)删除指定联系人信息(删除节点)DelContact函数
删除一个节点三种情况:
①如果删除的联系人是头节点所存放的信息,则需要将头节点指向头节点的下一个节点,使头节点的下一个节点成为新的头节点。
②如果删除的联系人不是头节点所存放的信息,则需要通过遍历找到目标节点的上一个节点,然后将目标节点上一个节点的指针域指向目标节点后一个节点,最后将目标节点的空间用free释放掉即可。
③如果头节点为NULL时,没有可以删除的节点,这时给个提醒然后直接退出
void DelContact(contact** head)
{
assert(head);
if (*head == NULL)
{
printf("通讯录为空,删除失败!\n");
exit(0);
}
char name[20];
printf("请输入要删除联系人的姓名:>");
scanf("%s", name);
//删除
contact* pmove = *head;
contact* plast = *head;
//找到要删除的节点,同时移动节点的时候当移动到最后一个节点还没有找到就停止移动节点防止越界
while (strcmp(pmove->data.name, name) != 0 && pmove->next!=NULL)
{
plast = pmove;//保存移动节点的上一个节点
pmove = pmove->next;
}
if (strcmp(pmove->data.name, name) == 0)
{
//如果删除的是表头节点
if (pmove == *head)
*head = pmove->next;
else//如果删除的是中间的节点
plast->next = pmove->next;
printf("删除成功!\n");
}
else
printf("无此联系人,无法删除!\n");
}
5)修改联系人信息 ModifyContact函数
修改联系人信息之前,我们首先要找到要存放该信息的节点,所以这里的思路跟SearchContact函数思路相似。找到之后直接对该节点的数据域进行赋值就行了。代码如下:
void ModifyContact(contact** head)
{
char name[20];
printf("请输入你要修改联系人的姓名\n");
scanf("%s", name);
contact* pos = *head;
//找到该联系人
while (strcmp(pos->data.name, name) != 0 && pos->next != NULL)
{
pos = pos->next;
}
if (strcmp(pos->data.name, name) == 0)
{
printf("请输入姓名:>");
scanf("%s", pos->data.name);
printf("请输入单位:>");
scanf("%d", &(pos->data.units));
printf("请输入QQ:>");
scanf("%s", pos->data.QQ);
printf("请输入电话:>");
scanf("%s", pos->data.tele);
printf("请输入地址:>");
scanf("%s", pos->data.addr);
printf("修改成功!\n");
}
else
{
printf("未查找到该联系人,无法修改!\n");
exit(0);
}
}
6)通讯录排序 SortContact函数
排序的思想:
(这里我按照“不秃也很强的博客”排序思想进行讲解一下,当然你也可以可以使用其他的排序方法,比如冒泡排序法)
①一趟排序:先用一个指针p_current作为基点,第二个指针pmove从基点后面一个数据向后遍历,如果pmove指向的数据大于p_current指向的数据就将两者的数据进行交换,然后pmove向后移动,直到遍历所有数据;
②排序的趟数:第一趟排序完成后,p_current向后移动,直到p_current->next指向NULL时,就是所有的趟数
这里交换完数据后,还要记得改变指针域的指向
代码如下:
void SortContact(contact** head)//利用姓名进行排序
{
contact* pmove = *head;
contact* pcurrent = *head;
contact temp;//交换时的临时变量
if (*head == NULL)
{
printf("通讯录为空,无法排序!\n");
exit(0);
}
if ((*head)->next == NULL)//链表只有一个节点时不需要排序直接返回
{
exit(0);
}
while (pcurrent->next != NULL)
{
pmove = pcurrent;//
while (pmove != NULL)
{
if (strcmp(pmove->data.name, pcurrent->data.name) < 0)
{
temp = *pmove;
*pmove = *pcurrent;
*pcurrent = temp;//交换数值
temp.next = pmove->next;
pmove->next = pcurrent->next;
pcurrent->next = temp.next;//交换指针域
}
pmove = pmove->next;
}
pcurrent = pcurrent->next;
}
printf("排序成功!\n");
}
7)释放空间 DestroyContact 函数
我们用malloc 相关函数开辟空间后,要用free函数进行释放空间,避免造成内存泄漏的问题。
代码如下:
void DestroyContact(contact** head)
{
contact* pmove = *head;
while (*head != NULL)
{
pmove = *head;//记录表头的地址
*head = (*head)->next;//表头后移,直到最后一个节点
free(pmove);
pmove->next = NULL;
}
}
下面我们开始实现文件相关函数的实现
文件相关函数
8)将通讯录中的数据保存到文件中 SaveContact 函数
首先,用 fopen 函数以读的方式打开文件
为了便于观察保存的文件数据,我们现在文件中写一个表头
fprintf(pfWrite, "%-10s\t%5s\t%10s\t%12s\t %20s\n",
"姓名", "单位", "QQ", "电话", "地址");
之后开始录入数据:
通过循环遍历:遍历指针从头节点到指向NULL时,停止录入
用 fprintf 函数(该函数和 printf 函数的使用方法相似,只是多了一个文件(你要写入的文件,即你用fopen打开的文件)的指针参数)
最后,关闭文件
整体代码如下:
void SaveContact(contact** head)
{
assert(*head);
contact* pmove = *head;
FILE* pfWrite = fopen("Contact.txt", "w");
if (pfWrite == NULL)
{
perror("SaveContact");
exit(0);
}
int i = 0;
fprintf(pfWrite, "%-10s\t%5s\t%10s\t%12s\t %20s\n",
"姓名", "单位", "QQ", "电话", "地址");
while(pmove!=NULL)
{
fprintf(pfWrite, "%-10s\t%4d\t %6s\t%12s%20s\n", pmove->data.name,
pmove->data.units,
pmove->data.QQ,
pmove->data.tele,
pmove->data.addr);
pmove = pmove->next;
}
fclose(pfWrite);
pfWrite = NULL;
}
9)从文件中录入信息 LoadContact 函数
当文件中事先有内容时 ,我们可以使用该函数将文件中的数据导入到通讯录中,这样我们就可以进行查看、编辑相关操作
首先,用 fopen 函数以读的方式打开文件
由于我们文件中有一个表头,表头后面才是我们想要的数据,所以要先将文件指针跳过表头,使它指向我们要录入数据的开头:
由于我们输入表头时,其后有一个 '\n', 所以我们可以用 '\n' 做表头结束的标志。
while (fgetc(pfRead) != '\n');
此时表头内容已经跳过,下面开始读取数据:
这里我们用 feof 函数, 该函数遇到文件结尾时返回非零,所以可以用 !feof() 作为循环结束条件(非零的否定就是零,条件为假,结束循环)
录入数据时,就相当于我们增加联系人函数,只不过在增加联系人函数中,我们是从键盘录入数据;而在这里我们用 fscanf 函数 从文件中录入数据。所以这个过程也就是创建节点的过程。
void LoadContact(contact** head)
{
FILE* pfRead = fopen("Contact.txt", "r");
if (pfRead == NULL)
{
perror("LoadContact");
exit(0);
}
while (fgetc(pfRead) != '\n');//将文件指针移动到表头末尾
int leap = ftell(pfRead);//计算文件指针的偏移量
fseek(pfRead, leap, SEEK_SET);//将文件指针后移,跳过表头,只读取数据
while (!feof(pfRead))
{
contact* pnew = *head;
contact* pmove = *head;
pnew = (contact*)malloc(sizeof(contact));
pnew->next = NULL;
//将文件的内容写入节点中
fscanf(pfRead, "%10s\t%4d\t %6s\t%12s%20s\n", pnew->data.name,
&(pnew->data.units),
pnew->data.QQ,
pnew->data.tele,
pnew->data.addr);
//链接节点
if (*head == NULL)//第一次添加
{
*head = pnew;
pnew->next = NULL;
}
else
{
while (pmove->next != NULL)
{
pmove = pmove->next;
}
pmove->next = pnew;
pnew->next = NULL;
}
}
printf("登录成功!\n");
fclose(pfRead);
pfRead = NULL;
}
要注意的是 :当我们从文件中读取数据时,一定要注意读取的格式,我们是以什么样的形式写入到文件的,就要以什么样的形式从文件读取出来。
最后,关闭文件。
——————————————————————————
如果你觉得这篇博客对你有帮助的话 ,希望你能够给我点个赞,鼓励一下我。感谢感谢……