目录
前言
为何要实现高级通讯录
高级通讯录实现:
创建通讯录
打印菜单
初始化通讯录
实现加载功能
实现添加功能
实现增容功能
实现删除功能
实现查询功能
实现修改功能
实现查询所有联系人功能
实现排序功能
实现清空功能
实现保存功能
实现退出功能
通讯录总代码:
前言
前面已经写了一篇简易版本通讯录的实现,下面再来写一篇功能比较齐全的,可增容,增容空间可自己掌控,也可将数据保存起来的,又添加了清空列表等一些功能的高级通讯录!
为何要实现高级通讯录
对于简易版本的通讯录来说,我们是直接指定了通讯录的大小,通讯录的空间是直接给死了,那么此时我们会发现两个弊端:
第一个弊端:
假设我们所添加的联系人的个数,小于,我们给定通讯录的大小,此时多余的空间就被浪费掉了!又假设我们添加联系人的大小,大于,给定通讯录的大小,此时我们发现通讯录的空间不够我们来封装联系人信息了! 此时我们就得优化通信录给定空间上的问题,不能再给其指定大小的空间,而是让其空间有灵活性,此时我们以动态增容的方式来给定通讯录的空间,就很好的规避了这一问题!
第二个弊端:
我们知道程序运行起来我们输入的数据都是保存在内存上的,在简易版本中我们在运行通讯录之后添加的数据,在程序结束的时候都会被清除。当第二次运行起来的时候,这些数据都已经不存在了,而我们既然要保存联系人的信息,就得做到数据持久化的保存,才能真正保存联系人信息,要做到持久化我们就得将数据保存到硬盘中,也就是文件当中,此时我们可以用文件操作的方式来实现持久化的保存联系人信息,就很好的规避了这一问题!
高级通讯录实现:
为了规避以上问题,我们再来实现一个能增容能保存以及再完善一些功能的高级通讯录!
创建通讯录
我们先创建一个结构体,用来封装联系人的:姓名、性别、年龄...等信息!再创建一个结构体,用来:操控联系人信息、记录联系人个数、记录空间数....等!
具体实现看代码:
#define NAME_SIZE 20 #define EAX_SIZE 6 #define PHONE_SIZE 15//创建封装通讯录内容的结构体,并给其重命名 typedef struct information {char name[NAME_SIZE];int age;char eax[EAX_SIZE];char phone[PHONE_SIZE]; }information;//创建管理通讯录的结构体,并给其重命名 typedef struct contict {information* data;//结构体指针int sz;//联系人个数int count;//增容的数量 }contict;
打印菜单
我们要创建一个菜单供用户选择,让用户选择操作的功能,此时我们用do while循环里面嵌套Switch case语句来实现用户对功能的选择以及菜单的显示!,
具体看代码:
//定义所用到的常量 //因为常量太多用枚举类型一次性定义 //让这些常量代替我们对功能的选择 enum Constant {EXIT,ADD,DEL,SEL,MODIFY,SHOW,SQRT,CLEAR };//菜单 void menu(void) {printf("******************************\n");printf("***** 1、add 2、del *****\n");printf("***** 3、sel 4、modify *****\n");printf("***** 5、show 6、sqrt *****\n");printf("***** 7、clear 0、exit *****\n");printf("******************************\n"); }int main() { //创建结构体变量contict con;//初始化通讯录init_contict(&con);int input = 0;do{//菜单menu();printf("请选择-> ");scanf("%d", &input);switch (input){case ADD:ADDcontict(&con);//添加联系人break;case DEL:DELcontict(&con);//删除指定联系人break;case SEL:SELcontict(&con);//查询指定联系人break;case MODIFY:MODIFYcontict(&con);//修改指定联系人信息break;case SHOW:SHOWcontict(&con);//显示所有联系人信息break;case SQRT:SQRTcontict(&con);//排序联系人break;case CLEAR:CLEARcontict(&con);//清空通讯录break;case EXIT:SAVEcontictp(&con);//退出的时候保存通讯录联系人信息FREEcontict(&con);//退出的时候释放空间printf("退出成功\n");break;default:printf("选择有误请重新选择-> \n");break;}} while (input);return 0; }
先大致了解一下逻辑,而代码里面的函数功能,我们下面一一实现与介绍!
初始化通讯录
因为是动态增容版本,我们在初始化的时候空间是由我们自行开辟的,而C语言里面给我们提供了动态内存开辟的函数,我们按照这样的思路来开辟空间,先开辟小一点的空间用,如果空间不够用了我们再调整空间,让其变大!在初始化开辟空间的时候,我们使用calloc函数来申请空间,因为calloc函数在动态空间申请的时候会将空间中的每个字节的内容都初始化为0,然后我们再将记录联系人的个数的变量初始化为0,再将记录空间数量的变量初始化为我们所开辟的联系人空间的个数!
因为是能保存联系人信息的通讯录,为了用户的后续使用和关闭,所以在初始化的时候,还得要调用一个功能,就是将保存在文件中的联系人的信息读到我们动态申请的空间中,随后继续进行操作,通俗点将就是将已经存在的联系人的信息拿到通讯录里面!这个功能就是加载功能!
具体看代码实现:
//初始化通讯录 #define IN 3 void init_contict(contict* pc) {assert(pc);//calloc函数在开辟空间的时候会将每个字节的内容都初始化为0information* p = (information*)calloc(IN, sizeof(information));//判断是否开辟成功if (p == NULL){perror("calloc");return;}pc->data = p;pc->sz = 0;pc->count = IN;//加载文件信息到通讯录中read_contict(pc);//将原来存在的联系人信息拿到通讯录中 }
实现加载功能
就是将文件中的联系人的信息,加载到通讯录中,以便后续操作,完整的实现了持久化保存功能,比如说:但我们第一次使用通讯录的时候会添加联系人的信息,在最后关闭的时候,这些信息会被保存到文件中,当我们第二次再进行操作的时候,就将这些保存在文件中的联系人信息加载到通讯录中,以供我们操作,也更完整的实现了保存功能!具体点说就是对保存联系人信息的文件进行读操作,将文件里的信息读到通讯录的空间里!
具体实现看代码:
//加载文件信息到通讯录中 void read_contict(contict* pc) {assert(pc);//打开文件FILE* pf = fopen("AddressBook.txt", "rb");//判断是否打开成功if (pf == NULL){perror("fopen");return;}//读数据information tmp = { 0 };int i = 0;while (fread(&tmp, sizeof(information), 1, pf)){//读取数据要考虑增容问题Enhancement(pc);pc->data[i] = tmp;pc->sz++;i++;}//关闭文件fclose(pf);pf = NULL; }
实现添加功能
添加联系人的方式其实很简单,我们直接在通讯录里面写入值就行了,每写入一个让记录联系人个数的变量+1,再判断一下空间的容量够不够是否需要增容!(我们将增容封装成一个函数,直接调用一下就行),增容的同时让记录空间的变量同样变成增容之后空间的个数!
具体实现看代码:
//添加联系人 void ADDcontict(contict* pc) {assert(pc);//判断是否要增容Enhancement(pc);//增容功能后续实现printf("请输入姓名-> ");scanf("%s", pc->data[pc->sz].name);printf("清输入年龄-> ");scanf("%d", &(pc->data[pc->sz].age));printf("请输入性别-> ");scanf("%s", pc->data[pc->sz].eax);printf("请输入电话-> ");scanf("%s", pc->data[pc->sz].phone);pc->sz++;printf("添加成功\n"); }
实现增容功能
在添加联系以及后面我们从文件中读取联系人信息的时候,我们都可能遇到实现申请好的空间不够用的情况,此时我们就要进行调整空间的大小,让空间变大也就是增容,我们知道C语言中,realloc函数(不懂可以看主要动态内存管理文章),可以调整申请好的动态空间的大小,所以在实现增容这个功能的时候我们用,realloc函数,来实现增容空间的功能!增容的同时我们也让记录空间个数的变量进行增加!
具体实现看代码:
//判断是否要增容 #define IT 2 void Enhancement(contict* pc) {assert(pc);if (pc->sz == pc->count){//增容information* p = (information*)realloc(pc->data, (pc->count + IT) * sizeof(information));if (p == NULL){perror("realloc");return;}pc->data = p;pc->count += IT;printf("增容成功\n");} }
实现删除功能
实现删除指定联系人,原理其实很简单,就是将要删除的联系人的信息,被其后面的联系人覆盖掉,以此类推从后往前一直覆盖直到联系人全部遍历完,通俗点说就是让其后面的每个联系人都往前走一步!
画图简单明了:
具体实现看代码:
//判断该联系人是否存在 There(char* name, contict* pc) {int i = 0;for (i = 0; i < pc->sz; i++){if (strcmp(name, pc->data[i].name) == 0){return i;}}return -1; }//删除指定联系人 void DELcontict(contict* pc) {assert(pc);//判断联系人列表是否为空if (pc->sz == 0){printf("联系人列表为空 无法删除\n");return;}char name[NAME_SIZE];printf("请输入要删除联系人的姓名-> ");scanf("%s", name);//判断该联系人是否存在int flag = There(name, pc);//因为后续功能都要用到这一步,用一个函数来封装!if (flag == -1){printf("该联系人不存在\n");return;}//删除联系人int i = 0;for (i = flag; i < pc->sz - 1; i++){pc->data[i] = pc->data[i + 1];}--pc->sz;printf("删除成功\n"); }
实现查询功能
查询就指定联系人就很简单了,就只是找到该联系人,然后再将该联系人的信息打印出来即可!
具体实现看代码:
//判断该联系人是否存在 There(char* name, contict* pc) {int i = 0;for (i = 0; i < pc->sz; i++){if (strcmp(name, pc->data[i].name) == 0){return i;}}return -1; }//查找指定联系人信息 void SELcontict(contict* pc) {assert(pc);if (pc->sz == 0){printf("联系人列表为空,无法查询\n");return;}char name[NAME_SIZE];printf("请输入要查询联系人的姓名-> ");scanf("%s", name);//判断该联系人是否存在int flag = There(name, pc);if (flag == -1){printf("该联系人不存在\n");return;}printf("%-10s %-5s %-5s %-13s\n", "姓名", "年龄", "性别", "电话");printf("%-10s %-5d %-5s %-13s\n", pc->data[flag].name,pc->data[flag].age,pc->data[flag].eax,pc->data[flag].phone);printf("查询成功\n"); }
实现修改功能
修改联系人跟查询联系人一个思路,就是找到该联系人的位置,然后将其信息修改掉,也就是用scanf函数重新给其输入值即可!
具体实现看代码:
//判断该联系人是否存在 There(char* name, contict* pc) {int i = 0;for (i = 0; i < pc->sz; i++){if (strcmp(name, pc->data[i].name) == 0){return i;}}return -1; }//修改指定联系人信息 void MODIFYcontict(contict* pc) {assert(pc);if (pc->sz == 0){printf("联系人列表为空,无法修改\n");return;}char name[NAME_SIZE];printf("请输入要修改联系人的姓名-> ");scanf("%s", name);//判断该联系人是否存在int flag = There(name, pc);if (flag == -1){printf("该联系人不存在\n");return;}printf("请输入姓名-> ");scanf("%s", pc->data[flag].name);printf("清输入年龄-> ");scanf("%d", &(pc->data[flag].age));printf("请输入性别-> ");scanf("%s", pc->data[flag].eax);printf("请输入电话-> ");scanf("%s", pc->data[flag].phone);printf("修改成功\n"); }
实现查询所有联系人功能
查询所有联系人信息,就是将所有联系人信息都打印出来,我们只需要运用一个for遍历联系人列表即可!
具体实现看代码:
//显示所有联系人信息 void SHOWcontict(contict* pc) {assert(pc);if (pc->sz == 0){printf("联系人列表为空\n");return;}printf("%-10s %-5s %-5s %-13s\n", "姓名", "年龄", "性别", "电话");int i = 0;for (i = 0; i < pc->sz; i++){printf("%-10s %-5d %-5s %-13s\n", pc->data[i].name,pc->data[i].age,pc->data[i].eax,pc->data[i].phone);} }
实现排序功能
在排序的时候我们,给定两种排序方法:按照年龄排序、按照姓名排序 供用户选择。用qsort函数(不了解的可以看主页指针进阶里面有详细介绍qsort)来分别实现两种排序功能!
具体看代码:
//排序联系人信息//按照年龄排序 int con_age(const void* e1, const void* e2) {//升序return ((information*)e1)->age - ((information*)e2)->age;//降序//return ((information*)e2)->age - ((information*)e1)->age; } //按照姓名排序 int con_name(const void* e1, const void* e2) {//升序return strcmp(((information*)e1)->name, ((information*)e2)->name);//降序//return strcmp(((information*)e2)->name, ((information*)e1)->name); }void SQRTcontict(contict* pc) {assert(pc);if (pc->sz == 0){printf("联系人列表为空,无法排序\n");return;}int n = 0;printf("*********************\n");printf("*** 1、按年龄排序 ***\n");printf("*** 2、按姓名排序 ***\n");printf("*********************\n");printf("请选择排序方式-> ");scanf("%d", &n);if (n == 1){qsort(pc->data, pc->sz, sizeof(information), con_age);printf("按照年龄排序成功\n");}else if (n == 2){qsort(pc->data, pc->sz, sizeof(information), con_name);printf("按照姓名排序成功\n");}else{printf("选择排序无效\n");}}
实现清空功能
要清空联系人信息,就只需要将我们通讯录申请的空间释放掉,随后再以读的形式打开一下文件,打开之后什么都不干,读的形式会将文件的内容都覆盖掉,会自动清空文件里的内容,再关闭文件,最后调用一下初始化函数,让它重新开辟一片空间!就完成清空功能了
具体实现看代码:
//清空通讯录信息 void CLEARcontict(contict* pc) {assert(pc);free(pc->data);pc->data = NULL;//打开文件FILE* pf = fopen("AddressBook.txt", "wb");//wb在打开文件的时候会覆盖前面的内容!if (pf == NULL){perror("fopen");return;}//啥都不写入直接关闭文件!//关闭文件fclose(pf);pf = NULL;init_contict(pc);printf("清空成功\n"); }
实现保存功能
要让联系人信息持久化的保存,我们将其保存到文件中,进行文件操作(不懂看主页文件操作文章有详细讲解),只需要将每个联系人的信息写入文件中即可!
具体实现看代码:
//退出的时候保存通讯录联系人信息 void SAVEcontictp(contict* pc) {assert(pc);//打开文件FILE* pf = fopen("AddressBook.txt", "wb");//以二进制的写入形式打开//判断是否打开成功if (pf == NULL){perror("fopen");return;}//开始写数据int i = 0;for (i = 0; i < pc->sz; i++){//一个一个的写入数据fwrite(pc->data+i, sizeof(information),1, pf);}//关闭文件fclose(pf);pf = NULL;printf("保存成功\n"); }
实现退出功能
在退出的时候,先将通讯录里面联系人的信息,保存到文件中,随后再将动态开辟的通讯录的空间释放就行,随后将指针置为NULL,避免野指针。注意:先保存再释放二者顺序不可乱,不然会出错!
具体实现看代码:
//退出的时候释放空间 void FREEcontict(contict* pc) {assert(pc);free(pc->data);pc->data = NULL;pc->sz = 0;pc->count = 0; }
通讯录总代码:
//动态+可保存数据版本的通讯录#include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h>//创建封装通讯录内容的结构体 #define NAME_SIZE 20 #define EAX_SIZE 6 #define PHONE_SIZE 15 typedef struct information {char name[NAME_SIZE];int age;char eax[EAX_SIZE];char phone[PHONE_SIZE]; }information;//创建管理通讯录的结构体 typedef struct contict {information* data;//结构体指针int sz;//联系人个数int count;//增容的数量 }contict;//定义所用到的常量 //因为常量太多用枚举类型一次性定义 //让这些常量代替我们对功能的选择 enum Constant {EXIT,ADD,DEL,SEL,MODIFY,SHOW,SQRT,CLEAR };//菜单 void menu(void) {printf("******************************\n");printf("***** 1、add 2、del *****\n");printf("***** 3、sel 4、modify *****\n");printf("***** 5、show 6、sqrt *****\n");printf("***** 7、clear 0、exit *****\n");printf("******************************\n"); }//判断是否要增容 #define IT 2 void Enhancement(contict* pc) {assert(pc);if (pc->sz == pc->count){//增容information* p = (information*)realloc(pc->data, (pc->count + IT) * sizeof(information));if (p == NULL){perror("realloc");return;}pc->data = p;pc->count += IT;printf("增容成功\n");} }//加载文件信息到通讯录中 void read_contict(contict* pc) {assert(pc);//打开文件FILE* pf = fopen("AddressBook.txt", "rb");//判断是否打开成功if (pf == NULL){perror("fopen");return;}//读数据information tmp = { 0 };int i = 0;while (fread(&tmp, sizeof(information), 1, pf)){//读取数据要考虑增容问题Enhancement(pc);pc->data[i] = tmp;pc->sz++;i++;}//关闭文件fclose(pf);pf = NULL; }//初始化通讯录 #define IN 3 void init_contict(contict* pc) {assert(pc);//calloc函数在开辟空间的时候会将每个字节的内容都初始化为0information* p = (information*)calloc(IN, sizeof(information));//判断是否开辟成功if (p == NULL){perror("calloc");return;}pc->data = p;pc->sz = 0;pc->count = IN;//加载文件信息到通讯录中read_contict(pc);//先不用管这个功能后续会慢慢了解//主要了解上面初始化的代码! }//添加联系人 void ADDcontict(contict* pc) {assert(pc);//判断是否要增容Enhancement(pc);printf("请输入姓名-> ");scanf("%s", pc->data[pc->sz].name);printf("清输入年龄-> ");scanf("%d", &(pc->data[pc->sz].age));printf("请输入性别-> ");scanf("%s", pc->data[pc->sz].eax);printf("请输入电话-> ");scanf("%s", pc->data[pc->sz].phone);pc->sz++;printf("添加成功\n"); }//判断该联系人是否存在 There(char* name, contict* pc) {int i = 0;for (i = 0; i < pc->sz; i++){if (strcmp(name, pc->data[i].name) == 0){return i;}}return -1; }//删除指定联系人 void DELcontict(contict* pc) {assert(pc);//判断联系人列表是否为空if (pc->sz == 0){printf("联系人列表为空 无法删除\n");return;}char name[NAME_SIZE];printf("请输入要删除联系人的姓名-> ");scanf("%s", name);//判断该联系人是否存在int flag = There(name, pc);if (flag == -1){printf("该联系人不存在\n");return;}//删除联系人int i = 0;for (i = flag; i < pc->sz - 1; i++){pc->data[i] = pc->data[i + 1];}--pc->sz;printf("删除成功\n"); }//查找指定联系人信息 void SELcontict(contict* pc) {assert(pc);if (pc->sz == 0){printf("联系人列表为空,无法查询\n");return;}char name[NAME_SIZE];printf("请输入要查询联系人的姓名-> ");scanf("%s", name);//判断该联系人是否存在int flag = There(name, pc);if (flag == -1){printf("该联系人不存在\n");return;}printf("%-10s %-5s %-5s %-13s\n", "姓名", "年龄", "性别", "电话");printf("%-10s %-5d %-5s %-13s\n", pc->data[flag].name,pc->data[flag].age,pc->data[flag].eax,pc->data[flag].phone);printf("查询成功\n"); }//修改指定联系人信息 void MODIFYcontict(contict* pc) {assert(pc);if (pc->sz == 0){printf("联系人列表为空,无法修改\n");return;}char name[NAME_SIZE];printf("请输入要修改联系人的姓名-> ");scanf("%s", name);//判断该联系人是否存在int flag = There(name, pc);if (flag == -1){printf("该联系人不存在\n");return;}printf("请输入姓名-> ");scanf("%s", pc->data[flag].name);printf("清输入年龄-> ");scanf("%d", &(pc->data[flag].age));printf("请输入性别-> ");scanf("%s", pc->data[flag].eax);printf("请输入电话-> ");scanf("%s", pc->data[flag].phone);printf("修改成功\n"); }//显示所有联系人信息 void SHOWcontict(contict* pc) {assert(pc);if (pc->sz == 0){printf("联系人列表为空\n");return;}printf("%-10s %-5s %-5s %-13s\n", "姓名", "年龄", "性别", "电话");int i = 0;for (i = 0; i < pc->sz; i++){printf("%-10s %-5d %-5s %-13s\n", pc->data[i].name,pc->data[i].age,pc->data[i].eax,pc->data[i].phone);} }//排序联系人信息//按照年龄排序 int con_age(const void* e1, const void* e2) {//升序return ((information*)e1)->age - ((information*)e2)->age;//降序//return ((information*)e2)->age - ((information*)e1)->age; } //按照姓名排序 int con_name(const void* e1, const void* e2) {//升序return strcmp(((information*)e1)->name, ((information*)e2)->name);//降序//return strcmp(((information*)e2)->name, ((information*)e1)->name); }void SQRTcontict(contict* pc) {assert(pc);if (pc->sz == 0){printf("联系人列表为空,无法排序\n");return;}int n = 0;printf("*********************\n");printf("*** 1、按年龄排序 ***\n");printf("*** 2、按姓名排序 ***\n");printf("*********************\n");printf("请选择排序方式-> ");scanf("%d", &n);if (n == 1){qsort(pc->data, pc->sz, sizeof(information), con_age);printf("按照年龄排序成功\n");}else if (n == 2){qsort(pc->data, pc->sz, sizeof(information), con_name);printf("按照姓名排序成功\n");}else{printf("选择排序无效\n");}}//清空通讯录信息 void CLEARcontict(contict* pc) {assert(pc);free(pc->data);pc->data = NULL;//打开文件FILE* pf = fopen("AddressBook.txt", "wb");//wb在打开文件的时候会覆盖前面的内容!if (pf == NULL){perror("fopen");return;}//啥都不写入直接关闭文件!//关闭文件fclose(pf);pf = NULL;init_contict(pc);printf("清空成功\n"); }//退出的时候释放空间 void FREEcontict(contict* pc) {assert(pc);free(pc->data);pc->data = NULL;pc->sz = 0;pc->count = 0; }//退出的时候保存通讯录联系人信息 void SAVEcontictp(contict* pc) {assert(pc);//打开文件FILE* pf = fopen("AddressBook.txt", "wb");//以二进制的写入形式打开//判断是否打开成功if (pf == NULL){perror("fopen");return;}//开始写数据int i = 0;for (i = 0; i < pc->sz; i++){//一个一个的写入数据fwrite(pc->data+i, sizeof(information),1, pf);}//关闭文件fclose(pf);pf = NULL;printf("保存成功\n"); }//通讯录 void Test(void) {//创建结构体变量contict con;//初始化通讯录init_contict(&con);int input = 0;do{//菜单menu();printf("请选择-> ");scanf("%d", &input);switch (input){case ADD:ADDcontict(&con);//添加联系人break;case DEL:DELcontict(&con);//删除指定联系人break;case SEL:SELcontict(&con);//查询指定联系人break;case MODIFY:MODIFYcontict(&con);//修改指定联系人信息break;case SHOW:SHOWcontict(&con);//显示所有联系人信息break;case SQRT:SQRTcontict(&con);//排序联系人break;case CLEAR:CLEARcontict(&con);//清空通讯录break;case EXIT:SAVEcontictp(&con);//退出的时候保存通讯录联系人信息FREEcontict(&con);//退出的时候释放空间printf("退出成功\n");break;default:printf("选择有误请重新选择-> \n");break;}} while (input); }int main() {//测试通讯录Test();return 0; }