文章目录
- 1 基础要求
- 2 通讯录功能
- 2.1 引入单链表的文件
- 2.2 定义联系人数据结构
- 2.3 打开通讯录
- 2.4 保存数据后销毁通讯录
- 2.5 添加联系人
- 2.6 删除联系人
- 2.7 修改联系人
- 2.8 查找联系人
- 2.9 查看通讯录
- 3 通讯录代码展示
- 3.1 SeqList_copy.h
- 3.2 SeqList_copy.c
- 3.3 Contact.h
- 3.4 Contact.c
- 3.5 test.c
- 3.6 调试控制台截图
1 基础要求
单链表,文件操作,结构体,二级指针
思路:利用上一篇博客-单链表,对单链表简易修改,应用到通讯录.
2 通讯录功能
通讯录只能能存100个人
用户信息:名字,性别,年龄,电话,地址
通讯录的展示,增,删,改,查,找.
联系人信息存储
2.1 引入单链表的文件
为了显现区别,笔者备份单链表的文件并且更名为SeqList_copy.h
和SeqList_copy.c
创建Contact.h
和Contact.c
以及测试代码功能的test.c
test.c
和Contact.c
引用的头文件都是Contact.h
和SeqList_copy.h
2.2 定义联系人数据结构
在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;
2.3 打开通讯录
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.4 保存数据后销毁通讯录
销毁通讯录之前一定要保存数据,不然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);
}
2.5 添加联系人
使用单链表一节中尾插法添加数据
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");
}
2.6 删除联系人
借助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");
}
2.7 修改联系人
注意
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");
}
2.8 查找联系人
同样根据姓名查看详细联系人的通讯录信息
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);
}
2.9 查看通讯录
第一次自己写的时候,
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;}
}
3 通讯录代码展示
3.1 SeqList_copy.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#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);
3.2 SeqList_copy.c
#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;
}
3.3 Contact.h
#pragma once
//创建保存联系人数据的结构
#define NAME_MAX 120
#define SEX_MAX 10
#define TEL_MAX 20
#define ADDR_MAX 50typedef 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);
3.4 Contact.c
#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;}
}
3.5 test.c
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;
}
3.6 调试控制台截图
这里忘记啥问题了,一转眼就忘了
在多次语法错误,死循环,输出乱码,代码崩溃后,终于简单完成通讯录的功能!