1.前言
好久不见,甚是想念!最近因为大过年+开学好久没有更新了,接下来就更新又一个保姆级教学----也就是我们的通讯录的第二个版本,也就是用链表实现,话不多说,咱们直接开干!
2.代码实现
首先,东西还是那几个东西,在实现通讯录的时候,我们当然也要写一个单链表,所以,通过这篇文章,我们也可以好好的复习一下链表哦,不会的同学可以好好复习一下。我们可以用SList.c和SList.h来代表链表的实现,然后紧接着又是我们熟悉的Contact.c、Contact.h,最后就是我们的测试文件Contest.c,总共还是五个文件。(tips:建议看完我上一篇博客也就是顺序表实现通讯录再来看这里,会有不同的收获)。
接下来就可以开始写代码啦!还是老样子,代码和注释都在一起,灰常详细哦,同学们细品,有哪里有疑问可以在评论区留言哦(个人实现,如有不足请多多指教)!
tips:由于通讯录的注释在上一篇博客中注释了,所以这一次就没有太多注释咯,思路基本是一样的,就是实现方式不同,如果有不会的建议先看看我的上一篇博客哦!多多支持!
//SList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include"Contact.h"
typedef struct PersonInfo SLTDataType;
typedef struct SListNode
{
SLTDataType data; //节点数据
struct SListNode* next; //指针保存下一个节点的地址
}SLTNode;
//打印单链表
void SLTPrint(SLTNode* phead);
//开辟一个新节点
SLTNode* SLTBuyNode(SLTDataType x);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//查找
void SLTFind(SLTNode** pphead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsertForward(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除指定位置之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SLTDestroy(SLTNode** pphead);
//SList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead; //让phead一直指向第一个数据,让pcur遍历链表
while (pcur) //当pcur不为空的时候执行循环
{
printf("%d->", pcur->data); //打印pcur指向的当前数据
pcur = pcur->next; //然后指向下一个节点的地址
}
printf("NULL\n");
}
SLTNode* SLTBuyNode(SLTDataType x) //为x开辟一个专用的节点
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); //用一个malloc开辟大小为SLTNode的新节点
newnode->data = x; //将要插入的数据x放入新节点中
newnode->next = NULL; //当然新节点的下一个节点是空
return newnode; //返回去要插入的新节点
}
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x); //返回新节点
//如果链表为空,新节点作为phead
if (*pphead == NULL)
{
*pphead = newnode;
return;
}
//链表不为空,找尾节点
SLTNode* ptail = *pphead; //ptail表示的是尾节点,所以ptail的next表示的肯定要是空
while (ptail->next) //只有ptail的下一个不为空才能进行循环
{
ptail = ptail->next; //遍历链表,找到ptail下一个为空的节点,就是末节点
}
ptail->next = newnode;
}
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//头插的话,链表为空和不为空都是在前面插,把新的节点当作新的phead,所以不用分情况
SLTNode* newnode = SLTBuyNode(x); //传新节点进来
newnode->next = *pphead; //新节点的下一个必定是原来的phead
*pphead = newnode; //新节点作为新的phead
}
void SLTPopBack(SLTNode** pphead)
{
assert(pphead);
//链表不能为空,空的话不能删
assert(*pphead);
//如果链表只有一个节点
if ((*pphead)->next == NULL)
{
free(*pphead); //直接free掉头,就相当于删除
*pphead = NULL;
return;
}
//如果链表有多个节点
SLTNode* ptail = *pphead; //ptail就是尾节点,是即将要删除的点
SLTNode* prev = NULL; //prev就是尾节点的前一个节点
while (ptail->next) //遍历链表,让ptail是尾节点
{
prev = ptail;
ptail = ptail->next; //使prev成为ptail的前一个节点
}
prev->next = NULL; //ptail是要删除的点,删除掉ptail后,prev就是新的尾节点,所以prev的下一个一定是空
//销毁尾节点
free(ptail);
ptail = NULL;
}
void SLTPopFront(SLTNode** pphead)
{
assert(pphead);
//链表不能为空
assert(*pphead);
//让第二个节点成为新的头,原来的头节点释放掉
SLTNode* newhead = (*pphead)->next; //新的头是旧头的下一个
free(*pphead); //把旧的头释放掉
*pphead = newhead; //新的头成为新头节点
}
//void SLTFind(SLTNode** pphead, SLTDataType x)
//{
// assert(pphead);
// //遍历链表
// SLTNode* pcur = *pphead; //定义一个pcur遍历链表
// while (pcur)
// {
// if (pcur->data == x) //如果pcur指向的节点是x的节点,说明找到了
// {
// //说明找到了
// return pcur;
// }
// pcur = pcur->next;
// }
// //循环结束还未找到
// return NULL;
//}
void SLTInsertForward(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
assert(*pphead); //由于pos不能为空,所以链表也不能为空
//如果pos刚好是头节点
if (pos == *pphead)
{
SLTPushFront(pphead, x);
return;
}
//如果pos不是头节点
SLTNode* prev = *pphead;
while (prev->next != pos) //遍历链表,找pos之前的节点prev
{
prev = prev->next;
}
SLTNode* newnode = SLTBuyNode(x); //创建要插入的节点
prev->next = newnode; //把要插入的节点插到原来在pos之前的节点prev后面
newnode->next = pos; //插入节点的后面当然就是pos
}
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x); //先为x开辟新节点
newnode->next = pos->next;
pos->next = newnode;
}
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(pos);
assert(*pphead);
//如果pos刚好是头节点,就无prev,直接执行头删
if (pos == *pphead)
{
SLTPopFront(pphead);
return;
}
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next; //寻找pos的前驱节点prev
}
prev->next = pos->next; //要先写这一步的原因是pos如果提前释放了就找不到pos的下一个了
free(pos);
pos = NULL; //删除pos节点
}
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next); //pos->next不能为空,否则无法执行删除
SLTNode* del = pos->next; //定义del为要删除的节点
pos->next = pos->next->next; //所以pos要连接的节点就是del的下一个节点
free(del);
del = NULL; //删除pos的下一个节点
}
void SLTDestroy(SLTNode** pphead)
{
assert(pphead);
//assert(*pphead);
//由于链表是一个一个节点构成的,无法像顺序表一样一次性销毁,要一个一个节点的进行销毁
SLTNode* pcur = *pphead; //定义一个pcur遍历链表,并一个一个销毁
while (pcur)
{
SLTNode* next = pcur->next; //先从第二个开始进行销毁
free(pcur);
pcur = next; //每完成一个指向下一个节点
}
*pphead = NULL; //不要忘了还有头节点未销毁
}
//Contact.h
#pragma once
#pragma once
#define NAME_MAX 100
#define SEX_MAX 6
#define TEL_MAX 12
#define ADDR_MAX 100
//前置声明
typedef struct SListNode contact;
//用户数据
typedef struct PersonInfo
{
char name[NAME_MAX];
char gender[SEX_MAX];
int age;
char phone[TEL_MAX];
char addr[ADDR_MAX];
}Info;
//初始化通讯录
void InitContact(contact** con);
//添加通讯录数据
void AddContact(contact** con);
//删除通讯录数据
void DelContact(contact** con);
//展示通讯录数据
void ShowContact(contact* con);
//查找通讯录数据
void FindContact(contact* con);
//修改通讯录数据
void ModifyContact(contact** con);
//销毁通讯录数据
void DestroyContact(contact** con);
//Contact.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Contact.h"
#include"SList.h"
void InitContact(contact** con)
{
//哨兵位
contact* head = (contact*)malloc(sizeof(contact));
head->next = NULL;
}
void AddContact(contact** con)
{
Info info;
printf("请输入联系人姓名:\n");
scanf("%s", &info.name);
printf("请输入联系人性别:\n");
scanf("%s", &info.gender);
printf("请输入联系人年龄:\n");
scanf("%d", &info.age);
printf("请输入联系人电话:\n");
scanf("%s", &info.phone);
printf("请输入联系人地址:\n");
scanf("%s", &info.addr);
SLTPushBack(con, info);
}
contact* FindByName(contact* con,char name[])
{
contact* pcur = con;
while (pcur)
{
if (strcmp(pcur->data.name, name) == 0)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
void DelContact(contact** con)
{
printf("请输入要删除的联系人姓名:\n");
char name[NAME_MAX];
scanf("%s", name);
contact* ret = FindByName(*con, name);
if (ret == NULL)
{
printf("用户不存在!\n");
return;
}
SLTErase(con, ret);
printf("删除成功!\n");
}
void ShowContact(contact* con)
{
printf("%s\t%s\t%s\t%s\t%s\n", "姓名", "性别", "年龄", "电话", "地址");
contact* pcur = con;
while (pcur)
{
printf("%s %s %d %s %s\n", pcur->data.name, pcur->data.gender, pcur->data.age, pcur->data.phone, pcur->data.addr);
pcur = pcur->next;
}
}
void FindContact(contact* con)
{
char name[NAME_MAX];
printf("请输入要查找的联系人姓名:\n");
scanf("%s", name);
contact* ret = FindByName(con, name);
if (ret == NULL)
{
printf("用户不存在!\n");
return;
}
printf("%s\t%s\t%s\t%s\t%s\n", "性名", "性别", "年龄", "电话", "地址");
printf("%s %s %d %s %s\n", ret->data.name, ret->data.gender, ret->data.age, ret->data.phone, ret->data.addr);
}
void ModifyContact(contact** con)
{
char name[NAME_MAX];
printf("请输入要修改的联系人姓名:\n");
scanf("%s", &name);
contact* ret = FindByName(*con, name);
if (ret == NULL)
{
printf("用户不存在!\n");
return;
}
printf("请输入联系人姓名:\n");
scanf("%s", ret->data.name);
printf("请输入联系人性别:\n");
scanf("%s", ret->data.gender);
printf("请输入联系人年龄:\n");
scanf("%d", &ret->data.age);
printf("请输入联系人电话:\n");
scanf("%s", ret->data.phone);
printf("请输入联系人地址:\n");
scanf("%s", ret->data.addr);
}
void DestroyContact(contact** con)
{
SLTDestroy(con);
}
//Contest.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
#include"Contact.h"
//通讯录菜单
void menu()
{
printf("******************* 通讯录 ********************\n");
printf("******1.添加联系人 2.删除联系人*****\n");
printf("******3.修改联系人 4.查找联系人*****\n");
printf("******5.查看通讯录 0.退出通讯录*****\n");
printf("***************************************************\n");
}
int main()
{
int op = -1;
//创建通讯录结构对象
contact* con = NULL;
InitContact(&con);
do
{
menu();
scanf("%d", &op);
switch (op)
{
case 1:
//添加联系人
AddContact(&con);
break;
case 2:
//删除联系人
DelContact(&con);
break;
case 3:
//修改联系人
ModifyContact(&con);
break;
case 4:
//查找联系人
FindContact(con);
break;
case 5:
//查看通讯录
ShowContact(con);
break;
case 0:
//退出通讯录
printf("您已退出您的通讯录.....\n");
break;
default:
break;
}
} while (op);
//销毁通讯录
DestroyContact(&con);
return 0;
}
品玩了吗?是不是很有收获呢?相信你对链表有了很大的理解了吧!咱们下期见!每天干货满满!