单链表,文件操作,结构体,二级指针
思路:利用上一篇博客-单链表,对单链表简易修改,应用到通讯录.
通讯录只能能存100个人
用户信息:名字,性别,年龄,电话,地址
通讯录的展示,增,删,改,查,找.
联系人信息存储
为了显现区别,笔者备份单链表的文件并且更名为SeqList_copy.h
和SeqList_copy.c
创建Contact.h
和Contact.c
以及测试代码功能的test.c
test.c
和Contact.c
引用的头文件都是Contact.h
和SeqList_copy.h
在Contact.h
头文件中
定义名字最大长度,性别最大长度,电话最大长度,地址最大长度
#define NAME_MAX 120
#define SEX_MAX 10
#define TEL_MAX 20
#define ADDR_MAX 50
//前置声明
typedef struct SListNode contact;
//定义结构体
typedef struct ContactInfo
{
char name[NAME_MAX];
char sex[SEX_MAX];
int age;
char tel[TEL_MAX];
char addr[ADDR_MAX];
}ConInfo;
void ContactInit(contact** pcon)
{
FILE* pf = fopen("Contact_Info.txt", "rb"); //二进制方式打开文件
if (pf == NULL)
{
perror("fopen error.\n"); //主动报错,打开失败
return;
}
ConInfo info;
//回顾一下fread
//size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
//从流中读取一个由count元素组成的数组,每个元素的大小为size字节,并将它们存储在ptr指定的内存块中。
//流的位置指示器按读取的总字节数前进。
//如果成功读取的总字节数为(size * count)
while (fread(&info, sizeof(info), 1, pf))
{
SLPushBack(pcon, info);
}
printf("历史数据已导入通讯录\n");
}
销毁通讯录之前一定要保存数据,不然2.3打开有存储联系人的通讯录的作用就没有了
void ContactSave(contact* con)
{
FILE* pf = fopen("Contact_Info.txt", "wb");
if (pf == NULL)
{
perror("fopen error\n");
return;
}
contact* cur = con;
while (cur)
{
fwrite(&(cur->data), sizeof(cur->data), 1, pf);
cur = cur->next;
}
printf("成功保存通讯录数据\n");
}
void ContactDestory(contact** pcon)
{
ContactSave(*pcon);
SLDestory(pcon);
}
使用单链表一节中尾插法添加数据
void ContactAdd(contact** pcon)
{
ConInfo info;
printf("请输入添加联系人姓名:\n");
scanf("%s", &info.name);
printf("请输入添加联系人性别:\n");
scanf("%s", &info.sex);
printf("请输入添加联系人年龄:\n");
scanf("%d", &info.age);
printf("请输入添加联系人电话:\n");
scanf("%s", &info.tel);
printf("请输入添加联系人地址:\n");
scanf("%s", &info.addr);
SLPushBack(pcon, info);
printf("已添加联系人数据\n");
}
借助FindByName()函数,找到索引.然而缺陷明显,找到的索引是第一次出现的输入的姓名,同样,只能通过姓名查找
//我们这里通过姓名删除联系人
contact* FindByName(contact* con, char name[])
{
contact* cur = con;
while (cur)
{
if (strcmp(cur->data.name,name) == 0)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
void ContactDel(contact** pcon)
{
char name[NAME_MAX];
printf("请输入你要删除的联系人姓名:\n");
scanf("%s", name);
contact* pos = FindByName(*pcon, name);
if (pos == NULL)
{
printf("该联系人不存在\n");
return;
}
SLErase(pcon, pos);
printf("已删除该联系人\n");
}
注意
age
年龄是int
整型,其他四个数据结构都是数组,用scanf
时,这里的数组相当于首元素地址,不用&
.
void ContactModify(contact* con)
{
char name[NAME_MAX];
printf("请输入你要修改的联系人姓名:\n");
scanf("%s", name);
contact* pos = FindByName(con, name);
if (pos == NULL)
{
printf("该联系人不存在\n");
return;
}
printf("该联系人姓名修改为:\n");
scanf("%s", pos->data.name);
printf("该联系人性别修改为:\n");
scanf("%s", pos->data.sex);
printf("该联系人年龄修改为:\n");
scanf("%d", &pos->data.age);
printf("该联系人电话修改为:\n");
scanf("%s", pos->data.tel);
printf("该联系人地址修改为:\n");
scanf("%s", pos->data.addr);
printf("修改成功\n");
}
同样根据姓名查看详细联系人的通讯录信息
void ContactFind(contact* con)
{
char name[NAME_MAX];
printf("请输入你要查找的联系人姓名:\n");
scanf("%s", name);
contact* pos = FindByName(con, name);
if (pos == NULL)
{
printf("该联系人不存在\n");
return;
}
printf("找到了\n");
printf("%s %s %d %s %s\n",
pos->data.name,
pos->data.sex,
pos->data.age,
pos->data.tel,
pos->data.addr
);
}
第一次自己写的时候,
cur=cur->next
放在while
循环外面了.QAQ
void ContactShow(contact* con)
{
printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "地址");
contact* cur = con;
while (cur)
{
printf("%s %s %d %s %s\n",
cur->data.name,
cur->data.sex,
cur->data.age,
cur->data.tel,
cur->data.addr);
cur = cur->next;
}
}
#pragma once
#include
#include
#include
#include
#include"Contact.h"
//定义链表的结构
//typedef int SLDataType
typedef struct ContactInfo SLDataType; //更改SLDataType的类型
typedef struct SListNode
{
SLDataType data; //保存的数据
struct SListNode* next; //指针变量存放下一个节点的地址
}SLNode;
//1 打印链表
void SLPrint(SLNode* phead);
//2 尾插
void SLPushBack(SLNode** pphead, SLDataType x);
//3 头插
void SLPushFront(SLNode** pphead, SLDataType x);
//4 尾删
void SLPopBack(SLNode** pphead);
//5 头删
void SLPopFront(SLNode** pphead);
//SLFind()找到要查找数据的下标
//找节点的函数这里传一级实际上就可以了,因为不改变头结点
//但是这里要写二级指针,因为要保持接口一致性
//缺点:这个函数有一定的局限性,他找到数据x的下标是第一次出现的x的下标后,不会找后续的x的下标
SLNode* SLFind(SLNode** pphead, SLDataType x);
//6 定点前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x);
//7 定点后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x);
//8 删除pos节点
void SLErase(SLNode** pphead, SLNode* pos);
//9 删除pos后的节点
void SLEraseAfter(SLNode* pos);
//10 销毁链表
void SLDestory(SLNode** pphead);
#include"SList_copy.h"
//1 创建新的节点的函数
SLNode* SLBuyNode(SLDataType x)
{
SLNode* node = (SLNode*)malloc(sizeof(SLNode));
node->data = x;
node->next = NULL;
return node;
}
//2 尾插
void SLPushBack(SLNode** pphead, SLDataType x) //保存第一个节点的指针的地址
{
assert(pphead);
//创建一个节点
SLNode* node = SLBuyNode(x);
if (*pphead == NULL)
{
*pphead = node;
return;
}
//链表不为空,找尾
SLNode* pcur = *pphead;
while (pcur->next)//相当于pcur->next!=NULL
{
pcur = pcur->next;
}
pcur->next = node;
}
//3 头插
void SLPushFront(SLNode** pphead, SLDataType x)
{
assert(pphead);
SLNode* node = SLBuyNode(x);
//新结点跟头结点连接起来
node->next = *pphead;
//让新节点成为头结点
*pphead = node;
}
//4 尾删
void SLPopBack(SLNode** pphead)
{
assert(pphead);
assert(*pphead); //第一个节点不能为空
//判断只有一个结点的情况
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
return;
}
//找到尾节点和尾节点的前一个节点
SLNode* pre = NULL;
SLNode* ptail = *pphead;
while (ptail->next)
{
pre = ptail;
ptail = ptail->next;
}
//pre的next指针不指向ptail,而是指向ptail的下一个节点
//pre->next = NULL;
pre->next = ptail->next;
//因为在SLPrint(SLNode* phead)函数中有判断节点是否为空while (pcur),所以置空
free(ptail);
ptail = NULL;
}
//5 头插
void SLPopFront(SLNode** pphead)
{
assert(pphead);
assert(*pphead); //第一个节点不能为空
SLNode* del = *pphead;
*pphead = (*pphead)->next;
free(del);
del = NULL;
}
//6 定点前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{
assert(pphead);
assert(pos); //pos不能为空
assert(*pphead); //NULL前插入数据是荒谬的,所以assert第一个节点
SLNode* node = SLBuyNode(x);
//当只有一个结点(pos就是第一个节点)
//if (((*pphead)->next) == NULL||pos==*pphead),可以简化成以下代码
if (pos == *pphead) {
node->next = *pphead;
*pphead = node;
return;
}
//找到pos的前一个节点
SLNode* pre = *pphead;
while (pre->next != pos)
{
pre = pre->next;
}
//处理pre node pos的位置
node->next = pos;
pre->next = node;
}
//7 定点后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x)
{
assert(pos);
SLNode* node = SLBuyNode(x);
node->next = pos->next;
pos->next = node;
}
//8 删除pos节点
void SLErase(SLNode** pphead, SLNode* pos)
{
assert(*pphead);
assert(pphead);
assert(pos);
//pos是头结点
if (pos == *pphead)
{
*pphead = (*pphead)->next;
free(pos);
return;
}
//pos不是头结点,找pos的前一个节点
SLNode* pre = *pphead;
while (pre->next != pos)
{
pre = pre->next;
}
pre->next = pos->next;
free(pos);
pos = NULL; //代码规范
}
//9 删除pos后的节点
void SLEraseAfter(SLNode* pos)
{
//删除pos之后的节点也不能空
assert(pos && pos->next);
SLNode* del = pos->next;
pos->next = del->next;
free(del);
}
void SLDestory(SLNode** pphead)
{
assert(pphead);
SLNode* pcur = *pphead;
while (pcur)
{
SLNode* next = pcur->next;
free(pcur);
pcur = next;
}
//头结点记得置空
*pphead = NULL;
}
#pragma once
//创建保存联系人数据的结构
#define NAME_MAX 120
#define SEX_MAX 10
#define TEL_MAX 20
#define ADDR_MAX 50
typedef struct ContactInfo
{
char name[NAME_MAX];
char sex[SEX_MAX];
int age;
char tel[TEL_MAX];
char addr[ADDR_MAX];
}ConInfo;
//前置声明
typedef struct SListNode contact;
//1.打开通讯录
void ContactInit(contact** pcon);
//2.保存数据后销毁通讯录
void ContactDestory(contact** pcon);
//3.添加联系人
void ContactAdd(contact** pcon);
//4.删除联系人
void ContactDel(contact** pcon);
//5.修改联系人
void ContactModify(contact* con);
//6.查找联系人
void ContactFind(contact* pcon);
//7.查看通讯录
void ContactShow(contact* pcon);
#define _CRT_SECURE_NO_WARNINGS
#include"Contact.h"
#include"SList_copy.h"
//1.打开通讯录
void ContactInit(contact** pcon)
{
FILE* pf = fopen("Contact_Info.txt", "rb");
if (pf == NULL)
{
perror("fopen error.\n"); //主动报错,打开失败
return;
}
ConInfo info;
//回顾一下fread
//size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
//从流中读取一个由count元素组成的数组,每个元素的大小为size字节,并将它们存储在ptr指定的内存块中。
//流的位置指示器按读取的总字节数前进。
//如果成功读取的总字节数为(size * count)
while (fread(&info, sizeof(info), 1, pf))
{
SLPushBack(pcon, info);
}
printf("历史数据已导入通讯录\n");
}
void ContactSave(contact* con)
{
FILE* pf = fopen("Contact_Info.txt", "wb");
if (pf == NULL)
{
perror("fopen error\n");
return;
}
contact* cur = con;
while (cur)
{
fwrite(&(cur->data), sizeof(cur->data), 1, pf);
cur = cur->next;
}
printf("成功保存通讯录数据\n");
}
void ContactDestory(contact** pcon)
{
ContactSave(*pcon);
SLDestory(pcon);
}
void ContactAdd(contact** pcon)
{
ConInfo info;
printf("请输入添加联系人姓名:\n");
scanf("%s", &info.name);
printf("请输入添加联系人性别:\n");
scanf("%s", &info.sex);
printf("请输入添加联系人年龄:\n");
scanf("%d", &info.age);
printf("请输入添加联系人电话:\n");
scanf("%s", &info.tel);
printf("请输入添加联系人地址:\n");
scanf("%s", &info.addr);
SLPushBack(pcon, info);
printf("已添加联系人数据\n");
}
//我们这里通过姓名删除联系人
contact* FindByName(contact* con, char name[])
{
contact* cur = con;
while (cur)
{
if (strcmp(cur->data.name,name) == 0)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
void ContactDel(contact** pcon)
{
char name[NAME_MAX];
printf("请输入你要删除的联系人姓名:\n");
scanf("%s", name);
contact* pos = FindByName(*pcon, name);
if (pos == NULL)
{
printf("该联系人不存在\n");
return;
}
SLErase(pcon, pos);
printf("已删除该联系人\n");
}
void ContactModify(contact* con)
{
char name[NAME_MAX];
printf("请输入你要修改的联系人姓名:\n");
scanf("%s", name);
contact* pos = FindByName(con, name);
if (pos == NULL)
{
printf("该联系人不存在\n");
return 1;
}
printf("该联系人姓名修改为:\n");
scanf("%s", pos->data.name);
printf("该联系人性别修改为:\n");
scanf("%s", pos->data.sex);
printf("该联系人年龄修改为:\n");
scanf("%d", &pos->data.age);
printf("该联系人电话修改为:\n");
scanf("%s", pos->data.tel);
printf("该联系人地址修改为:\n");
scanf("%s", pos->data.addr);
printf("修改成功\n");
}
void ContactFind(contact* con)
{
char name[NAME_MAX];
printf("请输入你要查找的联系人姓名:\n");
scanf("%s", name);
contact* pos = FindByName(con, name);
if (pos == NULL)
{
printf("该联系人不存在\n");
return;
}
printf("找到了\n");
printf("%s %s %d %s %s\n",
pos->data.name,
pos->data.sex,
pos->data.age,
pos->data.tel,
pos->data.addr
);
}
void ContactShow(contact* con)
{
printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "地址");
contact* cur = con;
while (cur)
{
printf("%s %s %d %s %s\n",
cur->data.name,
cur->data.sex,
cur->data.age,
cur->data.tel,
cur->data.addr);
cur = cur->next;
}
}
test()
我封装了五个函数,都是用来测试通讯录功能的,调试结束后,就将函数封装在一起,写一个菜单完成通讯录功能.
#define _CRT_SECURE_NO_WARNINGS
#include"Contact.h"
#include"SList_copy.h"
void test1()
{
contact *pcontact=NULL;
ContactInit(&pcontact);
}
void test2()
{
contact* pcontact = NULL;
ContactInit(&pcontact);
ContactAdd(&pcontact);
ContactDel(&pcontact);
ContactShow(pcontact);
}
void test3()
{
contact* pcontact = NULL;
ContactInit(&pcontact);
ContactShow(pcontact);
ContactAdd(&pcontact);
ContactShow(pcontact);
ContactDel(&pcontact);
ContactShow(pcontact);
}
void test4()
{
contact* pcontact = NULL;
ContactInit(&pcontact);
ContactAdd(&pcontact);
ContactAdd(&pcontact);
ContactShow(pcontact);
ContactFind(pcontact);
ContactShow(pcontact);
}
void test5()
{
contact* pcontact = NULL;
ContactInit(&pcontact);
ContactAdd(&pcontact);
ContactShow(pcontact);
ContactModify(pcontact);
ContactShow(pcontact);
ContactDestory(&pcontact);
}
void menu()
{
printf("\n");
printf("****************************\n");
printf("****************************\n");
printf("************通讯录***********\n");
printf("*********1.添加联系人********\n");
printf("*********2.删除联系人********\n");
printf("*********3.修改联系人********\n");
printf("*********4.查找联系人********\n");
printf("*********5.查看通讯录********\n");
printf("*********0.退 出************\n");
printf("****************************\n");
printf("****************************\n");
}
int main()
{
//test1();
//test2();
//test3();
//test4();
//test5();
int op = 0;
contact* con=NULL;
ContactInit(&con);
do {
menu();
printf("请选择:\n");
scanf("%d", &op);
switch (op)
{
case 1:
ContactAdd(&con);
break;
case 2:
ContactDel(&con);
break;
case 3:
ContactModify(con);
break;
case 4:
ContactFind(con);
break;
case 5:
ContactShow(con);
break;
case 0:
printf("退出了哈\n");
break;
default:
printf("输入错误\n");
break;
}
} while (op!=0);
ContactDestory(&con);
return 0;
}
在多次语法错误,死循环,输出乱码,代码崩溃后,终于简单完成通讯录的功能!